From 5964fa0391260bf228801b348ef2529bd5a1fc86 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Sat, 23 Aug 2025 20:34:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=80=B3=E6=9C=BA=E9=BA=A6?= =?UTF-8?q?=E5=85=8B=E9=A3=8E=E5=90=AF=E5=8A=A8=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/tools/SimpleAudioReceiver.swift | 130 +++++++++++++----- 1 file changed, 97 insertions(+), 33 deletions(-) diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift index 5795d17af..c40ed7eec 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift @@ -195,11 +195,9 @@ public class SimpleAudioReceiver: NSObject { /** * 开始采集音频数据 * @param bufferHandler 音频缓冲区处理回调 - * @throws 音频配置或启动错误 + * @throws 音频引擎启动失败时抛出错误 */ func startCapture(bufferHandler: @escaping (AVAudioPCMBuffer) -> Void) throws { - - guard let audioEngine = audioEngine else { throw NSError(domain: "SimpleAudioReceiver", code: -1, userInfo: [NSLocalizedDescriptionKey: "Audio engine not initialized"]) } @@ -216,59 +214,92 @@ public class SimpleAudioReceiver: NSObject { // 重置音频引擎(在获取格式之前) audioEngine.reset() - try setupAudioSession() + try setupAudioSession() + // 重新获取输入节点和格式(reset后需要重新获取) let inputNode = audioEngine.inputNode let inputFormat = inputNode.outputFormat(forBus: 0) print("硬件输入格式: 采样率=\(inputFormat.sampleRate)Hz, 声道数=\(inputFormat.channelCount), 格式=\(inputFormat.commonFormat.rawValue)") - // 验证格式是否有效 - if inputFormat.sampleRate <= 0 || inputFormat.channelCount <= 0 { - // 如果格式无效,使用默认格式 - let defaultFormat = AVAudioFormat(standardFormatWithSampleRate: 48000, channels: 1)! - print("使用默认音频格式: 采样率=\(defaultFormat.sampleRate)Hz, 声道数=\(defaultFormat.channelCount)") + // 创建目标格式 - 统一使用16000Hz单声道 + guard let targetFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, + sampleRate: 16000, + channels: 1, + interleaved: false) else { + throw NSError(domain: "SimpleAudioReceiver", code: -2, userInfo: [NSLocalizedDescriptionKey: "无法创建目标音频格式"]) + } + + print("使用目标音频格式: 采样率=\(targetFormat.sampleRate)Hz, 声道数=\(targetFormat.channelCount)") + + // 检查硬件格式是否有效 + let isHardwareFormatValid = inputFormat.sampleRate > 0 && inputFormat.channelCount > 0 - inputNode.installTap(onBus: 0, + if isHardwareFormatValid { + print("使用硬件原生格式安装tap") + // 使用硬件原生格式安装tap,然后在回调中进行格式转换 + inputNode.installTap(onBus: 0, bufferSize: 1024, - format: defaultFormat) { (buffer, time) in - bufferHandler(buffer) - } + format: inputFormat) { (buffer, time) in + // 如果硬件格式与目标格式不匹配,进行转换 + if inputFormat.sampleRate != targetFormat.sampleRate || + inputFormat.channelCount != targetFormat.channelCount { + // 执行格式转换 + if let convertedBuffer = self.convertAudioBuffer(buffer, to: targetFormat) { + bufferHandler(convertedBuffer) + } else { + print("音频格式转换失败,跳过此缓冲区") + } + } else { + bufferHandler(buffer) + } + } } else { - // 使用硬件原生格式 - inputNode.installTap(onBus: 0, + print("硬件音频格式无效,使用目标格式直接安装tap") + // 硬件格式无效时,直接使用目标格式安装tap + inputNode.installTap(onBus: 0, bufferSize: 1024, - format: inputFormat) { (buffer, time) in - bufferHandler(buffer) - } + format: targetFormat) { (buffer, time) in + // 直接使用目标格式的缓冲区 + bufferHandler(buffer) + } } if #available(iOS 13.0, *) { - try inputNode.setVoiceProcessingEnabled(true) + do { + try inputNode.setVoiceProcessingEnabled(true) + } catch { + print("启用语音处理失败: \(error.localizedDescription)") + } } audioEngine.prepare() - + try audioEngine.start() print("音频引擎启动成功") } /** * 安全地移除音频tap - * 避免在移除不存在的tap时出错 + * 避免在移除tap时出现崩溃 + */ + /** + * 安全地移除音频tap + * 避免在移除tap时出现崩溃 */ private func removeTapSafely() { guard let audioEngine = audioEngine else { return } let inputNode = audioEngine.inputNode - // 尝试移除tap,如果没有tap也不会出错 - do { - inputNode.removeTap(onBus: 0) - print("成功移除音频tap") - } catch { - // 如果没有tap可移除,这是正常的 - print("移除tap时出现错误(可能没有tap存在): \(error)") + // 检查是否有tap需要移除 + if inputNode.numberOfInputs > 0 { + do { + inputNode.removeTap(onBus: 0) + print("成功移除音频tap") + } catch { + print("移除音频tap时出错: \(error.localizedDescription)") + } } } private func runMicrophoneCapture() { @@ -429,6 +460,40 @@ private func audioBufferToData(_ buffer: AVAudioPCMBuffer) -> Data { } } +/** + * 转换音频缓冲区格式 + * @param buffer 原始音频缓冲区 + * @param targetFormat 目标音频格式 + * @return 转换后的音频缓冲区,转换失败时返回nil + */ +private func convertAudioBuffer(_ buffer: AVAudioPCMBuffer, to targetFormat: AVAudioFormat) -> AVAudioPCMBuffer? { + guard let converter = AVAudioConverter(from: buffer.format, to: targetFormat) else { + print("无法创建音频转换器: 从\(buffer.format.sampleRate)Hz到\(targetFormat.sampleRate)Hz") + return nil + } + + // 计算转换后的帧数 + let capacity = AVAudioFrameCount(Double(buffer.frameLength) * targetFormat.sampleRate / buffer.format.sampleRate) + guard let convertedBuffer = AVAudioPCMBuffer(pcmFormat: targetFormat, frameCapacity: capacity) else { + print("无法创建转换后的音频缓冲区") + return nil + } + + var error: NSError? + let status = converter.convert(to: convertedBuffer, error: &error) { _, outStatus in + outStatus.pointee = .haveData + return buffer + } + + if status == .error { + print("音频转换失败: \(error?.localizedDescription ?? "未知错误")") + return nil + } + + return convertedBuffer +} + + /** * 配置音频会话 * 设置录音和播放所需的音频会话参数 @@ -438,10 +503,10 @@ private func setupAudioSession() throws { try audioSession.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetooth]) try audioSession.setActive(true) - // 设置首选的音频参数 - try audioSession.setPreferredSampleRate(48000) - // 设置输入声道数 - try audioSession.setPreferredInputNumberOfChannels(1) + // 设置首选的音频参数 - 修改为16000Hz以匹配Azure Speech要求 + try audioSession.setPreferredSampleRate(16000) + // 设置输入声道数 + try audioSession.setPreferredInputNumberOfChannels(1) print("音频会话配置成功: 采样率=\(audioSession.sampleRate)Hz, 输入声道=\(audioSession.inputNumberOfChannels)") } @@ -574,4 +639,3 @@ private class LinkedBlockingQueue { } } } -