Libmad on Android with the NDK

So i was porting all the decoders i had build for the onset detection tutorial to C++, using libmad as the mp3 decoder of choice. After getting that to work on the desktop properly i had to make it work on Android too. Now, there’s no build of libmad in the NDK for obvious reasons, so i had to build that myself. As the autotools configure script of libmad is not useable with the NDK toolchain i used the config.h file from, which has all the settings needed for building libmad on Android. Compiling libmad is then a simple matter of creating a proper and file. The file looks like this:

Now there’s a couple of things that initially bogged down the performance of this. I tested it with the song “Schism” by tool which is a 6:47min long song, encoded at 192kbps. The file weights in at 9.31mb, pretty big for an mp3 imo. NativeMP3Decoder is just a libmad based implementation of the MP3Decoder in the onset detection tutorial framework. So it has a simple NativeMP3Decoder.readSamples method which will fill a float array with as many samples as there are elements in the float array. If the input file is in stereo the channels get mixed down to mono by averaging. The NativeMP3Decoder.readSamples() method internally calls a native method with a similar signature. Instead of a float array i pass in a direct ByteBuffer that has enough storage to hold all the samples requested. The native wrapper then writes the samples to this direct buffer which in turn then gets copied to the float array passed into the NativeMP3Decoder.readSamples() method. It looks something like this:

The call to buffer.get( samples ) kills it all. Without any optimizations (thumb code, -O0, -DFPM_DEFAULT == standard fixed point math in libmad, no arm assembler optimized fp math) decoding the complete files takes 184 seconds on my Milestone. Holy shit, batman! If i eliminate the buffer.get( samples ) call that gets down to 44 seconds! Incredible. Now i still thought that is way to slow so i started adding optimizations. The first thing i did was compiling to straight arm instead of thumb code. You can tell the NDK toolchain to do so by placing this in the file:

With this enabled decoding takes 36 seconds. The next thing i did was agressive optimization via -O3 as a CFLAG. That shaved off only 2 more seconds, so nothing to write home about. The last optimization is libmad specific. The config.h file i linked to above does not define the fixed point math mode libmad should use. Now, when you have a look at fixed.h of libmad you can see quiet some options for fixed point math there. There’s also a dedicated option for arm processors that uses some nice little arm assembler code to do the heavy lifting. You can enable this by passing -DFPM_ARM as a CFLAG. Now that did wonders! i’m now down to 20 seconds for decoding 407 seconds of mp3 encoded audio. That’s roughly 20x real-time which is totally ok with me. The song i chose is at the extreme end of the song length spectrum i will have to handle in my next audio game project. A song a user uses will be processed once and waiting for that 20 seconds is ok in my book.

I’m afraid i won’t release the source of the ported audio framework as it’s a bit of a mess and would need some work to clean up. What i can give you is the plain source for the native side of the NativeMP3Decoder class if you guarantee me not to laugh. My C days are long over so there’s probably a shitload of don’ts in there. The “handle” system is also kind of creative but good enough for my needs. I learned how to use the low level libmad api by looking at the code here. I actually like doing it this way more than with the shitty callback high-level API. Your mileage may vary. So here it is, be afraid:

To compile that for Android all you have to do is download libmad and put the source files into your Android project’s jni folder along with the code above. Then use the from above et voila you got yourself a native mp3 decoder for Android. You can use it in combination with the AndroidAudioDevice class of the last post. If you feel adventureous you could even extend it to return stereo data.

  • Ogryzok

    Man, this is awesome! With your article I ported my sound mixer component very fast. Many many thanks!

  • Mario

    glad to help. i’d be interested in what exactly you do. care to elaborate a little on your sound mixer?

  • Surye

    This is exactly the start I needed. Can I use this in my GPL’d project?

  • Mario

    libmad sucks license wise. i gave it up in favor of libmpg123, it’s lgpl without all the other license crap that comes with libmad. For something similar like the above for libmpg123 check out the libgdx sources. All my code is Apache License 2.0 so you can basically do what you want as long as you leave the license headers in the files.

  • Neo

    I tried libmad like you did but it’s strange that I got external/qemu/tcg/tcg.c:1363 problem after a few frame being decoded and the emulator switched off. Have you faced the same problem and know how to solve it?

  • Mario

    Hm, i’m afraid i didn’t experience that problem. I’d suggest looking into libmpg123 which has a better license anyway. You’d have to license libmad for anything commercial.

  • Neo

    I tried libmpg123 also. But I got an output file which is smaller than the input one and when I tried to “play -t cdr output” then I heard only noise ( I used your code ). I really don’t know what happened. Any idea?

  • Thanh

    Hi Mario. I downloaded your source code for libmg123. It worked but for some file it could not decode the whole file. I did something like this :

    int index = readSample(…,buffer,….);
    ….write buffer to file….
    if(index == 0)
    }while (1)

    Do you have any idea?

  • Thanh

    I debugged and I got “Trouble with mpg123: Error reading the stream. (code 18)” while decoding file. Still don’t know why this happened to me :)

  • Rizwan

    Hi Mario,

    is it possible to obtain raw stream from android audio jack . actually I want to read sensor data . I hope you can help me .


  • aleyram

    Thank you for sharing, I found very useful…

  • Steve

    Hi Mario!

    I’ve tried using libmad as well for my Android apps and I followed your codes, but somehow it took 1 minute to decode 4:45 mp3 file. I’ve been wondering why it is so slow.
    Any tips?