import Foundation import MicrosoftCognitiveServicesSpeech import AVFoundation /// Azure ASR工具类,负责实现语音识别服务接口 @available(iOS 13.0, *) class AzureAsrHelper: NSObject { // MARK: - 属性 /// 事件处理回调 private var eventHandler: (String, [String: Any]) -> Void /// 语音配置信息 private var speechSubscriptionKey: String = "" private var serviceRegion: String = "" /// 语音识别相关 private var speechConfig: SPXSpeechConfiguration? private var recognizer: SPXSpeechRecognizer? private var audioConfig: SPXAudioConfiguration? /// 状态标志 private var isInitialized = false private var _isContinuousRecognitionActive = false /// 当前语言和支持的语言 private var currentLanguage = "zh-CN" private var supportedLanguages: [String] = ["zh-CN", "en-US"] private var isAutoDetectLanguage = false /// 音频会话管理器 private let audioSessionManager = AudioSessionManager.shared // MARK: - 初始化 init(eventHandler: @escaping (String, [String: Any]) -> Void) { self.eventHandler = eventHandler super.init() } deinit { dispose() } // MARK: - ASR Service 接口实现 /// 初始化语音识别服务 /// - Parameters: /// - speechSubscriptionKey: Azure 语音服务订阅密钥 /// - serviceRegion: Azure 服务区域 (如 eastasia) /// - supportedLanguages: 支持的语言代码数组 (可选) /// - Returns: 初始化是否成功 func initialize(speechSubscriptionKey: String, serviceRegion: String, supportedLanguages: [String]? = nil) -> Bool { print("[AzureAsrHelper] 初始化 Azure 语音服务") // 检查配置是否为空 if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { print("[AzureAsrHelper] 错误: Azure 配置信息不完整") eventHandler("error", ["message": "Azure 配置信息不完整"]) return false } // 释放之前的资源 dispose() // 记录配置信息 self.speechSubscriptionKey = speechSubscriptionKey self.serviceRegion = serviceRegion // 设置语言 if let languages = supportedLanguages, !languages.isEmpty { self.supportedLanguages = languages } // 根据支持的语言数量决定是否启用自动语言检测 isAutoDetectLanguage = self.supportedLanguages.count >= 2 // 如果只有一种语言,设置为当前语言 if !isAutoDetectLanguage && !self.supportedLanguages.isEmpty { currentLanguage = self.supportedLanguages[0] } // 创建识别器和设置回调 if !createRecognizerAndSetupCallbacks() { return false } print("[AzureAsrHelper] Azure 语音服务初始化成功") isInitialized = true return true } /// 创建识别器并设置回调 private func createRecognizerAndSetupCallbacks() -> Bool { // 释放之前的 recognizer recognizer = nil audioConfig = nil do { // 创建语音配置 speechConfig = try SPXSpeechConfiguration(subscription: speechSubscriptionKey, region: serviceRegion) // 设置音频输入参数 try setupAudioSession() // 直接使用麦克风音频配置 audioConfig = try SPXAudioConfiguration() // 设置语言配置 if isAutoDetectLanguage { // 设置自动语言检测 speechConfig?.setPropertyTo("Continuous", by: SPXPropertyId.speechServiceConnectionLanguageIdMode) // 创建自动语言检测配置 let autoDetectSourceLanguageConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages) // 创建识别器 recognizer = try SPXSpeechRecognizer( speechConfiguration: speechConfig!, autoDetectSourceLanguageConfiguration: autoDetectSourceLanguageConfig, audioConfiguration: audioConfig! ) } else { // 设置指定的识别语言 speechConfig?.speechRecognitionLanguage = currentLanguage // 创建识别器 recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!) } // 设置所有回调 setupAllCallbacks() return true } catch { print("[AzureAsrHelper] 错误: 创建识别器失败: \(error.localizedDescription)") eventHandler("error", ["message": "创建识别器失败: \(error.localizedDescription)"]) return false } } /// 设置音频会话 private func setupAudioSession() throws { print("[AzureAsrHelper] 开始配置音频会话...") audioSessionManager.configureForVoiceInteraction() } /// 设置所有回调 private func setupAllCallbacks() { guard let recognizer = recognizer else { return } // 最终识别结果 recognizer.addRecognizedEventHandler { [weak self] _, event in guard let self = self else { return } if event.result.reason == SPXResultReason.recognizedSpeech { let detectedLanguage = self.getDetectedLanguage(from: event.result) print("[AzureAsrHelper] 识别结果: \(event.result.text ?? ""), 语言: \(detectedLanguage)") self.eventHandler("result", [ "text": event.result.text ?? "", "detectedLanguage": detectedLanguage ]) } } // 识别中事件 recognizer.addRecognizingEventHandler { [weak self] _, event in guard let self = self else { return } if event.result.reason == SPXResultReason.recognizingSpeech { let detectedLanguage = self.getDetectedLanguage(from: event.result) // print("[AzureAsrHelper] 识别中: \(event.result.text ?? ""), 语言: \(detectedLanguage)") self.eventHandler("recognizing", [ "text": event.result.text ?? "", "detectedLanguage": detectedLanguage ]) } } // 会话事件 recognizer.addSessionStartedEventHandler { [weak self] _, _ in guard let self = self else { return } print("[AzureAsrHelper] 识别会话已开始") self._isContinuousRecognitionActive = true self.eventHandler("sessionStarted", [:]) } recognizer.addSessionStoppedEventHandler { [weak self] _, _ in guard let self = self else { return } print("[AzureAsrHelper] 识别会话已结束") self._isContinuousRecognitionActive = false self.eventHandler("sessionStopped", [:]) } // 取消事件 recognizer.addCanceledEventHandler { [weak self] _, event in guard let self = self else { return } let reason = event.reason.rawValue let errorDetails = event.errorDetails ?? "未知错误" print("[AzureAsrHelper] 识别取消: \(errorDetails)") self.eventHandler("canceled", [ "reason": reason, "errorDetails": errorDetails ]) self._isContinuousRecognitionActive = false } } /// 执行一次性语音识别 /// - Returns: 是否成功启动识别 func recognizeOnce() -> Bool { if !isInitialized { print("[AzureAsrHelper] 错误: 语音服务未初始化") eventHandler("error", ["message": "语音服务未初始化"]) return false } // 如果正在连续识别,先停止 if _isContinuousRecognitionActive { stopContinuousRecognition() } // 确保识别器已创建 if recognizer == nil && !createRecognizerAndSetupCallbacks() { return false } do { // 通知会话开始 eventHandler("sessionStarted", [:]) // 执行识别 try recognizer?.recognizeOnceAsync { [weak self] result in guard let self = self else { return } if result.reason == SPXResultReason.recognizedSpeech { let detectedLanguage = self.getDetectedLanguage(from: result) self.eventHandler("result", [ "text": result.text ?? "", "detectedLanguage": detectedLanguage ]) } else if result.reason == SPXResultReason.noMatch { print("[AzureAsrHelper] 无匹配结果") self.eventHandler("noMatch", [:]) } else if result.reason == SPXResultReason.canceled { do { let details = try SPXCancellationDetails(fromCanceledRecognitionResult: result) let errorDetails = details.errorDetails ?? "未知错误" self.eventHandler("error", ["message": "识别取消: \(errorDetails)"]) } catch { print("[AzureAsrHelper] 错误: 获取取消详情失败: \(error.localizedDescription)") self.eventHandler("error", ["message": "识别取消,无法获取详细原因"]) } } } return true } catch { print("[AzureAsrHelper] 错误: 识别异常: \(error.localizedDescription)") eventHandler("error", ["message": "识别异常: \(error.localizedDescription)"]) return false } } /// 开始连续语音识别 /// - Returns: 是否成功启动识别 func startContinuousRecognition() -> Bool { if !isInitialized { print("[AzureAsrHelper] 错误: 语音服务未初始化") eventHandler("error", ["message": "语音服务未初始化"]) return false } // 检查是否已经在识别 if _isContinuousRecognitionActive { print("[AzureAsrHelper] 已经在进行连续识别中,忽略请求") return true } // 确保识别器已创建 if recognizer == nil { print("[AzureAsrHelper] 尝试重新创建识别器...") if !createRecognizerAndSetupCallbacks() { eventHandler("error", ["message": "无法创建语音识别器"]) return false } } // 尝试启动连续识别 do { // 启动连续识别 try recognizer?.startContinuousRecognition() _isContinuousRecognitionActive = true print("[AzureAsrHelper] 连续识别已启动") return true } catch { _isContinuousRecognitionActive = false print("[AzureAsrHelper] 错误: 开始连续识别失败: \(error.localizedDescription)") // 发送错误通知 eventHandler("error", ["message": "开始连续识别失败: \(error.localizedDescription)"]) return false } } /// 停止连续语音识别 /// - Returns: 是否成功停止识别 func stopContinuousRecognition() -> Bool { if !_isContinuousRecognitionActive { print("[AzureAsrHelper] 未进行连续识别,忽略停止请求") return true } print("[AzureAsrHelper] 停止连续语音识别") // 防止空指针异常 guard let recognizer = recognizer else { print("[AzureAsrHelper] 警告: 识别器为空,但状态显示活跃") _isContinuousRecognitionActive = false eventHandler("sessionStopped", [:]) return true } // 先标记为非活跃状态,防止重复调用 _isContinuousRecognitionActive = false // do { // try recognizer.stopContinuousRecognition() // } catch { // print("[AzureAsrHelper] 错误: 停止连续识别失败: \(error.localizedDescription)") // } // // 异步执行停止操作,避免阻塞主线程 DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self else { return } do { // 在后台线程执行停止操作 try recognizer.stopContinuousRecognition() // 主线程回调通知结果 DispatchQueue.main.async { print("[AzureAsrHelper] 连续识别已停止") self.eventHandler("sessionStopped", [:]) } } catch { // 主线程回调通知错误 DispatchQueue.main.async { print("[AzureAsrHelper] 错误: 停止连续识别失败: \(error.localizedDescription)") // 停止失败,恢复状态 self._isContinuousRecognitionActive = true self.eventHandler("error", ["error": "停止识别失败: \(error.localizedDescription)"]) } } } // 立即返回,不阻塞调用方 return true } /// 检查连续识别是否活跃 /// - Returns: 连续识别是否处于活跃状态 func isContinuousRecognitionActive() -> Bool { return _isContinuousRecognitionActive } /// 释放资源 func dispose() { // 停止所有识别操作 if _isContinuousRecognitionActive { stopContinuousRecognition() } // 释放识别器 recognizer = nil audioConfig = nil speechConfig = nil // 重置状态 isInitialized = false _isContinuousRecognitionActive = false print("[AzureAsrHelper] 资源已释放") } /// 从结果中获取检测到的语言 private func getDetectedLanguage(from result: SPXSpeechRecognitionResult) -> String { if isAutoDetectLanguage { do { let langResult = try SPXAutoDetectSourceLanguageResult(result) return langResult.language ?? currentLanguage } catch { print("[AzureAsrHelper] 错误: 获取检测到的语言失败: \(error.localizedDescription)") return currentLanguage } } else { return currentLanguage } } }