Playing sounds in iPhone games - slowdowns - iphone

I'm using the AVAudioPlayer to play sound FX on a separate thread in my app. Right now my app is fairly simple, I have a UIScrollView for a scrolling background, and a couple of CALayers that represent a player and an enemy in a simple side-scroller type game (think Mario or Kung Fu or whatnot)...I can move the player left and right, with the view scrolling appropriately with no problems. I have an "attack" button that's playing a sound effect...and many times when I play the sound effect, I get a little bit of a hitch, graphically. Is this just par for the course on the iPhone using Quartz? Is there some way to have this perform a little more smoothly without starting to investigate OpenGL?

Use NSData dataWithContentsOfMappedFile instead of dataWithContentsOfFile.
Also AVAudioPlayer handles it's own threading, you don't need to create one. [AVAudioPlayer play] returns after it started playing, not after it is done playing.
And don't forget to release your AVAudioPlayer when you no longer need it, for example if you only want to play a sound onces, implemement the audioPlayerDidFinishPlaying:successfully: delegate and release it in there.

How are you playing the sound? If I was to make a guess it would be that each time the sound is played the OS is actually loading it from disk as well as playing it.
Update:
Ok, you really don't need to be creating a new thread to play a sound. This alone could cause a stutter in a game and is also redundant - The AVAudioPlayer play method is asynchronous - it returns immediately and not when the sound ends.

I had the same problem with slowdown. To solve it, I first play and pause the sound when it first loads at volume 0 - this works better than prepareToPlay which still seems to result in slowdown:
player.volume = 0.0;
[player play];
[player pause];
player.volume = 1.0;
When it comes time to play a sound I just [player play];. Then every update cycle I loop through all my active sounds and catch ones that are about to end and rewind them instead of letting them expire - this avoids the overhead and slowdown of starting up the sound from scratch. The example here stops the sounds 1/15th of a second from the end of the sound:
for ({each player in active sounds})
{
if ((player.duration - player.currentTime) <= (1.0/15.0))
{
[player pause];
player.currentTime = 0.0;
}
}
Now it's ready to go for the next play. Seems to work fine!
Update: Turns out I was still getting some pauses the very first time the sound was played. I think playing it at zero volume wasn't actually doing anything, but I changed it to play at 0.0001 volume and then pause - it's inaudible but seems to do the job of prepping the sound.

Had the same problem at first. Per documentation you need to call "prepareToPlay" every time a sound has finished playing. Adopting the AVAudioPlayerDelegate protocol and adding the following method will do this.
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
[player prepareToPlay];
}

(Using an answer rather than a comment for length and so I can show code):
My sound playing method is as follows:
- (void)playSoundThreaded:(id)soundPlayer
{
if(soundPlayer == nil)
return;
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[(AVAudioPlayer*)soundPlayer play];
[pool release];
}
- (void)playSound:(AVAudioPlayer*)player
{
if(player == nil)
return;
[NSThread detachNewThreadSelector:#selector(playSoundThreaded:)
toTarget:self withObject:(id)player];
}
And the sound is loaded up as follows:
fxPlayer = [[AVAudioPlayer alloc] initWithData:
[NSData dataWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:#"voicehiya" ofType:#"caf"]] error:NULL];
We've had some inkling that the sound is being reloaded from disk every time...but have no idea as to why that is or how to prevent it.

I think the thread is over complicating your player. Try this for a go:
/* setup the sound player */
AVAudioPlayer *player1=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:audioFilePath] error:NULL];
player1.numberOfLoops = 0;
player1.volume=1.0f;
[player1 prepareToPlay];
and then playSound becomes
[player play];
And don't forget to have the AudioSession category set correctly too.

Why not just use AudioServicesPlaySystemSound? This is well suited to sound effects.
I use it in my game (vConqr) which is also based on a UIScrollView, with numerous other views (so all having their own CALayer), and it doesn't significantly impact performance.

I've been investigating this for a few days now thanks to a project I'm on at work. I added some home-rolled profiling to my code and found that the calls to play sounds were causing frame rate issues in my OpenGL game. I've got a few recommendations for you based on what I've found:
Use an AVAudioPlayer playing an mp3 for your background music.
Use OpenAL for your sound effects.
For sound effects, use the audio converter command line tool from apple to get your sounds to .caf format. This is an uncompressed format that doesn't require the CPU to decompress your sound to memory before sending it to the sound card. It can go directly, saving you time. Instructions on using the tool can be found HERE. (scroll to the very bottom of the page)
Use the lowest bit depths that still sound good. You don't really need 128 kbps - you can probably get away with 20 for a lot of things. Lower bit rate = less processing = faster sound.
I'll let you know if I find anything else that's useful! Good luck!

Related

Play overlapping sound in iOS

