import AVFoundation import os.log public enum AudioIntent { case voice case playback } public class AudioSessionHub { public static let shared = AudioSessionHub() private let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AudioSessionHub") private var currentIntent: AudioIntent? private let audioSession = AVAudioSession.sharedInstance() private var isInterrupted = false private init() { setupNotifications() } public func begin(_ intent: AudioIntent) { if intent == currentIntent && audioSession.isOtherAudioPlaying == false { return } currentIntent = intent do { switch intent { case .voice: try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .mixWithOthers]) // 如果有蓝牙HFP设备,强制设置为首选输入 if let bluetoothInput = audioSession.availableInputs?.first(where: { $0.portType == .bluetoothHFP }) { try audioSession.setPreferredInput(bluetoothInput) os_log("已设置蓝牙HFP为首选输入: %{public}@", log: logger, type: .info, bluetoothInput.portName) } case .playback: try audioSession.setCategory(.playback, mode: .default, options: [.allowBluetooth, .mixWithOthers]) } try audioSession.setActive(true, options: .notifyOthersOnDeactivation) os_log("音频会话已设置为:%{public}@", log: logger, type: .debug, String(describing: intent)) // 输出当前音频路由信息 let hasBluetoothDevice = hasBluetoothAudioDevice() let hasBluetoothMic = hasBluetoothMicrophone() let currentInput = getCurrentInputDevice() os_log("蓝牙音频设备状态: %{public}@", log: logger, type: .info, hasBluetoothDevice ? "已连接" : "未连接") os_log("蓝牙麦克风状态: %{public}@", log: logger, type: .info, hasBluetoothMic ? "已连接" : "未连接") os_log("当前输入设备: %{public}@", log: logger, type: .info, currentInput) if hasBluetoothDevice { os_log("%{public}@", log: logger, type: .debug, getCurrentAudioRoute()) } } catch { os_log("设置音频会话失败:%{public}@", log: logger, type: .error, error.localizedDescription) } } public func appWillEnterForeground() { if let intent = currentIntent { begin(intent) } } public func appDidEnterBackground() { do { try audioSession.setActive(false, options: .notifyOthersOnDeactivation) os_log("音频会话已释放(应用进入后台)", log: logger, type: .debug) } catch { os_log("释放音频会话失败:%{public}@", log: logger, type: .error, error.localizedDescription) } } /// 获取当前音频路由信息 public func getCurrentAudioRoute() -> String { let currentRoute = audioSession.currentRoute var routeInfo = "当前音频路由:\n" // 输出设备 routeInfo += "输出设备:\n" for output in currentRoute.outputs { routeInfo += " - \(output.portName) (\(output.portType.rawValue))\n" } // 输入设备 routeInfo += "输入设备:\n" for input in currentRoute.inputs { routeInfo += " - \(input.portName) (\(input.portType.rawValue))\n" } return routeInfo } /// 检查是否有蓝牙音频设备连接 public func hasBluetoothAudioDevice() -> Bool { let currentRoute = audioSession.currentRoute // 检查输出设备 for output in currentRoute.outputs { if output.portType == .bluetoothA2DP || output.portType == .bluetoothHFP || output.portType == .bluetoothLE { return true } } // 检查输入设备(这是关键!) for input in currentRoute.inputs { if input.portType == .bluetoothHFP { return true } } return false } /// 检查是否有蓝牙麦克风作为输入设备 public func hasBluetoothMicrophone() -> Bool { let currentRoute = audioSession.currentRoute for input in currentRoute.inputs { if input.portType == .bluetoothHFP { return true } } return false } /// 获取当前输入设备信息 public func getCurrentInputDevice() -> String { let currentRoute = audioSession.currentRoute if let input = currentRoute.inputs.first { return "\(input.portName) (\(input.portType.rawValue))" } return "无输入设备" } private func setupNotifications() { NotificationCenter.default.addObserver( self, selector: #selector(handleAudioSessionInterruption), name: AVAudioSession.interruptionNotification, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(handleAudioRouteChange), name: AVAudioSession.routeChangeNotification, object: nil ) } /// 处理音频中断 @objc private func handleAudioSessionInterruption(notification: Notification) { guard let userInfo = notification.userInfo, let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return } switch type { case .began: // 中断开始 isInterrupted = true os_log("音频会话被中断", log: logger, type: .debug) case .ended: // 中断结束 isInterrupted = false // 检查是否应该恢复 if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt { let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue) if options.contains(.shouldResume), let intent = currentIntent { // 恢复之前的音频意图 begin(intent) os_log("音频会话中断结束,已恢复", log: logger, type: .debug) } } @unknown default: os_log("未知的音频中断类型: %{public}@", log: logger, type: .error, "\(type)") } } /// 处理音频路由变化 @objc private func handleAudioRouteChange(notification: Notification) { guard let userInfo = notification.userInfo, let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } switch reason { case .newDeviceAvailable: os_log("新音频设备已连接", log: logger, type: .info) os_log("%{public}@", log: logger, type: .debug, getCurrentAudioRoute()) // 重新应用当前意图以确保正确路由 if let intent = currentIntent { begin(intent) } case .oldDeviceUnavailable: os_log("音频设备已断开", log: logger, type: .info) os_log("%{public}@", log: logger, type: .debug, getCurrentAudioRoute()) // 重新应用当前意图以确保正确路由 if let intent = currentIntent { begin(intent) } case .categoryChange: os_log("音频类别已变化", log: logger, type: .info) default: break } } deinit { NotificationCenter.default.removeObserver(self) } }