iOS AudioSessionSetActive() blocking main thread?
Asked Answered
M

2

12

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);     
}
Margaret answered 24/8, 2013 at 13:46 Comment(3)
Did you ever find a solution to this problem? I'm having the exact same issue.Raleigh
If I don't call the AudioSessionSetActive(NO) my UI remains responsive and doesn't drop frames. When I time that method call, it takes about .5 seconds when the audio is playing. But, like you said, if you don't call it, the audio remains ducked.Raleigh
Hey, I had the same problem and found a solution: #75826648Atworth
R
8

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.

Raleigh answered 12/3, 2016 at 20:36 Comment(4)
Can we use dispatch queue instead of this with background queue ?Lighten
@TheiCoderIt worked perfectly to me with dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ ..... }Aam
You shoudn't ever create threads by yourself, especially for such small task. You should use thread pool.Dezhnev
@Aam thanks, your code worked for me and made the lag disappear. But in your code, you are missing two brackets. The correct code for objc would be: dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ ..... });Lettielettish
P
1

A Swift 5 solution to this:

// Create a background serial queue to call AVAudioPlayer.setActive() on
queue = DispatchQueue(label: "backgroundAudio", qos: .userInitiated, attributes: [], autoreleaseFrequency: .inherit, target: nil)

// ... sometime later, activate or deactivate the audio instance
queue.async {
    do {
        try AVAudioSession.sharedInstance().setActive(false, options: [/* my options */])
    } catch {
        print("AVAudioSession.sharedInstance().setActive(false) failed: \(error)")
    }
}

Peddada answered 22/9, 2020 at 14:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.