MacOS下切换音频设备后AVAudioEngine配置变更通知未触发问题
我想用AVAudioEngine将包含麦克风输入在内的多个音频输入混合到文件中,但切换输入/输出设备后,有时AVAudioEngineConfigurationChangeNotification不会触发,导致引擎停止工作。
我在AVAudioEngine的输入节点上安装了tap,当默认输入或输出设备变更时,这个tap会停止工作。原本我通过接收AVAudioEngineConfigurationChangeNotification后重启引擎并重新连接节点来修复,但有时该通知不会触发——此时tap已经失效,但AVAudioEngine.isRunning仍返回true。
根据苹果文档描述:
当音频引擎的I/O单元检测到音频输入或输出硬件的通道数或采样率发生变化时
这是不是意味着当新旧设备格式相同时,该通知不会触发?且此时引擎应该无缝切换设备?
我尝试连接sink节点并打印接收内容,同样出现失效问题。基本上任何设备变更(无论输入还是输出)都会中断引擎,但并非所有变更都会触发通知,即便引擎已经失效。请问有什么解决办法?
基础配置代码
audioEngine.inputNode .installTap( onBus: 0, bufferSize: 1024, format: audioEngine.inputNode.inputFormat(forBus: 0) ) { buffer, _ in do { let sampleBuffer = try buffer.convertToCMSampleBuffer() self.didOutputSampleBuffer.send((self.id, sampleBuffer)) } catch { print(error) } } let notification = NSNotification.Name.AVAudioEngineConfigurationChange NotificationCenter.default.addObserver( self, selector: #selector(onInputChanged), name: notification, object: nil ) audioEngine.prepare() audioEngine.start()
设备变更处理代码
// 重新创建tap以应对格式变化 audioEngine.start()
补充监听音频会话路由变更通知
仅依赖AVAudioEngineConfigurationChangeNotification并不够,同时监听AVAudioSessionRouteChangeNotification。这个通知会在音频路由(如设备切换)发生任何变化时触发,不受设备格式是否相同的影响。在回调中检查AVAudioSessionRouteChangeReason,只要是与设备切换相关的类型(比如newDeviceAvailable、oldDeviceUnavailable),就执行引擎重启逻辑。主动检测tap存活状态
在tap的回调中记录最后一次接收音频数据的时间,设置一个定时检查任务(比如每2秒检查一次),如果超过设定阈值(比如3秒)未收到数据,则判定tap已失效,主动触发引擎重启流程。标准化引擎重启流程
不要直接调用start(),需遵循完整的重启步骤:@objc func handleDeviceChange() { // 停止引擎并清理资源 audioEngine.stop() audioEngine.inputNode.removeTap(onBus: 0) // 重新配置输入节点tap audioEngine.inputNode.installTap( onBus: 0, bufferSize: 1024, format: audioEngine.inputNode.inputFormat(forBus: 0) ) { buffer, time in do { let sampleBuffer = try buffer.convertToCMSampleBuffer() self.didOutputSampleBuffer.send((self.id, sampleBuffer)) } catch { print(error) } } // 重新准备并启动引擎 audioEngine.prepare() try? audioEngine.start() }确保音频会话配置正确
启动引擎前,先配置并激活AVAudioSession,确保会话类别与你的使用场景匹配(比如混合输入输出需用.playAndRecord):let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(.playAndRecord, mode: .default) try audioSession.setActive(true) } catch { print("音频会话配置失败:\(error)") }
内容的提问来源于stack exchange,提问作者Homid Way

