iOS AudioSessionSetActive()是否会阻塞主线程?

12
在我的iOS应用程序中,我正在尝试实现“ ducking”: 当我的应用程序播放短暂的“命令式”声音时,任何背景音乐都应该降低音量。播放完毕后,音乐音量应恢复到其原始值。目前为止,ducking基本上按预期工作。然而,在audioPlayerDidFinishPlaying:中调用AudioSessionSetActive(NO)以结束ducking时,此时发生任何UI更新都会有一个小的暂停。包括自定义绘制,以及例如文本的自动滚动等内容。现在问题来了:这是iOS6中已知的问题吗?我在iPod / iOS5上运行相同的代码,那里没有看到这种行为。还是我从代码中漏掉了什么?也许你们中的一些人已经遇到过同样的问题并找到了可行的解决方案。非常感谢您的支持,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
你有没有找到解决这个问题的方法?我也遇到了完全相同的问题。 - Don Fitz
如果我不调用AudioSessionSetActive(NO),我的用户界面会保持响应并且不会掉帧。当我计时该方法的调用时,当音频正在播放时大约需要0.5秒的时间。但正如你所说,如果你不调用它,音频仍然会被压缩。 - Don Fitz
嘿,我曾经遇到过同样的问题,并找到了解决方案:https://dev59.com/0Ks9pogBymzxlkE85k17 - Lukas
2个回答

7
在一番思考和调试后,我找到了这个问题的答案。正如原问题所述,在淡出后将音频级别恢复,必须停用音频会话。
问题在于停用会话会导致0.5秒的延迟,阻塞UI线程并导致应用程序无响应(在我的情况下,计时器失去了0.5秒并出现了卡顿)。
为了解决这个问题,我在单独的线程上进行了停用计时器的调用。这解决了UI阻塞问题,并允许音频按预期淡出。下面的代码显示了解决方案。请注意,这是C#代码,因为我正在使用Xamarin,但它可以很容易地被翻译为Objective-C或Swift以获得相同的结果:
        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();
        }

在我连接我的AVAudioPlayer之前,我调用ActivateAudioSession,一旦我的播放器完成播放,我就调用DeactivateAudioSession(这是为了恢复音频级别必要的)。在新线程上启动去激活会将音频级别降低,但不会阻塞用户界面。


我们可以使用调度队列来代替后台队列吗? - The iCoder
2
@TheiCoder,使用dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ ..... }对我来说完美运行。 - Osk
你不应该自己创建线程,尤其是对于这样的小任务。你应该使用线程池。 - Alexander Danilov
1
@Osk 谢谢,你的代码对我很有用,让延迟消失了。但是在你的代码中,缺少两个括号。正确的 objc 代码应该是:dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ ..... }); - Phil_G

1

以下是一个关于 Swift 5 的解决方案:

// 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)")
    }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接