An MP3 is not a container. It is an optional ID3v2 tag, a run of self-describing MPEG audio frames, and an optional ID3v1 trailer. Regions below expand; the MPEG frame header is drawn as a register map. Hover any field to light its exact bits and read the decode; click a field with a + to open its lookup table. Color marks kind (see the key). Example: FF FB 90 C0, a real MPEG-1 Layer III frame at 128 kbps, 44100 Hz, mono.
Top to bottom, what a reader walks. Only the frames are required.
Prepended metadata. A 10-byte header, then frames, until padding. The tag size is synchsafe (7 bits per byte) so the length can never contain a false 0xFF sync. Frame sizes differ by version: v2.3 is a plain big-endian uint32, v2.4 is synchsafe. When the unsync flag is set the writer stuffed a 0x00 after every 0xFF; strip those before reading frame sizes.
A real 2.3.0 tag. The size is the whole point: four bytes, seven bits each.
Encoding 0x01 means UTF-16, so the text opens with a byte-order mark.
A 3.4 MB PNG front cover. The image begins immediately after the description.
Each frame opens with the 4-byte header drawn below; it carries everything needed to compute the frame length and step to the next sync.
VBR cannot be sized from one bitrate, so the encoder writes a header into the first frame after the side-info block. "Xing" is true VBR, "Info" is CBR by LAME. Offset from frame start: 36 (MPEG-1 stereo), 21 (MPEG-1 mono or MPEG-2 stereo), 13 (MPEG-2 mono) — that is 4 (header) + the side-info size. The tag stays at this offset even when the frame is CRC-protected: LAME holds it at the CRC-absent position so Xing/Info readers still find it, so a reader must not shift by the 2 CRC bytes.
The Fraunhofer encoder's VBR header. Unlike Xing/Info it sits at a fixed offset, frame start + 36, independent of version, channel mode, and side-info size. All fields big-endian; a frame carries at most one of Xing/Info/VBRI. The fixed +36 is no accident: Fraunhofer wrote VBRI after the largest fixed-size header area, so it never depends on channel mode the way Xing's side-info placement does.
The original tag: a fixed 128-byte block at the very end. Superseded by ID3v2, but many files carry both.
Year, comment, and the one byte that separates ID3v1.1 from v1.0.
Both of these change where a field actually sits, so a reader who ignores them lands mid-garbage while every offset on this page still looks right.
A decoder finds audio by scanning for a frame sync: eleven set bits, in practice a byte FF followed by one whose top three bits are set. Tag text can contain that pattern by accident, so ID3 can rewrite the payload to prevent it: every FF that would look like a sync gets a 00 inserted after it. A reader must strip FF 00 back to FF BEFORE parsing anything, because sizes were computed on the unsynchronised bytes.
When a frame's format flags set grouping, compression, encryption or (2.4) a data-length indicator, those bytes are prepended to the payload. The frame size counts them. So the text of a compressed TIT2 does not begin at frame+10, and nothing in the frame header says so except the flag bits.
00 00 02 01 is 257, not 513. Seven bits per byte: (0<<21)|(0<<14)|(2<<7)|1. Read as a plain u32 it would be 513, and every subsequent offset in the tag would be wrong by 256 bytes. The encoding exists so no length field can itself contain a byte that looks like a frame sync.The 4-bit bitrate index and 2-bit sample-rate index in the header select from these, by version and layer — the first thing a decoder needs. Bitrate in kbps; index 0000 is free format (measure the length), 1111 is invalid.
| index | V1 L1 | V1 L2 | V1 L3 | V2 L1 | V2 L2/L3 |
|---|---|---|---|---|---|
| 0000 | free | free | free | free | free |
| 0001 | 32 | 32 | 32 | 32 | 8 |
| 0010 | 64 | 48 | 40 | 48 | 16 |
| 0011 | 96 | 56 | 48 | 56 | 24 |
| 0100 | 128 | 64 | 56 | 64 | 32 |
| 0101 | 160 | 80 | 64 | 80 | 40 |
| 0110 | 192 | 96 | 80 | 96 | 48 |
| 0111 | 224 | 112 | 96 | 112 | 56 |
| 1000 | 256 | 128 | 112 | 128 | 64 |
| 1001 | 288 | 160 | 128 | 144 | 80 |
| 1010 | 320 | 192 | 160 | 160 | 96 |
| 1011 | 352 | 224 | 192 | 176 | 112 |
| 1100 | 384 | 256 | 224 | 192 | 128 |
| 1101 | 416 | 320 | 256 | 224 | 144 |
| 1110 | 448 | 384 | 320 | 256 | 160 |
| 1111 | — | — | — | — | — |
Sample rate (Hz):
| index | MPEG-1 | MPEG-2 | MPEG-2.5 |
|---|---|---|---|
| 00 | 44100 | 22050 | 11025 |
| 01 | 48000 | 24000 | 12000 |
| 10 | 32000 | 16000 | 8000 |
| 11 | reserved | ||
FF E0), not 12 (FF F0) — MPEG-2.5's version bits are 00, so a 12-bit sieve silently drops every 8 / 11.025 / 12 kHz file. Then reject the impossible: version 01, layer 00, bitrate 1111, sample rate 11. The sieve is exactly b0 == 0xFF and b1 & 0xE0 == 0xE0.A frame is self-describing: its byte length comes from the header alone, by integer division. Never cache it as a constant.
floor(144 × bitrate / sample_rate) + padding (bitrate in bits/s). Layer I uses a 4-byte slot: (floor(12 × bitrate / sample_rate) + padding) × 4. MPEG-2/2.5 Layer III uses 72, not 144 (half the samples per frame). The integer floor is why a 128 kbps / 44.1 kHz stream alternates 417 and 418 bytes frame to frame: 144×128000/44100 = 417.9, so frames are 417 and the padding bit adds the missing byte often enough to average the true rate. Cache the first 417 as "the frame size" and you desync on the next frame.main_data_begin at the start of the side info says how many bytes backward (up to 511) this frame's data really begins. So "one frame = one frame of audio" is false — cutting a stream at a frame boundary corrupts audio across the cut, and a frame is self-contained only when main_data_begin = 0. This is why you cannot byte-splice MP3 losslessly the way you can WAV.576 + 529 = 1105 samples at the front (576 encoder delay + the decoder's own 529, which FFmpeg writes as start_pad = 528 + 1). iTunes stores the same thing independently in an iTunSMPB comment frame — and the two can disagree, so a careful player has to pick one.0000 is legal (a constant but non-standard rate, length found by scanning to the next sync) — a parser that rejects it loses those files. And at EOF, ID3v1 is not alone: APEv2 and Lyrics3v2 also live there in a documented precedence, an accretion stack behind the last frame. Note too that the ID3v2.3 extended-header size is a plain integer while v2.4's is synchsafe — the mirror of the frame-size split.