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
October 20th, 2009 on 7:00 pm
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.
October 20th, 2009 on 7:38 pm
That is a very good point I failed to notice, I’ll update the code to reflect the change thanks for your input.
October 21st, 2009 on 2:28 pm
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.
October 21st, 2009 on 3:24 pm
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.
October 28th, 2009 on 1:38 am
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
October 28th, 2009 on 6:50 am
Hi,
I’m sorry, but the post is made by Stephen. I never worked with suh file sizes. Maybe Stephen has an answer.
October 28th, 2009 on 12:20 pm
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
October 29th, 2009 on 1:57 am
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
October 29th, 2009 on 12:31 pm
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
October 29th, 2009 on 3:53 pm
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
October 29th, 2009 on 6:04 pm
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
November 6th, 2009 on 8:57 am
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?
November 6th, 2009 on 12:14 pm
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
November 11th, 2009 on 3:54 pm
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.
November 23rd, 2009 on 7:24 pm
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.
November 25th, 2009 on 12:30 am
You are doing great job on this blog. Waiting for part 2
November 25th, 2009 on 1:12 am
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?
November 25th, 2009 on 1:33 am
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
November 25th, 2009 on 1:43 pm
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
December 15th, 2009 on 2:26 am
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
January 21st, 2010 on 4:55 pm
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