This question may be too vague to pass StackOverflow's
standards, but I have to try posting it because I am running out of options... :/
Long story short: I have an app that experiences random periods of slowness. It does not happen too often (probably once every month), but when it does only a complete reboot of the iDevice it runs on helps. Symptoms are: 2-3 second response times and sluggish, choppy animations; the whole app basically becomes unusable.
I had ran the app through every possible diagnostics tool, none of which found anything wrong; no memory leaks or unusually high CPU usage. But, this is not surprising, considering the app is extremely simple, a tracker app for a card game.
All this led me to believe that the AVAudioPlayer
I use to play sounds when the user taps a button
might be the cause of the problem (it is the only, relatively high complexity element in the entire app). I am, however, not sure, which is where I need help. I include a sample code here, and perhaps someone with experience in iOS
audio playback could look at it and see if there is some mistake I overlooked.
Here we go:
First, I initialize a "silent player" that keeps playing a silent track every second to keep AVAudioPlayer
alive. It is necessary because of the relatively long response time AVAudioPlayer
experiences when being called after a longer period of inactivity.
NSString *silenceFilePath = [[NSBundle mainBundle] pathForResource: @"silence" ofType: @"wav"];
NSURL *silenceFileURL = [[NSURL alloc] initFileURLWithPath: silenceFilePath];
silencePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: silenceFileURL error: nil];
[silencePlayer setDelegate: self];
[silencePlayer prepareToPlay];
silenceTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:@selector(repeatSilence)
userInfo:nil
repeats:YES];
The NSTimer calls the following method:
- (void) repeatSilence
{
if (isAudioON == YES)
{
if (silencePlayer.playing)
{
[silencePlayer stop];
silencePlayer.currentTime = 0;
[silencePlayer play];
}
else
{
[silencePlayer play];
}
}
}
The rest is fairly straightforward. I initiate another AVAudioPlayer to play a specific button sound (there are two of these):
NSString *buttonSoundFilePath = [[NSBundle mainBundle] pathForResource: @"button_pushed" ofType: @"wav"];
NSURL *buttonFileURL = [[NSURL alloc] initFileURLWithPath: buttonSoundFilePath];
buttonPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: buttonFileURL error: nil];
[buttonPlayer setDelegate: self];
[buttonPlayer prepareToPlay];
And when the button is pushed I play the sound (the same way as I did with the silent player):
if (isAudioON == YES)
{
if (buttonPlayer.playing)
{
[buttonPlayer stop];
buttonPlayer.currentTime = 0;
[buttonPlayer play];
}
else
{
[buttonPlayer play];
}
}
And really all there is to it. I am afraid, however, that, despite the simple nature of this method, somehow this continuous audio playback creates a rare instance where iOS just goes crazy. But all this is just a theory, and that is why I need someone with more experience to take a look at my code and share his opinion.
Thanks!
Update: I found another few lines of code that might also be relevant to the problem. With them, I set the AVAudioPlayer to work simultaneously with music playing in the background (from another app for instance):
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[AVAudioSession sharedInstance] setDelegate:self];
From what I gather from your question, it seems that the only sounds you want to be playing are relatively simple and short sound effects. If this is in fact the case, I would not use the AVAudioPlayer, as it is a resource heavy library for something as simple as playing sound effects at a fixed volume. I would use Audio Services directly. Here is an example of a Sound Effect Class that I pulled from somewhere else (can't remember where anymore).
SoundEffect.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
@interface SoundEffect : NSObject
{
SystemSoundID soundID;
}
- (id)initWithSoundNamed:(NSString *)filename;
- (void)play;
@end
SoundEffect.m
#import "SoundEffect.h"
@implementation SoundEffect
- (id)initWithSoundNamed:(NSString *)filename
{
if ((self = [super init]))
{
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
if (fileURL != nil)
{
SystemSoundID theSoundID;
OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileURL, &theSoundID);
if (error == kAudioServicesNoError)
soundID = theSoundID;
}
}
return self;
}
- (void)dealloc
{
AudioServicesDisposeSystemSoundID(soundID);
}
- (void)play
{
AudioServicesPlaySystemSound(soundID);
}
@end
Once you have this class, you can easily play any sound effect by using the following code:
SoundEffect *mySoundEffect = [[SoundEffect alloc] initWithSoundNamed:@"mySound.wav"];
[shareSoundEffect play];
For optimization, I would load all of your sound effects somewhere and store them statically, so that the actual sound files are preloaded, then just call "play" on them whenever you need them. Doing this is a MUCH less resource heavy way of playing sound effects than using the AVAudioPlayer. Who knows, maybe it will solve your problem.
Preloading the sound file:
What I meant by this is simpler than it sounds. I just mean that you will create a static reference to each Sound Effect object after initializing them when the program first loads. There are several ways to do this. I typically use the +load method. It would look something like this:
Add a static variable in the implementation of the SoundEffect class
static SoundEffect *mySoundEffect;
Next, initialize that variable in the +load method for the Sound Effect class.
+(void)load
{
mySoundEffect = [[SoundEffect alloc] initWithSoundNamed:@"mySound.wav"];
}
Now just create a static method for the sound effect class like this:
+(void)playMySound
{
[mySoundEffect play];
}
After that, you can just play that sound at any time by calling:
[SoundEffect playMySound];
A note about volume controls:
I use this code in several of my IOS apps, and I can confirm the following:
What I observe from the code above, I would suggest two changes.
1) You have used two instances of AVAudioPlayer, one for playing silent sound continuously every second. A second instance of AVAudioPlayer is for playing the sound when the button is clicked. Running two instances of AVAudioPlayer at the same time, as at some point of time when memory is low on the device and the silent sound and button sound needs to be played at the same time the device will act sluggish. It's better to use one AVAudioPlayer instance to play both sounds.
2) Keep playing the silent sound until the user clicks the button. When user clicks on the button, pause the timer and stop the current sound and play the button sound using the same AVAudioPlayer instance. This way the timer that is not a recommend mostly will be stoped for a while when the button sound needs to be played, when the button sound is finished playing resume the timer to play silent sound with same the AVAudioPlayer instance. This will take away a lot of load from the memory and processing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With