Skip to content

MSIVST/WMAEncodeRS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WMAEncodeRS

A command-line Windows Media Audio encoder for Windows — a Rust port of lvqcl's WMAEncode, extended to also emit xWMA (the RIFF format used by XAudio2 in games, à la Microsoft's xWMAEncode). It turns WAV / raw PCM into fully-working .wma or .xwma using the built-in Windows Media Format runtime (wmvcore.dll) — the same encoder Windows itself uses — so output is maximally compatible and DRM-free. It is bidirectional: a .wma input is decoded back to WAV.

WAV/PCM -> WMA via the built-in Windows Media Format SDK (wmvcore). Supports WMA Standard, Professional, Lossless, and Voice in CBR / VBR / two-pass modes, with tagging, raw-PCM input, stdin input, stdout piping, and .xwma output.

Highlights

  • Memory-safe at the boundary. All unsafe is confined to src/encoder.rs and src/xwma.rs (the COM/FFI boundary); the crate root sets #![deny(unsafe_code)], so every other module is compiler-guaranteed unsafe-free. (Calling the Windows Media SDK requires unsafe; it can only be isolated and audited, not removed.)
  • Bug fixes vs. the original C++: tag strings include the UTF-16 NUL terminator (WMT_TYPE_STRING length); 64-bit WAV offset/seek (two-pass on files > 2 GB); broken-pipe-safe stdout (--print-formats | head won't panic).
  • xWMA output with an accurate dpds seek table (total length exact; seek points sub-millisecond), validated by round-tripping through Microsoft's xwmaencode.exe.
  • Unicode-safe. Non-ASCII filenames and tags (incl. CJK) are handled without panic or mojibake; the console output code page is set to UTF-8 at startup.
  • Bidirectional. Encodes WAV→WMA/xWMA and decodes WMA→WAV and xWMA→WAV.

Build

Requires the Rust toolchain (x86_64-pc-windows-msvc) and a normal Windows install (the Windows Media runtime). No external libraries to link — the windows crate binds wmvcore.dll directly.

cargo build --release
# -> target/release/WMAEncodeRS.exe

Prebuilt x86-64 binaries are published on the Releases page.

Usage

WMAEncodeRS <input> [output] [options]
  • <input> — WAV PCM file, or - for stdin
  • [output] — output file; .xwma extension (or --xwma) selects xWMA; - streams the result to stdout; omitted derives <input>.wma
WMAEncodeRS song.wav                      # -> song.wma, WMA Standard CBR 128k
WMAEncodeRS song.wav out.wma -q 75        # VBR quality 75
WMAEncodeRS song.wav out.wma -c pro -b 192
WMAEncodeRS song.wav out.wma -c lossless
WMAEncodeRS song.wav out.wma -m cbr2pass -b 96
WMAEncodeRS song.wav sfx.xwma -b 48       # xWMA for XAudio2 (48 kbps)
WMAEncodeRS song.wma song.wav             # decode WMA -> WAV (bidirectional)
WMAEncodeRS song.wav out.wma -v           # verbose: print format details

Decoding (WMA / xWMA → WAV)

A .wma or .xwma input is decoded to a PCM WAV (output .wav, or derived if omitted):

WMAEncodeRS music.wma music.wav
WMAEncodeRS sfx.xwma  sfx.wav

.wma decodes directly via the Windows Media reader. .xwma is decoded by reconstructing an ASF from its packets, then decoding that — output is ~99.9% identical to Microsoft's xwmaencode decoder (sub-LSB boundary differences). Also, decoded length may exceed the original — WMA adds encoder-delay padding. See more in Caveats below.

xWMA (XAudio2) output

.xwma output (or --xwma) produces a RIFF/XWMA file: WMA2 (Standard) CBR with a dpds seek table — the format XAudio2 plays directly. It is always WMA2/CBR; supported bitrates are 20, 32, 48, 64, 96, 160, 192 kbps (default 48). This reimplements both directions of Microsoft's xWMAEncode natively in Rust (no temp tools, no DirectX SDK): PCM→xWMA, and xWMA→WAV (see Decoding above).

Piping (Foobar2000 and friends)

WMA/ASF can't truly stream to a pipe (the muxer must seek back to patch header sizes), so when the output is - the result is encoded to a temp file and then streamed to stdout. WAV-on-stdin is fully supported — the model Foobar2000's converter uses:

cat song.wav | WMAEncodeRS -s - out.wma          # WAV in via stdin
cat song.wav | WMAEncodeRS -s - -  > out.wma     # WAV in, WMA out via stdout

Options

--quality <n>, -q <n>   VBR quality (10,25,50,75,90,98)
--bitrate <n>, -b <n>   bitrate in kbps (default 128; xWMA default 48)
--codec, -c             standard|std (default), professional|pro, lossless|lsl, voice
--mode,  -m             cbr (default), cbr2pass, vbr, vbr2pass
--xwma                  emit xWMA (RIFF/XWMA for XAudio2); also auto-on for .xwma
--out-samplerate n      target sample rate
--out-channels n        target channel count
--out-bitdepth n        target bit depth
--ignorelength, -i      ignore the length in the WAV header
--bufferstdin           buffer stdin for two-pass
--raw                   headerless raw PCM input
--raw-samplerate / --raw-channels / --raw-bitdepth
--priority, --nice      run at idle priority
--verbose, -v           print format details to stderr
--silent, -s            suppress progress
--print-formats         list every codec/format the system supports
--help, -h              full help; --longhelp  extended help with examples
Tagging: --title --tracknumber --artist --album --year --genre
         --composer --albumartist --discnumber --comment  --tag name=value

Notes on WMA behavior

Encoding goes through the Microsoft encoder, so output behaves exactly like Windows-produced WMA. A few inherent codec/container traits are by design and not controllable by any front-end:

  1. CBR leaky-bucket bandwidth limiting (use VBR/2-pass to avoid it)
  2. WMA Pro being undecodable on very old players (Standard is the default).
  3. Fixed-size ASF packet padding, and ASF's fixed header overhead on very short files.

This tool produces unprotected (DRM-free) files, handles the codec's bitrate↔sample-rate format matrix gracefully, and writes correct UTF-16 tags.

Caveats & technical notes

WMA vs. xWMA vs. XMA — they are not the same thing

Format Container Codec Typical use This tool
.wma ASF WMA Std/Pro/Lossless/Voice Windows desktop / media players / general audio ✅ encode + decode
.xwma RIFF WMA2 only XAudio2 game audio (Windows, Xbox One/Series, Xbox 360) ✅ encode + decode
.xma RIFF XMA (a different codec) Xbox 360 only (decoded by dedicated 360 hardware) ❌ not supported

The common trap: .xma is not WMA. XMA is a separate, Xbox-360-specific codec (a relative of WMA Pro) that has nothing to do with wmvcore or the WMA encoder. It can only be produced with xma2encode from the Xbox 360 XDK (a restricted SDK); there is no public Windows API/DLL that encodes XMA, so it is out of scope here. .wma is for general Windows audio; .xwma is for XAudio2 game audio.

ASF "Play Duration" rounding (foobar2000 length warning)

A player such as foobar2000 may warn that an encoded .wma's reported length is slightly shorter than the decoded length (e.g. 5:26.008000 vs 5:26.008163). The audio is complete — this is a metadata rounding artifact, not missing samples.

ASF stores durations as integers in 100-nanosecond units, and WMA decodes in fixed ~2048-sample frames, so the true length rarely lands on an exact tick. When wmvcore writes the ASF header it rounds the Play Duration down, losing the sub-tick remainder (here ~163 µs, about 7 samples at 44.1 kHz — inaudible). This is wmvcore's behavior, not the front-end's: the original wmaencode.exe (and Windows Media Player) produce the same warning, because we all hand PCM to the same IWMWriter muxer, which computes and writes that field. We don't get to override it through the high-level API.

Note: do not try to "fix" this by padding the file with silence — WMA can only add whole ~46 ms frames (vastly larger than the 0.163 ms discrepancy), and any appended silence becomes real audio that defeats WMA's gapless trim metadata, manufacturing an audible gap. The only safe fix would be to post-process the header's duration field (left undone here as a cosmetic, risky change).

xWMA → WAV decode: Why an ASF is reconstructed

The only WMA decoder available on a normal PC is the Windows Media runtime, reached through IWMSyncReader — which only opens ASF, not RIFF/XWMA. An .xwma is WMA2 audio (decodable) in a RIFF wrapper (not openable). So to decode it we must get the WMA2 packets into a container the reader accepts.

We can't feed raw packets to IWMSyncReader (no such entry point), and driving the WMA decoder DMO (WMADMOD.DLL) directly requires the WMA2 codec private data (samples- per-block, encode options, …) that the bare xWMA fmt chunk (cbSize = 0) deliberately omits. So instead we ask IWMCodecInfo3 for the WMA2 stream config matching the xWMA's rate/channels/bitrate — that config carries the missing codec private data — and write the (already-compressed) packets into a fresh ASF with IWMWriterAdvanced::WriteStreamSample. That ASF is then decoded by the normal .wma → WAV path. The reconstruction is the mechanism that supplies the codec metadata the container left out, and it reuses a single decoder instead of a second implementation.

xWMA decode is reference-accurate, not bit-exact

Because the reconstructed-ASF decode routes packets through IWMSyncReader rather than xWMA's native decoder, the decoded PCM is ~99.9 % byte-identical to Microsoft's xwmaencode (in testing, 162 of 182,318 bytes differed; ±1-LSB, ~−90 dB).

The differences sit at packet boundaries because WMA is a lapped-transform codec: consecutive frames overlap and cross-fade (overlap-add / inverse MDCT), so edge samples are reconstructed from two packets at once. Two things make the boundary samples round differently between the two decode paths: (1) decoder priming/preroll — our reconstructed stream's warm-up isn't bit-identical to the native xWMA decoder's; and (2) floating-point rounding order in the inverse transform, which tips the final quantize-to-int by ±1 on occasional edge samples. Long identical interiors, tiny ±1 differences at the seams — the expected fingerprint. Lossy decoders are only required to be correct within tolerance, not bit-exact across implementations (the same is true of decoding one MP3 with two decoders). It is inaudible and irrelevant to playback; it would only matter for byte-exact reproducibility (e.g. hashing decoded output), which would require driving WMADMOD.DLL directly instead.


Settings for adding WMAEncodeRS as a custom external encoder in foobar2000's Converter.

foobar2000 → Converter → Encoder presets → Add New

Encoder (full path to the binary): \path\to\your\WMAEncodeRS.exe

Extension — what the output file gets: wma (use xwma if you're targeting XAudio2 instead — see the variants below)

Parameters — foobar pipes WAV to stdin (-) and substitutes the output path for %d:

  • %d -s -b 192

Format islossy (WMA Standard/Pro are lossy). Choose lossless only for the WMA-lossless preset below.

Highest BPS mode supported16 (WMA Standard/xWMA decode/encode at 16-bit).

What each token means

Token Meaning
- read WAV from stdin (foobar pipes it in)
%d foobar replaces this with the output file path
-s silent (suppress progress on stderr)
-b 192 bitrate 192 kbps (or -q 75 for VBR, etc.)

Order matters only in that the input (-) comes before the output (%d) — they're positional, exactly like WMAEncodeRS <input> [output].

Ready-made presets

Goal Extension Parameters Format is
WMA Standard CBR 192k wma - %d -s -b 192 lossy
WMA Standard VBR q75 wma - %d -s -q 75 lossy
WMA Professional 192k wma - %d -s -c pro -b 192 lossy
WMA Lossless wma - %d -s -c lossless lossless
WMA Voice 20k (mono) wma - %d -s -c voice -b 20 lossy
xWMA 48k (XAudio2) xwma - %d -s -b 48 --xwma lossy
xWMA 192k (XAudio2) xwma - %d -s -b 192 --xwma lossy

Ready-made preset — WMA 2-pass VBR (ABR)

Goal Parameters
WMA Std 2-pass ABR 128k - %d -s -m vbr2pass -b 128 --bufferstdin
WMA Std 2-pass ABR 192k - %d -s -m vbr2pass -b 192 --bufferstdin
WMA Pro 2-pass ABR 192k - %d -s -m vbr2pass -b 192 -c pro --bufferstdin
CBR 2-pass 192k (alt) - %d -s -m cbr2pass -b 192 --bufferstdin

If you'd rather not buffer the whole track in RAM, drop --bufferstdin — WMAEncodeRS will use a temp file for the second pass instead. RAM buffering is faster; temp-file is lighter on memory.

Note: there is no quality-based 2-pass in WMA — 2-pass modes are always bitrate-targeted. If you want quality-based, that's 1-pass vbr with -q (e.g. - %d -s -q 90).

(For xWMA the --xwma flag is optional since the xwma extension auto-selects it, but it's harmless to include.)

Two important notes

  1. Don't pipe the output (- as output) in foobar. Use %d so foobar gives a real file path — WMA/ASF can't be written to a non-seekable pipe (the muxer must seek back to patch the header). %d is the correct, file-based path and works fine.

  2. Tags: leave foobar's converter tagging on as usual — but note WMAEncodeRS can also write tags itself if you pass e.g. --title "%title%" --artist "%artist%". You generally don't need to; foobar tags the output WMA after encoding. If you do pass them, foobar substitutes the %field% values.

Testing & fuzzing

cargo test runs unit/regression tests. The untrusted-input parsers (WAV header, xWMA, CLI) are fuzzed with cargo-fuzz (libFuzzer) — see fuzz/. Fuzzing found and fixed two robustness bugs in the WAV header parser (a divide-by-zero on a zero-channel fmt chunk, and a multi-GB allocation from an absurd fmt size), both now guarded by regression tests. Parsers are unsafe-free, so these are clean error-returns rather than memory-safety issues.

License

MIT. Original work © 2011 lvqcl; Rust port retains the same license — see LICENSE.

About

Rust port of WMAEncode. Turns WAV/PCM into fully-working .WMA or .xWMA files.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages