Playing Sounds in Android
Let's take a closer look at how to play sounds on an Android device with SoundPool and MediaPlayer.
Join the DZone community and get the full member experience.
Join For FreeWhether you created a game and would like to add sound effects, or play music from your application, Android gives you a couple of options to do so, namely:
- The SoundPool
- The MediaPlayer
The general guidelines on which one to use and when, are that SoundPool is best for short sound clips (notifications sounds, sound effects in games), while MediaPlayer is better suited for larger sound files like songs.
And we wish things were that simple. Let's take a closer look at these two options, starting with the SoundPool. Say we have three short sound clips (.WAV,.MP3. etc..) that we would like to incorporate in our application. We first start by putting them in our application resources folder, by creating a sub-folder named "raw". We can then access the three sounds in our application:
package com.ts.sounds;
import android.media.SoundPool;
// other imports
// ...
public class OurSoundPlayer{
public static final int S1 = R.raw.s1;
public static final int S2 = R.raw.s2;
public static final int S3 = R.raw.s3;
}
Next, we introduce our SoundPool and store those three sounds in a map, to be able to access them each by their own key:
private static SoundPool soundPool;
private static HashMap soundPoolMap;
/** Populate the SoundPool*/
public static void initSounds(Context context) {
soundPool = new SoundPool(2, AudioManager.STREAM_MUSIC, 100);
soundPoolMap = new HashMap(3);
soundPoolMap.put( S1, soundPool.load(context, R.raw.s1, 1) );
soundPoolMap.put( S2, soundPool.load(context, R.raw.s2, 2) );
soundPoolMap.put( S3, soundPool.load(context, R.raw.s3, 3) );
}
What we did, was basically to construct a SoundPool with a maximum number of simultaneous streams of 2, and of type STREAM_MUSIC, which is what game applications would use. The last argument to the constructor is supposed to be for the sample-rate converter quality, but seems to have no noticeable effect. Now we can add our method for actually playing the sounds:
/** Play a given sound in the soundPool */
public static void playSound(Context context, int soundID) {
if(soundPool == null || soundPoolMap == null){
initSounds(context);
}
float volume = ....// whatever in the range = 0.0 to 1.0
// play sound with same right and left volume, with a priority of 1,
// zero repeats (i.e play once), and a playback rate of 1f
soundPool.play(soundPoolMap.get(soundID), volume, volume, 1, 0, 1f);
}
Now, we can play sounds by calling OurSoundPlayer.playSound(..) from anywhere in our application, by passing it the context and the sound we want. Looks simple enough. There is however a potential problem with using the SoundPool, as it might not play the sound, at least not the first time around, while we'll get the following message in our Android logs:
"sample x not ready"
'x' being the soundID in the pool. All we get is that message in the logs. No exception, no error message. Android simply doesn't play the sound. What is happening is that we can't play sounds immediately after initiating the loading process, because it needs to iterate through the list of sounds calling the appropriate SoundPool.load() method. The whole collection of samples needs to be loaded into memory in order to allow faster playbacks.
There are a number of solutions to this issue:
- Allow all the sounds to be loaded, either by initializing the pool early in the application flow, or adding a delay in code, like a wait counter
- If we' re using Android 2.2 (API 8) and up, implement SoundPool.setOnLoadCompleteListener to check when the loading is done
- Use the MediaPlayer. It uses no pool and loads the specific sound we want on request.
The first option is probably the most common, but the least desirable, since we have to determine the arbitrary time needed for the sound loading to complete. The second option requires us to implement a listener, so we'll know when we can play the sound. The last solution with MediaPlayer introduces a small delay, but solves the problem in a relatively simple way. Here's the revised OurSoundPlayer.playSound(..) method:
/** Play the sound using android.media.MediaPlayer */
public static void playSound(Context context, int soundID){
MediaPlayer mp = MediaPlayer.create(context, soundID);
mp.start();
}
The MediaPlayer has other methods to pause, reset and release the resources it uses. As it is usually the case, there are trade-offs in choosing MediaPlayer over SoundPool. MediaPlayer is slower, so it might not be ideal for games, but provides a number of callbacks, which give us more control over some events, like when the sound has finished playing, or if there was an error, for example.
With SoundPool we have no way of knowing when the sound has finished playing (unless we call SoundPool's stop method ourselves). We can however play several streams simultaneously, control the pitch of the sounds for Doppler or synthesis effects, and looping comes out-of-the-box (although that can be implemented pretty easily in code).
SoundPool is optimized to play a collection of small sound effects. But while MediaPlayer can play pretty much anything whether local or over the network, SoundPool is limited to small, local sound files, so playing songs for example is a no-op, and it is yet another case when Android doesn't give us errors or exceptions, but simply refuses to play the sound. Welcome to SoundPool's Sound of...Silence, which occurs when the sound hasn't finished loading, as well as when the sound file is too large.
But what's the audio file's size limit for SoundPool? The Android docs don't give any indication at the time of this writing, which makes using that class kind of an uneasy choice. If we don't hear our sounds, we need to downsize our audio files until we do.
Both MediaPlayer and SoundPool have their advantages and shortcomings. Using one or the other depends on the particular context where the application is used. Nothing of course prevents us from using both in different parts of a same application.
Note: this article is based on personal experience in developing Android applications for the most common Android versions on the market at this time (2.2 and 2.3, i.e. Froyo and Gingerbread) on the Samsung Galaxy.
From Tony's Blog.
Opinions expressed by DZone contributors are their own.
Comments