Playing Sound Effects

In Chapter 3 we discussed the difference between streaming music and playing back sound effects. The latter are stored in memory and are usually no longer than a few seconds. Android provides us with a class called SoundPool that makes playing back sound effects really easy.

We can simply instantiate a new SoundPool instances as follows: SoundPool soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);

The first parameter defines how many sound effects we can play simultaneously at most. This does not mean that we can't have more sound effects loaded, it only restricts how many sound effects can be played concurrently. The second parameter defines which audio stream the SoundPool will output the audio to. We choose the music stream that we have set the volume controls for as well. The final parameter is currently unused and should default to 0.

that we have set the volume controls for as well. The final parameter is currently unused and should default to 0.

To load a sound effect from an audio file into heap memory, we can use the SoundPool.load() method. We store all our files in the assets/ directory, so we need to use the overloaded SoundPool.load() method, which takes an AssetFileDescriptor. How do you we get that AssetFileDescriptor? Easy, via the AssetManager we worked with before. Here's how we'd load an OGG file called explosion.ogg from the assets/ directory via the SoundPool:

AssetFileDescriptor descriptor = assetManager.openFd("explosion.ogg"); int explosionId = soundPool.load(descriptor, 1);

Getting the AssetFileDescriptor is straightforward via the AssetManager.openFd() method. Loading the sound effect via the SoundPool is just as easy. The first argument of the SoundPool.load() method is our AssetFileDescriptor, and the second argument specifies the priority of the sound effect. This is currently not used, and should be set to 1 for future compatibility.

The SoundPool.load() method returns an integer, which serves as a handle to the loaded sound effect. When we want to play the sound effect, we specify this handle so the SoundPool knows what effect to play.

Playing the sound effect is again very easy: soundPool.play(explosionId, 1.0f, 1.0f, 0, 0, 1);

The first argument is the handle we received from the SoundPool.load() method. The next two parameters specify the volume to be used for the left and right channels. These values should be in a range between 0 (silent) and 1 (ears explode). Next come two arguments we'll rarely use. The first one is the priority, which is currently unused and should be set to 0. The other argument specifies how often the sound effect should be looped. I wouldn't recommend looping sound effects, so you should generally use 0 here. The final argument is the playback rate. Setting it to something higher than 1 will play back the sound effect faster than it was recorded, and setting it to something lower than 1 will play back the sound effect slower.

When we don't need a sound effect anymore and want to free some memory, we can use the SoundPool.unload() method:

soundPool.unload(explosionId);

We simply pass in the handle we received from the SoundPool.load() method for that sound effect and it will get unloaded from memory.

Generally we'll have a single SoundPool instance in our game, which we'll use to load, play, and unload sound effects as needed. When we are done with all our audio output and don't need the SoundPool anymore, we should always call the SoundPool.release() method, which will release all resources the SoundPool uses up. After the release you can't use the SoundPool anymore, of course. Also, all sound effects loaded by that SoundPool will be gone.

Let's write a simple test activity that will play back an explosion sound effect each time we tap the screen. We already know everything we need to know to implement this, so Listing 4-9 shouldn't hold any big surprises.

Listing 4-9. SoundPoolTest.java; Playing Back Sound Effects package com.badlogic.androidgames;

import java.io.IOException;

import android.app.Activity;

import android.content.res.AssetFileDescriptor;

import android.content.res.AssetManager;

import android.media.AudioManager;

import android.media.SoundPool;

import android.os.Bundle;

import android.view.MotionEvent;

import android.view.View;

import android.view.View.OnTouchListener;

import android.widget.TextView;

public class SoundPoolTest extends Activity implements OnTouchListener { SoundPool soundPool; int explosionId = -1;

^Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView textView = new TextView(this); textView.setOnTouchListener(this); setContentView(textView);

setVolumeControlStream(AudioManager.STREAM_MUSIC); soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);

AssetManager assetManager = getAssets(); AssetFileDescriptor descriptor = assetManager

.openFd("explosion.ogg"); explosionId = soundPool.load(descriptor, 1); } catch (IOException e) {

textView.setText("Couldn't load sound effect from asset, " + e.getMessage());

^Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { if (explosionId != -1) {

return true;

We start off by deriving our class from Activity and letting it implement the OnTouchListener interface so we can later process taps on the screen. Our class has two members: the SoundPool, and the handle to the sound effect we are going to load and play back. We set that to -1 initially, indicating that the sound effect has not yet been loaded.

In the onCreate() method, we do what we've done a couple of times before: create a TextView, register the activity as an OnTouchListener, and set the TextView as the content view.

The next line sets the volume controls to control the music stream, as discussed before. We then create the SoundPool and configure it so it can play 20 concurrent effects at once. That should suffice for the majority of games.

Finally we get an AssetFileDescriptor for the explosion.ogg file I put in the assets/ directory from the AssetManager. To load the sound, we simply pass that descriptor to the SoundPool.load() method and store the returned handle. The SoundPool.load() method throws an exception in the case something goes wrong while loading, in which case we catch that and display an error message.

In the onTouch() method we simply check whether a finger went up, which indicates that the screen was tapped. If that's the case and the explosion sound effect was loaded successfully (indicated by the handle not being -1), we simply play back that sound effect.

When you execute that little activity, simply touch the screen to make the world explode. If you touch the screen in rapid succession, you'll notice that the sound effect is played multiple times in an overlapping manner. It would be pretty hard to exceed the 20 playbacks maximum we configured the SoundPool with. However, if that happened, one of the currently playing sounds would just be stopped to make room for the new requested playback.

Notice that we didn't unload the sound or released the SoundPool in the preceding example. This is for brevity. Usually you'd release the SoundPool in the onPause() method when the activity is going to be destroyed. Just remember to always release or unload anything you no longer need.

While the SoundPool class is very easy to use, there are a couple of caveats you have to be aware of:

The SoundPool.load() method executes the actual loading asynchronously. This means that you have to wait for a little bit before you call the SoundPool.play() method with that sound effect, as the loading might not be finished yet. Sadly there's no way to check when the sound effect is done loading. That's only possible with the SDK version 8 of SoundPool, and we want to support all Android versions. Usually it's not a big deal, though, as you will most likely load other assets as well before the sound effect is played for the first time.

SoundPool is known to have problems with MP3 files and long sound files, where long is defined as "longer than 5 to 6 seconds." Both problems are undocumented, so there are no strict rules for deciding whether your sound effect will be troublesome or not. As a general rule I'd suggest sticking to OGG audio files instead of MP3s, and trying for the lowest possible sampling rate and duration you can get away with before the audio quality becomes poor.

NOTE: As with any API we discuss, there's more functionality in SoundPool. I briefly told you that you can make sound effects loop. For this you get an ID from the SoundPool.play() method that you can use to pause or stop a looped sound effect. Check out the SoundPool documentation on the Android Developers site if you need that functionality.

0 0

Post a comment