Memory leaks with audioPlayerDidFinishPlaying - objective-c

I have created an iPhone card game with a BGM and some sound FX. The game is working fine when I cancel the sound voids, but it seems to generate memory leaks each time I link a sound to a card. For each sound void, the code is like this:
- (void)cardLostSound {
NSString *pathForCardLostSoundFile = [[NSBundle mainBundle]
pathForResource:#"CardLost" ofType:#"wav"];
NSURL *cardLostSoundFile = [[NSURL alloc]
initFileURLWithPath:pathForCardLostSoundFile];
cardLostPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:cardLostSoundFile
error:NULL];
[cardLostSoundFile release];
[cardLostPlayer prepareToPlay];
[cardLostPlayer play];
[self performSelector:#selector(audioPlayerDidFinishPlaying:successfully:)
withObject:cardLostPlayer afterDelay:[cardLostPlayer duration]];//This is a hack I
found to force application of void audioPlayerDidFinishPlaying, which doesn't seem to
work on its own…
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
if (player == self. cardLostPlayer){
[self.characterComboPlayer release];
else if (player == self.cardBackPlayer){
[self.cardBackPlayer release];
}//etc.
}
Having audioPlayerDidFinishPlaying release the players doesn't seem to do a thing against memory leaks. Without it, the app always crashes after the same amount of time. With it, the game is slow, but lasts longer. Then, the app crashes anyway. When I just cancel these lines, I don't have any crash. Is there a way to keep the sounds, while having the app run smoothly and without memory leaks?

Related

Memory leak from looping SKVideoNode (only on actual device)

I have a memory leak that I cannot diagnose. I have tried multiple approaches to creating a seamlessly looped video - Besides AVPlayerLooper, all of the approaches I've encountered and tried involve creating an observer to watch AVPlayerItemDidPlayToEndTimeNotification and then either seeking to the beginning of the video (in the case of AVPlayer) or inserting the video to be looped into the video queue (in the case of AVQueuePlayer). Both seem to have similar performance, but both also have a consistent memory keep related to the seekToTime method (in the case of AVPlayer) and the insertItem method (in the case of AVQueuePlayer). My end goal is to create a subclass of SKVideoNode that loops by default. Below is my code for the subclass:
#import "SDLoopingVideoNode.h"
#import <AVFoundation/AVFoundation.h>
#interface SDLoopingVideoNode()
#property AVQueuePlayer *avQueuePlayer;
#property AVPlayerLooper *playerLooper;
#end
#implementation SDLoopingVideoNode
-(instancetype)initWithPathToResource:(NSString *)path withFiletype:(NSString *)filetype
{
if(self == [super init])
{
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:path ofType:filetype];
NSURL *videoURL = [NSURL fileURLWithPath:resourcePath];
AVAsset *videoAsset = [AVAsset assetWithURL:videoURL];
AVPlayerItem * videoItem = [AVPlayerItem playerItemWithAsset:videoAsset];
self.avQueuePlayer = [[AVQueuePlayer alloc] initWithItems:#[videoItem]];
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter addObserverForName:AVPlayerItemDidPlayToEndTimeNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
AVPlayerItem *video = [[AVPlayerItem alloc] initWithURL:videoURL];
[self.avQueuePlayer insertItem:video afterItem:nil];
NSLog(#"Video changed");
}];
self = (SDLoopingVideoNode*)[[SKVideoNode alloc] initWithAVPlayer: self.avQueuePlayer];
return self;
}
return nil;
}
#end
And here is how the subclass is initialized in didMoveToView:
SDLoopingVideoNode *videoNode = [[SDLoopingVideoNode alloc]initWithPathToResource:#"147406" withFiletype:#"mp4"];
[videoNode setSize:CGSizeMake(self.size.width, self.size.height)];
[videoNode setAnchorPoint:CGPointMake(0.5, 0.5)];
[videoNode setPosition:CGPointMake(0, 0)];
[self addChild:videoNode];
[videoNode play];
Short answer is, you will not be able to get that working with AVPlayer. Believe me, I have tried. Instead, it is possible to do seamless looping by using the H264 hardware to decode and then re-encode each video frame as a keyframe, github link here. I have also built a seamless looping layer that supports a full alpha channel. Performance even for full screen 1x1 video on and iPad or iPad pro is great. Also, no memory leaks with this code.

how to stop the infinite loop and allow to accept the touches during this loop

while (true)
{
endtime = CFAbsoluteTimeGetCurrent();
double difftime = endtime - starttime;
NSLog(#"The tine difference = %f",difftime);
if (difftime >= 10)
{
NSURL *url= [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/a0.wav" , [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops=0;
[audioPlayer play];
starttime = endtime;
}
}
when i call this method my application enters in an infinite loop and doesnot accept any touches again how can solve this please?
It looks like you want it to play a sound every 10 seconds. You would be better using a timer like this...
NSTimer *yourTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(repeatedAction)
userInfo:nil
repeats:YES];
Then have your repeating function...
- (void)repeatedAction
{
// do your stuff in here
}
The UI will be responsive all the time and you can have a cancel button with an action like this...
- (void)cancelRepeatedAction
{
[self.yourTimer invalidateTimer];
}
This will stop the time rand the repeated action.
HOWEVER
You are also trying to download a file EVERY time you run your action.
A better approach would be to download the file once and store it.
Even better would be to download the file asynchronously.
This is not how you structure an event-driven application. You are blocking the event processing queue (NSRunLoop) by not returning out of this function. So no touch events will get a chance to be processed until you return from this loop.
You need to play the sounds asynchronously - which AVAudioPlayer can do for you. Read up on the ADC documentation about queueing and playing music in AVFoundation.
For a start, you don't need the while loop at all. Just start the playback, and register for notifications of playback progress. Make the _audioPlayer an ivar of your controller class, as its lifetime persists beyond the method used to load the file and initiate playback:
NSURL *url= [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/a0.wav" , [[NSBundle mainBundle] resourcePath]]];
NSError *error;
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
_audioPlayer.numberOfLoops=0;
[_audioPlayer play];
Then define a delegate method which implements the AVAudioPlayerDelegate protocol, and provide at least this method:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
}
You can also provide a touch event handler in your view, which calls a pauseAudio: method in the controller class, which then calls:
[audioPlayer pause];
Have a good read through the docs, and study the sample code:
iOS Developer Library - ADC AVAudioPlayer Class Reference
Have a look at how this app is structured, with a controller registering for notifications and using a delegate for callbacks. It also uses a timer and features GUI updates.
iOS Developer Library - avTouch - media player sample code

xcode objective c - when I call 2 methods - only the last called method runs and first one is skipped

-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[self NextHeading]; // this plays an mp3 file
[self NextHeadingMeaning]; // this plays an Mp3 file
}
Only [self NextHeadingMeaning] method is called and NextHeading method is missed each time
-(IBAction) NextHeading{
[audio stop];
NSString *Filename = [[NSString alloc]initWithFormat:#"CH%#S%#",Heading,Meaning];
Filepath = [[NSBundle mainBundle]pathForResource:Filename ofType:#"mp3"];
audio = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:Filepath] error:NULL];
audio.delegate = self;
[audio play];
[Filename autorelease];
}
-(IBAction) NextHeadingMeaning {
[audio stop];
NSString *Filename = [[NSString alloc] initWithFormat:#"CH%#S%#",bold**Chapter**bold, Meaning];
Filepath = [[NSBundle mainBundle]pathForResource:Filename ofType:#"mp3"];
audio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:Filepath] error:NULL];
audio.delegate = self;
[audio play];
[Filename autorelease];
}
Why is this happening and how can I resolve it ?
Please advice, thanks in advance.
You just used a single iVar (audio) as an player, and when you send NextHeading & NextHeadingMeaning message, the audio init with your sound_1.mp3 file firstly (it'll take some seconds if the mp3 file is big), then at the next moment (your first mp3 file might not inited, or has inited, but stopped followed by next message), you redo the init action with another mp3 file (sound_2.mp3), and finally, when the second mp3 file init done, audio plays sound_2.mp3. That's why you think the NextHeading is skipped.
So, to solve this problem, you can use a NSMutableArray iVar (e.g. audioPlayers), and create a local audio for both NextHeading & NextHeadingMeaning, and push it to audioPlayers.
And I think it is better to preload sound files if you can. :)
EDIT:
There's a playAtTime: method instead of play, you can delay the second audio player's playing time by this method, just like this:
[audioPlayer playAtTime:(audioPlayer.deviceCurrentTime + delay)];
delay is in seconds (NSTimeInterval).
There is no way that the first call is skipped, put a breakpoint in it or output something with NSLog() and you'll see. Most probable cause is that the first method doesn't do what you expect and this could be for various reasons - for example condition or specific timeout.
Edit:
After looking your code, it seems that you're missing some basic stuff like variable naming, variable scope and so. To simply make your code run, just replace the NSString *Filename.. string from the second method and probably it'll work. A better choice would be to visit Start Developing iOS Apps Today and follow the roadmap.

Memory Leak while creating and copying Images with NSFiIlemanager on iPad App

I have a memory leak which crashes my App while copying / creating images width NSFileManager.
When i profile my App with "Allocations", everything looks fine. The Allocated Memory Goes up from aprox 1.5 MB to 6 MB during every recoursion and then drops to 1.5MB again.
But the "Real Memory" and "Virtuel Memory" grows to aprox 150MB and then the App crashes.
I receive Memory Warnings Level 1 and 2 before.
here is the function us use:
-(void) processCacheItems:(NSMutableArray*) originalFiles
{
if ( [originalFiles count] == 0 )
{
[originalFiles release];
return;
}
else
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *curFileName = [originalFiles lastObject];
NSString *filePath = [documentsDirectoryPath stringByAppendingPathComponent:curFileName];
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
CGSize destinationSize = CGSizeMake(150,150);
CGSize previewDestinationSize = CGSizeMake(1440.0, 1440.0);
UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath]; // AUTORELEASED
// create thumb and copy to presentationfiles directory
UIImage *thumb = [originalImage resizedImageWithContentMode:UIViewContentModeScaleAspectFit
bounds:destinationSize
interpolationQuality:kCGInterpolationHigh]; // AUTORELEASED
// the resizedImageWithContentMode: does not semm to make the problem, because when i skip this and just use the original file the same problem occours
NSString *thumbPath = [thumbsDirectoryPath stringByAppendingPathComponent:curFileName];
[fileManager createFileAtPath:thumbPath contents:UIImageJPEGRepresentation(thumb, 0.9) attributes:NULL];
// create thumb and copy to presentationfiles directory
UIImage *previewImage = [originalImage resizedImageWithContentMode:UIViewContentModeScaleAspectFit
bounds:previewDestinationSize
interpolationQuality:kCGInterpolationHigh]; // AUTORELEASED
NSString *previewImagePath = [previewsDirectoryPath stringByAppendingPathComponent:curFileName];
[fileManager createFileAtPath:previewImagePath contents:UIImageJPEGRepresentation(previewImage, 0.9) attributes:NULL];
// copy copy original to presentationfiles directory
NSString *originalPath = [originalFilesDirectoryPath stringByAppendingPathComponent:curFileName];
[fileManager copyItemAtPath:filePath toPath:originalPath error:NULL];
[originalFiles removeLastObject];
[pool drain];
[self processCacheItems:originalFiles]; // recursion
}
}
Thank you for your Hint.
I fond out, that the Problem was not a leak, but the memory Allocation was too big when scaling down Big Images in "resizedImageWithContentMode:" That made the App crash.
I changed the Image scaling to use the Image I/O framework.
Now it works fine.
UPDATE: This answer is outdated. If you are using ARC, ignore it .
How do you allocate NSFileManager?
I have experienced that allocating it via the + defaultManager method, which is deprecated, produces memory leaks (or Instruments says so, Instruments sometimes reports memory leaks where there are not).
Generally, you should allocate it with [[NSFileManager alloc] init] and release when you no longer need it.

