A few months ago, I implemented a minuscule MPEG Audio Layer II decoder. While I still consider this as a cool hack, it’s not of too much use nowadays. Everyone uses MP3 or Vorbis; MPEG Audio Layer II is only used together with MPEG Video (think VCD, SVCD and DVB), but you usually don’t have MP2 audio files on your disk. So the aim was clear: I wanted to have an as-small-as-possible MP3 decoder, too.
This time, I didn’t even try to implement it myself. Layer II was already hard to understand, but Layer III is about 3 to 5 times more complex. Considering the bad quality of the standard, I decided against a fresh reimplementation. Instead, I looked at the large pool of existing open-source decoding software.
The most common MP3 decoder in the F/OSS world is mpg123 and the mpglib library based off it, so this was naturally my first choice. Digging deeper into the source, I found that it is too complex, hard to read and even harder to port. It is chock full of #ifdefs, uses assembler code and floating-point math all over the place and is quite big overall. So I checked the next alternative: FFmpeg. The MPEG Audio decoder of libavcodec is already implemented in a single file, so I used it as a starting point. I had to dig a bit through all the FFmpeg intricacies (to find out that the bitstream reader is actually a bunch of intertwined and largely redundant macros) to get it together, but finally it worked.
The harder part was to make a really small Win32 executable. Creating a debug build in Visual C++ Express was quite easy, but the Release build without C runtime library dependencies took me quite some time to finish. The special problem were some weird floating-point operations like frexp() and pow() used during initialization. These operations don’t map directly to a x87 FPU instruction, so the compiler insisted on calling a library function. I needed to create a replacement of these two functions, but finding out how to implement them wasn’t trivial at all. Well, at least I learned a thing or two about floating-point assembler programming :)
All in all, the result is roughly 1680 lines of code (not counting empty lines, comments and 600 lines of tables) that compile to a 28k executable. The fabulous executable compressor kkrunchy manages to pack this down to a reasonable 13312 bytes. Not quite as small as the Layer II decoder, but OK. I guess that a carefully-designed full reimplementation would be possible in 8 or 10k, but I’ll leave it to someone else to do that.
The small size comes at a cost, though. The FFmpeg MP3 decoder is neither fast nor bug-free. It uses around 1-2% CPU on my Athlon64 3000+ (fully optimized, of course), while other decoders can’t be measured at all (i.e. below 1%). An even greater problem is a bug in the huffmann decoder that renders some (but not all) silent parts in some (but not all) files wrong. I first thought that has to be a problem of my port, but it’s there in vanilla FFmpeg, too, so I’m writing a bug report and leave it to Fabrice and the others to fix it :)
[Update 2007-02-05: Two days ago, Michael Niedermayer (a lead FFmpeg developer) fixed the bug. Now minimp3 decodes all files correctly o/]
If you don’t mind the shortcomings of the current implementations, feel invited to try it or use it in your projects. My personal incentive was to get rid of bass.dll or fmod.dll when writing demos – but if anyone feels like implementing a full-featured graphical MP3 player in, say, 64k, I’d love to see that, too :)
- minimp3.tar.gz (51k) — the source code and compiled example application