You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
8.4 KiB

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)
}
}