从断开连接的蓝牙设备捕获输入时,AVAudioSession 服务会重置

bcl*_*mer 5 core-audio ios avcapturesession avaudiosession avaudioengine

TL;DR -AVAudioSessionAVAudioSessionMediaServicesWereLostNotification在指定蓝牙端口时触发,AVAudioSession.setPreferredInput并且该设备在使用AVCaptureSession或主动读取输入时断开连接AVAudioEngine。示例项目在这里

https://openradar.appspot.com/FB8921592

问题

通过我认为的“快乐路径”,或者至少应该得到 Apple 支持的路径,我可以始终如一AVAudioSession地触发它AVAudioSessionMediaServicesWereLostNotificationAVAudioSessionMediaServicesWereResetNotification通知。

在这两个通知之间,您的应用无法执行任何音频或视频 I/O 或处理(编码或解码)。这是一个总的媒体服务关闭 1 到 2 秒。

触发

这仅在使用时发生AVAudioSession.setPreferredInput,其中您设置的端口是蓝牙设备。从该端口主动录制时,您关闭蓝牙设备。

发生这种情况时,在服务丢失之前不会触发来自AVAudioSessionAVAudioEngine或 的其他通知AVCaptureSession,因此无法抢先处理这种情况。用户可以随时关闭耳机,导致服务全面失败。

一些代码

首先我设置了音频会话

try! AVAudioSession.sharedInstance().setCategory(.record, mode: .videoRecording, options: .allowBluetooth)
try! AVAudioSession.sharedInstance().setActive(true)
Run Code Online (Sandbox Code Playgroud)

然后指定正确的端口

let bluetoothPort = AVAudioSession.sharedInstance().availableInputs?
    .first(where: { $0.portType == .bluetoothHFP })
guard let validPort = newPort else {
    print("Couldn't find a valid bluetooth port")
    return
}
print("Swapping \(AVAudioSession.sharedInstance().currentRoute.inputs.first!.portName) for \(validPort.portName)")
try! AVAudioSession.sharedInstance().setPreferredInput(newPort)
Run Code Online (Sandbox Code Playgroud)

最后设置 AVCaptureSession

let cap = AVCaptureSession()
cap.automaticallyConfiguresApplicationAudioSession = false
let device = AVCaptureDevice.default(for: .audio)!
let input = try! AVCaptureDeviceInput(device: device)
let output = AVCaptureAudioDataOutput()
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "test-queue"))
cap.beginConfiguration()
cap.addInput(input)
cap.addOutput(output)
cap.commitConfiguration()
cap.startRunning()
Run Code Online (Sandbox Code Playgroud)

或者 AVAudioEngine

let engine = AVAudioEngine()
let inputNode = engine.inputNode
let bus = 0
inputNode.installTap(onBus: bus, bufferSize: 1024, format: inputNode.inputFormat(forBus: 0)) { (buffer, time) in
    // anything really
}
engine.prepare()
try! engine.start()
Run Code Online (Sandbox Code Playgroud)

我确实有一个存储库,可以使用AVAudioEngine或来演示问题AVCaptureSession,您可以在此处找到它 - https://github.com/bclymer/ios-preferredinput-crash。README 中有更明确的重现步骤。

我的以下设备受到影响(全部都是)

?????????????????????????????
? Device Name ? iOS Version ?
?????????????????????????????
? iPhone 7    ? iOS 14.2    ?
? iPhone Xr   ? iOS 14.0.1  ?
? iPad Air 2  ? iOS 13.7    ?
?????????????????????????????
Run Code Online (Sandbox Code Playgroud)

我在寻找什么

有没有办法在从突然断开连接的外部设备读取 I/O 时不重置服务?

我知道但不可接受的一个解决方案是依赖 Apple 的“最后在”音频路由方法。连接蓝牙设备时,如果 ,它会自动成为默认路由AVAudioSession.preferredInput == nil。但是,我需要能够在任意数量的外部设备之间进行明确选择。使用隐式路由对我不起作用。