首页 > 解决方案 > 用户切换耳机时如何更改 AVAudioPlayerNode 格式

问题描述

我需要播放基于输出设备的采样率生成的任意音调。用户需要连接有线或无线耳机才能收听音频。

由于现代耳机可以具有 44100 或 48000 的原生采样率,我需要以某种方式重新初始化我AVAudioPlayerNode的不同AVAudioFormat. 不做任何事情,音频开始听起来失真。但是,在尝试attach、或. 有时它会在我第一次尝试切换耳机时锁定执行(我同时连接了 2 个耳机,一个 SR 为 44100 的通用耳机和 SR 为 48000 的 Airpods),并且很少会在第二个时冻结尝试。detachconnectdisconnectNodeOutput

这是我的代码:

private let engine = AVAudioEngine()

private let audioUnit = MyAudioUnit()

init() {
    let audioSession = AVAudioSession.sharedInstance()
    let sampleRate = audioSession.sampleRate
    format = AVAudioFormat(
        commonFormat: .pcmFormatFloat32,
        sampleRate: sampleRate,
        channels: AVAudioChannelCount(audioSession.outputNumberOfChannels),
        interleaved: false
    )!

    engine.attach(audioUnit)
    engine.connect(
        audioUnit,
        to: engine.mainMixerNode,
        format: format
    )

    NotificationCenter.default.addObserver(
        self,
        selector: #selector(self.handleInterruption),
        name: Notification.Name.AVAudioEngineConfigurationChange,
        object: engine
    )
}

@objc
private func handleInterruption(_ notification: Notification) {
    DispatchQueue.main.async {
        let audioSession = AVAudioSession.sharedInstance()
        let sampleRate = audioSession.sampleRate
        self.format = AVAudioFormat(
            commonFormat: .pcmFormatFloat32,
            sampleRate: sampleRate,
            channels: AVAudioChannelCount(audioSession.outputNumberOfChannels),
            interleaved: false
        )!

        self.engine.detach(self.audioUnit)
        self.engine.attach(self.audioUnit)
        self.engine.connect(
            self.audioUnit,
            to: self.engine.mainMixerNode,
            format: self.format
        )
    }
}

// method called on main thread only
// BTW, using audioUnit.stop() instead of pause() would also introduce a deadlock
func setActive(_ active: Bool) {
    ...

    if active {
        try! engine.start()
        audioUnit.play()
    } else {
        audioUnit.pause()
        engine.stop()
        engine.reset()
    }
}

我已经尝试了许多“重新连接”的变体,AVAudioEngine但它们都陷入了以下僵局:

在此处输入图像描述

在此处输入图像描述

我也试过把它放在后台线程上,但这没关系。一旦应用程序尝试AVAudioEngine再次使用,一切都会卡住。

那么,是否有适当的方法重新连接AVAudioPlayerNode以更新采样率?或者也许可以不依赖耳机的原生采样率,而让音频正常地以静态采样率发声?提前致谢!

标签: iosavaudioengineavaudioplayernode

解决方案


使用您当前的设置,当音频路由和采样率发生变化时,您不需要做任何事情。

您当前的设置如下:

+------+    +-----+    +------+
|player| -> |mixer| -> |output|
+------+    +-----+    +------+

在播放器和输出节点之间安装混音器的好处之一是混音器将为您进行采样率转换。因此,即使扬声器的采样率为 48000Hz,您的播放器也可以输出 44100Hz。

还有一点需要注意的是,在将播放器连接到混音器时,format参数应该是播放器节点输出的格式。不过,这应该可以通过设置为 来推断formatnil如下所示:

engine.connect(player, to: engine.mainMixerNode, format: nil)

推荐阅读