Friday, June 12, 2009

Calculating MP3 frame length

I recently wrote a perl script to parse MP3 files and dump the data for each frame using the (mostly) excellent MPEG audio frame header documentation. Everything was working great until I used LAME to convert some files to have a 22.05 kHz sampling rate, which changes the files from MPEG1 to MPEG2.

When I parsed the new files, my script correctly found the first frame, but then it went haywire, unable to find subsequent frames. After double- and triple-checking the documentation and much hair-pulling, I began adding a lot of debug code to my parser, including a full dump of all of the bytes in the frame. Here's what I saw:


Header: ff f3 60 64
Frame: 00 00 00 01 a4 00 00 00 00 00 00 03
48 00 00 00 00 4c 41 4d 45 33 2e 39 38 2e
32 55 55 55 55 55 55 4c 41 4d 45 33 2e 39
38 2e 32 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
ff f3 62 64 87 00 00 01 a4 00 00 00 00 00
00 03 48 00 00 00 00 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 4c 41 4d 45
33 2e 39 38 2e 32 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55


Isn't it interesting that there are four bytes almost identical to the frame header in the middle of the frame? In fact, the only difference is that the third byte changes from 0x60 to 0x62, which is still a valid frame header, and differs from this frame's header only in that the padding bit is set.

The aforementioned documentation clearly says this about calculating frame length:

For Layer II & III files use this formula:

FrameLengthInBytes = 144 * BitRate / SampleRate + Padding

Keep in mind that this formula worked fine when the file was MPEG1. But on a hunch, I added a conditional that changed the constant in the formula to 72 when the file is an MPEG2, and my script now successfully parsed the entire file and correctly calculated its length when adding the duration of each frame.

The lesson here is that the documentation is incorrect about the formula for determining the frame length when the file is MPEG2 layer 3. Why does it have the wrong formula? Because of this statement:

Frame size is the number of samples contained in a frame. It is constant and always 384 samples for Layer I and 1152 samples for Layer II and Layer III.

In reality, MPEG2 layer 3 is a special case that only has 576 samples per frame, which necessitates the change to the constant in the formula.

2 comments:

Bluddy said...
This comment has been removed by the author.
Mark Johnson said...

Thanks for the tip on the MPEG2 Layer 3 constant difference. This helped me solve a problem in the MediaStreamSource