Android Development

Creating Sound Effects in Android: Part 1

by Stephen Flockton on Oct.20, 2009, under how to, tutorial

In today’s tutorial I am going to show you my method of creating, managing and using sound effects in Android. In this first part I’ll show you the basic method of encapsulating your sound management code. This method works best when you have a typical application, or simple game all in one thread.Part 2 will show you a more advanced way to manage your sound across multiple classes.

The method I use to play sounds is to use the Sound Pool classes rather then the Media Player classes that the Android dev-guide seems to suggest. While there is nothing wrong with using the Media Player classes for simple applications they did not provide the flexibility I needed.

Before I get into the code I will recommend using .ogg files for your sound and music. There have been reports that Sound Pool is unstable for other file types although I was unable to confirm that myself.Even if there was no problem with other file types I would still recommend .ogg for the excellent file compression.

Now on with the code. The first thing we need to do is to create a Sound Manager class to encaspulate all the sound code.

public class SoundManager {
 
private  SoundPool mSoundPool;
private  HashMap mSoundPoolMap;
private  AudioManager  mAudioManager;
private  Context mContext;

This first bit of code is to set up the member variables needed:

mSoundPool: The Android provided object we use to create and play sounds.
mSoundPoolMap: A Hashmap to store the sounds once they are loaded.
mAudioManager: A handle to the service that plays the sounds we want.
mContext: A handle to the application Context.

public void initSounds(Context theContext) {
    mContext = theContext;
    mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
    mSoundPoolMap = new HashMap();
    mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
}

This first method sets up the Sound Manager. We start by passing in the Context of the application and getting a handle to it. The next line Create the sound pool object. only the first argument is interesting.The Int passed here states how many concurrent audio streams you want.In other words how many sounds can play at once.If you try to play more then this number then it stops the oldest stream.

The next two lines simply create the Hash map and handle to the audio-manager.

public void addSound(int index, int SoundID)
{
    mSoundPoolMap.put(index, mSoundPool.load(mContext, SoundID, 1));
}

This function is where we start adding sounds. Each sound gets an index that is used to play it back. Make sure you give each of your sounds different indexes. The SoundId argument is from your raw resources file.You can pass this by calling R.raw.mysound to get the id for a sound called mysound for example. I’ll show how to use this function fully later in the tutorial.

public void playSound(int index)
{
float streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
streamVolume = streamVolume / mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    mSoundPool.play(mSoundPoolMap.get(index), streamVolume, streamVolume, 1, 0, 1f);
}
 
public void playLoopedSound(int index)
{
    float streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    streamVolume = streamVolume / mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    mSoundPool.play(mSoundPoolMap.get(index), streamVolume, streamVolume, 1, -1, 1f);
}

These two functions control sound playback. You simply pass in the sounds index number that you created in addSound(). The steamVolume variable is used to get the current volume set for media on the device.This is because you don’t want the audio playback to be louder or quieter then the user as set the phone to.

You need to divide it by the streams maximum value in order to get a float value between 0.0 and 1.0 that the play function requires (Thank you to Gyuri for pointing that out for me).

The mSoundPool.play function is quite simple.You first pass the index of the sound you wish to play(remember that you set this index yourself in addSound). The second two are the left and right volumes respectively.I would advise against setting these to anything other then the stream volume unless you have good reason.

The next variable is the priority of the sound. This is currently not implemented so setting it to one is fine. The next variable is more useful as it defines if the sound is looped or not. A -1 will cause it to loop and and a 1 will cause it to be a number single play. This is the reason for the two almost identical functions. I wanted one that lets me loop a sound if I wished.

The final variable affects the rate of play. 1 is normal speed and the range goes from 0.5 to 2.0 for between half and double speed play.

Using the class is very simple. All you need to do is create a member variable where you need you sounds. Once you have that you can initialise the sound manager and start adding sounds as show below.

mSoundManager = new SoundManager();
mSoundManager.initSounds(getBaseContext());
mSoundManager.addSound(1, R.raw.sound);

Then to play a sound simply call play with is index.

mSoundManager.playSound(1);

That is all there is to it. I have created a simple project to illustrate the use of the class and its available here: SoundTutorial.zip.

Look out for part two where I’ll explain more advanced version of this class.

Stephen Flockton

