I'm calling playSoundFromBundle from the code below to play sounds (aif files). I have a sound that does a single click and then a fading sound. Both sounds are in the same file. Sometimes I get two clicks and then the fade. Meaning, click, click...fade. A single click isn't what should play. I'm guessing the sound starts (click sound), gets interrupted and then restarts (full sound...click/fade) because of other processing that may be going on. It seems random when it will occur. I put the sound on its own thread to try and avoid the double clicking. Is there anything else I can do to ensure the sound plays correctly?
- (void) playSoundFromBundleThreaded:(NSArray*)arr{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *fileName = (NSString*)[arr objectAtIndex:0];
NSString *fileExt = (NSString*)[arr objectAtIndex:1];
NSError *err;
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource: fileName ofType: fileExt inDirectory:#"/"]] error: &err];
self.audioPlayer = newPlayer;
self.audioPlayer.numberOfLoops = 0;
self.audioPlayer.volume = .5;
if (self.audioPlayer == nil)
{
NSLog(#"Problem initializing Sound - %#", [err description]);
}
else
{
[self.audioPlayer play];
}
[newPlayer release];
[pool release];
}
- (void) playSoundFromBundle:(NSString*)fileName fileExtension:(NSString*)fileExt{
NSArray *arr = [NSArray arrayWithObjects:fileName, fileExt, nil];
[NSThread detachNewThreadSelector: #selector(playSoundFromBundleThreaded:) toTarget:self withObject:arr];
}
No idea if this would work, but check out the Audio Session stuff. It's intended to manage things like whether the playing of other sounds (such as by the music player) will interrupt audio from your application.
Related
I'm working on application which has record audio and play that recorded file which is store in document directory.
Here is my code:
Record button method.
(IBAction)stopButtonPressed:(id)sender{
UIButton *button = (UIButton *)sender;
if (button.selected) { // Play recorded file.
self.stopButton.selected = NO;
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioRecorder.url error:&error];
self.audioPlayer.delegate = self;
self.audioPlayer.volume = 1.0;
[self.audioPlayer prepareToPlay];
[self.audioPlayer play];
}
else{ // Stop recording.
self.stopButton.selected = YES;
if (recordTimer) {
[recordTimer invalidate];
}
[self.audioPlayer stop];
self.recordButton.selected = NO;
[audioRecorder stop];
self.readyLabel.text = #"READY";
[self insertNewRecording];
}
}
I take it you're using ARC, in which case you'll need to retain the AVAudioPlayer as it sets to nil automatically. In your header file enter the following`
#property (nonatomic, strong) AVAudioPlayer *audioPlayer;
Hope this works for you,
Cheers Jim
um..I am not sure. But as a test, maybe you can try if you can play the file by specifying its name? E.g. you got a forest.mp3 to play and the key part of the code can be like this:
path = [[NSBundle mainBundle] pathForResource:#"forest" ofType:#"mp3"];
NSData *sampleData = [[NSData alloc] initWithContentsOfFile:path];
NSError *audioError = nil;
av = [[AVAudioPlayer alloc] initWithData:sampleData error:&audioError];
If everything went well, maybe it's something to do with audioRecorder.url in your code?
Hope this gives you some ideas.
I have this code:
int ran = 1 + arc4random() % 18;
NSString *soundFileIs=[NSString stringWithFormat:#"ton%i.mp3",ran];
NSString *paths = [[NSBundle mainBundle] resourcePath];
NSString *audioFile = [paths stringByAppendingPathComponent:soundFileIs];
NSData *cdata =[NSData dataWithContentsOfFile:audioFile];
audioPlayer = [[AVAudioPlayer alloc] initWithData:cdata error:nil];
audioPlayer.delegate = self;
[audioPlayer play];
When I shake device, it plays random my 18 sounds. How can I make to stop previously playing sound, and play next sound? Everything works great, just I hear 1st sound when I shake for first time, when I shake 2nd time, i hear 1st and 2nd sound playing if my 1st sound didn't stop play. Thank you.
Before playing anything, check to see if it's already playing and call stop. Your code will then be:
int ran = 1 + arc4random() % 18;
NSString *soundFileIs=[NSString stringWithFormat:#"ton%i.mp3",ran];
NSString *paths = [[NSBundle mainBundle] resourcePath];
NSString *audioFile = [paths stringByAppendingPathComponent:soundFileIs];
NSData *cdata =[NSData dataWithContentsOfFile:audioFile];
if (audioPlayer.playing)
[audioPlayer stop];
audioPlayer = [[AVAudioPlayer alloc] initWithData:cdata error:nil];
audioPlayer.delegate = self;
[audioPlayer play];
when you shake that time call
[audioPlayer stop];
first check audioplayer is playing a sound or not if not then don't call stop methord if playing then 1st call stop methord
call
if([audioPlayer playing])
[audioPlayer stop];
method on shaking the device to stop the current song.
then use this delegate method to play the next song.
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
In the phone below I am readying an AVAudioPlayer for four different sounds in my app. If I do not do this, it causes the app to delay about 1.5 seconds is I just do it where I need to hear it. So is there any way to make this code look better or work more efficiently because now I am readying these sounds in my App-Delegate and it slows down the launch a little bit. So here is the code and let me know if there is any way to make this code below better:
- (void)readySounds {
NSString *path = [[NSBundle mainBundle] pathForResource:#"TrackUno" ofType:#"mp3"];
NSURL *file = [[NSURL alloc] initFileURLWithPath:path];
AVAudioPlayer *one = [[AVAudioPlayer alloc] initWithContentsOfURL:file error:nil];
[file release];
self.tr1 = one;
[one release];
[tr1 prepareToPlay];
[tr1 setDelegate:self];
NSString *path2 = [[NSBundle mainBundle] pathForResource:#"TrackDos" ofType:#"mp3"];
NSURL *file2 = [[NSURL alloc] initFileURLWithPath:path2];
AVAudioPlayer *two = [[AVAudioPlayer alloc] initWithContentsOfURL:file2 error:nil];
[file2 release];
self.tr2 = two;
[two release];
[tr2 prepareToPlay];
[tr2 setDelegate:self];
NSString *path3 = [[NSBundle mainBundle] pathForResource:#"Click" ofType:#"mp3"];
NSURL *file3 = [[NSURL alloc] initFileURLWithPath:path3];
AVAudioPlayer *three = [[AVAudioPlayer alloc] initWithContentsOfURL:file3 error:nil];
[file3 release];
self.clk = three;
[three release];
[clk prepareToPlay];
[clk setDelegate:self];
NSString *path4 = [[NSBundle mainBundle] pathForResource:#"HSSound" ofType:#"mp3"];
NSURL *file4 = [[NSURL alloc] initFileURLWithPath:path4];
AVAudioPlayer *four = [[AVAudioPlayer alloc] initWithContentsOfURL:file4 error:nil];
[file4 release];
self.hs = four;
[four release];
[hs prepareToPlay];
[hs setDelegate:self];
}
If initialization times are your sole concern, then you could possibly* create this on a secondary thread and improve the perceived launch times. It would then init the streams and decoders, and open the audio files from disk from a secondary thread.
If playback, stream count, memory usage, CPU, load times, decoding, filtering, resampling, etc. are still factors: You are using the highest level APIs, you can drop down a few levels and have all the control you want over each and every one of these aspects.
You can make it look better by refactoring it -- the method does a similar sequence four times.
*'possibly' because I have only used the lower level APIs, I don't know if AVAudioPlayer has threading constraints.
Example refactoring with added error checking:
Not compiled, but you get the idea:
- (AVAudioPlayer *)newAudioPlayerForFile:(NSString *)fileName extension:(NSString *)extension inBundle:(NSBundle *)bundle
{
assert(fileName && extension && bundle);
NSURL * url = [bundle URLForResource:fileName ofType:extension];
if (nil == url) {
assert(0 && "could not locate resource");
return nil;
}
NSError * error = 0;
AVAudioPlayer * spieler = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
assert(spieler);
if (nil != error) {
NSLog(#"Error encountered creating audio player for file (%#): %#", fileName, error);
[spieler release], spieler = nil;
return nil;
}
[spieler prepareToPlay];
[spieler setDelegate:self];
return spieler;
}
- (void)readySounds
{
NSAutoreleasePool * pool = [NSAutoreleasePool new];
NSBundle * mainBundle = [NSBundle mainBundle];
self.tr1 = [[self newAudioPlayerForFile:#"TrackUno" extension:#"mp3" inBundle:mainBundle] autorelease];
self.tr2 = [[self newAudioPlayerForFile:#"TrackDos" extension:#"mp3" inBundle:mainBundle] autorelease];
self.clk = [[self newAudioPlayerForFile:#"Click" extension:#"mp3" inBundle:mainBundle] autorelease];
self.hs = [[self newAudioPlayerForFile:#"HSSound" extension:#"mp3" inBundle:mainBundle] autorelease];
[pool release], pool = nil;
}
I found that the solution to load in the background was a problem... it worked and then the 30th time I used the sound from the main thread all sound stopped working. There are a couple of things that could have happened - I had to create my own autorelease pool and the background thread could have gone away junking the object.
Thinking about it I only have short sound effects - creating the first AVAudioPlayer was slow but the subsequent ones were fine. So to solve it I created a tiny silent wav file and used that as the first one to load. I used audacity to create the file and did Tracks > Add New > Audio Track, then I did Generate > Silence, picked a very small interval like .01s, then I File > Export'ed the sound and added the file to XCode.
I force the small, silent wav file to load in viewDidLoad and that shortened the lag for me.
Hope this is helpful to someone with a similar problem.
I am getting a memory leak when i click the play button....
I am testing with that "Leak" tool under "Run and performance tool"....on simulator
I am getting that leak when i click the play button first time.....
Here is my code....
-(IBAction)play
{
[self setPlayer];
[self playme];
}
-(IBAction)stop
{
[self stopme];
[self releasePlayer];
}
-(void)setPlayer
{
NSURL *file = [[NSURL alloc] initFileURLWithPath:
[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"shut up.mp3"]];
NSError *err = nil;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err];
[file release];
player.numberOfLoops = -1;
[player prepareToPlay];
player.volume=1.0;
}
-(void)playme
{
if (!isPlaying)
{
[player play];
isPlaying=YES;
}
}
-(void)stopme
{
if (isPlaying)
{
[player stop];
isPlaying=NO;
}
}
-(void)releasePlayer
{
if(!isPlaying)
{
[player release];
player=nil;
}
isPlaying=NO;
}
I think, the below statement is the source of memory leak,
player = [[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err];
Here is the SO posts which has discussed the same issue.
AVAudioPlayer memory leak
AVAudioPlayer memory leak
AVAudioPlayer Memory Leak - Media Player Framework
Here is the blog post
AVAudioPlayer Memory Leak
EDITED:
As per the blog tutorial your code must be look like below.
-(void)setPlayer
{
NSURL *file = [[NSURL alloc] initFileURLWithPath:
[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"shut up.mp3"]];
NSError *err = nil;
NSData *data = [NSData dataWithContentsOfFile:file];
AVAudioPlayer *player = [AVAudioPlayer alloc];
if([player initWithData:audioData error:NULL])
{
player.numberOfLoops = -1;
[player prepareToPlay];
player.volume=1.0;
[player autorelease];
}
else
{
[player release];
player = nil;
}
[file release];
}
The leak-free version stores the pointer returned by alloc, rather than the pointer returned by initWithData:error:. That way, whatever happens, the player can still be released.
The blog post in Jhaliya's answer describes a leak that's specific to the situation when your player can't init the audio, for example when it can't find the file.
The real problem with your code is that you only release the player if the user explicitly stops the audio. If the audio plays through to the end, you have a player instance with a retainCount of 1. Then if the user hits play again, you create a new player and assign it to the player variable, leaking the old one.
The easiest solution to this is to make player a retained property:
#property(nonatomic,retain)AVAudioPlayer *player;
Then, instead of assigning to the ivar directly, use the mutator to set the player, which will implicitly release the previously set instance, if there is one:
[self setPlayer:[[[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err] autorelease];
And don't forget to release it in your dealloc:
-(void)dealloc {
[player release];
[super dealloc];
}
Trying to create a playlist of music files that are NOT part of the user's iphone library so I'm using AVAudioPlayer and creating the playlist functionality myself. It works on the first pass (meaning the first song is played). When the first song finishes and it goes to play the 2nd AVAudioPlayer crashes in prepareToPlay.
- (void) create {
Song *song;
NSArray *parts;
NSString *path;
NSString *name;
NSError *err;
if (currentIndex > [queue count])
currentIndex = 0;
if (currentIndex < 0)
currentIndex = ([queue count] - 1);
song = (Song *) [queue objectAtIndex:currentIndex];
name = song.fileName;
parts = [name componentsSeparatedByString:#"."];
path = [[NSBundle mainBundle] pathForResource:[parts objectAtIndex:0] ofType:[parts objectAtIndex:1]];
NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
[[AVAudioSession sharedInstance] setDelegate: self];
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &err];
[[AVAudioSession sharedInstance] setActive: YES error: &err];
appPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: url error: &err];
[url release];
[parts release];
[appPlayer prepareToPlay];
[appPlayer setVolume: 1.0];
[appPlayer setDelegate: self];
}
- (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL) flag {
[appPlayer stop];
[appPlayer release];
appPlayer = nil;
[self next];
}
The call to [self next] just increases currentIndex and calls create again.
The only other thing (I know, the infamous "only other thing" statement) is that this is all going on inside a Singleton. There's lots of things that could cause the music to start and stop playing, change the queue, etc so I thought it would be best to wrap it all up in one spot.
Any thoughts?
I was able to track this down myself. I re-wrote the whole section to have multiple AVAudioPlayer objects but still had issues. Turns out the [parts release] line was the issue, it was causing an over-release situation in the autorelease routine.
One of the reason of having exception in prepareToPlay method is enabled "All Exceptions" breakpoint. Go to Breakpoint Navigator and disable it.