|
|
|
@ -123,6 +123,9 @@ public class SimpleAudioReceiver: NSObject { |
|
|
|
|
|
|
|
/** |
|
|
|
* 开始音频输入 |
|
|
|
* 参数:audioSourceType 音频源类型(microphone 或 external);audioDataCallback 音频数据回调 |
|
|
|
* 返回值:无 |
|
|
|
* 异常:无(内部捕获并打印错误,不抛出) |
|
|
|
*/ |
|
|
|
public func startAudioRecord(audioSourceType: AudioSourceType = .microphone, audioDataCallback: AudioDataCallback?) { |
|
|
|
_isWriting=true |
|
|
|
@ -131,8 +134,10 @@ public class SimpleAudioReceiver: NSObject { |
|
|
|
print("音源设制---------- startAudioRecord=audioSourceType:\(audioSourceType)") |
|
|
|
switch audioSourceType { |
|
|
|
case .microphone: |
|
|
|
// 根据当前可用输入设备动态判定是否存在蓝牙HFP麦克风 |
|
|
|
isReceiverHFP = hasBluetoothHFPInput() |
|
|
|
// 唤醒后禁用通话音道(HFP),改为手机麦克风 + A2DP 输出 |
|
|
|
isReceiverHFP = false |
|
|
|
// 动态检测是否存在耳机/蓝牙输出,用于后续路由选项计算 |
|
|
|
isHeadphones = detectHeadphonesOutput() |
|
|
|
runMicrophoneCapture() |
|
|
|
case .external: |
|
|
|
isReceiverHFP = false |
|
|
|
@ -155,6 +160,23 @@ public class SimpleAudioReceiver: NSObject { |
|
|
|
return session.currentRoute.inputs.contains { $0.portType == .bluetoothHFP } |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 检测是否存在耳机/蓝牙输出设备(用于决定是否启用A2DP而非扬声器) |
|
|
|
* 参数:无 |
|
|
|
* 返回值:存在返回true,否则返回false |
|
|
|
* 异常:无 |
|
|
|
*/ |
|
|
|
private func detectHeadphonesOutput() -> Bool { |
|
|
|
let session = AVAudioSession.sharedInstance() |
|
|
|
let outputs = session.currentRoute.outputs |
|
|
|
return outputs.contains { out in |
|
|
|
out.portType == .bluetoothA2DP || |
|
|
|
out.portType == .bluetoothHFP || |
|
|
|
out.portType == .bluetoothLE || |
|
|
|
out.portType == .headphones |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 开启音频写入线程 |
|
|
|
* 修复:使用userInitiated QoS避免优先级反转 |
|
|
|
@ -246,13 +268,50 @@ public class SimpleAudioReceiver: NSObject { |
|
|
|
private func runExternalCapture() { |
|
|
|
if audioSourceType == .external { |
|
|
|
do { |
|
|
|
// 根据通话状态进行安全的音频会话配置 |
|
|
|
try safeConfigureAudioSessionForMic() |
|
|
|
// 停止麦克风采集,确保不走通话音道 |
|
|
|
micCapture.stopCapture() |
|
|
|
// 外部模式会话配置:禁止HFP,仅允许A2DP/耳机输出 |
|
|
|
try safeConfigureAudioSessionForExternal() |
|
|
|
} catch { |
|
|
|
print("设置音频会话失败: \(error.localizedDescription)") |
|
|
|
} |
|
|
|
//pushAudioData(data: Data()) |
|
|
|
micCapture.stopCapture() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 外部音频源的安全音频会话配置(禁止HFP通话音道) |
|
|
|
* 参数:无 |
|
|
|
* 返回值:无 |
|
|
|
* 异常:可能抛出AVAudioSession设置错误(内部捕获并兜底) |
|
|
|
*/ |
|
|
|
private func safeConfigureAudioSessionForExternal() throws { |
|
|
|
let audioSession = self.audioSession |
|
|
|
do { |
|
|
|
// 外部模式不录麦,仅做播放路由;有耳机时只允许A2DP,禁止HFP |
|
|
|
let outputs = audioSession.currentRoute.outputs |
|
|
|
let hasHeadphones = outputs.contains { out in |
|
|
|
out.portType == .bluetoothA2DP || |
|
|
|
out.portType == .bluetoothLE || |
|
|
|
out.portType == .headphones |
|
|
|
} |
|
|
|
let desiredCategory: AVAudioSession.Category = .playback |
|
|
|
let desiredMode: AVAudioSession.Mode = .videoChat |
|
|
|
let desiredOptions: AVAudioSession.CategoryOptions = hasHeadphones |
|
|
|
? [.allowBluetoothA2DP, .mixWithOthers] |
|
|
|
: [.mixWithOthers, .defaultToSpeaker] |
|
|
|
|
|
|
|
let needsSetCategory = (audioSession.category != desiredCategory) || (audioSession.mode != desiredMode) |
|
|
|
if needsSetCategory { |
|
|
|
try audioSession.setCategory(desiredCategory, mode: desiredMode, options: desiredOptions) |
|
|
|
} |
|
|
|
// 取消扬声器强制,确保耳机/A2DP优先 |
|
|
|
try audioSession.overrideOutputAudioPort(.none) |
|
|
|
try audioSession.setActive(true, options: .notifyOthersOnDeactivation) |
|
|
|
} catch { |
|
|
|
print("外部模式音频会话配置失败,兜底到playback: \(error)") |
|
|
|
try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers, .defaultToSpeaker]) |
|
|
|
try audioSession.setActive(true, options: .notifyOthersOnDeactivation) |
|
|
|
} |
|
|
|
} |
|
|
|
/** |
|
|
|
@ -592,4 +651,3 @@ private class LinkedBlockingQueue<T> { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|