import Foundation import AVFoundation import MicrosoftCognitiveServicesSpeech /// Azure 语音识别辅助类 class AzureAsrHelper: NSObject { private var recognizer: SPXSpeechRecognizer? private var speechConfig: SPXSpeechConfig? private var audioConfig: SPXAudioConfig? private var initialized = false private var isContinuousRecognitionActive = false private var currentLanguage = "zh-CN" private var subscriptionKey = "" private var serviceRegion = "" private var isAutoDetectLanguage = false private var supportedLanguages = ["zh-CN", "en-US"] // 音频会话管理 private let audioSession = AVAudioSession.sharedInstance() // 事件回调 private var eventHandler: (([String: Any]) -> Void)? /// 设置事件处理器 /// /// - Parameter handler: 事件处理回调 func setEventHandler(_ handler: @escaping ([String: Any]) -> Void) { self.eventHandler = handler } /// 初始化语音识别服务 /// /// - Parameters: /// - speechSubscriptionKey: Azure 语音服务订阅密钥 /// - serviceRegion: Azure 语音服务区域 /// - supportedLanguages: 支持的语言列表,默认为 ["zh-CN", "en-US"] /// - Returns: 是否初始化成功 func initialize(speechSubscriptionKey: String, serviceRegion: String, supportedLanguages: [String] = ["zh-CN", "en-US"]) -> Bool { print("[AzureAsrHelper] 初始化 Azure 语音服务") // 检查配置是否为空 if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { print("[AzureAsrHelper] 错误: Azure 配置信息不完整") return false } // 释放之前的资源 dispose() // 保存配置 self.subscriptionKey = speechSubscriptionKey self.serviceRegion = serviceRegion // 设置语言 if supportedLanguages.isEmpty { print("[AzureAsrHelper] 警告: 传入的支持语言列表为空,将使用默认语言") } else { self.supportedLanguages = supportedLanguages } // 根据支持的语言数量决定是否启用自动语言检测 self.isAutoDetectLanguage = supportedLanguages.count >= 2 // 如果只有一种语言,设置为当前语言 if !isAutoDetectLanguage && !supportedLanguages.isEmpty { self.currentLanguage = supportedLanguages[0] } // 创建语音配置 do { speechConfig = try SPXSpeechConfig(subscription: speechSubscriptionKey, region: serviceRegion) // 设置语言配置 if isAutoDetectLanguage { // 设置自动语言检测 try speechConfig?.setPropertyTo("Continuous", byId: SPXPropertyId.SpeechServiceConnection_LanguageIdMode) } else { // 设置指定的识别语言 speechConfig?.speechRecognitionLanguage = currentLanguage } // 创建音频配置 - 使用默认麦克风 audioConfig = SPXAudioConfig.default() // 创建识别器 if isAutoDetectLanguage { let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(sourceLanguages: supportedLanguages) recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, autoDetectSourceLanguageConfiguration: autoDetectConfig, audioConfiguration: audioConfig!) } else { recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!) } // 配置音频会话 try configureAudioSession() initialized = true print("[AzureAsrHelper] Azure 语音服务初始化成功") return true } catch { print("[AzureAsrHelper] 错误: 创建识别器失败: \(error.localizedDescription)") return false } } /// 配置音频会话 private func configureAudioSession() throws { print("[AzureAsrHelper] 开始配置音频会话...") do { // 设置音频会话类别和模式 try audioSession.setCategory(.record, mode: .measurement, options: [.duckOthers, .allowBluetooth]) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) } catch { print("[AzureAsrHelper] 警告: 通过AudioSessionManager配置音频会话失败") throw error } } /// 一次性语音识别 /// /// - Parameter completion: 完成回调,返回是否成功、识别文本、识别语言和可能的错误信息 func recognizeOnce(completion: @escaping (Bool, String?, String?, String?) -> Void) { if !initialized { completion(false, nil, nil, "语音服务未初始化") return } // 重置 recognizer if !resetRecognizer() { completion(false, nil, nil, "重置识别器失败") return } do { // 激活音频会话 try audioSession.setActive(true) // 添加识别事件处理 recognizer?.addRecognizedEventHandler { [weak self] _, event in guard let self = self else { return } if event.result.reason == .recognizedSpeech { let detectedLanguage = self.getDetectedLanguage(from: event.result) print("[AzureAsrHelper] 识别结果: \(event.result.text ?? ""), 语言: \(detectedLanguage)") completion(true, event.result.text, detectedLanguage, nil) } } recognizer?.addRecognizingEventHandler { [weak self] _, event in guard let self = self else { return } if event.result.reason == .recognizingSpeech { let detectedLanguage = self.getDetectedLanguage(from: event.result) // print("[AzureAsrHelper] 识别中: \(event.result.text ?? ""), 语言: \(detectedLanguage)") } } // 添加会话事件处理 recognizer?.addSessionStartedEventHandler { _, _ in print("[AzureAsrHelper] 识别会话已开始") } recognizer?.addSessionStoppedEventHandler { _, _ in print("[AzureAsrHelper] 识别会话已结束") } // 添加取消事件处理 recognizer?.addCanceledEventHandler { _, event in if let cancellationDetails = try? SPXCancellationDetails(fromCanceledRecognitionResult: event.result) { let errorDetails = cancellationDetails.errorDetails ?? "未知错误" print("[AzureAsrHelper] 识别取消: \(errorDetails)") completion(false, nil, nil, "识别取消: \(errorDetails)") } } // 执行识别 let result = try recognizer?.recognizeOnceAsync().get() if result?.reason != .recognizedSpeech { completion(false, nil, nil, "未能识别语音") } } catch { completion(false, nil, nil, "识别异常: \(error.localizedDescription)") } } /// 重置识别器 /// /// - Returns: 是否重置成功 private func resetRecognizer() -> Bool { if !initialized { print("[AzureAsrHelper] 错误: 语音服务未初始化") return false } // 检查配置是否为空 if subscriptionKey.isEmpty || serviceRegion.isEmpty { print("[AzureAsrHelper] 错误: Azure 配置信息不完整") return false } do { // 释放之前的 recognizer recognizer = nil // 创建音频配置 - 使用默认麦克风 audioConfig = SPXAudioConfig.default() // 重新创建识别器 if isAutoDetectLanguage { let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(sourceLanguages: supportedLanguages) recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, autoDetectSourceLanguageConfiguration: autoDetectConfig, audioConfiguration: audioConfig!) } else { recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!) } return true } catch { print("[AzureAsrHelper] 错误: 重置识别器失败: \(error.localizedDescription)") return false } } /// 获取检测到的语言 /// /// - Parameter result: 识别结果 /// - Returns: 检测到的语言代码 private func getDetectedLanguage(from result: SPXSpeechRecognitionResult) -> String { if isAutoDetectLanguage { do { if let autoDetectResult = try SPXAutoDetectSourceLanguageResult(fromRecognitionResult: result) { return autoDetectResult.language } return "" } catch { print("[AzureAsrHelper] 错误: 获取检测到的语言失败: \(error.localizedDescription)") return "" } } else { return currentLanguage } } /// 开始连续语音识别 /// /// - Returns: 是否成功启动连续识别 func startContinuousRecognition() -> Bool { if !initialized { print("[AzureAsrHelper] 错误: 语音服务未初始化") return false } // 检查配置是否为空 if subscriptionKey.isEmpty || serviceRegion.isEmpty { print("[AzureAsrHelper] 错误: Azure 配置信息不完整") return false } // 如果已经在进行连续识别,直接返回 if isContinuousRecognitionActive { print("[AzureAsrHelper] 已经在进行连续识别中,忽略请求") return true } // 重置 recognizer if !resetRecognizer() { print("[AzureAsrHelper] 尝试重新创建识别器...") return false } do { // 激活音频会话 try audioSession.setActive(true) // 添加识别事件处理 recognizer?.addRecognizedEventHandler { [weak self] _, event in guard let self = self else { return } if event.result.reason == .recognizedSpeech { let detectedLanguage = self.getDetectedLanguage(from: event.result) let eventData: [String: Any] = [ "eventType": "finalResult", "text": event.result.text ?? "", "language": detectedLanguage ] self.eventHandler?(eventData) } } // 识别中事件 recognizer?.addRecognizingEventHandler { [weak self] _, event in guard let self = self else { return } if event.result.reason == .recognizingSpeech { let detectedLanguage = self.getDetectedLanguage(from: event.result) let eventData: [String: Any] = [ "eventType": "recognizing", "text": event.result.text ?? "", "language": detectedLanguage ] self.eventHandler?(eventData) } } // 会话事件 recognizer?.addSessionStartedEventHandler { [weak self] _, _ in guard let self = self else { return } let eventData: [String: Any] = [ "eventType": "sessionStarted" ] self.eventHandler?(eventData) } recognizer?.addSessionStoppedEventHandler { [weak self] _, _ in guard let self = self else { return } self.isContinuousRecognitionActive = false let eventData: [String: Any] = [ "eventType": "sessionStopped" ] self.eventHandler?(eventData) } // 取消事件 recognizer?.addCanceledEventHandler { [weak self] _, event in guard let self = self else { return } self.isContinuousRecognitionActive = false var errorMessage = "未知错误" if let cancellationDetails = try? SPXCancellationDetails(fromCanceledRecognitionResult: event.result) { errorMessage = cancellationDetails.errorDetails ?? "未知错误" } let eventData: [String: Any] = [ "eventType": "error", "error": "识别取消: \(errorMessage)" ] self.eventHandler?(eventData) } // 开始连续识别 try recognizer?.startContinuousRecognition() isContinuousRecognitionActive = true print("[AzureAsrHelper] 连续识别已启动") return true } catch { print("[AzureAsrHelper] 错误: 开始连续识别失败: \(error.localizedDescription)") return false } } /// 停止连续语音识别 /// /// - Returns: 是否成功停止连续识别 func stopContinuousRecognition() -> Bool { if !initialized { print("[AzureAsrHelper] 错误: 语音服务未初始化") return false } if !isContinuousRecognitionActive { print("[AzureAsrHelper] 未进行连续识别,忽略停止请求") return true } do { print("[AzureAsrHelper] 停止连续语音识别") if recognizer == nil { if isContinuousRecognitionActive { print("[AzureAsrHelper] 警告: 识别器为空,但状态显示活跃") } isContinuousRecognitionActive = false // 通知停止成功 let eventData: [String: Any] = [ "eventType": "success", "message": "连续识别已停止" ] eventHandler?(eventData) return true } // 停止连续识别 try recognizer?.stopContinuousRecognition() // 延迟一点时间确保处理完成 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in guard let self = self else { return } // 重置状态 self.isContinuousRecognitionActive = false // 恢复音频会话 do { try self.audioSession.setActive(false, options: .notifyOthersOnDeactivation) } catch { // 忽略错误 } print("[AzureAsrHelper] 连续识别已停止") // 通知停止成功 let eventData: [String: Any] = [ "eventType": "success", "message": "连续识别已停止" ] self.eventHandler?(eventData) } return true } catch { // 强制重置状态 isContinuousRecognitionActive = false print("[AzureAsrHelper] 警告: 停止连续识别失败: \(error.localizedDescription)") // 通知停止失败,但仍然视为处理完成 let eventData: [String: Any] = [ "eventType": "success", "message": "连续识别已停止(但有错误)" ] eventHandler?(eventData) return false } } /// 释放资源 func dispose() { // 如果正在进行连续识别,先停止 if isContinuousRecognitionActive { _ = stopContinuousRecognition() } // 恢复音频会话 do { try audioSession.setActive(false, options: .notifyOthersOnDeactivation) } catch { // 忽略错误 } // 释放资源 recognizer = nil speechConfig = nil audioConfig = nil initialized = false isContinuousRecognitionActive = false print("[AzureAsrHelper] 资源已释放") } /// 检查连续识别是否处于活跃状态 /// /// - Returns: 是否正在进行连续识别 func isContinuousRecognitionActive() -> Bool { return isContinuousRecognitionActive } }