From 1bc1f23d334bd38ca21734b5c6c84ebd800e980d Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 6 Aug 2025 19:20:11 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(=E8=AF=AD=E9=9F=B3=E6=9C=8D=E5=8A=A1):?= =?UTF-8?q?=20=E5=9C=A8=E8=AE=BE=E7=BD=AE=E8=AF=AD=E9=9F=B3=E8=AF=86?= =?UTF-8?q?=E5=88=AB=E8=AF=AD=E8=A8=80=E6=97=B6=E5=90=8C=E6=AD=A5=E6=9B=B4?= =?UTF-8?q?=E6=96=B0TTS=E8=AF=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加TTS语言同步功能,当设置ASR支持的语言时自动更新TTS语音角色 完善语言映射表,支持多种语言的语音角色配置 --- .../agent_service/AgentService.kt | 122 ++++++++++++- .../agent_service/AgentServiceImpl.swift | 169 ++++++++++++++++-- 2 files changed, 269 insertions(+), 22 deletions(-) diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index a1921b405..6a72831c8 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -1113,7 +1113,7 @@ object AgentService : CoroutineScope { } /** - * 设置支持的语音识别语言 + * 设置支持的语音识别语言,同时更新TTS语言设置 */ fun setSupportedLanguages(languages: List): Boolean { if (!isInitialized) { @@ -1132,28 +1132,35 @@ object AgentService : CoroutineScope { Log.i(TAG, "设置支持的语言: ${languages.joinToString(", ")}") // 重新初始化ASR服务以应用新的语言设置 - val success = azureAsrHelper?.initialize( + val asrSuccess = azureAsrHelper?.initialize( subscriptionKey = this.azureSpeechKey, region = this.azureSpeechRegion, supportedLanguages = languages.toTypedArray(), audioSourceType = audioSourceType ) ?: false - if (success) { - Log.i(TAG, "语言设置更新成功") + // 同时更新TTS语言设置 + val ttsSuccess = updateTtsLanguage(languages.first()) // 使用第一个语言作为TTS语言 + + val overallSuccess = asrSuccess && ttsSuccess + + if (overallSuccess) { + Log.i(TAG, "ASR和TTS语言设置更新成功") sendEvent("languages_updated", mapOf( "languages" to languages, - "status" to "success" + "status" to "success", + "asr_success" to asrSuccess, + "tts_success" to ttsSuccess )) } else { - Log.e(TAG, "语言设置更新失败") + Log.e(TAG, "语言设置更新失败 - ASR: $asrSuccess, TTS: $ttsSuccess") sendEvent("error", mapOf( "code" to "LANGUAGE_UPDATE_FAILED", - "message" to "语言设置更新失败" + "message" to "语言设置更新失败 - ASR: $asrSuccess, TTS: $ttsSuccess" )) } - success + overallSuccess } catch (e: Exception) { Log.e(TAG, "设置支持语言异常: ${e.message}") sendEvent("error", mapOf( @@ -1164,6 +1171,105 @@ object AgentService : CoroutineScope { } } + /** + * 更新TTS语言设置 + * + * @param languageCode ASR语言代码,如 "zh-CN", "en-US" + * @return 是否更新成功 + */ + private fun updateTtsLanguage(languageCode: String): Boolean { + return try { + Log.i(TAG, "更新TTS语言设置: $languageCode") + + // 根据ASR语言代码映射到对应的TTS语音角色 + val voiceName = getTtsVoiceNameByAsrCode(languageCode) + + if (voiceName.isNotEmpty()) { + // 设置TTS语音角色 + val success = ttsService?.setVoice(voiceName) ?: false + + if (success) { + Log.i(TAG, "TTS语音角色设置成功: $voiceName") + } else { + Log.w(TAG, "TTS语音角色设置失败: $voiceName") + } + + success + } else { + Log.w(TAG, "未找到语言代码 $languageCode 对应的TTS语音角色") + false + } + } catch (e: Exception) { + Log.e(TAG, "更新TTS语言设置异常: ${e.message}") + false + } + } + + /** + * 根据ASR语言代码获取对应的TTS语音角色名称 + * 与LanguageManager中的映射保持一致 + * + * @param asrCode ASR语言代码,如 "zh-CN", "en-US" + * @return TTS语音角色名称 + */ + private fun getTtsVoiceNameByAsrCode(asrCode: String): String { + return when (asrCode) { + // 中文相关 + "zh-CN" -> "zh-CN-XiaoxiaoNeural" + "zh-TW" -> "zh-TW-HsiaoChenNeural" + "zh-HK" -> "zh-HK-HiuMaanNeural" + + // 英语相关 + "en-US" -> "en-US-AriaNeural" + + // 亚洲语言 + "ja-JP" -> "ja-JP-NanamiNeural" + "ko-KR" -> "ko-KR-SunHiNeural" + "th-TH" -> "th-TH-PremwadeeNeural" + "vi-VN" -> "vi-VN-HoaiMyNeural" + "id-ID" -> "id-ID-GadisNeural" + "hi-IN" -> "hi-IN-SwaraNeural" + "ms-MY" -> "ms-MY-YasminNeural" + "bn-IN" -> "bn-IN-TanishaaNeural" + "ta-IN" -> "ta-IN-PallaviNeural" + "te-IN" -> "te-IN-ShrutiNeural" + "mr-IN" -> "mr-IN-AarohiNeural" + "ur-IN" -> "ur-IN-GulNeural" + "tr-TR" -> "tr-TR-EmelNeural" + "fa-IR" -> "fa-IR-DilaraNeural" + + // 欧洲语言 + "fr-FR" -> "fr-FR-DeniseNeural" + "es-ES" -> "es-ES-ElviraNeural" + "pt-BR" -> "pt-BR-FranciscaNeural" + "it-IT" -> "it-IT-ElsaNeural" + "de-DE" -> "de-DE-KatjaNeural" + "ru-RU" -> "ru-RU-SvetlanaNeural" + "pl-PL" -> "pl-PL-AgnieszkaNeural" + "nl-NL" -> "nl-NL-ColetteNeural" + "sv-SE" -> "sv-SE-SofieNeural" + "cs-CZ" -> "cs-CZ-VlastaNeural" + "el-GR" -> "el-GR-AthinaNeural" + "ro-RO" -> "ro-RO-AlinaNeural" + "hu-HU" -> "hu-HU-NoemiNeural" + "uk-UA" -> "uk-UA-PolinaNeural" + "da-DK" -> "da-DK-ChristelNeural" + "fi-FI" -> "fi-FI-NooraNeural" + "nb-NO" -> "nb-NO-IselinNeural" + "hr-HR" -> "hr-HR-GabrijelaNeural" + "ca-ES" -> "ca-ES-JoanaNeural" + + // 阿拉伯语和非洲语言 + "ar-EG" -> "ar-EG-SalmaNeural" + "sw-KE" -> "sw-KE-ZuriNeural" + + else -> { + Log.w(TAG, "未支持的语言代码: $asrCode,使用默认英语语音") + "en-US-AriaNeural" + } + } + } + /** * 保存聊天消息 */ diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index b884a0c71..5362f880f 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -879,53 +879,194 @@ private func jsonToString(_ json: [String: Any]) -> String? { } /** - * 设置支持的语音识别语言 - * + * 设置支持的语音识别语言,同时更新TTS语言设置 + * * @param languages 支持的语言列表,如 ["zh-CN", "en-US"] * @return 是否设置成功 */ func setSupportedLanguages(_ languages: [String]) -> Bool { os_log("设置支持的语言: %{public}@", log: logger, type: .info, languages.joined(separator: ", ")) - + if !isInitialized { os_log("服务未初始化,无法设置语言", log: logger, type: .error) sendError("服务未初始化", code: "NOT_INITIALIZED") return false } - + if languages.isEmpty { os_log("语言列表为空,使用默认语言", log: logger, type: .info) return false } - + // 如果正在识别,需要先停止 let wasRecognizing = isRecognizing if wasRecognizing { _ = stopRecognition() } - + // 重新初始化ASR引擎,使用新的语言设置 - guard let success = azureAsrHelper?.initialize( + guard let asrSuccess = azureAsrHelper?.initialize( subscriptionKey: azureSpeechKey, region: azureSpeechRegion, supportedLanguages: languages, audioSourceType: .microphone - ), success else { + ), asrSuccess else { os_log("重新初始化ASR引擎失败", log: logger, type: .error) sendError("重新初始化ASR引擎失败", code: "ASR_INIT_ERROR") return false } - - os_log("成功设置支持的语言: %{public}@", log: logger, type: .info, languages.joined(separator: ", ")) - + + // 同时更新TTS语言设置 + let ttsSuccess = updateTtsLanguage(languages.first ?? "zh-CN") + + let overallSuccess = asrSuccess && ttsSuccess + + if overallSuccess { + os_log("ASR和TTS语言设置更新成功: %{public}@", log: logger, type: .info, languages.joined(separator: ", ")) + } else { + os_log("语言设置更新失败 - ASR: %{public}@, TTS: %{public}@", log: logger, type: .error, String(asrSuccess), String(ttsSuccess)) + } + // 如果之前正在识别,重新开始 if wasRecognizing { _ = startRecognition() } - - return true + + return overallSuccess } - + + /** + * 更新TTS语言设置 + * + * @param languageCode ASR语言代码,如 "zh-CN", "en-US" + * @return 是否更新成功 + */ + private func updateTtsLanguage(_ languageCode: String) -> Bool { + os_log("更新TTS语言设置: %{public}@", log: logger, type: .info, languageCode) + + // 根据ASR语言代码映射到对应的TTS语音角色 + let voiceName = getTtsVoiceNameByAsrCode(languageCode) + + if !voiceName.isEmpty { + // 设置TTS语音角色 + let success = azureTtsHelper?.setVoice(voiceName) ?? false + + if success { + os_log("TTS语音角色设置成功: %{public}@", log: logger, type: .info, voiceName) + } else { + os_log("TTS语音角色设置失败: %{public}@", log: logger, type: .error, voiceName) + } + + return success + } else { + os_log("未找到语言代码 %{public}@ 对应的TTS语音角色", log: logger, type: .error, languageCode) + return false + } + } + + /** + * 根据ASR语言代码获取对应的TTS语音角色名称 + * 与LanguageManager中的映射保持一致 + * + * @param asrCode ASR语言代码,如 "zh-CN", "en-US" + * @return TTS语音角色名称 + */ + private func getTtsVoiceNameByAsrCode(_ asrCode: String) -> String { + switch asrCode { + // 中文相关 + case "zh-CN": + return "zh-CN-XiaoxiaoNeural" + case "zh-TW": + return "zh-TW-HsiaoChenNeural" + case "zh-HK": + return "zh-HK-HiuMaanNeural" + + // 英语相关 + case "en-US": + return "en-US-AriaNeural" + + // 亚洲语言 + case "ja-JP": + return "ja-JP-NanamiNeural" + case "ko-KR": + return "ko-KR-SunHiNeural" + case "th-TH": + return "th-TH-PremwadeeNeural" + case "vi-VN": + return "vi-VN-HoaiMyNeural" + case "id-ID": + return "id-ID-GadisNeural" + case "hi-IN": + return "hi-IN-SwaraNeural" + case "ms-MY": + return "ms-MY-YasminNeural" + case "bn-IN": + return "bn-IN-TanishaaNeural" + case "ta-IN": + return "ta-IN-PallaviNeural" + case "te-IN": + return "te-IN-ShrutiNeural" + case "mr-IN": + return "mr-IN-AarohiNeural" + case "ur-IN": + return "ur-IN-GulNeural" + case "tr-TR": + return "tr-TR-EmelNeural" + case "fa-IR": + return "fa-IR-DilaraNeural" + + // 欧洲语言 + case "fr-FR": + return "fr-FR-DeniseNeural" + case "es-ES": + return "es-ES-ElviraNeural" + case "pt-BR": + return "pt-BR-FranciscaNeural" + case "it-IT": + return "it-IT-ElsaNeural" + case "de-DE": + return "de-DE-KatjaNeural" + case "ru-RU": + return "ru-RU-SvetlanaNeural" + case "pl-PL": + return "pl-PL-AgnieszkaNeural" + case "nl-NL": + return "nl-NL-ColetteNeural" + case "sv-SE": + return "sv-SE-SofieNeural" + case "cs-CZ": + return "cs-CZ-VlastaNeural" + case "el-GR": + return "el-GR-AthinaNeural" + case "ro-RO": + return "ro-RO-AlinaNeural" + case "hu-HU": + return "hu-HU-NoemiNeural" + case "uk-UA": + return "uk-UA-PolinaNeural" + case "da-DK": + return "da-DK-ChristelNeural" + case "fi-FI": + return "fi-FI-NooraNeural" + case "nb-NO": + return "nb-NO-IselinNeural" + case "hr-HR": + return "hr-HR-GabrijelaNeural" + case "ca-ES": + return "ca-ES-JoanaNeural" + + // 阿拉伯语和非洲语言 + case "ar-EG": + return "ar-EG-SalmaNeural" + case "sw-KE": + return "sw-KE-ZuriNeural" + + default: + os_log("未支持的语言代码: %{public}@,使用默认英语语音", log: logger, type: .error, asrCode) + return "en-US-AriaNeural" + } + } + func dispose() -> Bool { print("ai释放资源") if isRecognizing { From d33330eda0c46372a233e07e4144ff816bbe3841 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Wed, 6 Aug 2025 20:18:53 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dai=E5=A3=B0=E9=9F=B3?= =?UTF-8?q?=E6=97=B6=E5=A4=A7=E6=97=B6=E5=B0=8F=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E5=8A=A0=E5=85=A5=E8=AF=86=E5=88=AB=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controllers/agent_controller.dart | 20 ++++++++--- .../controllers/translation_controller.dart | 9 ++++- .../agent_service/AgentService.kt | 10 +++--- .../agent_service/AgentServiceImpl.swift | 22 +++++++++--- .../agent_service/AgentServicePlugin.swift | 9 ++++- .../agent_service/lib/agent_service.dart | 29 +++++++++++++-- .../azure_speech/AzureAsrHelper.kt | 2 +- .../Sources/azure_speech/AzureAsrHelper.swift | 36 ++++++++++++------- .../azure_speech/AzureSpeechPlugin.swift | 15 ++++++-- 9 files changed, 117 insertions(+), 35 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 5330abd4c..8debed827 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -3,6 +3,8 @@ import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'package:amap_service/amap_service.dart'; +import '../../../data/services/ble_manager.dart'; +import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/novice_guide_service.dart'; import '/core/utils/upload_oss.dart'; import '/data/models/user_Info.dart'; @@ -41,7 +43,10 @@ class AgentController extends GetxController { final ScrollController scrollController = ScrollController(); final noviceGuideService = Get.find(); - + // 确保服务已初始化 + // ========== 依赖服务 ========== + final BluetoothManager _bluetoothManager = Get.find(); + final BleManager _bleManager = Get.find(); //音乐播放管理 final _musiceManager = Get.find(); @@ -558,16 +563,16 @@ class AgentController extends GetxController { break; case AgentServiceEventType.ttsStarted: - // isSpeaking.value = true; - // Logger.i(TAG, 'TTS开始播放'); + isSpeaking.value = true; + print('TTS开始播放'); break; case AgentServiceEventType.ttsCompleted: case AgentServiceEventType.ttsStopped: case AgentServiceEventType.ttsCanceled: - // isSpeaking.value = false; + isSpeaking.value = false; - // Logger.i(TAG, 'TTS停止播放, $event.type'); + Logger.i(TAG, 'TTS停止播放, $event.type'); break; case AgentServiceEventType.playbackStarted: @@ -1035,6 +1040,11 @@ class AgentController extends GetxController { try { await AgentService.recognizeCallback(); + if (_bluetoothManager.currentDeviceRx.value != null) { + await AgentService.restoreOriginalAudioState(); + } else { + await AgentService.disableBluetoothAudio(); + } } catch (e) { Logger.e(TAG, '启动识别器失败: $e'); } diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 94cf4b25c..ae649a20e 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -9,6 +9,7 @@ import 'package:get_storage/get_storage.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; +import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/music_manager.dart'; import '../../../data/services/volcano_translation_service.dart'; import '../../../data/services/tts_service.dart'; @@ -30,6 +31,8 @@ class TranslationController extends GetxController { static const String TAG = 'TranslationController'; // 服务 + final BluetoothManager _bluetoothManager = Get.find(); + final BleManager _bleManager = Get.find(); final AsrService _asrService = Get.find(); final VolcanoTranslationService _translationService = Get.find(); @@ -483,7 +486,11 @@ class TranslationController extends GetxController { if (currentMode.value == 'faceToFace') { _asrService.stopContinuousRecognition(); } else { - await _ttsService.setAudioOutputDevice(0); + if (_bluetoothManager.currentDeviceRx.value != null) { + restoreOriginalAudioState(); + } else { + disableBluetoothAudio(); + } await _asrService.startContinuousRecognition(_audioSourceType); // 开始ASR活跃时长计时 _startAsrActiveTracking(); diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 76f1fc5dc..cd5762b9d 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -420,7 +420,7 @@ object AgentService : CoroutineScope { try { delay(maxIdleSeconds * 1000L) - if (isActive && isRecognitionActive && !hasSpeechDetected && !isTtsSpeaking && !isAiStreaming) { + if (isActive && isRecognitionActive && !isTtsSpeaking && !isAiStreaming) { stopRecognition() sendEvent( "auto_stop", @@ -522,12 +522,12 @@ object AgentService : CoroutineScope { override fun onRecognizing(recognizing: String, detectedLanguage: String) { if (recognizing.isNotEmpty()) { // 检测到语音,更新状态 - val previousHasSpeech = hasSpeechDetected - _hasSpeechDetected.set(true) + val previousHasSpeech = hasSpeechDetected + _hasSpeechDetected.set(true) if (!previousHasSpeech) { - restartIdleCheck() - } + restartIdleCheck() + } sendEvent( "recognizing", mapOf( diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index e0e0ece65..65fe56f08 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -291,8 +291,8 @@ class AgentServiceImpl: NSObject { // 使用更安全的方式检查self是否存在 DispatchQueue.main.async { [weak self] in guard let self = self else { return } - - if self.isRecognizing && !self.hasSpeechDetected && !self.isSpeaking && !self.isAiStreaming { + + if self.isRecognizing && !self.isSpeaking && !self.isAiStreaming { self.stopRecognition() self.sendEvent(name: "auto_stop", data: [ "reason": "idle_timeout", @@ -349,8 +349,14 @@ print("ai启动语音=\(audioSourceType)=\(useBle)") return true } - func recognizeCallback(useBle: Bool = false) -> Bool { - + func recognizeCallback() -> Bool { + print("重新设置识别回调=") + guard let asrsetupSuccess = azureAsrHelper?.setupEventListeners( + callback: self + ), asrsetupSuccess else { + sendError("初始化语音识别服务失败", code: "ASR_INIT_ERROR") + return false + } return true } @@ -365,7 +371,12 @@ print("ai启动语音=\(audioSourceType)=\(useBle)") return true } - + func disableBluetoothAudio() { + azureAsrHelper?.disableBluetoothAudio() + } + func restoreOriginalAudioState() { + azureAsrHelper?.restoreOriginalAudioState() + } func pushAudioData(_ audioData: Data) -> Bool { if !isRecognizing || !isInitialized { return false @@ -1572,6 +1583,7 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback { hasSpeechDetected = true if !previousHasSpeech { + print("onRecognizing restartIdleCheck") restartIdleCheck() } diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift index 0c0ef5703..fb8baa1f8 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift @@ -51,7 +51,14 @@ public class AgentServicePlugin: NSObject, FlutterPlugin { case "stopConversation": impl.stopRecognition() result(true) - + + case "disableBluetoothAudio": + impl.disableBluetoothAudio(); + return + + case "restoreOriginalAudioState": + impl.restoreOriginalAudioState(); + return case "processTextInput": guard let arguments = call.arguments as? [String: Any], let text = arguments["text"] as? String else { diff --git a/local_plugins/agent_service/lib/agent_service.dart b/local_plugins/agent_service/lib/agent_service.dart index e3e2eb1c4..f8294ef2a 100644 --- a/local_plugins/agent_service/lib/agent_service.dart +++ b/local_plugins/agent_service/lib/agent_service.dart @@ -261,6 +261,33 @@ class AgentService { } } + /// 恢复原始音频状态 + /// + /// 恢复原始音频状态 + /// 返回是否成功恢复原始音频状态 + static Future restoreOriginalAudioState() async { + try { + final bool result = + await _channel.invokeMethod('restoreOriginalAudioState'); + return result; + } on PlatformException catch (e) { + throw AgentServiceException(e.code, e.message ?? '恢复原始音频状态失败', e.details); + } + } + + /// 禁用蓝牙音频 + /// + /// 禁用蓝牙音频 + /// 返回是否成功禁用蓝牙音频 + static Future disableBluetoothAudio() async { + try { + final bool result = await _channel.invokeMethod('disableBluetoothAudio'); + return result; + } on PlatformException catch (e) { + throw AgentServiceException(e.code, e.message ?? '禁用蓝牙音频失败', e.details); + } + } + /// 停止对话 /// /// 停止语音识别 @@ -332,8 +359,6 @@ class AgentService { } } - - /// 停止语音合成 static Future stopAiSteam() async { try { diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt index 02d909d4a..0d00c776b 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt @@ -745,7 +745,7 @@ class AzureAsrHelper(private val context: Context) { */ fun saveAudioDataTo(buffer: ByteArray) { - if (audioSourceType == AudioSourceType.MICROPHONE) return + if (audioSourceType == AudioSourceType.MICROPHONE||!isContinuousRecognitionActive) return // 放入队列,由写线程写入 writeQueue.offer(buffer.copyOf()) diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift index 92b6b40c0..40846707f 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift @@ -131,7 +131,7 @@ public class AzureAsrHelper: NSObject { */ private func preInitializeAudioComponents() { if audioStream == nil { - audioStream = AudioStream() + audioStream = AudioStream(parentHelper: self) audioStream?.initAudioRecord() audioStream?.onAudioData = { [weak self] data in self?.recordfile?.saveAudioDataToWav(data) @@ -354,7 +354,7 @@ public class AzureAsrHelper: NSObject { do { // 【优化】检查是否已经初始化,避免重复创建 if audioStream == nil { - audioStream = AudioStream() + audioStream = AudioStream(parentHelper: self) audioStream?.initAudioRecord() audioStream?.onAudioData = { [weak self] data in self?.recordfile?.saveAudioDataToWav(data) @@ -548,6 +548,7 @@ public class AzureAsrHelper: NSObject { public func disableBluetoothAudio() { do { + print("禁用蓝牙音频功能,切换回正常音频模式") audioStream?.setAudioOutputRoute(.speaker) } catch { print("disableBluetoothAudio") @@ -559,6 +560,7 @@ public class AzureAsrHelper: NSObject { public func restoreOriginalAudioState() { do { + print("恢复原始音频设备状态(通常是重新启用蓝牙)") audioStream?.setAudioOutputRoute(.receiver) } catch { print("restoreOriginalAudioState") @@ -632,9 +634,18 @@ public class AzureAsrHelper: NSObject { private var isRunning = false public var isWriting = false private let bufferSize: Int = 4096 - private var writeThread: DispatchQueue? - private var currentRoute: AudioOutputRoute? - public var onAudioData: ((Data) -> Void)? + private var writeThread: DispatchQueue? + private var currentRoute: AudioOutputRoute? + public var onAudioData: ((Data) -> Void)? + + // 添加对外部类的弱引用 + private weak var parentHelper: AzureAsrHelper? + + // 添加初始化方法,接收外部类引用 + init(parentHelper: AzureAsrHelper) { + self.parentHelper = parentHelper + super.init() + } /** * 初始化 */ @@ -654,7 +665,7 @@ public class AzureAsrHelper: NSObject { // 创建新的写线程 writeThread = DispatchQueue(label: "audio.stream.writer") startWriteThread() - self.setAudioOutputRoute(.receiver) + //self.setAudioOutputRoute(.receiver) } /// 获取最佳音频格式 (iOS 通常支持标准采样率) @@ -717,7 +728,8 @@ public class AzureAsrHelper: NSObject { * @param data 音频数据字节数组 */ public func saveAudioDataTo(data: Data) { - if audioSourceType != .external { + // 通过父类引用调用方法 + if audioSourceType != .external || !(parentHelper?.isContinuousRecognitionActive() ?? false) { return } // print("外部data=\(data)") @@ -879,8 +891,7 @@ private func runMicrophoneCapture() { case bluetooth } public func setAudioOutputRoute(_ route: AudioOutputRoute) { - guard currentRoute != route else { return } - print("setAudioOutputRoute") + do { // if self.isWriting { @@ -890,14 +901,15 @@ private func runMicrophoneCapture() { try audioSession.setActive(false) } - + print("setAudioOutputRoutecurrent,route=\(route)") switch route { case .speaker: + print("setAudioOutputRoutecurrent,speaker") // 使用扬声器时必须用videoChat模式 try audioSession.setCategory( .playAndRecord, mode: .videoChat, - options: [.allowBluetooth, .mixWithOthers, .defaultToSpeaker] + options: [.defaultToSpeaker] ) try audioSession.overrideOutputAudioPort(.speaker) @@ -906,7 +918,7 @@ private func runMicrophoneCapture() { try audioSession.setCategory( .playAndRecord, mode: .voiceChat, - options: [.allowBluetooth, .mixWithOthers, .defaultToSpeaker] + options: [.allowBluetooth] ) try audioSession.overrideOutputAudioPort(.none) diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift index f07e56855..84c96f361 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift @@ -184,6 +184,15 @@ import os.log callback: currentAsrCallback! ) result(success) + + case "disableBluetoothAudio": + azureAsrHelper.disableBluetoothAudio(); + return + + case "restoreOriginalAudioState": + azureAsrHelper.restoreOriginalAudioState(); + return + case "stopContinuousRecognition": let success = azureAsrHelper.stopContinuousRecognition() @@ -224,7 +233,7 @@ import os.log return } do { - print("音频文件名称为: \(filePath)") + print("音频文件名称为: (filePath)") try azureAsrHelper.renameFile(filePath: filePath, newName: newName) result(true) } catch { @@ -239,7 +248,7 @@ import os.log return } do { - print("音频文件名称为: \(sourcePath)") + print("音频文件名称为: (sourcePath)") try azureAsrHelper.moveFile(sourcePath: sourcePath, destPath: destPath) result(true) } catch { @@ -253,7 +262,7 @@ import os.log return } do { - print("音频文件名称为: \(filePath)") + print("音频文件名称为: (filePath)") try azureAsrHelper.enableRecord(filePath: filePath) result(true) } catch {