  • Share/Bookmark
:, , ,

21 Comments for this entry

  • Gyuri

    Note that SoundPool.play takes a float for the volume, range 0.0 to 1.0. AudioManager.getStreamVolume returns an int, which you most likely need to divide (as a float) by AudioManager.getStreamMaxVolume for it to properly be in the 0 to 1 range.

  • Stephen Flockton

    That is a very good point I failed to notice, I’ll update the code to reflect the change thanks for your input.

  • Herman

    It seems the tutorial doesn’t use a typed HashMap for mSoundPoolMap but the ZIP you supplier does.

    Without the typed HashMap the application won’t compile.

    Good tutorial.

  • Stephen Flockton

    It seems WordPress removed the type declaration because it thought it was a formatting tag.

    Thanks for pointing it out hope the tutorial is useful for you.

  • K Diddy

    Hi Martin,

    Great post once again. Thanks Do you happen to know what restrictions there are on sound files in terms of size. I have an app that is going to have 9 3-5mb size mp3 files in it. Basically it’s music a friend of mine has made. My emulator force closes when I use the media player, saying it’s out of space. Would sound pool be better to use in this situation? Do you know if there is a limit to the amount of sound files you can have in an app. I was thinking about just creating the app then putting the sound files in right before I compile it and hope for the best. What do you think?

    Thanks,
    K

  • Martin

    Hi,
    I’m sorry, but the post is made by Stephen. I never worked with suh file sizes. Maybe Stephen has an answer.

  • Stephen Flockton

    Hi K Diddy,

    I don’t have access to my Dev PC at the moment to test it for you but I suspect that it will run out of memory using soundpool as well.

    I would strongly recomend you cut down on the size of the sound files. The Android VM only has so much memory for applications. 9, 3mb MP3’s is 27mb which is huge for an app.

    I would first convert the Mp3’s to .ogg files to get them smaller and because Android has better support for .ogg then MP3’s.

    If your app is dependent on so much sound then your best bet is to load them on the SDCard and decode and release them as you need.

    Hope this helps

    Stephen

  • K Diddy

    Thanks Martin. Do you have any insight on how to do the SD card thing? I mean when someone runs the app is their functions that I would use to get that data onto the SD from the apk?

    Also, do you think the limit is based on the mp3’s only? I mean Garmin came out with an app for the iPhone which is 1.2 gigs. How can they do that but my app is too big? Does Android really limit app size?

    Thanks!
    K

  • Stephen Flockton

    Hi K Diddy,

