EXC_BAD_ACCESS after audioPlayerDidFinishPlaying: called - iphone

I have a class that I call to utilize AVAudioPlayer and everything works fine and dandy when it comes to playing the audio, but when the -audioPlayerDidFinishPlaying: is called my NSLog() command says that the player is released; the problem is that the app crashes moments later. I should mention that audioPlayer is an ivar in this class. Here is the code:
-(id) initWithFileName:(NSString *)sndFileName
{
[super init];
sndFileToPlay = [[NSString alloc] initWithString:sndFileName];
return self;
}
-(void)dealloc {
[audioPlayer release];
self.audioPlayer.delegate = nil;
self.audioPlayer = nil;
[super dealloc];
}
-(void)play
{
[self playSound:sndFileToPlay];
}
-(void)playSound:(NSString *)fileName
{
NSString *fname, *ext;
NSRange range = [fileName rangeOfString:#"."];
int location = range.location;
if( location > 0 )
{
fname = [fileName substringWithRange:NSMakeRange(0, location)];
ext = [fileName substringFromIndex:location+1];
[self playSound:fname :ext];
}
}
—
-(void)playSound:(NSString *)fileName :(NSString *)fileExt
{
NSBundle *mainBundle = [NSBundle mainBundle];
NSURL *fileURL = [NSURL fileURLWithPath:
[mainBundle pathForResource:fileName ofType:fileExt] isDirectory:NO];
if (fileURL != nil)
{
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
error: nil];
[fileURL release];
[audioPlayer setDelegate:self];
[audioPlayer play];
}
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
successfully:(BOOL)flag
{
NSLog(#"Releasing");
[audioPlayer release];
}

There are several things wrong with your code.
For one, in your dealloc:
[audioPlayer release];
self.audioPlayer.delegate = nil;
self.audioPlayer = nil;
You are releasing the audioPlayer, then, on the released (and maybe deallocated) player you set the delegate to nil and then the property, which releases it again. Remove the [audioPlayer release];.
In your audioPlayerDidFinishPlaying:successfully: you're releasing the player as well, but you haven't set the variable to nil. That might cause a crash since by the time you access this variable again a different object might be at that memory address. Use the property instead and do it like in your dealloc:
self.audioPlayer.delegate = nil;
self.audioPlayer = nil;
Then, in playSound:: (argh, non-named second argument !) you over-release fileURL. The -[NSURL fileURLWithPath:isDirectory:] returns an autoreleased object, you may not release it.
Last but maybe not least you leak sndFileToPlay, you need to release it in your dealloc method. And instead of sndFileToPlay = [[NSString alloc] initWithString:sndFileName]; simply do sndFileToPlay = [sndFileName copy];.
You might want to read up on Objective-C memory management. It's not hard once you know the three or four rules-of-thumb.

You should clean up your code. If playSound is called several times, you are leaking AVAudioPlayer.
In your dealloc, you should put [audioPlayer release] after the two lines beneath.
Turn on NSZombieEnabled to debug, and make sure that the audioPlayer is not released when didFinish is called.

Related

Memory Leak when retaining property

I am trying to play a click sound on every button click in my app
For that i created a Utility class whose .h and .m is as follows
.h file
#interface SoundPlayUtil : NSObject<AVAudioPlayerDelegate,AVAudioSessionDelegate>
{
AVAudioPlayer *audioplayer;
}
#property (retain, nonatomic) AVAudioPlayer *audioplayer;
-(id)initWithDefaultClickSoundName;
-(void)playIfSoundisEnabled;
#end
.m file
#implementation SoundPlayUtil
#synthesize audioplayer;
-(id)initWithDefaultClickSoundName
{
self = [super init];
if (self)
{
NSString* BS_path_blue=[[NSBundle mainBundle]pathForResource:#"click" ofType:#"mp3"];
self.audioplayer =[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:BS_path_blue] error:NULL];
[self.audioplayer prepareToPlay];
}
return self;
}
-(void)playIfSoundisEnabled
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:soundStatus]==YES)
{
[self.audioplayer play];
}
}
-(void)dealloc
{
[audioplayer release];
[super dealloc];
}
#end
and on button click on any class i am doing
SoundPlayUtil *obj = [[SoundPlayUtil alloc] initWithDefaultClickSoundName];
[obj playIfSoundisEnabled];
[obj release];
Its working fine and i succeeded to play sound. Problem arises when i analysed the code.
Compiler shows that there is memory leak in initWithDefaultClickSoundName method in .m of utility class as i am sending alloc method to self.audioplayer and not releasing it.
What is the best place of releasing this object?
The issue is when you alloc the object it's retainCount will be 1, you are assigning that object to a retain property object. Then it'll again retain the object hence the retainCount will be 2.
The setter code of a retain property is something like:
- (void)setAudioplayer: (id)newValue
{
if (audioplayer != newValue)
{
[audioplayer release];
audioplayer = newValue;
[audioplayer retain];
}
}
Change the :
self.audioplayer =[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:BS_path_blue] error:NULL];
like;
self.audioplayer =[[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:BS_path_blue] error:NULL] autorelease];
or like:
AVAudioPlayer *player = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:BS_path_blue] error:NULL];
self.audioplayer = player;
[player release];
self.audioplayer =[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:BS_path_blue] error:NULL];
Here, you create a new object, then assign it to a retained property. However, apart from the property, you have no reference to the object again, so it leaks. You've increased the retain count twice.
To fix, in order of preference:
Convert to ARC ;)
Create a local variable, assign it to the property, then release it.
Object *object = [[Object alloc] init];
self.property = object;
[object release];
Add a autorelease call to the object as you are adding it: self.property = [[[Object alloc] init] autorelease];