I'm using AVAudioPlayer to play a little shot sound when a user clicks a button. The sounds lasts about 3 seconds and I want that, if a user hit a button multiple times, the shot should sound multiple times. If the user clicks twice in 2 seconds, then the second sound should overlap the first shot.
My problem is that the shot only sounds every 3 seconds (if the user clicks rapidly) instead of every hit of the button.
Inside ViewDidLoad
NSString *path = [[NSBundle mainBundle] pathForResource:#"shot" ofType:#"caf"];
urlShotCaf = [NSURL fileURLWithPath:path];
player = [[AVAudioPlayer alloc] initWithContentsOfURL:urlShotCaf error:nil] ;
[player prepareToPlay];
And when a person clicks the shot button
- (IBAction)tap:(id)sender {
clicks++;
[player play];
}
Can I do this with AVAudioPlayer? Should I use another framework?
As stated in reference here:
Play multiple sounds simultaneously, one sound per audio player, with precise synchronization
I guess you need a AVAudioPlayer for every sound you want to play simultaneously. Otherwise you could use any simple library like CocosDenshion that is really simple, easy to embed and powerful (it resides on OpenAL).
Just do
[SimpleAudioEngine sharedEngine] playEffect:#"yoursound.wav"];
and you are done.
Please try this. It works for me.
- (IBAction) tap:(id)sender {
if ([player isPlaying]) {
[player stop];
[player setCurrentTime:0.0];
}
[player play];
}
If high level frameworks fail, then you can dip down to AUSampler - an AudioUnit sample player (typically used for playback of sampled instruments, drum sounds, and so on). This should have very fast response times and support multiple active notes. Configure the sample's playback as one-shot. When the button is pressed, simulate a note on event. You could also map different samples (audio recordings) to different notes and velocity ranges.

Problem firing method after AVAudioPlayer is finished playing

I saw some similar questions with different solutions, but I want to know if there's a way using something similar to what I've already come up with.
Essentially, a uiview will take one color when the player starts playing, and go back to the starting color when it is finished playing
- (void)playSoundWithColor {
self.image = self.pressedImage;
[audioPlayer play];
while ([audioPlayer isPlaying]) {
if (audioPlayer.currentTime == audioPlayer.duration)
self.image = self.startingImage;
}
}
The best way to do what you're trying to do is to make your class an AVAudioPlayerDelegate and to use
audioPlayerDidFinishPlaying:successfully:
That method is called right when the player finishes and doesn't have any continuous loop and memory issues. It will give you the most reliable and efficient performance overall.

Player for iPhone Programming?

I am using AVAudioPlayer in my program, but having touble in something
I have 10 sounds, and as I click one to play or then others don't stop, I tried to solve by
[myPlayer stop];
myPlayer = nil;
it works, but takes time, so what should I do to stop when it click 2nd row on tableview for playing another sound?
If any query regarding question, you may ask in comments,
I think You hae added some observer before calling, [myPlayer play];... So you have to remove those observer

iPhone SDK: AVAudioRecorder will not record after calling [AVPlayer play]

I have a view controller that uses AVPlayer. this view controller can load a modal view controler where the user can record audio using AVAudioRecorder.
this is what happens:
if the user plays the composition in the fist controller with [AVPLayer play] the AVAudioRecorder will not record in the modal view controller. there are no errors but the current time returned by AVAudioRecorder is 0.0;
if the user dismisses the modal dialog and reloads it. AVAudioRecorder works fine.
this can be repeated over and over
AVAudioRecorder does not work the first time it is invoked after a [AVPlayer play] call
I have been fighting with this for days and just have reorganized my code related to both AVPlayer and AVAudioRecorder and it's still acting weird.
Any help or pointer is much appreciated
Thanks in advance
Jean-Pierre
I've been having the same problem too, and this is the solution I found.
When recording, write this line code after AVAudioRecord is initialized:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:NULL];
Then invoke record method.
How are you setting the AVAudioSession's category, i.e., play or record? Try something like
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
I've been having the same problem. I use AVPlayer to play compositions (previous recordings I've used AVAudioRecord for). However, I found that once I've used AVPlayer I could no longer use AVAudioRecorder. After some searching, I discovered that so long as AVPlayer is instantiated in memory and has been played at least once (which is usually what you do immediately after instantiating it) AVAudioRecorder will not record. However, once AVPlayer is dealloc'd, AVAudioRecorder is then free to record again. It appears that AVPlayer holds on to some kind of connection that AVAudioRecorder needs, and it's greedy...it won't let it go until you pry it from it's cold dead hands.
This is the solution I've found. Some people claim that instantiating AVPlayer takes too much time to keep breaking down and setting back up. However, this is not true. Instantiating AVPlayer is actually quite trivial. So also is instantiating AVPlayerItem. What isn't trivial is loading up AVAsset (or any of it's subclasses). You really only want to do that once. They key is to use this sequence:
Load up AVAsset (for example, if you're loading from a file, use AVURLAsset directly or add it to a AVMutableComposition and use that) and keep a reference to it. Don't let it go until you're done with it. Loading it is what takes all the time.
Once you're ready to play: instantiate AVPlayerItem with your asset, then AVPlayer with the AVPlayerItem and play it. Don't keep a reference to AVPlayerItem, AVPlayer will keep a reference to it and you can't reuse it with another player anyway.
Once it's done playing, immediately destroy AVPlayer...release it, set its var to nil, whatever you need to do. **
Now you can record. AVPlayer doesn't exist, so AVAudioRecorder is free to do its thing.
When you're ready to play again, re-instantiate AVPlayerItem with the asset you've already loaded & AVPlayer. Again, this is trivial. The asset has already been loaded so there shouldn't be a delay.
** Note that destroying AVPlayer may take more than just releasing it and setting its var to nil. Most likely, you've also added a periodic time observer to keep track of the play progress. When you do this, you receive back an opaque object you're supposed to hold on to. If you don't remove this item from the player AND release it/set it to nil, AVPlayer will not dealloc. It appears that Apple creates an intentional retain cycle you must break manually. So before you destroy AVPlayer you need to (example):
[_player removeTimeObserver:_playerObserver];
[_playerObserver release]; //Only if you're not using ARC
_playerObserver = nil;
As a side note, you may also have set up NSNotifications (I use one to determine when the player has completed playing)...don't forget to remove those as well.
If you need use AVPlayer and AVAudioRecorder at the same time do following:
set audio category to "play and record" (as described above or with C-based Audio Session Function)
"record" method must be invoked after invocation of "play" method with some delay. (I set 0.5 sec)
If you don't provide this, playback will start, but recording will not start.
shouldn't it be [AVAudioRecorder record]; instead of play?
I've had the same issue: AVAudioRecorder did not start recording.
My example is a bit different: I have a tabbar based app with several AVAudioPlayers in the different views. For the sake of the example, lets say that the first loaded view has 3 AVAudioPlayers, while the second view has one AVAudioPlayer and one AVAudioRecorder. They are all initiated in the viewDidLoad method.
The recorder was not working in second view. When I hit my record button, the view slightly shaked and the recording did not begin.
Strangely, the simulator worked fine and did not show the same symptoms... (anyone has an idea why?)
After some research and reading this thread I have realized that I have not set the players in the first view to nil, so I guess they were still in the memory preventing the recorder to start. Once I have set them to nil in the viewDidDisappear method, it was working alright again.
here is what helped me:
-(void)viewDidDisappear:(BOOL)animated {
firstPlayer = nil;
secondPlayer = nil;
thirdPlayer = nil;
}
ViewDidUnload did not help because the View, and therefore the variablbes in the class itself were not unloaded when switching views.
Hope this may be useful for others as well.
#Aaron Hayman explanation is accurate but the solution for me was very simple. Just implement the didFinishPlaying method of the player delegate and release the player there.
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
self.playerWord = nil;
}

AVAudioPlayer to play short sounds on my iPhone?

I want to use AVAudioPlayer to play short sounds... because I have more control than System Sound.
I have several buttons which are hooked up to play several different short sounds.
Each sound file is about 1.5 - 2.0 seconds.
Everything works fine ...except ...I have to wait for the sound to stop before I can press the button and the sound will play again.
Currently the individual AVAudioPlayers are created on viewDidLoad ... and the sounds are called to play when the buttons are pressed
I tried creating the players when the button is pressed... this solves the above problem... but after a while the app crashes... all sound stops working.
Any ideas?
Thanks
Jonathan
If AVAudioPlayer stops playing sounds, it is usually because there are too many player instances created, usually leaked. You should make sure you release some after you are done.
You need to set the currentTime of the player to 0 before playing. Calling [player play] when it is already playing has no effect.
player.currentTime = 0;
[player play];
Creating new ones is fine, but make sure your header declares and does this:
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player successfully: (BOOL) completed {
[player release];
}
if your app is simple, that will release the memory of anything done that's no longer necessary, as per mahboudz' suggestion.
This might not totally prevent crashing, depending on your sound file sizes, and if you dealloc the scene. You might want to add
if (self.view.superview) { //release }
or something.
This is all memory handling. You may want to brush up on pointers to know how to handle your player objects, literally within any function, and still feel comfortable placing release in dealloc OR the didFinish: delegate function, and know why it doesn't belong in the other one.
The iPhone can be interrupted, stopped, and messed with at any time, so you have to know how to handle the memory and how to deal with it when it happens. I just had a crash of a similar nature, and everything above applied to it. NSXML delegates would be a nightmare without knowing pointers... same for AVAudioPlayer
If you plan to use a lof of short sounds, you might want to think about switching to OpenAL.
It's not a lot more complicated and you will save yourselve some trouble in more complicated audio settings later on.
There's a tutorial and some useful code available here (archived here).
this is MAYBE because you have created the AVAudioPlayers inside the viewDidLoad(). I remember I had the same problem. Just initialize them inside the class but not there. hope it works!
EDIT:
ok i saw the date... 2009...