You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AVAudioSession.routeChangeNotification两次触发致蓝牙耳机断开回退麦克风问题

问题原因

核心原因是AVAudioSession配置的选项存在优先级冲突:

  • 你同时启用了.allowBluetooth和.defaultToSpeaker两个CategoryOptions,.allowBluetooth会优先将音频路由到已连接的蓝牙HFP设备,触发第一次categoryChange类型的路由变更通知
  • .defaultToSpeaker的系统优先级高于蓝牙路由规则,系统检测到该配置后,会强制将音频路由切回内置扬声器/麦克风,因此触发第二次categoryChange通知,导致蓝牙路由自动回退
修复方案
  • 第一步:移除冲突的.defaultToSpeaker配置,新增.allowBluetoothA2DP兼容高音质蓝牙输出场景,避免强制覆盖蓝牙路由规则
  • 第二步:在路由变更通知中手动判断当前连接的音频设备,如果没有蓝牙设备,再通过overrideOutputAudioPort方法手动切换到扬声器,满足无蓝牙时默认走扬声器的需求
  • 第三步:新蓝牙设备连接时,重新激活AVAudioSession保证路由规则生效
修改后的代码示例

AppDelegate中AVAudioSession配置

let session = AVAudioSession.sharedInstance()
do {
    if #available(iOS 10.0, *) {
        try session.setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .allowBluetoothA2DP])
    } else {
        session.perform(NSSelectorFromString("setCategory:withOptions:error:"), with: AVAudioSession.Category.playAndRecord, with: [
            AVAudioSession.CategoryOptions.allowBluetooth,
            AVAudioSession.CategoryOptions.allowBluetoothA2DP
        ])
        try session.setMode(.default)
    }
    try session.setActive(true)
} catch {
    print(error)
}

ViewController中通知监听逻辑

@objc private func didRouteChangeNotification(_ notification: Notification) {
    guard let userInfo = notification.userInfo,
    let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue:reasonValue) else {
        return
    }
    let session = AVAudioSession.sharedInstance()
    var hasBluetoothDevice = false
    // 校验当前路由是否包含蓝牙设备
    for outputPort in session.currentRoute.outputs {
        switch outputPort.portType {
        case .bluetoothHFP, .bluetoothA2DP, .bluetoothLE:
            hasBluetoothDevice = true
        default: break
        }
    }
    // 无蓝牙设备时手动切换到扬声器
    if !hasBluetoothDevice {
        do {
            try session.overrideOutputAudioPort(.speaker)
        } catch {
            print("切换扬声器失败:\(error)")
        }
    }
    switch reason {
    case .newDeviceAvailable:
        debugPrint("newDeviceAvailable")
        // 新设备连接后重新激活session
        do {
            try session.setActive(true)
        } catch {
            print("session激活失败:\(error)")
        }
    case .oldDeviceUnavailable:
        debugPrint("oldDeviceUnavailable")
    case .categoryChange:
        debugPrint("categoryChange")
    default: ()
    }
}

内容的提问来源于stack exchange,提问作者Mike Simz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 13:39:00