Problem with AVAudioplayer or delegate getting dallocated prematurely

I am making a counting game for kids. At the start the child is asked find a number of items, for example: "Can you find five bikes". This is all randomly put together from seperate arrays of sounds with parts of the sentence: "Can you find" + "5" + "bikes" =three seperate mp3s. When the child starts clicking on the items, they disappear and the voice counts up. "1", "2", "3","4", "5", and in the end "bikes".
I use the audioPlayerDidFinishPlaying: delegate method to put the sounds together and that works out fine ... most of the time. But sometimes the app crashes, with a "bad_access" error. After using NSZombie I got: -[AVAudioPlayer performSelector:withObject:]: message sent to deallocated instance
I think this is because either the audioplayer itself or the delegate is released prematurely.
I always use this function to play the sounds:
-(void)spillVoice:(NSString*) filnavn{
NSString *audioFilePath=[[NSBundle mainBundle] pathForResource:filnavn ofType:#"mp3"];
NSURL *audioFileURL=[NSURL fileURLWithPath:audioFilePath];
self.voicespiller=[[[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL error:nil] autorelease];
self.voicespiller.delegate=self;
[self.voicespiller prepareToPlay];
[self.voicespiller play];
NSLog(#"spiller lyden");
}
And here is the delegate (it performs different actions based on what sound has finnished):
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)which_player successfully:(BOOL)the_flag{
NSLog(#"ny lyd?");
[self.jenteBilde stopAnimating];
if(self.etterLyd==#"ingenting"){
NSLog(#"ingenting");
}
else if(self.etterLyd==#"lesobjekt"){
NSLog(#"lesobjekt");
self.etterLyd=#"ros";
[self spillVoice:[self.objektNedArray objectAtIndex: [self.objektnr intValue]]];
[self.jenteBilde startAnimating];
}
else if(self.etterLyd==#"introtall"){
NSLog(#"introtall");
self.etterLyd=#"introobjekt";
[self spillVoice:[self.telleOppArray objectAtIndex: [self.tilfeldig intValue]]];
[self.jenteBilde startAnimating];
}
else if(self.etterLyd==#"introobjekt"){
NSLog(#"introobjekt");
self.etterLyd=#"ingenting";
[self spillVoice:[self.objektOppArray objectAtIndex: [self.objektnr intValue]]];
[self.jenteBilde startAnimating];
}
else if(self.etterLyd==#"ros"){
NSLog(#"ros");
NSMutableArray *rosArray=[[NSMutableArray alloc] initWithObjects:#"TT_flott",#"TT_bravo",#"TT_fint",#"TT_du_er_kjempeflink",#"TT_hurra",#"TT_helt_riktig",nil];
int result=(arc4random() % (rosArray.count));
self.etterLyd=#"ingenting";
[self spillVoice:[rosArray objectAtIndex: result]];
[self.jenteBilde startAnimating];
}
}
It seems to me that the AVaudioplayers autorelease hits in too early, even though the sound has not yet finnished. I have tried not autoreleasing, and instead releasing explicitly in the delegate function. But the problem is that the sound doesnt always get to play to the end (when the child finds a new item before the voice has finnished reading)...
Can any of you shed any light on this. I would be very grateful!
You need to retain the AVAudioPlayer until you're done with it. The easiest way to do this would be to make your voicespiller property a retained property, and change your implementation to the following:
-(void)spillVoice:(NSString*)filnavn {
// stop any previous player
[self.voicespiller stop];
NSString *audioFilePath=[[NSBundle mainBundle] pathForResource:filnavn ofType:#"mp3"];
NSURL *audioFileURL=[NSURL fileURLWithPath:audioFilePath];
[self setVoicespiller:[[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL error:nil] autorelease];
self.voicespiller.delegate=self;
[self.voicespiller prepareToPlay];
[self.voicespiller play];
NSLog(#"spiller lyden");
}
When you call the property's mutator ([self setVoicespiller:...]), the previous player will be released and the new one will be set. Then just make sure to call [voicespiller release] in your dealloc method.