Tertium
Tertium

Reputation: 6308

open al sounds don't play after the incoming call, until app restart

I'm playing game sounds using OpenAL, and bg music using standard AV. Recently i've found that after the incoming call all openal sounds don't work while bg music is still playing. If I force stop app and start again sounds appear again. Do smbd happen to know what's happening to openal during/after the incoming call?

Upvotes: 4

Views: 1478

Answers (2)

Meekohi
Meekohi

Reputation: 10887

I had a hard time figuring this out so wanted to add my answer here. This is all specifically in Xamarin, but I suspect it applies generally and is similar to @Tertium's answer

  • You can prevent iOS from interrupting your audio in some situations (e.g., getting a phone call but declining it), using AVAudioSession.SharedInstance().SetPrefersNoInterruptionsFromSystemAlerts(true, out NSError err);

  • You will still be interrupted in some situations (e.g., you accept a phone call). To catch these you must AVAudioSession.Notifications.ObserveInteruption(myAudioInterruptionHandler); when you launch your app.

Inside this handler, you can determine if you're shutting down or coming back like so:

void myAudioInterruptionHandler(object sender, AVAudioSessionInterruptionEventArgs args) {
  args.Notification.UserInfo.TryGetValue(
    new NSString("AVAudioSessionInterruptionTypeKey"),
    out NSObject typeKey
  );
  bool isBeginningInterruption = (typeKey.ToString() == "1");
  // ...
}

Interruption Begins

When the interruption begins, stop whatever audio is playing (you'll need to handle this on your own based on your app, but probably by calling AL.SourceStop on everything). Then, critically,

ContextHandle audioContextHandle = Alc.GetCurrentContext();
Alc.MakeContextCurrent(ContextHandle.Zero);

If you don't do this right away, iOS will fry your ALC context and you are doomed. Note that if you have a handler for AudioRouteChanged this is too late, you must do it in the AudioInterruption handler.

Interruption Ends

When you're coming back from the interruption, first reboot your iOS audio session: AVAudioSession.SharedInstance().SetActive(true);

You may also need to reset your preferred input (I think this step is optional if you always use the default input) AVAudioSession.SharedInstance().SetPreferredInput(Input, out NSError err)

Then restore your context Alc.MakeContextCurrent(audioContextHandle);

Upvotes: 0

Tertium
Tertium

Reputation: 6308

Ok, it seems I've found a solution. I'm using obj-c sound manager, so I just added beginInterruption and endInterruption delegate methods of AVAudioSession (and AVAudioPlayer) to my class.

beginInterruption looks like:

alcMakeContextCurrent(NULL);

and endInterruption looks something like:

    NSError * audioSessionError = NULL;
    [audioSession setCategory:soundCategory error:&audioSessionError];
    if (audioSessionError)
    {
        Log(@"ERROR - SoundManager: Unable to set the audio session category");
        return;
    }

    // Set the audio session state to true and report any errors
    audioSessionError = NULL;
    [audioSession setActive:YES error:&audioSessionError];
    if (audioSessionError)
    {
        Log(@"ERROR - SoundManager: Unable to set the audio session state to YES with error %d.", (int) result);
        return;
    }

        //music players handling
    bool plays = false;
    if (musicPlayer[currentPlayer] != nil)
        plays = [musicPlayer[currentPlayer] isPlaying];
    if (musicPlayer[currentPlayer] != nil && !plays)
        [musicPlayer[currentPlayer] play];

    alcMakeContextCurrent(context);

Yes, this works if you're using only openAL sounds. But to play long tracks you should use AVAudioPlayer. But here's the Apple magic again! If you play music along with OpenAL sounds something odd happens. Cancel the incoming call and AVAudioSessionDelegate::endInterruption with AVAudioPlayerDelegate::audioPlayerEndInterruption will never called. Only beginInterruption, not the end. Even AppDelegate::applicationWillEnterForeground will not be called, and app just don't know that we've returned.

But the good news is that you can call your endInterruption in AppDelegate::applicationDidBecomeActive method, and openAL context will be restored. And this works!

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    if (MySoundMngr != nil)
    {
        [MySoundMngr endInterruption];
    }

    // Restart any tasks that were paused and so on....
}

Upvotes: 2

Related Questions