Pages

Monday, February 3, 2014

My adventures on playing and looping sounds on android, part 1

General wisdom

If you want to do simple looping, use SoundPool and MediaPlayer, they both have a function to do this.

If you want to do low-level manipulations of samples, .e.g. mucking around with byte arrays etc., adding effects, to create your own programmatic sound samples, use AudioTrack.

Use JetPlayer for midi tracks.

I'll provide some code about SoundPool and MediaPlayer to give a general idea.

AudioTrack and JetPlayer both deserve their own blog entry... TODO .

Soundpool

public class MainActivity extends Activity implements OnLoadCompleteListener {
    private final static int INVALID_STREAM_ID = -1;

//...

    private SoundPool soundPool;
    private int streamId = INVALID_STREAM_ID;
    private void setupSound() {
        final int SINGLE_STREAM = 1; // you can use multiple
        soundPool = new SoundPool(SINGLE_STREAM, AudioManager.STREAM_MUSIC, 0);
        soundPool.setOnLoadCompleteListener(this);
        // R.raw.click is located in res/raw/click.ogg
        streamId = soundPool.load(this, R.raw.click, 1); // load the sonic content
    }

//...

    private final static int SP_PLAY_ONCE = 0;
    private final static int SP_PLAY_LOOP = -1;
    /**
     *
     * This part below actually starts playing the sound,
     * otherwise you risk the chance of failing
     * to play because the system hasn't
     * finished loading yet
     */
    @Override
    public void onLoadComplete(SoundPool pool, int id, int status) {
        // to loop, see SP_PLAY_LOOP and replace SP_PLAY_ONCE
        pool.play(id, .5f, .5f, 1, SP_PLAY_ONCE, 1.0f);
    }

    @Override
    protected void onDestroy() {
        if (soundPool != null)
        {
            if (streamId != INVALID_STREAM_ID)
                soundPool.unload(streamId);
            soundPool.release();
        }
        super.onDestroy();
    }

You can also use the onLoadComplete callback to do specific stuff e.g. to start looping stuff under certain conditions, set a delay before playing the sonic content, etc.

MediaPlayer

public class MainActivity extends Activity implements onCompletionListener {

private MediaPlayer mediaPlayer;

// ...

   mediaPlayer = MediaPlayer.create(this, R.raw.click);
   // mediaPlayer.setLooping(true);
   mediaPlayer.start();
  // to reuse the mediaPlayer object, with a different sound
  // you have to #reset, #setDataSource #prepare

// ...

    @Override
    public void onCompletion(MediaPlayer mp) {
        // do stuff...
    }

    @Override
    protected void onDestroy() {
        if (mediaPlayer != null)
        {
            mediaPlayer.reset();
            mediaPlayer.release();
        }
        super.onDestroy();
    }

Getting the media file length in milliseconds

private int getSoundFileLengthInMs(int resId)
{
    MediaPlayer mp = MediaPlayer.create(this, resId);
    int duration = mp.getDuration();
    mp.release();
    return duration;
}

Stuff I learned from trial and error

  1. Do not combine Animation callbacks to do your timing with your sounds if you're doing precision work, in the order of milliseconds. Do not use those callbacks with anything important. Ever. I don't know why I have to relearn that lesson...
  2. Looping sounds using callbacks with either MediaPlayer's OnCompletionListener or some combination of SoundPool and Handler and Runnables, will not give you enough metronome like precision.
  3. Do not use TimerTask, to loop and set delays. The constant allocation of objects will cost you cpu-time and memory. Use a single Handler and single Runnable, to set delays.
    private MediaPlayer mediaPlayer; // initialized and prepared somewhere else
    
    //...    
    
        private Handler handler = new Handler();
        private Runnable runnable = new Runnable() {
            @Override
            public void run() {
               // do stuff beforehand...
               mediaPlayer.start();
            }
        };
    
    //...
    
        handler.postDelayed(this, delayInMs);
    
  4. AudioTrack is the only viable solution if you're planning on building a dynamic metronome application, due to the high degree of control.

The end!

No comments:

Post a Comment

Please help to keep this blog clean. Don't litter with spam.