Nexus Lollipop soundpool.load() not so sweet

When I got my first taste of the Android L pre-release I noticed something rather disturbing when running some games.  I was using a Nexus 5 and every now and again a long pause where an app would become non-responsive. Sometimes it was a couple of seconds and other times it was 10, 20 or more seconds without response, then it would finally burst back to life. After doing a little digging and I tracked it to an issue with the Nexus family using the SoundPool.load() function.  This issue lives on with the latest Android 5.0.2 build.

Issue Description

The Android SoundPool class allows developers to pre-load several short sound clips so the may be played at any time without the lag of decompressing on the fly.  This is perfect for game developers where sounds may be played many times. On the Nexus family of devices, the execution of the SoundPool.load() function may take several seconds to load a very small audio file, instead of the few milliseconds as has been the case with the same files on previous Android versions, or with other devices running Lollipop.  The issue relates to the ‘AwesomePlayer’ media engine. There is a way to confirm if this is an issue in your code.  Go to the media section of the developer options and check the ‘Use NuPlayer (experimental)’ checkbox.  Run your app again and you should see none of the unresponsiveness that has lead you to this post. If the delay remains it is likely not being caused by the SoundPool.load() issue.

Issue Status

As of Android 5.0.2 this is an unresolved issue in the Android Open Source Project (AOSP) issue tracker. It’s can be reproduced on most of the Nexus devices that have support for 5.0, from the Nexus 4 through to 5, 7 (2012 & 2013) and 9.  I have not been lucky enough to get my hands on a Nexus 6 to confirm if it suffers the same affliction.

Poor app design exposed

The Android media engine requiring seconds to load a small sound file is clearly not acceptable, but what it does is exposes apps that are executing the sound pool loading code from the UI thread. For a few small audio files that were taking a few milliseconds each, this was never noticed. But best practice dictates load functions like this should be happening on a background thread. Structure you app so the thread has maximum time to load all it needs to load without needing to hold up the user.  Once this change is made chances are this issue will no longer be a problem in your app and you won’t be waiting for the AOSP fix.

Changes to SoundPool in API 21

While on the subject of the SoundPool class, Android 5.0 (API 21) also brings some recommended usage changes.  So read on if you have not changed the way you create your SoundPool on Lollipop devices. If you take a look at the SoundPool interface the constructor public SoundPool(int maxStreams, int streamType, int srcQuality) is now deprecated. The recommended usage is to create and configure the SoundPool instance using the SoundPool builder class (SoundPool.Builder) The SoundPool Builder is only available on API 21+, so you will need to create your SoundPool differently based on the SDK version of the device your app is running on. When your code is executed on devices running 5.0+ create the SoundPool using the builder, as follows:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
protected void createSoundPoolWithBuilder(){
    AudioAttributes attributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_GAME)
        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
        .build();

    mSoundPool = new SoundPool.Builder().setAudioAttributes(attributes).setMaxStreams(6).build();
}

For devices running 4.4 and older use the SoundPool constructor:

@SuppressWarnings("deprecation")
protected void createSoundPoolWithConstructor(){
    mSoundPool = new SoundPool(6, AudioManager.STREAM_MUSIC, 0);
}

Check the device version to call the appropriate function to create the SoundPool:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    createSoundPoolWithBuilder();
} else{
    createSoundPoolWithConstructor();
}

Hopefully this has sorted your Android 5.0 SoundPool woes.

Update March 2015: This issue was still present for my Nexus devices after initially updating to Android 5.1, although it seems on a clean install of 5.1 the default media engine is now NuPlayer which does not have exhibit the SoundPool issue.

lollipop

4 Comments

Filed under Android 5.0, Android SDK, Audio, SoundPool

4 responses to “Nexus Lollipop soundpool.load() not so sweet

  1. Andres

    “Once this change is made chances are this issue will no longer be a problem in your app and you won’t be waiting for the AOSP fix.”
    Unless there is something I’m missing, this does not solve the problem AT ALL. My game has almost 100 small sounds. Normally I can load the all pretty much instantly. Even if I load them all ahead of time, I still have to wait until all are loaded before the game can start… right now even if I do it on a background thread it takes almost 2 minutes. Nobody waits that long for an app to load….

    Like

    • You’re right @Andres, what I detail is not a fix for the issue, it is a workaround as there is no known fix. What I do is start loading the sound pool as soon as the app starts and then by the time the user navigates and starts the game, the sounds should have finished loading, if the load has not finished I start the game regardless, not an ideal user experience but better than making the user wait. In your case 2 minutes is a really long time and as you say it is not acceptable to make a user wait that long. You may need to get creative and only load a few of the most common sounds into the SoundPool before starting and then continue to load the remainder of the sounds while the game is in play.

      Like

  2. Pingback: Gravity Grid Dev Update: Faster load times – Space Turkey Games

  3. Pingback: Gravity Grid Dev Update: Faster load times – LAWSONRY

Leave a comment