restarting openAL after application interruption on the iPhone02 Feb
Hey All,
I have been fiddling with openAL on the iPhone for awhile now, and have discovered a few things. If you havent already read it, (and want a basic overview of openAL on the iPhone) have a look at the openAL tutorial I posted a while back.
Today I want to talk a bit about how the Audio Sessions system interacts with the openAL stuff on the iPhone, and how to get it all working in your app. It took me a looong time to figure this out and get all the little bits in the right place at the right time. Many thanks to the contributors at the apple iphone dev forums, couldn't have done it without them. In any case, hopefully this will save someone out there some time trying to figure this all out.
First off: WTF are Audio Sessions? From the docs:
"Audio Session Services is a C interface for managing an application’s audio behavior in the context of other applications."
So basically, if you want your app to play nice with other sound making things on the iPhone, you will need to deal with Audio Sessions. The biggest reason to do this is so that you can play your iPod music and have it mix with the sound that your app is generating.
So, lets get right into it.
First off, you need to let the Audio Session Service know that your app exists and give it a way to talk to you via a method callback:
OSStatus result = AudioSessionInitialize(NULL, NULL, interruptionListenerCallback, self);
The first two params that I am passing as NULL specify which run loop and what run loop mode you want to use when calling the callback method. passing NULL basically says: "Use the main run loop, and the default run mode", which for our purposes here is going to be fine.
the third param is the callback method pointer, more on that in a moment.
and lastly, the final param is the object that is the target of the callback message.
OK, now we have let the Audio Sessions know that our app exists, the next thing to do is tell it what kind of sound we will be generating. We do this with an audio category.
There are a handful of valid categories:
kAudioSessionCategory_UserInterfaceSoundEffects
kAudioSessionCategory_AmbientSound
kAudioSessionCategory_MediaPlayback
kAudioSessionCategory_LiveAudio
kAudioSessionCategory_RecordAudio
kAudioSessionCategory_PlayAndRecord
If any of these sound like your app, then look em up in the docs and double check. I am going to talk about the two that I use for most everything: kAudioSessionCategory_AmbientSound and kAudioSessionCategory_MediaPlayback.
kAudioSessionCategory_AmbientSound: This one is for 'longer' sound playback, but works fine for short sounds as well. It is what i refer to as the 'game category'; good for games. good to play longer looping background sounds (like a rockin soundtrack, or atmospheric noises) as well as short effects (like gunshots or interface clicks) This category will also allow the iPod music to mix with your app sounds.
kAudioSessionCategory_MediaPlayback: this is good if you have your own music and do NOT want to allow the ipod to play while your app is active. Basically this one tells the iPhone that you want your app to be the only source of sound. Good for things that generate music, or where it doesnt make sense to allow for iPod sound.
OK, so how do we tell the audio session about our category?
UInt32 category = kAudioSessionCategory_AmbientSound; OSStatus result = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
Pretty easy really, just set the property and you are done.
OK, with just those few lines of code your app will play nice with other apps and allow (or not) iPod music to mix with your sound.
But there is one more thing we need to do: handle sound interruptions. Like an alarm or phone call. (note: if you get a phone call and answer it, then your app will be dismissed. However if you get a phone call an ignore it, then your app will get the interruption call. Of course, on the Touch you dont get calls but you can still get alarm clock interruptions etc..)
OK, so we need to define a callback method. this needs to be some ugly C code, so all you Cocoa-only types, just hold your nose and cut and paste :-)
void interruptionListenerCallback (void *inUserData, UInt32 interruptionState ) { // you could do this with a cast below, but I will keep it here to make it clearer YourSoundControlObject *controller = (YourSoundControlObject *) inUserData; if (interruptionState == kAudioSessionBeginInterruption) { [controller _haltOpenALSession]; } else if (interruptionState == kAudioSessionEndInterruption) { [controller _resumeOpenALSession]; } }
What is going on here? Pretty simple really; the inUserData is a pointer to your object (or whatever obejct you put in for the last param above when you called AudioSessionInitialize()), and interruption state is an enum so you know if you are being interrupted or resumed.
I like to get right out of the ugly C code and right back into the lovely land of Cocoa by calling some cocoa methods on my controller object. (though you could just call the openAL and audio session functions from right there, but it is ugly)
So, how do we halt and resume openAL?
first thing, let the audio sessions know that you are shutting down
// Deactivate the current audio session AudioSessionSetActive(NO);
Next, shut down openAL in a nice way so you can keep your context intact:
// set the current context to NULL will 'shutdown' openAL
alcMakeContextCurrent(NULL);
// now suspend your context to 'pause' your sound world
alcSuspendContext(mContext);
easy.
And to come back from the above halting?
Basically the same thing in reverse, but with one added twist: you have to re-register your audio session category:
// Reset audio session UInt32 category = kAudioSessionCategory_AmbientSound; AudioSessionSetProperty ( kAudioSessionProperty_AudioCategory, sizeof (category), &category ); // Reactivate the current audio session AudioSessionSetActive(YES); // Restore open al context alcMakeContextCurrent(mContext); // 'unpause' my context alcProcessContext(mContext);
And that is it. That will allow your openAL app to play nice with the rest of the system and recover from interruptions.
edit: renato reminds me in the comments that you should always be checking your error codes. Otherwise openAL can choke on some small issue and it wont work until that code has been cleared.
I tend to put one of these after every call to openAL: like so:
alSourceQueueBuffers(sourceID, 1, &bufferID); ALEnum err = alGetError(); if (err != 0) NSlog(@"Error Calling alSourceQueueBuffers: %d",err); alSourcef(sourceID, AL_PITCH, pitch); err = alGetError(); if (err != 0) NSlog(@"Error Calling alSourcef with AL_PITCH: %d",err);
and so on.
Cheers!
-ben



My full name is Ben Britten Smith.
Hi Ben,
I found your post very useful, but I almost go crazy for a missing line of code.
For my program it was necessary to put a call to alGetError() between AudioSessionSetActive(YES) and alcMakeContextCurrent(mContext) to clean te OpenAL error variable.
I hope this information can be useful for others.
Cheers,
Renato.
Hey Renato!
Yes! you make a good point, and one that I have been remiss in mentioning.
You DO need to check the errors. I tend to leave that code out of my examples to keep them a bit more readable and brief, but you should technically be checking for an error after every call to openAL.
I will update the post :-)
Thanks for that!
Cheers!
-B
Hi Ben!
Thank you for the tutorial. It has been very helpfully! I made everything step by step and it works. Unfortunately I have a problem with resume sound. Method resumeOpenALSession is called after back to game. However after calling function:
AudioSessionSetProperty()
I’ve got message:
“AUIOClient_StartIO failed (560030580)”
All alGetError() returns 0 (zero) so everything should works. This is the resumeOpenALSession method:
(void) resumeOpenALSession
{
UInt32 category = kAudioSessionCategory_AmbientSound;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
alcMakeContextCurrent(mContext);
alcProcessContext(mContext);
}
Do you have an idea? Could you help me?
Aghhhhrrr… I made stupid mistake. I forgot call function AudioSessionSetActive(YES) in resumeOpenALSession metohod. Now everything works well.
Thank you for the great tutorial Ben. Good job!
Hey MDW!
I am glad it all worked out!
Cheers!
-B