import Foundation import AVFoundation import MicrosoftCognitiveServicesSpeech // 自定义语音处理组件,提供音频流处理等功能 import speech /** * Azure ASR Helper * * 基于微软Azure语音服务的ASR实现 * 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-recognize-speech */ class AzureAsrHelper: NSObject, IAsrService { private let tag = "AzureAsrHelper" // 核心组件 private var speechConfig: SPXSpeechConfiguration? private var recognizer: SPXSpeechRecognizer? private var audioConfig: SPXAudioConfiguration? // 状态管理 private var isContinuousRecognitionActive = false // 配置参数 private var currentLanguage = "zh-CN" private var supportedLanguages = ["zh-CN"] private var isAutoDetectLanguage = false // 音频源配置 private enum AudioSourceType { /** 使用设备麦克风 */ case microphone /** 使用外部提供的音频数据 */ case external } private var audioSourceType = AudioSourceType.microphone // 音频处理 private var externalAudioStream: ExternalAudioStream? // 事件监听处理 private var eventListeners = NSHashTable.weakObjects() // 音频会话 private let audioSession = AVAudioSession.sharedInstance() /** * 初始化语音识别服务 * * @param subscriptionKey 服务订阅密钥/访问令牌 * @param region 服务区域/应用ID * @param supportedLanguages 支持的语言列表,如["zh-CN", "en-US"] * @return 是否初始化成功 */ func initialize(subscriptionKey: String, region: String, supportedLanguages: [String]?) -> Bool { do { // 检查配置是否为空 if subscriptionKey.isEmpty || region.isEmpty { print("\(tag): Azure 配置信息不完整") return false } // 释放之前的资源 dispose() // 设置语言 if let supportedLanguages = supportedLanguages, !supportedLanguages.isEmpty { self.supportedLanguages = supportedLanguages } // 根据支持的语言数量决定是否启用自动语言检测 self.isAutoDetectLanguage = self.supportedLanguages.count >= 2 // 如果只有一种语言,设置为当前语言 if !isAutoDetectLanguage && !self.supportedLanguages.isEmpty { self.currentLanguage = self.supportedLanguages[0] } // 创建语音配置 speechConfig = try SPXSpeechConfiguration(subscription: subscriptionKey, region: region) if isAutoDetectLanguage { // 直接启用语言检测模式 speechConfig?.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode") print("AzureAsrHelper: 启用语言检测模式") } else { // 设置指定的识别语言 speechConfig?.speechRecognitionLanguage = currentLanguage } // 创建识别器 return setupRecognizer() } catch { print("\(tag): 初始化失败: \(error.localizedDescription)") return false } } /** * 单次识别(非流式) * * @param audioData 要识别的音频数据字节数组 * @param callback 识别结果回调 * @return 是否成功开始识别 */ func recognizeOnce(audioData: Data, callback: @escaping (String) -> Void) -> Bool { return false } /** * 启动连续识别 * * @return 是否成功启动 */ func startContinuousRecognition() -> Bool { if isContinuousRecognitionActive { return true } guard let recognizer = recognizer else { print("\(tag): 启动连续识别失败: 识别器未初始化") return false } do { // 启动连续识别 try recognizer.startContinuousRecognition() isContinuousRecognitionActive = true return true } catch { print("\(tag): 启动连续识别失败: \(error.localizedDescription)") return false } } /** * 停止连续识别 * * @return 是否成功停止 */ func stopContinuousRecognition() -> Bool { guard let recognizer = recognizer, isContinuousRecognitionActive else { return true } do { // 停止连续识别 try recognizer.stopContinuousRecognition() isContinuousRecognitionActive = false return true } catch { print("\(tag): 停止连续识别失败: \(error.localizedDescription)") return false } } /** * 推送音频数据 * 在连续识别模式下推送新的音频数据 * * @param audioData 要识别的音频数据字节数组 * @return 是否成功处理 */ func pushAudioData(audioData: Data) -> Bool { if !isContinuousRecognitionActive { return false } if audioSourceType != .external { notifyEvent(eventType: .error, params: ["error": "当前不是外部音频流模式"]) return false } // 将音频数据推送到外部音频流 externalAudioStream?.pushAudio(audioData) return true } /** * 添加ASR事件监听器 * * @param listener 事件监听器 */ func addListener(_ listener: AsrEventListener) { eventListeners.add(listener as AnyObject) } /** * 移除ASR事件监听器 * * @param listener 要移除的事件监听器 */ func removeListener(_ listener: AsrEventListener) { eventListeners.remove(listener as AnyObject) } /** * 释放资源 * 在不再需要服务时调用,释放底层资源 */ func dispose() { // 停止识别 if isContinuousRecognitionActive { _ = stopContinuousRecognition() } // 停止音频处理 stopAudioProcessing() // 释放资源 recognizer = nil speechConfig = nil audioConfig = nil externalAudioStream = nil } /** * 设置识别参数 * * @param params 参数键值对 * @return 是否设置成功 */ func setParameters(_ params: [String: Any]) -> Bool { // 未实现自定义参数,后续可以根据需要添加 return true } /** * 获取连续识别是否活跃 */ func isRecognitionActive() -> Bool { return isContinuousRecognitionActive } // MARK: - 辅助方法 /** * 创建识别器 * * @return 是否创建成功 */ private func setupRecognizer() -> Bool { guard let speechConfig = speechConfig else { print("\(tag): 无法创建识别器:语音配置不存在") return false } do { if audioSourceType == .external { // 设置外部音频流 setupExternalAudioStream() } // 根据是否启用自动识别语言创建不同的识别器 if isAutoDetectLanguage && supportedLanguages.count > 0 { // 创建语言自动检测配置 let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages) if let audioConfig = self.audioConfig { // 使用指定的音频源创建识别器 recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig, autoDetectSourceLanguageConfiguration: autoDetectConfig, audioConfiguration: audioConfig) } else { // 使用默认音频源创建识别器 // 创建一个默认的麦克风配置而不是传nil let defaultAudioConfig = SPXAudioConfiguration() recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig, autoDetectSourceLanguageConfiguration: autoDetectConfig, audioConfiguration: defaultAudioConfig) } } else { // 使用单一语言配置创建识别器 if let audioConfig = self.audioConfig { // 使用指定的音频源创建识别器 recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig, audioConfiguration: audioConfig) } else { // 使用默认音频源创建识别器 // 创建一个默认的麦克风配置 let defaultAudioConfig = SPXAudioConfiguration() recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig, audioConfiguration: defaultAudioConfig) } } // 设置事件监听器 setupEventListeners() return true } catch { print("\(tag): 创建识别器失败: \(error.localizedDescription)") stopAudioProcessing() return false } } /** * 停止音频处理 */ private func stopAudioProcessing() { // 清理外部音频流 externalAudioStream = nil } /** * 设置外部音频流 */ private func setupExternalAudioStream() { externalAudioStream = ExternalAudioStream() // 使用ExternalAudioStream实例中创建的pullStream audioConfig = SPXAudioConfiguration(streamInput: externalAudioStream!.pullStream) } /** * 设置事件监听器 */ private func setupEventListeners() { guard let recognizer = recognizer else { return } // 修改为直接设置事件回调 recognizer.addRecognizingEventHandler { [weak self] (sender, event) in guard let self = self else { return } var detectedLanguage = "" // 修改event.result的处理,不使用可选链 let result = event.result if self.isAutoDetectLanguage { // 处理自动语言检测结果 if let properties = result.properties, let language = properties.getPropertyByName("SpeechServiceResponse_RecognitionLanguage") { detectedLanguage = language } } self.notifyEvent(eventType: .recognizing, params: [ "text": result.text, "language": detectedLanguage ]) } // 添加最终识别结果处理器 recognizer.addRecognizedEventHandler { [weak self] (sender, event) in guard let self = self else { return } // 修改event.result的处理,不使用可选绑定 let result = event.result var detectedLanguage = "" if self.isAutoDetectLanguage { // 处理自动语言检测结果 if let properties = result.properties, let language = properties.getPropertyByName("SpeechServiceResponse_RecognitionLanguage") { detectedLanguage = language } // 尝试另一种方式获取语言 do { let langResult = try SPXAutoDetectSourceLanguageResult(result) if let lang = langResult.language, !lang.isEmpty { detectedLanguage = lang } } catch { // 忽略错误,继续使用之前获取的语言 } } self.notifyEvent(eventType: .recognized, params: [ "text": result.text, "language": detectedLanguage ]) } // 添加会话开始事件处理器 recognizer.addSessionStartedEventHandler { [weak self] (sender, event) in guard let self = self else { return } self.notifyEvent(eventType: .sessionStarted) } // 添加会话结束事件处理器 recognizer.addSessionStoppedEventHandler { [weak self] (sender, event) in guard let self = self else { return } self.isContinuousRecognitionActive = false self.notifyEvent(eventType: .sessionStopped) } // 添加取消事件处理器 recognizer.addCanceledEventHandler { [weak self] (sender, event) in guard let self = self else { return } print("\(self.tag): 识别取消: \(event.errorDetails ?? "未知错误")") // 标记连续识别已停止 self.isContinuousRecognitionActive = false // 通知错误 self.notifyEvent(eventType: .canceled, params: [ "error": "识别取消", "errorDetails": event.errorDetails ?? "未知错误", "reason": String(event.reason.rawValue) ]) } } /** * 触发事件通知 */ private func notifyEvent(eventType: AsrEventType, params: [String: Any] = [:]) { let event = AsrEvent(type: eventType, params: params) // 在主线程通知事件 DispatchQueue.main.async { for case let listener as AsrEventListener in self.eventListeners.allObjects { listener.onEvent(event) } } } /** * 外部音频流 - 使用拉流模式 * 基于NSObject实现,将外部推送的音频数据转换为SDK可拉取的形式 */ class ExternalAudioStream: NSObject { // 内部持有一个SPXPullAudioInputStream实例 private(set) var pullStream: SPXPullAudioInputStream! // 音频数据缓冲区 private var buffer = Data() private let lock = NSLock() // 记录是否已关闭 private var isClosed = false /** * 初始化方法 */ override init() { super.init() // 创建拉流,使用官方文档推荐的参数类型 pullStream = SPXPullAudioInputStream( readHandler: { [weak self] (data: NSMutableData, size: UInt) -> Int in guard let self = self else { return 0 } self.lock.lock() defer { self.lock.unlock() } if self.buffer.isEmpty { return 0 } let bytesToRead = min(Int(size), self.buffer.count) data.append(self.buffer.prefix(bytesToRead)) self.buffer.removeFirst(bytesToRead) return bytesToRead }, closeHandler: { [weak self] in self?.close() } ) } /** * 推送音频数据到缓冲区 * * @param data 要推送的音频数据 */ func pushAudio(_ data: Data) { if isClosed { return } lock.lock() defer { lock.unlock() } // 添加到缓冲区 buffer.append(data) } /** * 关闭流 */ func close() { lock.lock() defer { lock.unlock() } isClosed = true buffer = Data() } } }