Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
liwei1dao 1 year ago
parent
commit
18ccca9a30
  1. 14
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift
  2. 2
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift
  3. 317
      local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift
  4. 426
      local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift

14
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift

@ -639,13 +639,13 @@ public class AudioSessionHub {
}
private func performVolumeControlWithMPVolumeView(action: VolumeControlAction, value: Float?, completion: @escaping (Bool, String) -> Void) {
// 确保在有音频会话的情况下进行音量控制
do {
try audioSession.setCategory(.playback, mode: .videoChat, options: [.defaultToSpeaker, .allowBluetooth,.mixWithOthers])
try audioSession.setActive(true)
} catch {
os_log("设置音频会话失败: %{public}@", log: logger, type: .error, error.localizedDescription)
}
// // 确保在有音频会话的情况下进行音量控制
// do {
// try audioSession.setCategory(.playback, mode: .videoChat, options: [.defaultToSpeaker, .allowBluetooth,.mixWithOthers])
// try audioSession.setActive(true)
// } catch {
// os_log("设置音频会话失败: %{public}@", log: logger, type: .error, error.localizedDescription)
// }
let volumeView = MPVolumeView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
volumeView.showsVolumeSlider = true

2
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift

@ -412,6 +412,7 @@ private func sendAstEvent(_ event: [String: Any]) {
return
}
do {
try azureAsrHelper.stopRecord(isSave: isSave)
result(true)
} catch {
@ -1051,7 +1052,6 @@ private class AudioDataCallbackImpl: SimpleAudioReceiver.AudioDataCallback {
* @param data 音频数据
*/
func onAudio(_ data: Data) {
print("音频数据回调方法: \(data.count)")
callback(data)
}
}

317
local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift

@ -0,0 +1,317 @@
import AVFoundation
class MicrophoneCapture: NSObject {
// MARK: - 单例实例
/**
* 单例实例,确保整个应用中只有一个音频捕获对象
* 避免多个实例同时访问麦克风资源导致冲突
*/
static let shared = MicrophoneCapture()
private var audioEngine: AVAudioEngine!
private var audioSession: AVAudioSession!
private var audioFormat: AVAudioFormat?
private var audioInputNode: AVAudioInputNode!
private var sampleRate: Double = 16000 // 修改为16000采样率,符合Microsoft要求
private var isCapturing = false
// 音频数据回调
var audioDataHandler: ((Data) -> Void)?
/**
* 私有初始化方法,防止外部直接创建实例
* 只能通过shared单例访问
*/
private override init() {
super.init()
setupAudioSession()
}
/// 获取最佳音频格式 (iOS 通常支持标准采样率)
public func getOptimalAudioFormat() -> AVAudioFormat? {
let sampleRate: Double = 16000 // Microsoft要求最低16kHz
return AVAudioFormat(
commonFormat: .pcmFormatInt16, // 16位little endian有符号整数
sampleRate: sampleRate,
channels: 1,
interleaved: true
)
}
// MARK: - 音频会话配置
/**
* 配置音频会话参数
* 设置音频类别、采样率、缓冲区大小等
*/
public func setupAudioSession() {
audioFormat = getOptimalAudioFormat()
audioSession = AVAudioSession.sharedInstance()
do {
// 设置音频会话参数
try audioSession.setCategory(.playAndRecord,
mode: .videoChat,
options: [//.allowBluetooth,
// .allowAirPlay,
.allowBluetoothA2DP,
.mixWithOthers,
.defaultToSpeaker
])
try audioSession.setPreferredSampleRate(sampleRate)
try audioSession.setPreferredIOBufferDuration(0.005) // 5ms缓冲
// 设置蓝牙设备优先级
if let bluetoothInput = audioSession.availableInputs?.first(where: { $0.portType == .bluetoothHFP }) {
try audioSession.setPreferredInput(bluetoothInput)
}
try audioSession.setActive(true)
print("音频会话配置成功")
} catch {
print("音频会话配置失败: \(error.localizedDescription)")
}
}
// MARK: - 开始捕获
/**
* 开始音频捕获
* 检查麦克风权限并启动音频引擎
*/
public func startCapture() throws {
guard !isCapturing else { return }
print("开始捕获")
// 检查麦克风权限
switch audioSession.recordPermission {
case .granted:
try setupAudioEngine()
case .denied:
throw NSError(domain: "麦克风权限被拒绝", code: 0)
case .undetermined:
audioSession.requestRecordPermission { granted in
if granted {
do {
try self.setupAudioEngine()
} catch {
print("音频引擎设置失败: \(error.localizedDescription)")
}
}
}
@unknown default:
throw NSError(domain: "未知权限状态", code: 1)
}
}
/**
* 检查音频格式是否符合Microsoft音频堆栈要求
* 如果符合则无需转换,直接使用原始格式
*/
private func isMicrosoftCompatibleFormat(_ format: AVAudioFormat) -> Bool {
// Microsoft支持的采样率:16kHz的整数倍,最低16kHz
let sampleRate = format.sampleRate
guard sampleRate >= 16000 && sampleRate.truncatingRemainder(dividingBy: 16000) == 0 else {
return false
}
// Microsoft支持的格式
switch format.commonFormat {
case .pcmFormatFloat32: // 32位IEEE little endian浮点
return true
case .pcmFormatInt32: // 32位little endian有符号整数
return true
case .pcmFormatInt16: // 16位little endian有符号整数
return true
default:
return false
}
}
/**
* 设置音频引擎和音频处理管道
* 根据Microsoft音频堆栈要求优化格式转换
*/
private func setupAudioEngine() throws {
audioEngine = AVAudioEngine()
audioInputNode = audioEngine.inputNode
// 获取实际硬件采样率
sampleRate = audioSession.sampleRate
// 移除之前的音频处理块,避免重复添加
audioInputNode.removeTap(onBus: 0)
// 设置音频格式
let inputFormat = audioInputNode.inputFormat(forBus: 0)
// iOS 13+ 启用语音处理
if #available(iOS 13.0, *) {
try audioInputNode.setVoiceProcessingEnabled(true)
}
// 检查输入格式是否符合Microsoft要求
let needsConversion = !isMicrosoftCompatibleFormat(inputFormat)
// if needsConversion {
// 需要格式转换
guard let targetFormat = audioFormat,
let converter = AVAudioConverter(from: inputFormat, to: targetFormat) else {
throw NSError(domain: "AudioSetup", code: 2)
}
print("输入格式不符合Microsoft要求,进行格式转换")
print("输入格式: \(inputFormat.sampleRate)Hz, \(inputFormat.commonFormat.rawValue)")
print("目标格式: \(targetFormat.sampleRate)Hz, \(targetFormat.commonFormat.rawValue)")
// 添加tap进行格式转换
audioInputNode.installTap(onBus: 0,
bufferSize: 1024,
format: inputFormat) { [weak self] buffer, when in
guard let strongSelf = self else { return }
// 创建目标格式的音频缓冲区
let convertedBuffer = AVAudioPCMBuffer(
pcmFormat: targetFormat,
frameCapacity: AVAudioFrameCount(
targetFormat.sampleRate * Double(buffer.frameLength) / buffer.format.sampleRate
)
)!
var error: NSError?
// 执行音频格式转换
let status = converter.convert(
to: convertedBuffer,
error: &error,
withInputFrom: { inNumPackets, outStatus in
outStatus.pointee = .haveData
return buffer
}
)
// 转换成功且无错误
if status == .haveData, error == nil {
let data = strongSelf.audioBufferToData(convertedBuffer)
print("转换后数据长度: \(data.count)")
strongSelf.audioDataHandler?(data)
}
}
// } else {
// // 格式已符合Microsoft要求,直接使用
// print("输入格式符合Microsoft要求,无需转换")
// print("格式: \(inputFormat.sampleRate)Hz, \(inputFormat.commonFormat.rawValue)")
// audioInputNode.installTap(onBus: 0,
// bufferSize: 1024,
// format: inputFormat) { [weak self] buffer, when in
// guard let strongSelf = self else { return }
//strongSelf.processAudioBuffer(buffer)
// // 直接处理原始音频数据
// let data = strongSelf.audioBufferToData(convertedBuffer)
// print("转换后数据长度: \(data.count)")
// strongSelf.audioDataHandler?(data)
// }
// }
// 启动引擎
do {
try audioEngine.start()
isCapturing = true
} catch {
print("音频引擎启动失败: \(error.localizedDescription)")
}
}
/**
* 将音频缓冲区转换为Data格式
* 支持16位整数和32位浮点格式
*/
private func audioBufferToData(_ buffer: AVAudioPCMBuffer) -> Data {
let frameLength = Int(buffer.frameLength)
let channelCount = 1
let dataLength = frameLength * channelCount * MemoryLayout<Int16>.size
// Handle 16-bit integer format
if let int16Data = buffer.int16ChannelData {
return Data(
bytes: int16Data.pointee,
count: dataLength
)
}
// Handle float format
else if let floatData = buffer.floatChannelData {
var int16Array = [Int16](repeating: 0, count: frameLength)
let floatBuffer = floatData.pointee
for i in 0..<frameLength {
let sample = floatBuffer[i]
let clamped = max(-1.0, min(sample, 1.0))
let scaled = clamped * Float(Int16.max)
int16Array[i] = Int16(scaled)
}
return Data(
bytes: int16Array,
count: dataLength
)
}
return Data() // Fallback for unsupported formats
}
// MARK: - 音频数据处理
/**
* 处理音频缓冲区数据
* 将浮点音频数据转换为16位PCM格式
*/
private func processAudioBuffer(_ buffer: AVAudioPCMBuffer) {
guard let floatData = buffer.floatChannelData?[0] else { return }
let channelCount = Int(buffer.format.channelCount)
let frameLength = Int(buffer.frameLength)
let sampleCount = frameLength * channelCount
// 转换为16位PCM数据(兼容大多数音频处理场景)
var pcmData = Data()
for i in 0..<sampleCount {
let sample = floatData[i] * Float32(Int16.max)
var pcmSample = Int16(sample)
pcmData.append(Data(bytes: &pcmSample, count: MemoryLayout<Int16>.size))
}
// 通过回调返回数据
DispatchQueue.main.async {
print("音频数据回调\(pcmData.count)")
self.audioDataHandler?(pcmData)
}
}
// MARK: - 停止捕获
/**
* 停止音频捕获
* 清理音频引擎和会话资源
*/
func stopCapture() {
guard isCapturing else { return }
audioEngine.stop()
audioInputNode.removeTap(onBus: 0)
// try? audioSession.setActive(false)
isCapturing = false
}
/**
* 重置音频捕获状态
* 用于单例模式下的状态清理
*/
func reset() {
stopCapture()
audioDataHandler = nil
}
deinit {
stopCapture()
}
}

426
local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift

@ -10,24 +10,17 @@ import os.log
*/
public class SimpleAudioReceiver: NSObject {
// // MARK: - 单例实现
// /**
// * 单例实例
// */
// public static let shared = SimpleAudioReceiver()
// /**
// * 私有初始化方法,防止外部创建实例
// */
// private override init() {
// super.init()
// initAudioRecord()
// }
private let tag = "SimpleAudioReceiver"
private let log = OSLog(subsystem: "com.azure.speech", category: "SimpleAudioReceiver")
/**
* 麦克风捕获实例
* 使用 MicrophoneCapture 的单例模式
*/
private var micCapture: MicrophoneCapture!
/**
* 音频来源类型
*/
@ -65,21 +58,17 @@ public class SimpleAudioReceiver: NSObject {
* 注意:由于是单例模式,此方法可以被多次调用但只会初始化一次
*/
public func initAudioRecord() {
// 防止重复初始化
guard audioEngine == nil else {
print("音频录制组件已初始化,跳过重复初始化")
return
}
if audioSession.recordPermission == .granted { //判断权限成功 预热麦克风
preConfigureAudioSession()
}
micCapture = MicrophoneCapture.shared
// .audioDataHandler = { data in
// self.writeQueue.offer(data)
// }
// 预配置音频会话
audioFormat = getOptimalAudioFormat()
print("初始化了")
pushAudioStream = SPXPushAudioInputStream()
// 初始化音频引擎
audioEngine = AVAudioEngine()
isRunning = true
// 创建新的写线程
@ -87,38 +76,7 @@ public class SimpleAudioReceiver: NSObject {
startWriteThread()
}
// 【新增】预配置音频会话方法
/**
* 预配置音频会话,在应用启动时调用以减少首次录音延迟
* 【优化重点】将耗时的音频会话配置提前到初始化阶段
*/
private func preConfigureAudioSession() {
do {
// 设置音频会话类别和模式
runMicrophoneCapture()
stopMicrophoneCapture()
print("音频会话预配置完成")
} catch {
print("音频会话预配置失败: \(error)")
}
}
/// 获取最佳音频格式 (iOS 通常支持标准采样率)
public func getOptimalAudioFormat() -> AVAudioFormat? {
let sampleRate: Double = 16000 // iOS 通常支持 16kHz
return AVAudioFormat(
commonFormat: .pcmFormatInt16,
sampleRate: sampleRate,
channels: 1,
interleaved: true
)
}
/**
* 设置音频配置
* @param sampleRate 采样率,默认16000
* @param channels 声道数,默认1
*/
/**
* 设置音频配置参数
* @param sampleRate 采样率
@ -184,10 +142,10 @@ public class SimpleAudioReceiver: NSObject {
do {
if self.audioDataCallback != nil {
print("写入数据长度: \(dataToWrite.count)")
self.audioDataCallback!.onAudio(dataToWrite)
}
if self.pushAudioStream != nil && self.audioDataCallback == nil {
try self.pushAudioStream?.write(dataToWrite)
}
if self.recordfile != nil {
@ -218,112 +176,21 @@ public class SimpleAudioReceiver: NSObject {
writeQueue.put(data)
}
// 私有方法:启动麦克风捕获
/**
* 启动麦克风捕获
* 优化:复用已初始化的音频引擎,减少启动延迟
* 运行麦克风捕获
*/
private func runMicrophoneCapture() {
do {
print("liwei------------启动麦克风权限")
// 【新增】首先配置音频会话
try audioSession.setCategory(
.playAndRecord,
mode: .default,
options: [
.defaultToSpeaker,
.allowBluetooth,
.mixWithOthers,
.duckOthers
]
)
// 【新增】请求麦克风权限(如果尚未授权)
if audioSession.recordPermission != .granted {
audioSession.requestRecordPermission { granted in
if !granted {
print("麦克风权限被拒绝")
}
}
}
// 【新增】激活音频会话(在配置音频引擎之前)
try audioSession.setActive(true)
// 检查音频引擎是否已初始化,避免重复创建
if audioEngine == nil {
audioEngine = AVAudioEngine()
}
// 如果音频引擎正在运行,先停止
if audioEngine?.isRunning == true {
audioEngine?.stop()
}
// 获取音频输入节点(麦克风)
guard let inputNode = audioEngine?.inputNode else {
throw NSError(domain: "AudioSetup", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法获取音频输入节点"])
}
// 移除之前的音频处理块,避免重复添加
inputNode.removeTap(onBus: 0)
// 获取硬件支持的原始音频格式
let hardwareFormat = inputNode.inputFormat(forBus: 0)
// iOS 13+ 启用语音处理
if #available(iOS 13.0, *) {
try inputNode.setVoiceProcessingEnabled(true)
// 确保正在写入状态
guard self._isWriting else { return }
try micCapture.startCapture()
try micCapture.audioDataHandler = { data in
self.writeQueue.put(data)
}
// 检查目标音频格式和转换器是否可用
guard let targetFormat = audioFormat, // 外部定义的期望音频格式
let converter = AVAudioConverter(from: hardwareFormat, to: targetFormat) else {
throw NSError(domain: "AudioSetup", code: 2)
}
// 在输入节点上安装录音回调
inputNode.installTap(onBus: 0,
bufferSize: UInt32(bufferSize), // 每次回调的缓冲区大小
format: hardwareFormat) { // 使用原始硬件格式
[weak self] buffer, time in // 弱引用避免循环引用
// 确保实例存在且正在写入状态
guard let self = self, self._isWriting else { return }
// 创建目标格式的音频缓冲区
let convertedBuffer = AVAudioPCMBuffer(
pcmFormat: targetFormat,
// 计算转换后的帧容量(考虑采样率差异)
frameCapacity: AVAudioFrameCount(
targetFormat.sampleRate * Double(buffer.frameLength) / buffer.format.sampleRate
)
)!
var error: NSError?
// 执行音频格式转换
let status = converter.convert(
to: convertedBuffer,
error: &error,
withInputFrom: { inNumPackets, outStatus in
outStatus.pointee = .haveData // 标记有数据可用
return buffer // 返回原始音频数据
}
)
// 转换成功且无错误
if status == .haveData, error == nil {
// 将音频缓冲区转换为二进制数据
let data = self.audioBufferToData(convertedBuffer)
// 将数据放入写入队列(后续处理)
self.writeQueue.put(data)
}
}
try audioEngine?.start()
// // 将数据放入写入队列(后续处理)
// self.writeQueue.put(data)
} catch {
print("麦克风启动失败: \(error)")
// 【新增】添加详细错误处理
@ -333,44 +200,12 @@ public class SimpleAudioReceiver: NSObject {
}
}
}
private func audioBufferToData(_ buffer: AVAudioPCMBuffer) -> Data {
let frameLength = Int(buffer.frameLength)
let channelCount = 1
let dataLength = frameLength * channelCount * MemoryLayout<Int16>.size
// Handle 16-bit integer format
if let int16Data = buffer.int16ChannelData {
return Data(
bytes: int16Data.pointee,
count: dataLength
)
}
// Handle float format
else if let floatData = buffer.floatChannelData {
var int16Array = [Int16](repeating: 0, count: frameLength)
let floatBuffer = floatData.pointee
for i in 0..<frameLength {
let sample = floatBuffer[i]
let clamped = max(-1.0, min(sample, 1.0))
let scaled = clamped * Float(Int16.max)
int16Array[i] = Int16(scaled)
}
return Data(
bytes: int16Array,
count: dataLength
)
}
return Data() // Fallback for unsupported formats
}
private func runExternalCapture() {
if audioSourceType == .external {
//pushAudioData(data: Data())
audioEngine?.pause()
//audioEngine?.inputNode.removeTap(onBus: 0)
micCapture.stopCapture()
}
}
/**
@ -385,7 +220,7 @@ public class SimpleAudioReceiver: NSObject {
// 启动录音引擎
do {
print("resumeRecord")
try audioEngine?.start()
try micCapture.startCapture()
} catch {
os_log("Failed to start audio engine: %@", log: log, type: .error, error.localizedDescription)
}
@ -398,37 +233,37 @@ public class SimpleAudioReceiver: NSObject {
* 强制使用设备内置扬声器或听筒
*/
public func disableBluetoothAudio() {
do {
print("禁用蓝牙音频功能,切换回正常音频模式")
// do {
// print("禁用蓝牙音频功能,切换回正常音频模式")
// 暂停当前音频引擎
if _isWriting {
audioEngine?.pause()
try audioSession.setActive(false)
}
// // 暂停当前音频引擎
// if _isWriting {
// audioEngine?.pause()
// try audioSession.setActive(false)
// }
// 设置音频会话为不允许蓝牙
try audioSession.setCategory(
.playAndRecord,
mode: .videoChat,
options: [.defaultToSpeaker, .mixWithOthers] // 移除 .allowBluetooth
)
// // 设置音频会话为不允许蓝牙
// try audioSession.setCategory(
// .playAndRecord,
// mode: .videoChat,
// options: [.defaultToSpeaker, .mixWithOthers] // 移除 .allowBluetooth
// )
// 强制使用扬声器
try audioSession.overrideOutputAudioPort(.speaker)
// // 强制使用扬声器
// try audioSession.overrideOutputAudioPort(.speaker)
// 重新激活音频会话
if _isWriting {
try audioSession.setActive(true)
try audioEngine?.start()
}
// // 重新激活音频会话
// if _isWriting {
// try audioSession.setActive(true)
// try audioEngine?.start()
// }
// 更新当前路由状态
currentRoute = .speaker
// // 更新当前路由状态
// currentRoute = .speaker
} catch {
print("禁用蓝牙音频失败: \(error.localizedDescription)")
}
// } catch {
// print("禁用蓝牙音频失败: \(error.localizedDescription)")
// }
}
/**
@ -436,37 +271,37 @@ public func disableBluetoothAudio() {
* 允许系统自动选择最佳音频设备
*/
public func restoreOriginalAudioState() {
do {
print("恢复原始音频设备状态(重新启用蓝牙)")
// do {
// print("恢复原始音频设备状态(重新启用蓝牙)")
// 暂停当前音频引擎
if _isWriting {
audioEngine?.pause()
try audioSession.setActive(false)
}
// // 暂停当前音频引擎
// if _isWriting {
// audioEngine?.pause()
// try audioSession.setActive(false)
// }
// 恢复完整的蓝牙音频支持
try audioSession.setCategory(
.playAndRecord,
mode: .videoChat,
options: [.defaultToSpeaker, .allowBluetooth, .mixWithOthers]
)
// // 恢复完整的蓝牙音频支持
// try audioSession.setCategory(
// .playAndRecord,
// mode: .videoChat,
// options: [.defaultToSpeaker, .allowBluetooth, .mixWithOthers]
// )
// 取消音频端口覆盖,让系统自动选择
try audioSession.overrideOutputAudioPort(.none)
// // 取消音频端口覆盖,让系统自动选择
// try audioSession.overrideOutputAudioPort(.none)
// 重新激活音频会话
if _isWriting {
try audioSession.setActive(true)
try audioEngine?.start()
}
// // 重新激活音频会话
// if _isWriting {
// try audioSession.setActive(true)
// try audioEngine?.start()
// }
// 重置路由状态,让系统自动选择
currentRoute = nil
// // 重置路由状态,让系统自动选择
// currentRoute = nil
} catch {
print("恢复原始音频状态失败: \(error.localizedDescription)")
}
// } catch {
// print("恢复原始音频状态失败: \(error.localizedDescription)")
// }
}
@ -474,8 +309,8 @@ public func restoreOriginalAudioState() {
guard _isWriting==true else { return }
_isWriting=false
audioEngine?.pause()
print("stopMicrophoneCapture")
micCapture.stopCapture()
print("stopMicrophoneCapture")
}
public func releaseAudioResources() {
@ -500,60 +335,57 @@ public func restoreOriginalAudioState() {
public func setAudioOutputRoute(_ route: AudioOutputRoute) {
do {
//
if self._isWriting {
print("当前正在识别")
try audioEngine?.pause()
// 先停用以避免冲突
try audioSession.setActive(false)
// do {
// //
// if self._isWriting {
// print("当前正在识别")
// micCapture.stopCapture()
// // 先停用以避免冲突
// try audioSession.setActive(false)
}
print("setAudioOutputRoutecurrent,route=\(route)")
switch route {
case .speaker:
print("setAudioOutputRoutecurrent,speaker")
// 使用扬声器时必须用videoChat模式
try audioSession.setCategory(
.playAndRecord,
mode: .videoChat,
options: [.defaultToSpeaker,.allowBluetooth,.mixWithOthers]
)
try audioSession.overrideOutputAudioPort(.speaker)
// }
// print("setAudioOutputRoutecurrent,route=\(route)")
// switch route {
// case .speaker:
// print("setAudioOutputRoutecurrent,speaker")
// // 使用扬声器时必须用videoChat模式
// try audioSession.setCategory(
// .playAndRecord,
// mode: .videoChat,
// options: [.defaultToSpeaker,.allowBluetooth,.mixWithOthers]
// )
// try audioSession.overrideOutputAudioPort(.speaker)
case .receiver:
// 听筒模式使用voiceChat节省资源
try audioSession.setCategory(
.playAndRecord,
mode: .voiceChat,
options: [.allowBluetooth]
)
try audioSession.overrideOutputAudioPort(.none)
// case .receiver:
// // 听筒模式使用voiceChat节省资源
// try audioSession.setCategory(
// .playAndRecord,
// mode: .voiceChat,
// options: [.allowBluetooth]
// )
// try audioSession.overrideOutputAudioPort(.none)
case .bluetooth:
// 完整蓝牙设备支持
try audioSession.setCategory(
.playAndRecord,
mode: .voiceChat,
options: [.allowBluetooth, .allowBluetoothA2DP]
)
try audioSession.overrideOutputAudioPort(.none)
// 不需要override,系统自动路由
}
self.currentRoute = route
if self._isWriting {
// case .bluetooth:
// // 完整蓝牙设备支持
// try audioSession.setCategory(
// .playAndRecord,
// mode: .voiceChat,
// options: [.allowBluetooth, .allowBluetoothA2DP]
// )
// try audioSession.overrideOutputAudioPort(.none)
// // 不需要override,系统自动路由
// }
// self.currentRoute = route
// if self._isWriting {
// 重新激活
try audioSession.setActive(true, options: [.notifyOthersOnDeactivation])
// // 重新激活
// try audioSession.setActive(true, options: [.notifyOthersOnDeactivation])
// try micCapture.startCapture()
try audioEngine?.start()
// writeThread?.async {
// self.writeQueue.close()
// }
}
} catch {
print("路由切换失败: \(error)")
}
// }
// } catch {
// print("路由切换失败: \(error)")
// }
}

Loading…
Cancel
Save