    I’m Stephen not Martin btw :) .

    From what i’ve read (not tested myself)the maximum practical size for an app is 100MB,this is because a lot of the phones only have 200mb internal memory.

    I’m assuming the Garmin app stores most of the maps on the SDCard.

    In either case the bottleneck for you is not the application size (although I would be careful with that, not many people want to download a 30mb app unless they really want it).

    But instead the bottleneck for you is how much VM Memory you have.If you load only the sounds you need in time to use them and release them when they are not needed you ’should’ be able to get around the lack of VM memory.

    If you threaded the loading you would also get rid of loading time pauses.

    Hope this helps

    Stephen

  • K Diddy

    Sorry about that Stephen. I wasn’t even paying attention the names. Thank you for your reply. In terms of loading them when I need them, I had actually thought that was what I was doing. When the app itself loads(and I can get it to load with less sound files in it) I only set the mediaplayer and tracks when they are selected, so only one is running. When it’s done I run release. My issue is that the app won’t even load when all of the tracks are in the raw folder, I get an error in eclipse saying there isn’t enough memory so it doesn’t even run and I don’t even get far enough to get a force close.

    Any thoughts?

    Thanks so much for your feedback.

    K

  • Stephen Flockton

    Hmmm I was hoping that wouldent be the case but I don’t have my Dev PC to test it for you.

    The only solution I can think of is that you would have to provide the files as an extra download and get them from the SD-card.

    I would check on the google code forums for work-arounds, I should think this kind of problem is quite common so someone may be better able to help you.

    Good Luck

    Stephen

  • Konstantin

    Thanks for useful post. I think addSound function has a typo. The first parameter in “put” function must be “Index” not “1″.
    I’ve also faced a problem while adding sound effects to my app. Sometimes when I click a button which has “playSound” in its OnClickListener, the sound is played twice(one directly after another). The problem occurs occasionally. Any ideas on that?

  • Stephen Flockton

    Hi Konstantin,

    Your right the line should read.

    mSoundPoolMap.put(index, mSoundPool.load(mContext, SoundID, 1));

    Thanks for pointing that out I’ll change it.

    As for your other problem I’ve not encountered it myself. Possibly the event is getting fired more then once for some reason?

    Stephen

  • Konstantin

    Hi Stephen,

    I used mp3 file instead of wav and the problem is not reproducible anymore. It seem that sometimes android fails to process wav correctly and plays it twice.

  • Kevin

    Hey guys,

    This may be slightly off topic.. and it’s a while since anyone replied.. but I am looking to do an MPC drum machine like app. I have started working on a basic app that will allow a user to load many samples/sounds for banks of drum pads to be used. After reading this, I am concerned that I am going to be heavily limited on Android for an app like this. There are several apps on iPhone, the best one that I can refer to is BeatMaker. It’s quite robust and has very good timing. I am looking to do something similar, but have a number of spins I’d like to apply. For one, I want more sounds available. Is Android severely limited compared to iPhone for applications like this?

    The other issue I am worried about is how to get very good timing for such an app. Is there any built in android timers that I haven’t found yet, that give pretty precise timing? I know that on iPhone, you’re basically using native code and not in a VM. With Android, being in a VM, I am concerned how much timing I can get in order to handle faster tempos during playback, and accurate timing of multiple sounds playing at the same time. I believe I will have to software-mix the various sounds that play at the same time, but am unsure where to look for such accuracy in a timer for Android. Any advice would be greatly appreciated.

  • Kubeczek

    You are doing great job on this blog. Waiting for part 2 :)

  • Kubeczek

    Uh.. I have some problem with a little more longer file. I have an ogg (1.5mb) witch should by ‘background music’. But when i try to play it with this metod it plays only few seconds… in LogCat i get a lot of ‘11-25 00:59:16.927: ERROR/AudioCache(126): Heap size overflow! req size: 1076224, max size: 1048576′ during loading.
    So I get another twice smaller ogg (850kb) and try… but with the same results. Is there any way to play this long sounds using this metod? Any suggestions?

  • Kubeczek

    Ok i find some information about this, here there are: http://osdir.com/ml/AndroidDevelopers/2009-06/msg03382.html.
    Shortly SoundPool is not designed for long sounds. It completely decompressed sound while loading so it get much bigger in the memory. Is there any better way to handle music than MediaPlayer? MediaPlayer makes my program laggy :( .

  • Stephen Flockton

    Hi Kubeczek,

    Part 2 will be up soon, I’m just crazy busy at the moment its hard to find time for everything.

    As for your problem, this code is not really suited for anything more the sound effects and I’m afraid media-player is your best bet for background music.

    Android sound support is pretty poor at the moment especially for Games hopefully they will patch some better support in for the ext version.

    Stephen

  • Mark

    Hi,

    I’m having trouble changing the index to anything but one. I changed the line

    mSoundPoolMap.put(1, mSoundPool.load(mContext, SoundID, 1));

    to

    mSoundPoolMap.put(index, mSoundPool.load(mContext, SoundID, 1));

    but even then, when I change

    mSoundManager.addSound(1, R.raw.sound);

    to

    mSoundManager.addSound(2, R.raw.sound);

    and change

    mSoundManager.playSound(1);

    to

    mSoundManager.playSound(2);

    (just change the index from 1 to 2), my program force closes every time I click the button. Any idea why this might be? I’m trying to add two different sound effects to my program, for something like a sound board.

    Thanks,
    Mark

  • Witter

    Hi Steven,

    I was trying for ages to get multiple sounds with the MediaPlayer class and came across your blog. Brill!

    One thing though in playSound I had to change this line

    mSoundPool.play(mSoundPoolMap.get(index), streamVolume, streamVolume, 1, 0, 1f);

    to

    mSoundPool.play(mSoundPoolMap.get(index).hashCode(), streamVolume, streamVolume, 1, 0, 1f);

    because ‘play’ expects an int as param 1 and ‘get’ returns an object.

    Dunno if that is the best way but it seems to work!

    Cheers

    Witter

Leave a Reply