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.
- Memory-safe at the boundary. All
unsafeis confined tosrc/encoder.rsandsrc/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 requiresunsafe; 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_STRINGlength); 64-bit WAV offset/seek (two-pass on files > 2 GB); broken-pipe-safe stdout (--print-formats | headwon't panic). - xWMA output with an accurate
dpdsseek table (total length exact; seek points sub-millisecond), validated by round-tripping through Microsoft'sxwmaencode.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.
- Adds
voxas a shorthand for the Voice codec. - Rejects unsupported VBR qualities and option combinations instead of ending
with the generic
Requested encoder not founderror or silently ignoring-q. - Clarifies quality VBR, Lossless profile availability, and reliable foobar2000 two-pass presets.
Supported operating systems are 64-bit Windows 10 and Windows 11. Windows XP,
Vista, 7, and 8 are not supported. Building requires the Rust toolchain
(x86_64-pc-windows-msvc) and the Windows Media runtime included with Windows.
No external libraries are required; the windows crate binds wmvcore.dll
directly.
cargo build --release
# -> target/release/WMAEncodeRS.exePrebuilt x86-64 binaries are published on the Releases page.
WMAEncodeRS <input> [output] [options]
<input>— WAV PCM file, or-for stdin[output]— output file;.xwmaextension (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 detailsA .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 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).
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--quality <n>, -q <n> one-pass VBR quality (10,25,50,75,90,98)
--bitrate <n>, -b <n> bitrate for CBR/two-pass (default 128; xWMA default 48)
--codec <name>, -c <name>
standard|std (default), professional|pro,
lossless|lsl, voice|vox
--mode <name>, -m <name>
cbr (Standard/Pro default without -q), 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 accepted for old command lines; has no effect
--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 show runtime DLL version and its codec/profile table
--help, -h full help; --longhelp extended help with examples
Tagging: --title --tracknumber --artist --album --year --genre
--composer --albumartist --discnumber --comment --tag name=value
-q implies one-pass vbr when --mode is omitted. Quality 100 does not
select WMA Lossless; use --codec lossless without --quality. Both two-pass
modes are bitrate-based and use -b. In quality-based VBR, the resulting
bitrate varies with the audio; the 128 kbps default applies only to
bitrate-based modes.
--print-formats asks the loaded Windows Media runtime for its actual audio
codecs and profiles through IWMCodecInfo3. It prints the loaded wmvcore.dll
path and file version first, followed by a table grouped by the codec names that
runtime exposes. It does not use a hard-coded profile list or scan third-party
codec DLLs.
The table's mode, pass count, bitrate or quality, sample rate, channel count,
bit depth, and channel mask come from the runtime's structured codec media
types. A six-channel row is labeled 6 (5.1 ...) only when the returned
Microsoft speaker mask confirms that layout; otherwise it is shown as
6 (layout unspecified). The Flags column retains runtime distinctions such
as Low Delay and A/V optimized.
Example header (the path, version, codecs, and rows depend on the current Windows installation):
Windows Media runtime:
DLL: %SystemRoot%\System32\wmvcore.dll
File version: <installed file version>
Source: codecs and profiles enumerated from this runtime via IWMCodecInfo3
A seekable input file is read directly for each pass. Literal - stdin cannot
be rewound, so a Standard/Professional two-pass encode always copies that input
to a temporary disk file and replays it for the second pass.
--bufferstdin is retained only so older command lines are still accepted. It
has no effect: it does not enable RAM buffering, does not force disk buffering,
and is not required for two-pass encoding.
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:
- CBR leaky-bucket bandwidth limiting (use VBR/2-pass to avoid it)
- WMA Pro being undecodable on very old players (Standard is the default).
- 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.
A low-delay WMA profile uses a smaller stream buffer window than the equivalent normal profile. It is intended for streamed audio that must switch quickly, such as live broadcasts or a service that lets the listener change channels. The benefit is reduced stream-switching latency and live-broadcast delay.
Low delay does not make offline encoding faster, reduce Windows audio-device
latency, or make the codec lossless. It is unrelated to --nice, --silent,
and --ignorelength. A two-pass file can use a low-delay output profile, but it
still requires two encoding passes; "low delay" describes the resulting stream's
buffering behavior, not the time needed to encode it.
Microsoft exposes low-delay WMA Standard and Professional formats only in CBR
mode, for either one or two passes. They are separate installed profiles whose
descriptions contain Low Delay. Their bitrate is one kilobit below the
equivalent normal profile: for example, 191 kbps selects the low-delay
counterpart of the normal 192 kbps profile. Therefore, -b 192 selects the
normal profile, while -b 191 selects low delay when that format is available.
| Goal | Command |
|---|---|
| Standard, one-pass low delay | WMAEncodeRS.exe input.wav output.wma -c std -m cbr -b 191 -v |
| Professional, one-pass low delay | WMAEncodeRS.exe input.wav output.wma -c pro -m cbr -b 191 -v |
| Standard, two-pass low delay | WMAEncodeRS.exe input.wav output.wma -c std -m cbr2pass -b 191 -v |
| Professional, two-pass low delay | WMAEncodeRS.exe input.wav output.wma -c pro -m cbr2pass -b 191 -v |
Available bitrates depend on the Windows Media runtime and the requested sample
rate, channel count, and bit depth. List the formats installed on the current
computer and look for Low Delay under the desired codec and CBR pass mode:
WMAEncodeRS.exe --print-formatsUse the exact odd bitrate shown by that profile. If 191 kbps is not listed for
the input format, choose another listed low-delay bitrate or adjust the output
format. Quality VBR and two-pass VBR cannot be low delay, so do not combine
-q, -m vbr, or -m vbr2pass with these profiles.
For foobar2000, use piped input for one pass and a seekable %s input for two
passes:
| Goal | foobar2000 parameters |
|---|---|
| Standard, one-pass low delay | - %d -s -c std -m cbr -b 191 |
| Professional, one-pass low delay | - %d -s -c pro -m cbr -b 191 |
| Standard, two-pass low delay | %s %d -s -c std -m cbr2pass -b 191 |
| Professional, two-pass low delay | %s %d -s -c pro -m cbr2pass -b 191 |
See Microsoft's documentation for Low-Delay Audio and Getting Low-Delay Audio Formats.
WMA Lossless supports high-resolution and discrete multichannel audio, but the
Windows runtime exposes specific profiles rather than every channel/rate/depth
combination. A typical current runtime offers stereo and 5.1 profiles up to
24-bit/96 kHz, but no 4-channel Lossless profile. Use --print-formats to see
the exact profiles installed on the current system.
--out-channels 2 can make a 4-channel input encodable by downmixing it to
stereo. The stereo result is then compressed losslessly, but the downmix does
not preserve the original discrete channel layout.
| 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.
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).
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.
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.
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 — without %s, foobar pipes WAV to stdin (-) and substitutes
the output path for %d:
- %d -s -b 192
For two-pass encoding, use %s instead. Foobar then creates a seekable
temporary WAV, which WMAEncodeRS can read once per pass.
Format is → lossy (WMA Standard/Pro are lossy). Choose lossless only for the WMA-lossless preset below.
Highest BPS mode supported → 16 (WMA Standard/xWMA decode/encode at 16-bit).
| Token | Meaning |
|---|---|
- |
read WAV from stdin (foobar pipes it in) |
%s |
foobar creates a temporary input WAV and substitutes its filename |
%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.) |
WMAEncodeRS accepts options before, between, or after the positional paths. The first positional argument is input and the second is output, so
%s %dis the clearest file-mode form.
| 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 |
| Goal | Parameters |
|---|---|
| WMA Std 2-pass ABR 128k | %s %d -s -c std -m vbr2pass -b 128 |
| WMA Std 2-pass ABR 192k | %s %d -s -c std -m vbr2pass -b 192 |
| WMA Pro 2-pass ABR 192k | %s %d -s -c pro -m vbr2pass -b 192 |
| CBR 2-pass 192k (alt) | %s %d -s -c std -m cbr2pass -b 192 |
%smakes foobar create the temporary input WAV. Neither-inor--bufferstdinis needed. If literal-stdin is used for a Standard/Pro two-pass encode, WMAEncodeRS automatically caches the input PCM in a temporary disk file and replays it for the second pass.--bufferstdinis accepted for old command lines but has no effect and does not select RAM buffering.
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.)
-
Don't pipe the output (
-as output) in foobar. Use%dso 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).%dis the correct, file-based path and works fine. -
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.
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.
MIT. Original work © 2011 lvqcl; Rust port retains the same license — see
LICENSE.