Problem: symphonia rejects every Layer II frame whose bit rate / mode combination ISO/IEC 11172-3 does not allow (mono above 192 kbit/s, stereo below 64 kbit/s). ffmpeg writes mono MP2 at 384 kbit/s by default, so such tracks played as silence in MPEG-TS / PS, Matroska and raw .mp2 files. Fix: new L0 crate filmcraft-mpa, a clean-room Layer I / II decoder written from ISO/IEC 11172-3 and 13818-3 (all modes incl. joint stereo, MPEG-1/2/2.5 rates, the disallowed combinations decoded with the allocation table their bit rate per channel selects). PacketDecoder::mpeg_audio uses it for layers I and II (packets of several frames too); raw .mp2 files decode with it, resyncing after damage. Oracle tests vs ffmpeg: 73-84 dB SNR on ffmpeg / twolame streams and synthetic Layer I / joint stereo frames; fuzzed damaged frames never panic. Co-Authored-By: Claude Opus 5.5 <[email protected]>
4.4 KiB
filmcraft-mpa
Clean-room MPEG-1 / MPEG-2 Audio Layer I and Layer II decoder (MP1 / MP2) in pure Rust. Layer L0:
depends only on filmcraft-bitstream and thiserror. Builds for wasm32-unknown-unknown; no
unsafe. No GPL/LGPL code (FFmpeg, libmad, mpg123, twolame) was consulted; FFmpeg (and twolame,
when installed) are used only as external fixture generators and test oracles.
Why
symphonia (the bootstrap decoder for MP3 and others) rejects every Layer II frame whose bit rate / channel mode combination ISO/IEC 11172-3 does not allow: mono above 192 kbit/s and stereo below 64 kbit/s. ffmpeg writes such streams by default (mono MP2 is 384 kbit/s unless asked otherwise), so those tracks played as silence in every container (#809).
Specification
ISO/IEC 11172-3:1993: §2.4.1 bit stream syntax (header, Layer I and Layer II audio data),
§2.4.2 semantics, §2.4.3.1 frame length, §2.4.3.2 synthesis subband filter (Figure A.2),
§2.4.3.3 Layer I and §2.4.3.4 Layer II decoding; Annex B: Table 3-B.1 scalefactors (computed as
2^(1 - i/3)), Table 3-B.2a-d Layer II allocation tables, Table 3-B.3 synthesis window (the 257
coefficients D[0..=256], exact multiples of 2⁻¹⁶, the rest from the prototype's symmetry;
a unit test checks the typed table is the smooth prototype), Table 3-B.4 quantization classes.
ISO/IEC 13818-3: the lower sampling frequencies (16 / 22.05 / 24 kHz), their bit rate tables and
the Layer II allocation table B.1 (its rows confirmed against FFmpeg's decode).
API
let mut dec = filmcraft_mpa::Decoder::new();
let h = filmcraft_mpa::parse_header(frame)?; // layer, rate, mode, frame size
let out = dec.decode(frame)?; // h.samples() per channel (384 or 1152)
dec.reset(); // before decoding from another position
Decoder
- Layer I and Layer II; mono, stereo, dual channel and joint (intensity) stereo with every bound; MPEG-1, MPEG-2 lower sampling frequencies and the unofficial MPEG-2.5; with or without CRC.
- Layer II table selection by sampling frequency and bit rate per channel, with the ranges of Table 3-B.2 taken as open-ended so the disallowed combinations decode the way encoders write them (mono 384 kbit/s uses table a at 48 kHz, b at 44.1 / 32 kHz; stereo 32 / 48 kbit/s uses c, or d at 32 kHz).
- Requantization
(2v + 1 - steps) / steps(the standard'sC · (s''' + D)for every class), grouped 3-, 5- and 9-step codewords, scalefactor selection information. - Output: planar f32 at full scale ±1.
- Errors (forbidden allocation, reserved header fields, data running past the frame) return
Err; garbage never panics (unit fuzz test and damaged-frame oracle test).
Accuracy (vs FFmpeg as an external oracle)
tests/oracle.rs, worst channel SNR against FFmpeg's float decoder. FFmpeg's own fixed-point
decoder agrees with its float decoder to ~77 dB (the error sits at the 16-bit LSB); the test
requires 70 dB, and a wrong table entry or misread field costs tens of dB.
| Stream | SNR |
|---|---|
| Layer II mono 48 kHz, FFmpeg's default 384 kbit/s (not allowed by 11172-3) | 77.1 dB |
| Layer II mono 44.1 kHz 320 kbit/s pink noise (not allowed) | 72.9 dB |
| Layer II stereo 48 kHz 32 kbit/s (table c; not allowed) | 83.2 dB |
| Layer II stereo 48 kHz 192 kbit/s, 44.1 kHz 256 / 96 kbit/s, 32 kHz 128 kbit/s | 83.3-83.6 dB |
| Layer II mono 32 kHz 48 kbit/s (table d) | 76.9 dB |
| Layer II MPEG-2: stereo 24 kHz 160 kbit/s, mono 22.05 kHz 64 kbit/s, stereo 16 kHz 32 kbit/s | 76.9-83.5 dB |
| Layer II joint stereo and dual channel from twolame (when installed) | 83.4-83.6 dB |
| Synthetic valid random frames: Layer I mono / stereo / dual / joint stereo at every bound, MPEG-1 and MPEG-2; Layer II joint stereo at every bound and dual channel with every allocation table | 74.1-80.0 dB |
No installed encoder writes Layer I, so Layer I is checked on synthetic frames (random
allocations, scalefactors and samples) that FFmpeg decodes as it would any stream. Through
filmcraft-codecs (tests/mpeg_oracle.rs), mono 384 kbit/s MP2 in MPEG-TS, Matroska and a raw
.mp2 file is within 3.1·10⁻⁵ of FFmpeg's decode.
Gaps
- Layer III is not decoded here (symphonia does it);
parse_headerreads its headers. - The free format (bit rate index 0) is refused: its frame length is only known from the next sync word.
- The CRC is read, not checked; ancillary data is skipped.