restarting openAL after application interruption on the iPhone

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

This entry was posted in code, iPhone, openAL. Bookmark the permalink.

14 Responses to restarting openAL after application interruption on the iPhone

  1. renato says:

    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.

  2. Ben says:

    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

  3. MDW says:

    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?

  4. MDW says:

    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!

  5. Ben says:

    Hey MDW!

    I am glad it all worked out!

    Cheers!
    -B

  6. Xood says:

    I am having big trouble getting the audio to resume correctly in the follwing case:
    – game is running
    – put it to standby (on/off) switch
    – now call this iphone or wait for an alert to pop up
    – tell the alert to remind you again later, or simply ignore the phone call
    – resume the game -> no sound

    For all other cases the interruption handler seems to work, but not for this case.
    As a workaround I put the whole context suspend, resume code into appwillresign, didbecomeactive, and this seems to work. But just does not feel right.
    Anyone got any ideas?

  7. ejoy2u says:

    I guess you got AUIOClient_StartIO failed.
    I got the same issue yesterday, but if I debug and set breakpoint in resumeOpenALSession, I can hear sound everytime back to game.
    So I put sleep after every line in resumeOpenALSession.
    Yes, it works. you don’t need to put the resume code to appwillresign, just sleep it a bit.
    Actually, I fixed 5 of 10 bugs by sleep when I was using OpenAL on iPhone.

  8. Oceansoul says:

    Brief question.
    This seems extremely helpful, but where do I have to put the several code pieces?
    And what is YourSoundControlObject ??

    Otherwise this article seems like a godsend. I built a small sound engine using OpenAL and the session problem was driving me crazy.
    So I´d be very glad for some more details on how to use your code snippets.

    Oceansoul

  9. Ben says:

    Hey Oceansoul,

    When you call AudioSessionInitialize() the last parameter is: self. This gets passed back into the callback function as the inUserData. self being whatever class you are calling the initialize from within.

    So when I put ‘yourControlObject’ i mean to say that you need to cast that void* to the right class so you can then call methods on it effectively. In my case my sound engine class is called BBSoundController, so I would cast that void* as a BBSoundController, then call _haltOpenALSession or whatever. Your class will obviously be whatever you have created.

    Similarly, all this code generally lives in that sound controller class. So if you built a small sound engine using OpenAL, the class that you initialize the session with will be “your sound control object”.

    Hope that helps!

    CHeers!
    -B

  10. Oceansoul says:

    Nevermind. I think I got it :).

  11. Oceansoul says:

    Whoa, that was a fast answer. Thanks for that. I just found it out, as well. And yes, it helped :).

    Cheers

  12. Ben says:

    Hey Oceansoul,

    No worries, glad you got it all figured out :-)

    My explanations can sometimes be a bit obtuse. I don’t like to just give big blocks of code right out, otherwise people generally just cut and paste it and don’t try to understand what is actually going on. And sometimes, my explanations are just way _too_ obtuse, and nobody understands what the hell I am on about :-) So your confusion is probably warranted :-)

    Also, this is a good time of day since it is the end of the day and I am just looking for excuses to not be working anymore :-)

    Cheers!
    -B

  13. Oceansoul says:

    Even if it means spamming your thread, I have to post one more thing.
    I just tried out your code and well .. since you´re about to go home please take this as a big ‘end-of-your-workday’-HUG :). You just saved me such a huge amount of nerves and time and … hell, years of my life which I would have spent brooding :).
    Your piece of code works like charm.

    Thanks a lot. I´ll recommend you ;).

    Oceansoul

  14. jeffmo77 says:

    Hmm, this looks awesome, and exactly what I need! But after implementing, it seems that interruptionListenerCallback never gets called. I’m doing this in a cocos2D project within a CCLayer class.

    Any ideas?

    Jeffmo77

Leave a Reply