简体   繁体   English

iOS AudioSessionSetActive() 阻塞主线程?

[英]iOS AudioSessionSetActive() blocking main thread?

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.在我的 iOS 应用程序中,我试图实现“闪避”:当我的应用程序播放短促的“类似命令”的声音时,任何背景音乐的音量都应该降低。 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.但是,当我在 audioPlayerDidFinishPlaying: 中调用 AudioSessionSetActive(NO) 时,为了结束闪避,此时发生的任何 UI 更新都会有一个小暂停。 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?这是 iOS6 中的已知问题吗? I'm running the same code on an iPod / iOS5, where I do not see this behavior.我在 iPod/iOS5 上运行相同的代码,但我没有看到这种行为。 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);     
}

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).问题在于,在播放音频时停用会话会导致 0.5 秒延迟,这会阻塞 UI 线程并导致应用程序无响应(在我的情况下,计时器丢失 0.5 秒并断断续续)。

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.这解决了 UI 阻塞问题并允许音频按预期闪避。 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:请注意,这是 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();
        }

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).我在连接 AVAudioPlayer 之前调用 ActivateAudioSession,一旦我的播放器播放完毕,我就会调用 DeactivateAudioSession(这是恢复音频电平所必需的)。 Starting the deactivation on a new thread ducks the audio level back up, but does not block the UI.在新线程上启动停用会降低音频级别,但不会阻塞 UI。

A Swift 5 solution to this:一个 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)")
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM