iOS AudioSessionSetActive() blocking main thread?

2020-03-01 18:13发布

问题:

in my iOS app, I'm trying to implement "ducking": while my app plays a short "command-like" sound, any background music should be lowered in volume. Having finished playing the sound, the music volume should go back to its original value.

As implemented, ducking basically works as expected. However, when I call AudioSessionSetActive(NO) in audioPlayerDidFinishPlaying: in order to end ducking, there is a small pause in any UI updates that occur at this point of time. This involves custom drawing, as well as for ex. automatic scrolling of text and so on.

Now, here's the question:

Is this a known problem in iOS6? I'm running the same code on an iPod / iOS5, where I do not see this behavior. Or am I missing something from the code? Maybe one of you already came across the same problem and found a workable solution.

Thanks a lot for your kind support,

Goetz

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //...

    NSError *err = nil;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&err];

    //...

}

- (void) playSound {

    // Enable ducking of music playing in the background (code taken from the Breadcrumb iOS Sample)
    UInt32 value = kAudioSessionCategory_MediaPlayback;
    AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(value), &value);

    // Required if using kAudioSessionCategory_MediaPlayback
    value = YES;
    AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof(value), &value);

    UInt32 isOtherAudioPlaying = 0;
    UInt32 size = sizeof(isOtherAudioPlaying);
    AudioSessionGetProperty(kAudioSessionProperty_OtherAudioIsPlaying, &size, &isOtherAudioPlaying);

    if (isOtherAudioPlaying) {   
        AudioSessionSetProperty(kAudioSessionProperty_OtherMixableAudioShouldDuck, sizeof(value),   &value);
    }

    AudioSessionSetActive(YES);

    // Initialization of the AVAudioPlayer  
    NSString  *soundFileName = [[NSBundle mainBundle] pathForResource:@"Beep" ofType:@"caf"];
    NSURL     *soundFileURL  = [[NSURL alloc] initFileURLWithPath:soundFileURL];
    self.soundPlayer  = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
    [self.soundPlayer setDelegate:self];
    [self.soundPlayer setVolume:[80.0/100.0];
    [self.soundPlayer play];
}


- (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {

    // Callback coming from the AVAudioPlayer

    // This will block the main thread; however, it is necessary to disable ducking again
    AudioSessionSetActive(NO);     
}

回答1:

After some head scratching, and debugging, I found the answer to this question. As the original question states, in order to bring the audio level back up after ducking, you must deactivate the audio session.

The problem is that deactivating the session causes .5 second delay when audio is playing, which blocks the UI thread and causes the application to go unresponsive (in my case, a timer looses .5 seconds and stutters).

To resolve the issue, I make my call to deactivate the timer on a separate thread. This resolves the UI blocking issue and allows the audio to duck as expected. The code below shows the solution. Note, this is C# code because I'm using Xamarin, but it could easily be translated to Objective-C or Swift for the same result:

        private void ActivateAudioSession()
        {
            var session = AVAudioSession.SharedInstance();
            session.SetCategory(AVAudioSessionCategory.Playback, AVAudioSessionCategoryOptions.DuckOthers);
            session.SetActive(true);
        }

        private void DeactivateAudioSession()
        {
            new System.Threading.Thread(new System.Threading.ThreadStart(() =>
               {
                   var session = AVAudioSession.SharedInstance();
                   session.SetActive(false);
               })).Start();
        }

I call ActivateAudioSession before I wire up my AVAudioPlayer and once my player is done playing, I call DeactivateAudioSession (which is necessary to bring the audio level back up). Starting the deactivation on a new thread ducks the audio level back up, but does not block the UI.