Monday, October 18, 2010

Fun with Android AudioTrack

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:
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);;
   }  
}
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:
public class AudioTest extends Activity
{     
   @Override
   public void onCreate(Bundle savedInstanceState) 
   {
      super.onCreate(savedInstanceState);                      
 
      new Thread( new Runnable( ) 
      {
         public void run( )
         {          
            final float frequency = 440;
            float increment = (float)(2*Math.PI) * frequency / 44100; // angular increment for each sample
            float angle = 0;
            AudioDevice device = new AndroidAudioDevice( );
            float samples[] = new float[1024];
 
            while( true )
            {
               for( int i = 0; i < samples.length; i++ )
               {
                  samples[i] = (float)Math.sin( angle );
                  angle += increment;
               }
 
               device.writeSamples( samples );
            }         
         }
      } ).start();
   }
}
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 :)

Source: apistudios

0 comments:

Post a Comment

Note: Only a member of this blog may post a comment.

 

sitemeter