I'm using AudioServicesAddSystemSoundCompletion in my app to detect when sound has finished and then trigger some other action.
For some reason I am getting the following behavior, it works for the first 8 to 12 sounds (that's at least what I tested) and then the callback defined for AudioServicesAddSystemSoundCompletion is not being called anymore.
Here is my code to create the sound:
NSString *soundPath = [[NSBundle mainBundle] pathForResource:[[soundFileName componentsSeparatedByString:#"."]objectAtIndex:0]ofType:#"wav"];
Log2(#"soundFileName: %#", soundFileName);
CFURLRef soundURL = (CFURLRef)[NSURL fileURLWithPath:soundPath];
AudioServicesCreateSystemSoundID(soundURL, &sound);
AudioServicesAddSystemSoundCompletion(sound, nil, nil, playSoundFinished, (void*) self);
to play the sound:
AudioServicesPlaySystemSound(sound);
and to do some stuff when the sound finished playing:
void playSoundFinished (SystemSoundID sound, void *data) {
pf //Typedef for PRETTY_FUNCTION log
AudioServicesRemoveSystemSoundCompletion(sound);
[AppState sharedInstance].b_touchesFREE = TRUE;
if([[AppState sharedInstance].ma_cardsToCheck count] >= 2)
{
[[AppState sharedInstance].vc_spiel pairFound];
}
if (((Card*)data).b_isTurnedFaceUp) {
[AppState sharedInstance].i_cardsTurnedFaceUp --;
}
[(Card*)data release];
}
Has anyone of you any idea why it works the first few times and then stops working?
Thx in advance.
Maverick1st
***** Edit *****
I just found out, that this happens when i try to play the sound a second time.
Could it be, that i forgot to release it somewhere?
But I always thought AudioServicesRemoveSystemSoundCompletion handles the memory management.
***** One more edit *****
So posting this on Stackoverflow made me think a bit deeper about the problem and i got the solution now (at least i think i got it ;)).
Unfortunately i cannot answer the question for myself for the next 7.5 hours so i have to edit the question.
Just for you to better understand my problem.
I'm programming a memory game and every Card is a Class containing its image for front and back and the sound it plays when its turned around.
since i only initialize the sound on creation of the card i was not sure if I should call AudioServicesRemoveSystemSoundCompletion every time the sound ends.
So i just tried it without AudioServicesRemoveSystemSoundCompletion and it works now.
Only thing i am not sure about now is if this could lead to a memory leak or something like that.
But for now it works fine.
If someone could tell me if this is ok regarding the memory use i'd be really happe. :)
Best regards.
Maverick1st
If you create the sound (and set sound completion) only once during the lifetime of the app, it should be ok from memory management standpoint. It is always you create - you release.
However, you should call
AudioServicesRemoveSystemSoundCompletion(sound);
AudioServicesDisposeSystemSoundID(sound);
when you don't need the sound anymore (most probably in the dealloc method of the object where you created (or keep reference to) the sound. Do not change the order of these two, otherways you have a memory leak.
Maybe you would find AppSoundEngine useful. It is simple to use wrapper for SystemSoundID and associated C functions.
Related
I Have the following code:
-(void) changeAnimation:(NSString*)name forTime:(int) times {
if(currentAnimation != #"attack")
{
id action = [CCAnimate actionWithAnimation:[self animationByName:name]];
id repeatAction = [CCRepeat actionWithAction:action times:times];
currentAction = [self runAction:repeatAction];
lastANimation = currentAnimation;
currentAnimation = name;
}
else if(currentAction.isDone)
{
//Here is where I would change the animation
//but I commented the code for now
}
}
So when I run this and click on the button that changes the animation to "attack" (by calling [mysprite changeAnimation:#"attack" forTime:1];), I get a EXC_BAD_ACCESS error from the "currentAction.isDone" line, the next time the function is called (the joystick will call changeAnimation to try and change the animation to "run" or "idle", but I want the attack animation to finish first). Any thoughts on whyI get this? currentAction is declared in my class.
Edit: there is nothing in the rest of the class that interacts with currentAction, beside a getter. Its declaration is in the .h (CCAction* surrentAction). Do I need to initialize it? I thought the returned value from runAction would be sufficient? ANyways, when I run the debugger, it is not nil, and assigned to the correct action.
Thanks,
Dave
Edit:
I ended up creating a sequence when "attacking" that calls a function that changes the currentAnimation, so i avoided the issue. Still no idea what was happening.
Here's the answer if your interested:
Other Post
More of the class is probably needed to really answer this properly, but the EXC_BAD_ACCESS typically happens because you're accessing something that has been released and is no longer available in memory.
I'm guessing that somewhere in your class you're releasing, either explicitly, or implicitly, the "currentAction" object asynchronously - and when you're checking later, it's done & gone and you're hitting this crasher.
In general, keeping a state variable or two that you always have known values on is a good way to go, and for the "actions" that you're going through, if they're asynchronous and doing their own memory management, leave them as such and work through some state variables that you maintain and control all the memory management around. It's a pretty reasonable pattern for asynchronous callbacks, either with the classic stuff or as you move into using blocks with iOS 4.0
I have seen a similar line of code floating about in Apples code:
(void)[[URLRequest alloc] initializeRequestWithValues:postBody url:verifySession httpHeader:nil delegate:self];
URLRequest is my own custom class. I didn't write this and I think the guy that did just grabbed it from Apple's example. To me this should leak and when I test it I'm pretty sure it leaks 16 bytes. Would it? I know how to fix it if it does but wasn't sure as it was taken from Apple's code.
EDIT: The problem was with the SDK, not the above code. See answer below for further details
Thought I might update this as after further testing and the release of iOS4 it has changed.
The above code doesn't leak and the memory footprint of the App returns to normal even after 200 iterations of the code. The leak did occur in iOS3 but was very small, in iOS4 it has completely disappeared both in simulator and device.
Some might wonder why you would want to implement this code but it works and make sense when dealing with lots of different NSURLConnections throughout your code running simultaneously.
Yes. This is a leak, which can easily be fixed by adding an autorelease:
[[[URLRequest alloc] initializeRequestWithValues:postBody url:verifySession httpHeader:nil delegate:self] autorelease];
Perhaps a better fix would be to create a class function that does this:
#interface URLRequest
{
// ...
}
// ...
+ (void) requestWithValues:/* ... */
// ...
#end
Then you could simply use [URLRequest requestWithValues: /* ... */] without invoking alloc.
Not at all sure what this code is supposed to accomplish. It does appear to break every single convention about initialization methods. What's the point of returning a void pointer from an initialization method? The entire point of an initialization method is to return an object. Where in Apple's code examples did you see this?
Having said that, I don't see why it would leak. Since it doesn't return an object there is nothing to leak external to the method. There might be something internally that leaks.
Edit:
It basically does an NSURLConnection.
Because we are submitting a lot of
forms with a lot of different values
we put it in an external class. All
the delegate methods like
didFailWithError: are in NSURLRequest
and connectionDidFinishLoading just
passes the data to its delegate. So it
doesn't really need to return anything
as it is done through a delegate
method.
Yeah, you need to redesign this. At present, this method is just a disaster waiting to happening. If nothing else, everyone else looking at this code will be utterly confused about what you are doing.
If you have no need to retain the object created, then move its allocation and clean up entirely within a method. Change the method name prefix from "initialize" to something like "setup", "configure", "acquire" etc so the name doesn't imply that it creates and returns and object.
If you need a one shot instance of a particular class, use a class method like Michael Aaron Safyan suggested (again without initialize in the name.) The class method should internally initialize an instance, perform the operations needed, return the data to wherever, then dealloc the instance.
That way, you won't have to worry about leaks and everyone else who may read your code (including yourself months down the road) will immediately understand what the code does.
I'm nearing the end of a big iPhone project and whilst checking for memory leaks stumbled on this huge one. I implemented the sound following this tutorial:
http://www.gehacktes.net/2009/03/iphone-programming-part-6-multiple-sounds-with-openal/
Works a charm, a lot of people use it but I get a huge leak a start of the project when sound is initially loaded in. Below are the lines of code that start of the leak:
[[Audio sharedMyOpenAL] loadSoundWithKey:#"music" File:#"Music" Ext:#"wav" Loop:true];
[[Audio sharedMyOpenAL] loadSoundWithKey:#"btnPress" File:#"BtnPress" Ext:#"wav" Loop:false];
[[Audio sharedMyOpenAL] loadSoundWithKey:#"ting1" File:#"GlassTing1" Ext:#"wav" Loop:false];
etc. etc. it loads in 20 sounds altogether. And more specifically in the Audio.m file this chunk of code:
+ (Audio*)sharedMyOpenAL {
#synchronized(self) {
if (sharedMyOpenAL == nil) {
sharedMyOpenAL = [[self alloc] init]; // assignment not done here
}
}
return sharedMyOpenAL;
}
I am unsure how to resolve this and any help on the matter would be greatly appreciated.
Thanks.
Isn’t the “leak” simply the Audio singleton? I am not sure how the leak detection works, but from a certain viewpoint most singletons are leaks, since they only release memory after your application exits.
If this really is the case, then it depends on whether you need to release the memory used by the sounds. The memory usage should not go up, so you don’t have to worry about the “traditional leak” scenario where your application takes more and more memory until it gets killed. The code you are using does not seem to support sound unloading, so that if you want to release the memory, you’ll have to add that code yourself.
And a personal viewpoint: Writing a sound effect engine using a singleton is not a good design. Managing the sounds becomes a pain (this is exactly the problem you are facing), the singleton adds a lot of unnecessary boilerplate code, etc. I see no reason the sounds should not be simple separate objects with their own lifecycle – this is the way I’ve done it in my attempt at an OpenAL SFX engine. Of course, I could be wrong.
Update: I suppose the magic ‘assignment not done here’ is the key. The singleton code is taken from the Apple documentation, but somebody inserted an extra assignment. The sharedFoo method should look like this:
+ (MyGizmoClass*)sharedManager
{
#synchronized(self) {
if (sharedGizmoManager == nil) {
[[self alloc] init]; // assignment not done here
}
}
return sharedGizmoManager;
}
When you perform the extra assignment to self, you create the leak you are looking for.
I'm using OpenAL on iPhone to play multiple audio samples simultaneously.
Can I get OpenAL to notify me when a single sample is done playing?
I'd like to avoid hardcoding the sample length and setting a timer.
I didn't have much luck with callbacks in OpenAL. In my state machines, I simply poll the source and delay the transition until it's done.
- (BOOL)playing {
ALint sourceState;
alGetSourcei(sourceID, AL_SOURCE_STATE, &sourceState);
return sourceState == AL_PLAYING;
}
// ... //
case QSTATE_DYING:
if (![audioSource playing])
[self transitionTo:QSTATE_DEAD];
If this isn't what you need, then you're best bet is probably a timer. You shouldn't need to hardcode any values. You can determine the playback time when you're populating your buffers.
A bit of insight into the "why" of the question might offer some additional choices.
If you have the OpenAL source abstracted into a class, I guess you can simply call performSelector:afterDelay: when you start the sound:
- (void) play
{
[delegate performSelector:#selector(soundHasFinishedPlaying)
afterDelay:self.length];
…
}
(If you stop the sound manually in the meantime, the callback can be cancelled, see the NSObject Class Reference.) Or you can poll the AL_SOURCE_STATE:
- (void) checkStatus
{
ALint state;
alGetSourcei(source, AL_SOURCE_STATE, &state);
if (state == AL_PLAYING)
return;
[timer invalidate];
[delegate soundHasFinishedPlaying];
}
I don’t know how to have OpenAL call you back. What exactly do you want the callback for? Some things can be solved better without a callback.
This OpenAL guide suggests a possible solution:
The 'stream' function also tells us if the stream is finished playing.
...and provides sample source code to illustrate the usage.
Wait, are you talking about having finished one sample (e.g., 1/44100 second for 44.1 KHz audio)? Or are you talking about knowing that a source has played through its buffer and has no more audio to play?
For the latter, I've had good results polling a source for the AL_BUFFERS_PROCESSED property when I stream buffers to a source; it might work for the single-buffer case to look for a non-zero value of this property.
I'm rather surprised at how few objects implement NSCopying. This is the third time in two weeks where I have needed to duplicate an existing object, without having to either go to disk and reload, or recreate the object and set its settings.
Now I have to duplicate an AVAudioPlayer sound. Why? I want to play the sound twice, without having the sound.currentTime determine the playback location of the second playback.
I thought it might be easy to do a category or subclass AVAudioPlayer and implement copyWithZone. However, the internals of AVAudioPlayer are hidden and I can't easily copy them.
I am now yearning for the good old BlockMove(&var, &newVar, sizeof(varType));
How best to duplicate an AVAudioPlayer sound or a UIView view?
The reason why many objects don't support NSCopying is because it's not always clear on what a copied object should be, and in particular, the excessively pedantic details of what it means to 'copy an object'.
I would take a pragmatic approach in this case. If you need an instance of AVAudioPlayer duplicated just once, I'd recommend this:
AVAudioPlayer *audioPlayer; // Assumed to be valid.
AVAudioPlayer *copiedPlayer = NULL;
copiedPlayer = [[[AVAudioPlayer allocate] initWithData:audioPlayer.data] autorelease];
This is not meant to be a general solution because it does not handle all cases. It's meant to be an example of how you can over come the problem while relying on some usage specific invariants. The most notable example in which this breaks is if audioPlayer was not initialized with a NSData object, but a url instead. It also makes certain relatively safe assumptions about the mutability of data, and how AVAudioPlayer was coded to handle those cases. (I did not look at the documentation long enough to see if such corner case behaviors are documented).
If this is something you need to do a lot, you can probably lash together a more complicated bit of code that implements NSCopying. Since a quick pass at the documentation turns up only two ways to init the object, via a NSData or via a url, and the instantiated object to be copied provides that information... then the implementation of a non-optimal deep copy NSCopying subclass is left as an exercise for the reader. :)
I saw an Apple sample file, CASound.mm, which has a -copyWithZone:
/* support NSCopying */
- (id)copyWithZone:(NSZone *)zone
{
CASound* copy = NSCopyObject(self, 0, zone);
copy->_impl = NULL;
[copy initWithSound: self];
return copy;
}
looking at initWithSound:
- (CASound*)initWithSound:(CASound *)other
{
CASoundImpl* otherImpl = (CASoundImpl*)other->_impl;
if (otherImpl->_url) {
return [self initWithContentsOfURL: otherImpl->_url];
} else if (otherImpl->_data) {
return [self initWithData: otherImpl->_data];
} else {
return NULL;
}
}
Now, I don't know if Apple likes us messing around with the private fields like _impl, but this basically boils down to the same thing as getting the AVPlayer all over again from the file, and not just making an in-memory copy of the existing sound.
I was actually hoping that maybe NSCopyObject would do the deed, but I see Apple doesn't rely on it for the entire copy.