Skip to content

Fix the mixed block boundary at 8 kHz and a truncated frame leak - #829

Merged
ajroetker merged 2 commits into
mainfrom
fix/mp3-mixed-block-boundary
Sep 21, 2026
Merged

ajroetker merged 2 commits into
mainfrom
fix/mp3-mixed-block-boundary

Conversation

@ajroetker

Copy link
Copy Markdown
Contributor

Follow-up to #823, fixing both review findings.

A truncated frame leaked what it had already copied

collectFramePayloads abandons a frame once a granule needs main data the reservoir cannot supply. That exit freed the granule array while the main data already copied for earlier granules in the same frame stayed allocated. Truncating l3-si.bit to around 1.5 kB reaches it with one granule's copy outstanding — the new test sweeps that range, and the testing allocator reports the leak without the fix.

The mixed block boundary is wider at 8 kHz

Mixed blocks transform their lowest bands with long windows, and the decoder placed that boundary at a fixed 36 samples. That is right at every rate except 8 kHz: the six long bands a low sampling frequency granule declares span 72 coefficients there, so coefficients 36 through 71 were requantized with short-window gains and everything after them shifted. ffmpeg puts it the same way in compute_band_indexes:

if switched mode, we handle the 36 first samples as long blocks. For 8000Hz, we handle the 72 first exponents as long blocks

The boundary now comes from the band tables through one helper — 3 * scalefactorBandShort(rate)[3], which equals scalefactorBandLong(rate)[long_end] for all nine rates — shared by the requantizer, the count1 pass, the reorder step and the stereo band map. Those four sites each carried their own 36.

Tests

  • a frame cut between its granules frees what it already copied — sweeps the truncation range that reaches the partial-granule exit; fails with the allocator's leak report on the pre-fix code.
  • the mixed block boundary follows the band tables, and widens at 8 kHz — 36 at eight rates, 72 at 8 kHz, cross-checked against the long table's eight-band (MPEG-1) and six-band (LSF) ends.
  • an 8 kHz mixed block keeps 72 coefficients on long gains — long bands at gain 1, short bands at gain 4; on the pre-fix code coefficient 36 comes back at 4.

zig build test-audio test-audio-internals — 954/954.

Two findings from the review of #823.

A frame the stream cuts short is abandoned once a granule needs main
data the reservoir cannot supply, and that exit freed the granule array
while the main data already copied for earlier granules in the same
frame stayed allocated. Truncating l3-si.bit to about 1.5 kB reaches it
with one granule's copy outstanding.

Mixed blocks transform their lowest bands with long windows, and the
decoder placed that boundary at a fixed 36 samples. That is right at
every rate except 8 kHz, where the wider scalefactor bands put it at 72:
the six long bands a low sampling frequency granule declares span 72
coefficients there, so coefficients 36 through 71 were being requantized
with short window gains and everything after them shifted. ffmpeg says
the same thing in compute_band_indexes: "if switched mode, we handle the
36 first samples as long blocks. For 8000Hz, we handle the 72 first
exponents as long blocks."

The boundary now comes from the band tables, through one helper the
requantizer, the reorder step and the stereo band map share, so it
follows whatever the tables say rather than a constant that happened to
hold for eight of the nine rates.
@ajroetker

Copy link
Copy Markdown
Contributor Author

/ci run 1b3dadf

@ajroetker
ajroetker merged commit b0e3bfb into main Sep 21, 2026
3 of 4 checks passed
@ajroetker
ajroetker deleted the fix/mp3-mixed-block-boundary branch September 21, 2026 05:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant