Android Audio: Play an MP3 file on an AudioTrack

In my previous article I outlined the stages you need to go through if you want to manually decode WAVs to PCM to play them on an AudioTrack. I promised to show how to do the same for MP3s and this is what this post is going to be about.

Again, the use case is more common than you might think. The only way you can play an MP3 file via direct Android API is MediaPlayer which is heavyweight, slow and presents only high-level API. If you need to mix or modify audio streams or manage them with low latency, you are on your own. But I will try to help you right now.

Introduction

I really suggest that you read the article about playing a WAV first. It contains some general 101 on digital audio that you need to understand what PCM is and how decoders are used.

While WAV files that we loaded in the previous article contained raw PCM data (that we might need to only trivially preprocess), MP3s contain audio data that is encoded with a complex algorithm that we don’t want to learn or implement. Thus, we need some third-party code that will allow us to convert MP3 data to raw PCM that is playable by AudioTrack.

There are two ways we can go – find a pure Java MP3 decoder or try compile a native MP3 decoder on Android. In this article we will only discuss the pure Java solution in detail. It is fast enough for many purposes, although I have to admit that I use a native solution in my production code (but I still did try the Java one and it worked well – it was just not fast enough for my use case that was very demanding in terms of performance).

Now, let’s see if we can find a good pure Java library and how we use it to play some MP3s.

JLayer

As the result of my endless search for a native MP3 decoder, I can recommend only one library: JLayer. It works perfectly and decodes MP3 with good performance. (I used the Java SE version since Android is almost Java SE. Perhaps the Java ME version would be faster – dunno, try it.) JLayer is under an LGPL license which is friendly to commercial apps.

Before you can use JLayer in your project, you need to include it into your app. I wrote an article on porting existing Java code to Android that might help you. Just be sure to include the JLayer code as source because it might need some modifications (as far as I remember, I just removed some unnecessary parts).

Here’s a piece of code that uses the JLayer API to load some PCM data from an MP3:

	public static byte[] decode(String path, int startMs, int maxMs) 
		throws IOException, com.mindtherobot.libs.mpg.DecoderException {
		ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);
		
		float totalMs = 0;
		boolean seeking = true;
		
		File file = new File(path);
		InputStream inputStream = new BufferedInputStream(new FileInputStream(file), 8 * 1024);
		try {
			Bitstream bitstream = new Bitstream(inputStream);
			Decoder decoder = new Decoder();
			
			boolean done = false;
			while (! done) {
				Header frameHeader = bitstream.readFrame();
				if (frameHeader == null) {
					done = true;
				} else {
					totalMs += frameHeader.ms_per_frame();

					if (totalMs >= startMs) {
						seeking = false;
					}
					
					if (! seeking) {
						SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
						
						if (output.getSampleFrequency() != 44100
								|| output.getChannelCount() != 2) {
							throw new com.mindtherobot.libs.mpg.DecoderException("mono or non-44100 MP3 not supported");
						}
						
						short[] pcm = output.getBuffer();
						for (short s : pcm) {
							outStream.write(s & 0xff);
							outStream.write((s >> 8 ) & 0xff);
						}
					}
					
					if (totalMs >= (startMs + maxMs)) {
						done = true;
					}
				}
				bitstream.closeFrame();
			}
			
			return outStream.toByteArray();
		} catch (BitstreamException e) {
			throw new IOException("Bitstream error: " + e);
		} catch (DecoderException e) {
			Log.w(TAG, "Decoder error", e);
			throw new com.mindtherobot.libs.mpg.DecoderException(e);
		} finally {
			IOUtils.safeClose(inputStream);			
		}
	}

Of course, this code is raw but you should be able to get the general idea from it.

Going the native way

If a pure Java solution just isn’t good enough for you, you can try porting one of the existing open source MP3 decoder libraries that are written in C. Theoretically, a decoder is just a mathematical algorithm that should be rather portable. In practice though, you will need to spend some time finding a library with a good license and then tailoring the code to make it compile and work on Android NDK. However, this is a possible and pretty beneficial way to go since a native implementation might be around 20x faster than the pure Java one.

Conclusion

If you need to decode MP3s in your app, don’t worry, you can do that. I hope this article was helpful in showing you how.

Tags: , , , , , , ,

2 Responses to “Android Audio: Play an MP3 file on an AudioTrack”

  1. Alon says:

    Hi,

    Great Article!
    I’m having trouble with adding JLayer to my project and I can’t your article on porting existing Java code to Android.
    Can you add a link to said article?

    Thank you

  2. Ismail Osunlana says:

    Thanks so much, this really helped me in decoding my mp3. Also you should have specified that you used jlayer extension and if possible provide link to a guide on how to import it.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>