Fun with AudioTrack

I mentioned the class AudioTrack a couple of times already. Let’s see how it works.

AudioTrack is available since Android 1.5 (API level 3) and offers an extremely simple way to send PCM data directly to the device’s audio hardware. You can use it in two modes: static and streaming. I will only look at the streaming mode here. Streaming mode means that you permanentley write new PCM data to the hardware, the framework will queue it in a buffer and play it back for you. AudioTrack supports various sampling rates and 2 PCM encodings, 8-bit and signed 16-bit PCM. An AudioTrack instance can either be in mono or stereo mode. Here’s a small class that can be used similar to the AudioDevice class of the onset detection tutorial:

Pretty simple eh? Now let’s start with the constructor. The first thing we do is to get the minimum buffer size for the AudioTrack instance we are going to create. This is achieved by a call to AudioTrack.getMinBufferSize(), passing in the sampling rate, wheter we are mono or stereo and the PCM encoding, in this case 16-bit signed. This buffer size is used by the AudioTrack internally for a buffer it stores all the samples in we’ll write to it. If the buffer is full it is flushed to the audio hardware. Now, in the next line we instantiate an AudioTrack. The first parameter dictates which audio stream our samples are going to be written to. There’s a couple of audio streams in the Android system, we’ll almost always want to use AudioManager.STREAM_MUSIC here. For the other stream types refer to the documentation. The next three parameters say what sampling rate we want to have, wheter we want the track to be mono or stereo and which PCM encoding we want to use. As with the AudioDevice class from the onset detection tutorial we use 44100Hz, mono, 16-bit signed PCM. The last parameter says wheter this AudioTrack is static or a streaming one, we want it to be a streaming one. All that’s left is to call AudioTrack.play() and we are ready to write samples to the audio hardware.

All the code in the onset detection tutorial worked with PCM data encoded as floats in the range [-1,1]. We want to emulate this here so i wrote a little method called AndroidAudioDevice.writeSamples( ) which takes a float array of mono float PCM samples and writes it to the hardware. For this the float samples have to be converted to 16-bit signed PCM which is done in the AudioDevice.fillBuffer() method, no rocket science here. Once we have the converted PCM we simply write it to the AudioTrack via AudioTrack.write() which takes a short array (our PCM samples), an offset into the array and the number of samples to use from the offset on. Extremely simple, even more than the equivalent Java Sound class SourceDataLine.

Now there’s a couple of interesting things about AudioTrack. First off, if you don’t write to it constantly it will pause itself to not hog any further system resources. Upon the next write it will start playback again introducing a wake up lag. Another not so nice thing is that due to the internal buffer of AudioTrack which has to be filled up completely before it is send to the hardware there’s noticeable lag between the time you write your first samples and when you hear them being played back. The minimum buffer size i get on my Milestone for the configuration is 8192, sadly the documentation doesn’t tell wheter that’s bytes or samples. If it’s bytes we divide that by two and then by the sampling frequency to get the total lag introduced by the internal buffer: 8192 / 2 / 44100 = 0,092 seconds, so nearly 100ms. That’s noticeable. In case the minimum buffer size is in samples it get’s even worse. The lag will be 200ms in that case. So that’S what you have to expect when using this class. Writting a software mixer based on AudioTrack is possible as long as you don’t need low latency. Synthesizing sounds each time the screen is touched for example is a bad idea as the lag is more than noticeable. Another property of AudioTrack is that the AudioTrack.write() method blocks. If you want to use it in a game you should do all your audio mixing in a seperate thread.

Still, the class is pretty niffty and it makes it easy to port all the examples from the onset detection tutorial to Android. I tested it with the WaveDecoder class and it worked like a charm, not eating up to much system resources while doing its thing. Here’s the sine wave generator sample ported to android:

You can also try to use the decoders included in the tutorial framework, however, the pure Java MP3 decoder will be to slow. Only the WaveDecoder works acceptably. When i’m done porting all decoders to C++ i might put out a small Android audio library so you can benefit from that a bit. Now go out and play :)

  • Henry

    It seems that the clicking sound issue had not been resolved. I think it is a physics problem. The clicking sound is a good indicator that the start or stop of the wave file did not start or end at an angle of zero. It will be fairly easy to control with a single tone, but if you have a wave file with more than one tone combined, the only way to stop or start this without a click is to control the amplitude of the sound during startup and ending.

    Sorry I am not a good programmer and I cannot give you a lot of code support, but I remember reading about a variable used to control the amplitude of audio output in the SDK.

    Hope this helps.

  • Henry

    Marios,

    Looks like I have given credit to Mike instead of you for posting the original code. My apology to you. And if you don’t mind, could you help give a pointer or two on where I can start to look?

  • Andy

    I second what Mark says… I’m in the same camp as he and MrSmither – namely, I get all kinds of static when trying to play a .wav file and would be utterly interested to find out how MrSmither solved it. (my code is essentially just like MrSmither’s code that he posted when he was originally having the problem)

    I have a mono audio file @ 11025 khz and 16bit PCM. I’ve verified and thrown away the .wav header and I can definitely hear the audio being played but, there is also a bunch of static… Would *love* to hear MrSmither’s solution of if you have any additional tips other than what you gave to MrSmither (also, fyi, I looked at your ‘audio-analysis’ code and still can’t see anything that I’m doing wrong)… I’ve spent days on this already.

  • Pingback: Android Stereo Right and Left PCM Raw Audio » James Becwar

  • Greg

    Whats the alternative to the AudioTrack if latency is a concern?

  • http://badlogicgames.com Mario

    There is none. In 2.3 they expose OpenSL via the NDK. Problem is it still has the same latency characteristics as Audiotrack. There is no cure i’m afraid. Android + Audio still sucks.

  • SqlNutkin

    Try

    http://devblog.cellls.com/?p=190

    The trick to removing the glitching in clicking is to create the AudioTrack with a bufferSizeInBytes of 4 times the minBufferSize. It may or may not help to not call track.play until the first buffer is written.

  • Appel

    Hi,

    I’m facing the problem that Eclipse doesn’t allow me to use AudioDevice (AudioTest => line 20).

    It is giving me the following error:
    “AudioDevice cannot be resolved to a type”

    I hope that anyone of you guys know what the problem could cause.

    Thanks in advance,

    Jelmerm, and the rest of my project members.

  • Halsafar

    I know this seems to be a common problem. I have basically implemented your example as is and I’m getting a clicking as it plays. I am on an HTC Incredible S. This is either missing samples or a hardware problem?

  • Halsafar

    Just to expand. I proved my samples are coming in correctly by dumping them to a file. It seems audio track just stinks at keeping up or something. Oddly enough if I lock my phone my app keeps going in the background (this is desired). When the phone is locked and I’m looking at the unlock screen (scroll bar down, draw pattern, pin, etc) the Audio goes perfect at this screen. Once I bring my app into the foreground again audio gets a bit stuttery again.

  • Stephan Celis

    Maybe the tutorial should have mentioned that you need to add the permission in the manifest file

    If you have problems with initializing the AudioDevice it could be that you don’t have permission to access the hardware

  • nikos p

    The right way to remove glitching is to avoid calling Math.sin() on runtime. Store the values for a period of a sine wave in an array and then copy these values to the samples array in the while loop.

  • David Zaki

    is this useful in streaming music over BT for Android apps?

  • TheForgottenOne

    package com.baglogic.audio;

    import android.media.AudioFormat;
    import android.media.AudioManager;
    import android.media.AudioTrack;

    public class AndroidAudioDevice
    {
    AudioTrack track;
    short[] buffer = new short[1024];

    public AndroidAudioDevice( )
    {
    int minSize =AudioTrack.getMinBufferSize( 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT );
    track = new AudioTrack( AudioManager.STREAM_MUSIC, 44100,
    AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT,
    minSize, AudioTrack.MODE_STREAM);
    track.play();
    }

    public void writeSamples(float[] samples)
    {
    fillBuffer( samples );
    track.write( buffer, 0, samples.length );
    }

    private void fillBuffer( float[] samples )
    {
    if( buffer.length < samples.length )
    buffer = new short[samples.length];

    for( int i = 0; i < samples.length; i++ )
    buffer[i] = (short)(samples[i] * Short.MAX_VALUE);;
    }

    }

  • http://k1gto.blogspot.com BH

    Thank you very much for this tutorial! I have used it to learn about Android sound, and produce my first Android sound app.

  • http://A Robotarmy

    Awesome job explaining – really helps to understand what is happening in android sound!!!

    Thank you!!

  • Umaid

    https://www.dropbox.com/s/yussgjvhx392a9s/AndroidAudioTest.zip Creating issue in stopping or release AudioTrack