Skip to content

Trim the MP3 info header frame and encoder delay - #101

Merged
ctoth merged 3 commits into
masterfrom
fix/mp3-info-header-delay
Oct 20, 2026
Merged

ctoth merged 3 commits into
masterfrom
fix/mp3-info-header-delay

Conversation

@ctoth

@ctoth ctoth commented Oct 6, 2026

Copy link
Copy Markdown
Owner

What

MP3s that carry an info header now start and end where their source did.

  • The Xing/Info header frame at the front of the file is no longer played.
  • When the header comes from LAME or libav (encoder name LAME, Lavc or Lavf), the encoder delay and end padding it declares are trimmed too.
  • MP3s with no info header decode exactly as before.

Why

go-mp3 (and Beep's wrapper around it) decodes every frame as audio, including the header frame, and does nothing with the delay fields. Measured through decodeSound on a 0.2 s tone encoded with LAME at 48 kHz:

frames decoded source frames
before 12672 9600
after 9600 9600

The extra 3072 frames were 1152 of header frame, 1105 of encoder plus decoder delay in front of the sound (47 ms late in total), and the rest padding after it.

Notes for review

  • The fix is all in internal/audio/native/mp3.go. mp3InfoTrim parses the header from the bytes decodeMP3 already holds; window drops the leading frames by decoding and discarding them, not by seeking, so the remaining samples are identical to a straight decode.
  • The decoder delay is taken as 529 frames. The tests pin it: they compare the decoded tone to the source formula with a tolerance of 0.04, and one frame of misalignment is an error of 0.08.
  • A header from any other encoder loses only the header frame. A header that would trim the whole stream is ignored with a warning.
  • The two fixtures under testdata/ (2.1 kB and 3.0 kB) were made with ffmpeg's libmp3lame from aevalsrc sines. ffmpeg's own decoder returns 9600 and 8820 frames for them, the same lengths the tests expect.
  • TestCharacterizeMP3 still passes unchanged: its fixture has no info header.
  • Not covered: MPEG-2/2.5 files and files with a CRC. The offsets for both are in the parser, but no fixture exercises them.

Testing

go test ./... passes; golangci-lint run ./... (v2.14.0) reports 0 issues.

ctoth added 3 commits October 5, 2026 20:17
- go-mp3 decodes the Xing/Info header frame as a frame of silence and
  leaves in the encoder delay and end padding that the header declares,
  so a LAME-encoded sound started about 47 ms late at 48 kHz and ran long
- decodeMP3 now reads the header: the header frame is always dropped,
  and the delay and padding are dropped when the encoder name is LAME,
  Lavc or Lavf
- The skipped frames are decoded and discarded rather than seeked past,
  so the rest of the stream is what a straight decode gives
- A header that would trim the whole stream is ignored
- Tests decode two LAME fixtures (CBR mono with an Info tag, VBR stereo
  with a Xing tag behind an ID3v2 tag) and compare them to the source
  sine, sample-aligned; a header from an unknown encoder loses only the
  header frame
- Files with no info header decode exactly as before
@ctoth
ctoth merged commit 87a2cc8 into master Oct 20, 2026
9 checks passed
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