MemoryLeak problem in following case

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];
}

How to create a audioplayer application like pandora app?

I am created an application which play multiple audio files from locally. Audio files are long. Audio player has following options for the user
Forward,
Rewind,
Next Track,
Previous Track,
I am planning to use AvAudioPlayer so that i can play an audio with long time. When i am changing the audio file ie pressing next track audio. The audioplayer instance is not getting released. This problem is appearing some times only. Please help me..!! I am help less..
Next Track Button IBAction Method
- (IBAction) nextTrackPressed
{
[audioPlay stopAudio];
if (audioPlay) {
audioPlay = nil;
[audioPlay release];
}
appDelegate.trackSelected += 1;
[self intiNewAudioFile];
[self play];
}
Initializing audio file through below method
-(void) intiNewAudioFile
{
NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init];
NSString *filePath = [[NSString alloc] init];
trackObject = [appDelegate.trackDetailArray objectAtIndex:appDelegate.trackSelected];
NSLog(#"%#",trackObject.trackName);
// Get the file path to the song to play.
filePath = [[NSBundle mainBundle] pathForResource:trackObject.trackName ofType:#"mp3"];
// Convert the file path to a URL.
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath];
if (audioPlay) {
audioPlay = nil;
[audioPlay release];
}
audioPlay = [[AudioPlayerClass alloc] init];
[audioPlay initAudioWithUrl:fileURL];
[filePath release];
[fileURL release];
[subPool release];
}
AudioPlayerClass Implementation
#import "AudioPlayerClass.h"
#implementation AudioPlayerClass
- (void) initAudioWithUrl: (NSURL *) url
{
curAudioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[curAudioPlayer setDelegate:self];
[curAudioPlayer prepareToPlay];
}
- (void) playAudio
{
[curAudioPlayer play];
}
- (void) pauseAudio
{
[curAudioPlayer pause];
}
- (void) stopAudio
{
[curAudioPlayer stop];
}
- (BOOL) isAudioPlaying
{
return curAudioPlayer.playing;
}
- (void) setAudiowithCurrentTime:(NSInteger) time
{
curAudioPlayer.currentTime = time;
}
- (NSInteger) getAudioFileDuration
{
return curAudioPlayer.duration;
}
- (NSInteger) getAudioCurrentTime
{
return curAudioPlayer.currentTime;
}
- (void) releasePlayer
{
[curAudioPlayer release];
}
- (void)dealloc {
[curAudioPlayer release];
[super dealloc];
}
#end
Your problem is here:
if (audioPlay) {
audioPlay = nil;
[audioPlay release];
}
You are setting audioPlay to nil before calling release which means that the release message is getting sent to nil. You need to reverse the order of these 2 lines.
if (audioPlay) {
[audioPlay release];
audioPlay = nil;
}

AVAudioPlayer works once

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.

Adding delegate to AVAudioPlayer cause "_NSAutoreleaseNoPool(): Object 0x55e060 of class NSCFString autoreleased with no pool in place - just leaking"

I have been using a class to play sounds using AVAudioPlayer. Since I want to release these sounds right after they are played, I added a delegate. That causes a "_NSAutoreleaseNoPool(): Object 0x55e060 of class NSCFString autoreleased with no pool in place - just leaking" error right after the sound completes playing, but before my -audioPlayerDidFinishPlaying is called.
Here are some sources:
#interface MyAVAudioPlayer : NSObject <AVAudioPlayerDelegate> {
AVAudioPlayer *player;
float savedVolume;
BOOL releaseWhenDone;
}
The main class .m:
- (MyAVAudioPlayer *) initPlayerWithName: (NSString *) name;
{
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: name ofType: #"caf"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
player = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL error: nil];
[fileURL release];
[player prepareToPlay];
return (self);
}
- (MyAVAudioPlayer *)getAndPlayAndRelease:(NSString *)name withVolume:(float) vol;
{
MyAVAudioPlayer *newMyAVPlayer = [self initPlayerWithName:name];
player.volume = vol;
[player play];
releaseWhenDone = YES;
[player setDelegate: self];
return newMyAVPlayer;
}
+ (void) getAndPlayAndReleaseAuto:(NSString *)name withVolume:(float) vol;
{
MyAVAudioPlayer *newMyAVPlayer = [[MyAVAudioPlayer alloc] getAndPlayAndRelease:name withVolume:vol];
// [newMyAVPlayer autorelease];
}
#pragma mark -
#pragma mark AVAudioPlayer Delegate Methods
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)playedPlayer successfully:(BOOL)flag {
if (releaseWhenDone) {
NSLog(#"releasing");
[playedPlayer release];
// [self release];
NSLog(#"released");
}
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
NSLog(#"Error while decoding: %#", [error localizedDescription] );
}
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player {
NSLog(#"Interrupted!");
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player {
NSLog(#"EndInterruption!");
}
- (BOOL) play;
{
player.currentTime = 0.0;
return [player play];
}
Commenting out the [player setDelegate: self]; makes the error go away, but then my audioPlayerDidFinishPlaying doesn't get called.
Any thoughts? Am I suddenly running in another thread?
I found the problem. My bug, of course.
In a lot of my class files, I was adding:
-(BOOL) respondsToSelector:(SEL) aSelector
{
NSLog(#"Class: %# subclass of %#, Selector: %#", [self class], [super class], NSStringFromSelector(aSelector));
return [super respondsToSelector:aSelector];
}
Mainly out of curiousity.
Well, when I added a delegate to my sound, then this method gets called before the delegate, and it gets called from whatever runloop the AVAudioPlayer happened to be in, and likely, a runloop with no autorelease pool.