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.
629 lines
24 KiB
629 lines
24 KiB
import Foundation
|
|
import AVFoundation
|
|
import UIKit
|
|
|
|
/// 统一音频会话管理类
|
|
/// 用于统一管理所有组件的音频会话配置
|
|
@objc class AudioSessionManager: NSObject {
|
|
// MARK: - 单例实现
|
|
|
|
@objc static let shared = AudioSessionManager()
|
|
|
|
// 音频会话
|
|
private let audioSession = AVAudioSession.sharedInstance()
|
|
|
|
// 音频会话状态
|
|
private var isConfigured = false
|
|
private var isActive = false
|
|
|
|
// 当前配置类型
|
|
private var currentCategory: AVAudioSession.Category?
|
|
private var currentMode: AVAudioSession.Mode?
|
|
private var currentOptions: AVAudioSession.CategoryOptions?
|
|
|
|
// 中断监听器
|
|
private var interruptionObserver: NSObjectProtocol?
|
|
|
|
// 路由变化监听器
|
|
private var routeChangeObserver: NSObjectProtocol?
|
|
|
|
// 私有初始化方法,确保只能通过shared访问
|
|
private override init() {
|
|
super.init()
|
|
NSLog("[AudioSessionManager] 音频会话管理器已初始化")
|
|
setupNotifications()
|
|
|
|
do {
|
|
// try configureSession(
|
|
// category: .playAndRecord,
|
|
// mode: .default,
|
|
// options: [.allowBluetooth, .allowBluetoothA2DP, .duckOthers, .defaultToSpeaker],
|
|
// force: true
|
|
// )
|
|
NSLog("[AudioSessionManager] 初始化音频会话配置成功")
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 初始化音频会话配置失败: \(error.localizedDescription)")
|
|
}
|
|
// BluetoothMediaButtonHelper.shared.startButtonListening()
|
|
}
|
|
|
|
deinit {
|
|
removeNotifications()
|
|
}
|
|
|
|
|
|
// MARK: - 通知设置
|
|
|
|
private func setupNotifications() {
|
|
// 监听中断事件
|
|
interruptionObserver = NotificationCenter.default.addObserver(
|
|
forName: AVAudioSession.interruptionNotification,
|
|
object: nil,
|
|
queue: .main) { [weak self] notification in
|
|
self?.handleInterruption(notification)
|
|
}
|
|
|
|
// 监听路由变化事件
|
|
routeChangeObserver = NotificationCenter.default.addObserver(
|
|
forName: AVAudioSession.routeChangeNotification,
|
|
object: nil,
|
|
queue: .main) { [weak self] notification in
|
|
self?.handleRouteChange(notification)
|
|
}
|
|
|
|
// 监听应用进入后台/前台
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(handleAppDidEnterBackground),
|
|
name: UIApplication.didEnterBackgroundNotification,
|
|
object: nil)
|
|
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(handleAppWillEnterForeground),
|
|
name: UIApplication.willEnterForegroundNotification,
|
|
object: nil)
|
|
}
|
|
|
|
private func removeNotifications() {
|
|
if let interruptionObserver = interruptionObserver {
|
|
NotificationCenter.default.removeObserver(interruptionObserver)
|
|
}
|
|
|
|
if let routeChangeObserver = routeChangeObserver {
|
|
NotificationCenter.default.removeObserver(routeChangeObserver)
|
|
}
|
|
|
|
NotificationCenter.default.removeObserver(self)
|
|
}
|
|
|
|
// MARK: - 通知处理
|
|
|
|
private func handleInterruption(_ 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:
|
|
// 中断开始
|
|
NSLog("[AudioSessionManager] 音频会话被中断")
|
|
isActive = false
|
|
|
|
case .ended:
|
|
// 中断结束,尝试恢复
|
|
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
|
|
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
|
if options.contains(.shouldResume) {
|
|
NSLog("[AudioSessionManager] 尝试恢复被中断的音频会话")
|
|
try? activateSession()
|
|
}
|
|
}
|
|
|
|
@unknown default:
|
|
break
|
|
}
|
|
}
|
|
|
|
private func handleRouteChange(_ 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:
|
|
// 新设备连接
|
|
let currentRoute = audioSession.currentRoute
|
|
for output in currentRoute.outputs where output.portType == .bluetoothA2DP || output.portType == .bluetoothHFP {
|
|
NSLog("[AudioSessionManager] 检测到蓝牙音频设备连接: \(output.portName)")
|
|
// 蓝牙设备连接,可能需要更新配置
|
|
if isActive && currentOptions?.contains(.allowBluetooth) == true {
|
|
try? forceReconfigure()
|
|
}
|
|
break
|
|
}
|
|
|
|
case .oldDeviceUnavailable:
|
|
// 设备断开连接
|
|
if let previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
|
|
for output in previousRoute.outputs where output.portType == .bluetoothA2DP || output.portType == .bluetoothHFP {
|
|
NSLog("[AudioSessionManager] 蓝牙音频设备断开连接: \(output.portName)")
|
|
// 蓝牙设备断开,重新配置到内置扬声器
|
|
if isActive {
|
|
try? forceReconfigure()
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
@objc private func handleAppDidEnterBackground() {
|
|
NSLog("[AudioSessionManager] 应用进入后台")
|
|
// 应用进入后台时可能需要调整音频会话
|
|
}
|
|
|
|
@objc private func handleAppWillEnterForeground() {
|
|
NSLog("[AudioSessionManager] 应用即将进入前台")
|
|
// 应用返回前台,检查并恢复音频会话
|
|
if isConfigured && !isActive {
|
|
try? activateSession()
|
|
}
|
|
}
|
|
|
|
// MARK: - 音频会话配置
|
|
|
|
/// 统一配置方法 - 所有音频配置都通过此方法进行
|
|
@objc func configureSession(category: AVAudioSession.Category,
|
|
mode: AVAudioSession.Mode,
|
|
options: AVAudioSession.CategoryOptions,
|
|
force: Bool = false) throws {
|
|
|
|
NSLog("[AudioSessionManager] 配置音频会话: 类别=\(category), 模式=\(mode), 选项=\(options), 强制=\(force)")
|
|
|
|
// 如果已配置且不强制重新配置,则直接返回
|
|
if isConfigured && !force &&
|
|
currentCategory == category &&
|
|
currentMode == mode &&
|
|
currentOptions == options {
|
|
NSLog("[AudioSessionManager] 音频会话已配置,跳过配置")
|
|
return
|
|
}
|
|
|
|
do {
|
|
// NSLog("[AudioSessionManager] 配置音频会话: 类别=\(category), 模式=\(mode)")
|
|
|
|
// 暂时停用当前会话(如果正在活动)
|
|
// if isActive {
|
|
// try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
|
|
// }
|
|
|
|
// 设置新的分类、模式和选项
|
|
try audioSession.setCategory(category, mode: mode, options: options)
|
|
|
|
// 设置采样率和缓冲区大小(对于语音识别优化)
|
|
// if mode == .spokenAudio {
|
|
// try audioSession.setPreferredSampleRate(16000)
|
|
// try audioSession.setPreferredIOBufferDuration(0.01) // 10ms
|
|
// }
|
|
|
|
// 重新激活会话
|
|
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
|
|
isActive = true
|
|
|
|
// 记录当前配置
|
|
currentCategory = category
|
|
currentMode = mode
|
|
currentOptions = options
|
|
isConfigured = true
|
|
|
|
// 检查并设置首选的输入设备(如果需要)
|
|
// if category == .playAndRecord || category == .record {
|
|
// try configurePreferredInput()
|
|
// }
|
|
return
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 配置音频会话失败: \(error.localizedDescription)")
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/// 配置首选输入设备
|
|
private func configurePreferredInput() throws {
|
|
// 获取可用输入设备
|
|
guard let availableInputs = audioSession.availableInputs else {
|
|
NSLog("[AudioSessionManager] 无法获取可用输入设备")
|
|
return
|
|
}
|
|
|
|
// 首选顺序:有线耳机 > 蓝牙耳机 > 内置麦克风
|
|
var preferredInput: AVAudioSessionPortDescription? = nil
|
|
|
|
// 先尝试找到有线耳机
|
|
if let wiredHeadset = availableInputs.first(where: { $0.portType == .headsetMic }) {
|
|
preferredInput = wiredHeadset
|
|
NSLog("[AudioSessionManager] 设置首选输入设备: 有线耳机")
|
|
}
|
|
// 如果没有有线耳机,尝试使用蓝牙
|
|
else if let bluetooth = availableInputs.first(where: { $0.portType == .bluetoothHFP }) {
|
|
preferredInput = bluetooth
|
|
NSLog("[AudioSessionManager] 设置首选输入设备: 蓝牙耳机")
|
|
}
|
|
// 如果没有外部设备,使用内置麦克风
|
|
else if let builtIn = availableInputs.first(where: { $0.portType == .builtInMic }) {
|
|
preferredInput = builtIn
|
|
NSLog("[AudioSessionManager] 设置首选输入设备: 内置麦克风")
|
|
}
|
|
|
|
// 设置首选输入设备
|
|
if let input = preferredInput {
|
|
try audioSession.setPreferredInput(input)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// 激活音频会话(如果已配置)
|
|
@objc func activateSession() throws {
|
|
if !isConfigured {
|
|
NSLog("[AudioSessionManager] 警告: 尝试激活未配置的音频会话,应用默认配置")
|
|
// 未配置,应用默认配置
|
|
try configureSession(
|
|
category: .playAndRecord,
|
|
mode: .spokenAudio,
|
|
options: [.allowBluetooth, .allowBluetoothA2DP, .duckOthers, .defaultToSpeaker],
|
|
force: true
|
|
)
|
|
} else if !isActive {
|
|
NSLog("[AudioSessionManager] 激活已配置的音频会话")
|
|
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
|
|
isActive = true
|
|
}
|
|
}
|
|
|
|
/// 停用音频会话(保留配置)
|
|
@objc func deactivateSession() throws {
|
|
if isActive {
|
|
NSLog("[AudioSessionManager] 停用音频会话")
|
|
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
|
|
isActive = false
|
|
}
|
|
}
|
|
|
|
/// 重新配置当前会话
|
|
@objc func forceReconfigure() throws {
|
|
NSLog("[AudioSessionManager] 强制重新配置音频会话")
|
|
try configureSession(
|
|
category: currentCategory ?? .playAndRecord,
|
|
mode: currentMode ?? .default,
|
|
options: currentOptions ?? [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker],
|
|
force: true
|
|
)
|
|
}
|
|
|
|
// MARK: - 状态检查
|
|
|
|
/// 检查麦克风权限
|
|
@objc func checkMicrophonePermission(completion: @escaping (Bool) -> Void) {
|
|
switch AVAudioSession.sharedInstance().recordPermission {
|
|
case .granted:
|
|
completion(true)
|
|
case .denied:
|
|
NSLog("[AudioSessionManager] 麦克风权限被拒绝")
|
|
completion(false)
|
|
case .undetermined:
|
|
NSLog("[AudioSessionManager] 请求麦克风权限")
|
|
AVAudioSession.sharedInstance().requestRecordPermission { granted in
|
|
DispatchQueue.main.async {
|
|
completion(granted)
|
|
}
|
|
}
|
|
@unknown default:
|
|
completion(false)
|
|
}
|
|
}
|
|
|
|
/// 获取当前音频会话的配置信息
|
|
@objc func getCurrentConfiguration() -> [String: Any] {
|
|
return [
|
|
"isConfigured": isConfigured,
|
|
"isActive": isActive,
|
|
"category": String(describing: currentCategory),
|
|
"mode": String(describing: currentMode),
|
|
"bluetoothEnabled": currentOptions?.contains(.allowBluetooth) == true,
|
|
"currentInputs": audioSession.currentRoute.inputs.map { [$0.portType.rawValue: $0.portName] },
|
|
"currentOutputs": audioSession.currentRoute.outputs.map { [$0.portType.rawValue: $0.portName] }
|
|
]
|
|
}
|
|
|
|
/// 检查当前是否有蓝牙耳机连接
|
|
@objc func hasBluetoothHeadsetConnected() -> Bool {
|
|
let outputs = audioSession.currentRoute.outputs
|
|
return outputs.contains { $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE }
|
|
}
|
|
|
|
/// 检查蓝牙是否启用
|
|
@objc func isBluetoothEnabled() -> Bool {
|
|
// 在iOS中无法直接检查蓝牙是否启用,我们可以通过检查音频会话配置来推断
|
|
|
|
// 检查音频会话是否配置了允许蓝牙
|
|
if let options = currentOptions {
|
|
return options.contains(.allowBluetooth) || options.contains(.allowBluetoothA2DP)
|
|
}
|
|
|
|
// 如果尚未配置音频会话,尝试检查当前路由中是否有蓝牙设备
|
|
let outputs = audioSession.currentRoute.outputs
|
|
let hasBluetooth = outputs.contains {
|
|
$0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE
|
|
}
|
|
|
|
return hasBluetooth
|
|
}
|
|
|
|
/// 获取当前音频路由
|
|
@objc func getCurrentAudioRoute() -> String {
|
|
let outputs = audioSession.currentRoute.outputs
|
|
if let output = outputs.first {
|
|
return "\(output.portType.rawValue) (\(output.portName))"
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
/// 获取当前输入设备
|
|
@objc func getCurrentInputDevice() -> String {
|
|
let inputs = audioSession.currentRoute.inputs
|
|
if let input = inputs.first {
|
|
return "\(input.portType.rawValue) (\(input.portName))"
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
// MARK: - 路由管理和监听
|
|
|
|
/// 获取当前路由信息
|
|
@objc func getCurrentRoute() -> AVAudioSessionRouteDescription {
|
|
return audioSession.currentRoute
|
|
}
|
|
|
|
/// 添加路由变更监听器
|
|
/// - Parameters:
|
|
/// - observer: 监听对象
|
|
/// - selector: 监听方法选择器
|
|
@objc func addRouteChangeListener(_ observer: Any, selector: Selector) {
|
|
NotificationCenter.default.addObserver(
|
|
observer,
|
|
selector: selector,
|
|
name: AVAudioSession.routeChangeNotification,
|
|
object: nil
|
|
)
|
|
}
|
|
|
|
/// 移除路由变更监听器
|
|
/// - Parameter observer: 要移除的监听对象
|
|
@objc func removeRouteChangeListener(_ observer: Any) {
|
|
NotificationCenter.default.removeObserver(
|
|
observer,
|
|
name: AVAudioSession.routeChangeNotification,
|
|
object: nil
|
|
)
|
|
}
|
|
/// 获取设备类型描述
|
|
private func getDeviceType(portType: AVAudioSession.Port) -> String {
|
|
switch portType {
|
|
case .bluetoothA2DP, .bluetoothHFP, .bluetoothLE:
|
|
return "bluetooth"
|
|
case .headphones, .headsetMic:
|
|
return "wired"
|
|
case .airPlay:
|
|
return "airplay"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// MARK: - 蓝牙媒体按钮配置
|
|
|
|
/// 配置用于获取蓝牙媒体按钮响应的音频会话
|
|
/// 此函数专门针对需要接收蓝牙耳机上媒体按钮事件的场景进行优化
|
|
/// - Returns: 配置是否成功
|
|
@objc func configureForBluetoothMediaButtons() -> Bool {
|
|
do {
|
|
// 配置音频会话以支持蓝牙媒体按钮
|
|
// 使用playback类别确保可以接收远程控制事件
|
|
// 使用spokenAudio模式优化语音交互
|
|
try configureSession(
|
|
category: .playback,
|
|
mode: .default, // 使用default模式,避免与spokenAudio可能的冲突
|
|
options: [
|
|
.allowBluetooth, // 允许通过蓝牙SCO连接进行输入和输出
|
|
.allowBluetoothA2DP, // 允许通过蓝牙A2DP连接进行高质量音频输出
|
|
.mixWithOthers // 允许与其他音频混合,避免中断
|
|
],
|
|
force: true // 强制重新配置,确保设置生效
|
|
)
|
|
|
|
NSLog("[AudioSessionManager] 已配置蓝牙媒体按钮响应的音频会话")
|
|
return true
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 配置蓝牙媒体按钮响应的音频会话失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - Azure语音服务配置
|
|
|
|
|
|
/// 配置用于语音合成(TTS)的音频会话
|
|
/// 此函数针对TTS使用场景优化音频播放
|
|
/// - Returns: 配置是否成功
|
|
@objc func configureForTts() -> Bool {
|
|
do {
|
|
// 配置音频会话以支持语音合成
|
|
// 使用playback类别用于音频播放
|
|
try configureSession(
|
|
category: .playback,
|
|
mode: .spokenAudio, // 针对语音输出优化
|
|
options: [
|
|
.allowBluetooth, // 允许通过蓝牙连接进行输出
|
|
.allowBluetoothA2DP, // 允许通过蓝牙A2DP连接进行高质量音频输出
|
|
.mixWithOthers // 允许与其他音频混合,避免中断
|
|
],
|
|
force: false
|
|
)
|
|
|
|
NSLog("[AudioSessionManager] 已配置TTS的音频会话")
|
|
return true
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 配置TTS的音频会话失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 配置用于语音识别(ASR)的音频会话
|
|
/// 此函数针对ASR使用场景优化音频输入
|
|
/// - Returns: 配置是否成功
|
|
@objc func configureForAsr() -> Bool {
|
|
do {
|
|
// 配置音频会话以支持语音识别
|
|
// 使用record类别专注于录音
|
|
try configureSession(
|
|
category: .record,
|
|
mode: .measurement, // 使用measurement模式获取最高质量的音频输入
|
|
options: [
|
|
.allowBluetooth // 允许通过蓝牙SCO连接进行输入
|
|
],
|
|
force: false
|
|
)
|
|
|
|
// 设置首选输入设备
|
|
try configurePreferredInput()
|
|
|
|
NSLog("[AudioSessionManager] 已配置ASR的音频会话")
|
|
return true
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 配置ASR的音频会话失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 配置用于语音合成+音频播放的音频会话(同时支持TTS和音乐播放)
|
|
/// 此函数针对同时需要TTS和背景音乐的场景进行优化
|
|
/// - Returns: 配置是否成功
|
|
@objc func configureForTtsWithMusic() -> Bool {
|
|
do {
|
|
// 配置音频会话以支持TTS和音乐播放
|
|
try configureSession(
|
|
category: .playback,
|
|
mode: .default, // 使用默认模式以适应多种音频类型
|
|
options: [
|
|
.allowBluetooth, // 允许通过蓝牙连接进行输出
|
|
.allowBluetoothA2DP, // 允许通过蓝牙A2DP连接进行高质量音频输出
|
|
.mixWithOthers, // 允许与其他应用的音频混合
|
|
.allowAirPlay // 支持AirPlay设备
|
|
],
|
|
force: false
|
|
)
|
|
|
|
NSLog("[AudioSessionManager] 已配置TTS+音乐播放的音频会话")
|
|
return true
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 配置TTS+音乐播放的音频会话失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 配置用于全语音助手模式的音频会话(优先级最高)
|
|
/// 此函数针对需要完全控制音频体验的语音助手场景
|
|
/// - Returns: 配置是否成功
|
|
@objc func configureForVoiceAssistant() -> Bool {
|
|
do {
|
|
// 配置音频会话以支持完整语音助手功能
|
|
try configureSession(
|
|
category: .playAndRecord,
|
|
mode: .spokenAudio,
|
|
options: [
|
|
.allowBluetooth, // 允许通过蓝牙连接
|
|
.allowBluetoothA2DP, // 允许通过蓝牙A2DP连接
|
|
.defaultToSpeaker, // 默认使用扬声器输出
|
|
.duckOthers, // 降低其他应用的音量
|
|
.interruptSpokenAudioAndMixWithOthers // 中断其他语音音频并混合
|
|
],
|
|
force: true // 强制重新配置,确保设置生效
|
|
)
|
|
|
|
// 设置首选输入设备
|
|
try configurePreferredInput()
|
|
|
|
NSLog("[AudioSessionManager] 已配置语音助手模式的音频会话")
|
|
return true
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 配置语音助手模式的音频会话失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 配置用于多任务模式的音频会话(与其他应用友好共存)
|
|
/// 此函数针对需要在后台继续工作但不中断其他应用音频的场景
|
|
/// - Returns: 配置是否成功
|
|
@objc func configureForMultitasking() -> Bool {
|
|
do {
|
|
// 配置音频会话以支持多任务
|
|
try configureSession(
|
|
category: .ambient, // 使用ambient类别不会中断其他音频
|
|
mode: .default,
|
|
options: [
|
|
.mixWithOthers, // 允许与其他应用的音频混合
|
|
.allowBluetooth, // 允许通过蓝牙连接
|
|
.allowBluetoothA2DP // 允许通过蓝牙A2DP连接
|
|
],
|
|
force: false
|
|
)
|
|
|
|
NSLog("[AudioSessionManager] 已配置多任务模式的音频会话")
|
|
return true
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 配置多任务模式的音频会话失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 配置用于语音交互(同时支持ASR和TTS)的音频会话
|
|
/// 此函数综合优化语音识别和语音合成,适用于需要双向交互的场景
|
|
/// - Returns: 配置是否成功
|
|
@objc func configureForVoiceInteraction() -> Bool {
|
|
do {
|
|
// 配置音频会话以支持语音交互
|
|
// 使用playAndRecord类别允许同时录音和播放
|
|
// 使用spokenAudio模式优化语音交互
|
|
try configureSession(
|
|
category: .playAndRecord,
|
|
mode: .spokenAudio,
|
|
options: [
|
|
// .allowBluetooth, // 允许通过蓝牙SCO连接进行输入和输出
|
|
.allowBluetoothA2DP, // 允许通过蓝牙A2DP连接进行高质量音频输出
|
|
],
|
|
force: false // 强制重新配置,确保设置生效
|
|
)
|
|
|
|
// 设置首选输入设备
|
|
// try configurePreferredInput()
|
|
|
|
NSLog("[AudioSessionManager] 已配置语音交互(ASR+TTS)的音频会话")
|
|
return true
|
|
} catch {
|
|
NSLog("[AudioSessionManager] 配置语音交互(ASR+TTS)的音频会话失败: \(error.localizedDescription)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
}
|