From 50d1bcd9a849fcf8700d69f03cb4552bd93607b7 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 31 Dec 2025 14:21:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(azure=5Fspeech):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AE=AF=E9=A3=9E=E8=AF=AD=E9=9F=B3=E8=AF=86=E5=88=AB=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8F=82=E6=95=B0=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在Azure语音识别插件中添加对讯飞语音识别服务的配置参数支持,包括appId、accessKeyId和accessKeySecret。这些参数现在可以通过初始化方法传递,并在Android和iOS平台实现相应的配置更新逻辑。 当支持的语言仅为中文或英文且讯飞配置完整时,自动启用讯飞语音识别服务。 --- .../speech_impl/azure_asr_service.dart | 9 +++++ .../azure_speech/AzureAsrHelper.kt | 31 ++++++++++++++--- .../azure_speech/AzureSpeechPlugin.kt | 8 ++++- .../azure_speech/XunFeiAsrHelper.kt | 34 +++++++++++++++++-- .../Sources/azure_speech/AzureAsrHelper.swift | 28 +++++++++++++-- .../azure_speech/AzureSpeechPlugin.swift | 8 ++++- .../azure_speech/XunFeiAsrHelper.swift | 32 +++++++++++++++-- 7 files changed, 135 insertions(+), 15 deletions(-) diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart index 1eb84856b..cec7285a1 100644 --- a/lib/data/services/speech_impl/azure_asr_service.dart +++ b/lib/data/services/speech_impl/azure_asr_service.dart @@ -29,6 +29,9 @@ class AzureAsrService extends GetxService implements AsrService { bool _isInitialized = false; late final String _subscriptionKey; late final String _serviceRegion; + late final String _xunfeiAppId; + late final String _xunfeiAccessKeyId; + late final String _xunfeiAccessKeySecret; final List _defaultSupportedLanguages = ['zh-CN', 'en-US']; @override @@ -69,6 +72,9 @@ class AzureAsrService extends GetxService implements AsrService { // final _env = _storage.read("ENV") as Map; _subscriptionKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; _serviceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? ''; + _xunfeiAppId = AppConfig.env('XUNFEI_ASR_APP_ID') ?? ''; + _xunfeiAccessKeyId = AppConfig.env('XUNFEI_ASR_ACCESS_KEY_ID') ?? ''; + _xunfeiAccessKeySecret = AppConfig.env('XUNFEI_ASR_ACCESS_KEY_SECRET') ?? ''; if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { throw Exception( @@ -125,6 +131,9 @@ class AzureAsrService extends GetxService implements AsrService { 'supportedLanguages': languages, 'audioSourceType': _audioSourceType.toString().split('.').last, 'useEchoCancellation': useEchoCancellation, + 'xunfeiAppId': _xunfeiAppId, + 'xunfeiAccessKeyId': _xunfeiAccessKeyId, + 'xunfeiAccessKeySecret': _xunfeiAccessKeySecret, }); _setupEventChannel(); 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 343c5ca00..75d42962b 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 @@ -44,6 +44,9 @@ class AzureAsrHelper(private val context: Context) { private var useXunfei = false private var xunFeiAsrHelper: XunFeiAsrHelper? = null + private var xunfeiAppId: String = "" + private var xunfeiAccessKeyId: String = "" + private var xunfeiAccessKeySecret: String = "" // 音频源配置 var audioSourceType = AudioSourceType.MICROPHONE @@ -92,7 +95,10 @@ class AzureAsrHelper(private val context: Context) { subscriptionKey: String, region: String, supportedLanguages: Array = arrayOf("zh-CN", "en-US"), - audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE + audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE, + xunfeiAppId: String = "", + xunfeiAccessKeyId: String = "", + xunfeiAccessKeySecret: String = "" ): Boolean { try { // 检查配置是否为空 @@ -108,13 +114,19 @@ class AzureAsrHelper(private val context: Context) { this.subscriptionKey = subscriptionKey this.region = region this.audioSourceType = audioSourceType + this.xunfeiAppId = xunfeiAppId + this.xunfeiAccessKeyId = xunfeiAccessKeyId + this.xunfeiAccessKeySecret = xunfeiAccessKeySecret // 设置语言 if (supportedLanguages.isNotEmpty()) { this.supportedLanguages = supportedLanguages } - this.useXunfei = supportedLanguages.all { it == "zh-CN" || it == "en-US" } + val xunfeiConfigReady = this.xunfeiAppId.isNotBlank() && + this.xunfeiAccessKeyId.isNotBlank() && + this.xunfeiAccessKeySecret.isNotBlank() + this.useXunfei = supportedLanguages.all { it == "zh-CN" || it == "en-US" } && xunfeiConfigReady // 根据支持的语言数量决定是否启用自动语言检测 this.isAutoDetectLanguage = supportedLanguages.size >= 2 @@ -283,7 +295,18 @@ class AzureAsrHelper(private val context: Context) { } if (xunFeiAsrHelper == null) { - xunFeiAsrHelper = XunFeiAsrHelper(context) + xunFeiAsrHelper = XunFeiAsrHelper( + context = context, + appId = xunfeiAppId, + accessKeyId = xunfeiAccessKeyId, + accessKeySecret = xunfeiAccessKeySecret + ) + } else { + xunFeiAsrHelper?.updateConfig( + appId = xunfeiAppId, + accessKeyId = xunfeiAccessKeyId, + accessKeySecret = xunfeiAccessKeySecret + ) } try { @@ -904,4 +927,4 @@ class AzureAsrHelper(private val context: Context) { } - \ No newline at end of file + diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt index eccd2e060..e684d456f 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt @@ -413,6 +413,9 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { val supportedLanguages = call.argument>("supportedLanguages") ?: listOf("zh-CN") val useExternalAudio = call.argument("useExternalAudio") ?: false + val xunfeiAppId = call.argument("xunfeiAppId") ?: "" + val xunfeiAccessKeyId = call.argument("xunfeiAccessKeyId") ?: "" + val xunfeiAccessKeySecret = call.argument("xunfeiAccessKeySecret") ?: "" try { FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") // @@ -428,6 +431,9 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { region = region, supportedLanguages = supportedLanguages.toTypedArray(), audioSourceType = audioSourceType, + xunfeiAppId = xunfeiAppId, + xunfeiAccessKeyId = xunfeiAccessKeyId, + xunfeiAccessKeySecret = xunfeiAccessKeySecret, ) result.success(success) @@ -1212,4 +1218,4 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { } } - \ No newline at end of file + diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt index daee140d5..4ebd748ff 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt @@ -15,12 +15,16 @@ import java.util.UUID import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec +// 讯飞语音识别助手 +//调用讯飞开放平台的实时语音转写大模型接口 +// 参考文档:https://www.xfyun.cn/doc/spark/asr_llm/rtasr_llm.html + class XunFeiAsrHelper(private val context: Context) { private val tag = "XunFeiAsrHelper" - private val appId = "43d87d22" - private val accessKeyId = "2bd01ce06ac35957fc0c082f9b6284ec" - private val accessKeySecret = "ODU3OTUyYjhjMjY5NDhkMmMxYWY2Zjlm" + private var appId: String = "" + private var accessKeyId: String = "" + private var accessKeySecret: String = "" private var webSocket: WebSocket? = null private val client = OkHttpClient() @@ -35,6 +39,25 @@ class XunFeiAsrHelper(private val context: Context) { // 是否第去除一个文字的标点符号 private var removeFirstPunctuation = true + constructor( + context: Context, + appId: String, + accessKeyId: String, + accessKeySecret: String + ) : this(context) { + updateConfig(appId = appId, accessKeyId = accessKeyId, accessKeySecret = accessKeySecret) + } + + fun updateConfig(appId: String, accessKeyId: String, accessKeySecret: String) { + this.appId = appId + this.accessKeyId = accessKeyId + this.accessKeySecret = accessKeySecret + } + + private fun isConfigValid(): Boolean { + return appId.isNotBlank() && accessKeyId.isNotBlank() && accessKeySecret.isNotBlank() + } + fun start(callback: AzureAsrHelper.ContinuousRecognizeCallback, language: String, isAutoDetect: Boolean, isRemoveFirstPunctuation: Boolean) { try { webSocket?.cancel() @@ -50,6 +73,11 @@ class XunFeiAsrHelper(private val context: Context) { isAutoDetectLanguage = isAutoDetect removeFirstPunctuation = isRemoveFirstPunctuation + if (!isConfigValid()) { + callback.onError(currentSessionId, 1007, "Xunfei config missing") + return + } + val url = getWebSocketUrl() val request = Request.Builder().url(url).build() 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 f6e66af2a..ababec198 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 @@ -59,6 +59,9 @@ public class AzureAsrHelper: NSObject { private var useXunfei = false private var xunFeiAsrHelper: XunFeiAsrHelper? + private var xunfeiAppId: String = "" + private var xunfeiAccessKeyId: String = "" + private var xunfeiAccessKeySecret: String = "" // 音频源配置 public enum AudioSourceType { @@ -94,7 +97,10 @@ public class AzureAsrHelper: NSObject { subscriptionKey: String, region: String, supportedLanguages: [String] = ["zh-CN", "en-US"], - audioSourceType: AudioSourceType = .microphone + audioSourceType: AudioSourceType = .microphone, + xunfeiAppId: String = "", + xunfeiAccessKeyId: String = "", + xunfeiAccessKeySecret: String = "" ) -> Bool { do { // 检查配置是否为空 @@ -109,6 +115,9 @@ public class AzureAsrHelper: NSObject { self.subscriptionKey = subscriptionKey self.region = region self.audioSourceType = audioSourceType + self.xunfeiAppId = xunfeiAppId + self.xunfeiAccessKeyId = xunfeiAccessKeyId + self.xunfeiAccessKeySecret = xunfeiAccessKeySecret // 设置语言 if !supportedLanguages.isEmpty { @@ -116,7 +125,10 @@ public class AzureAsrHelper: NSObject { } // 检查是否只包含中文或英文,如果是则使用讯飞 - self.useXunfei = supportedLanguages.allSatisfy { $0 == "zh-CN" || $0 == "en-US" } + let xunfeiConfigReady = !self.xunfeiAppId.isEmpty && + !self.xunfeiAccessKeyId.isEmpty && + !self.xunfeiAccessKeySecret.isEmpty + self.useXunfei = supportedLanguages.allSatisfy { $0 == "zh-CN" || $0 == "en-US" } && xunfeiConfigReady // 根据支持的语言数量决定是否启用自动语言检测 self.isAutoDetectLanguage = supportedLanguages.count >= 2 @@ -430,7 +442,17 @@ public class AzureAsrHelper: NSObject { } if xunFeiAsrHelper == nil { - xunFeiAsrHelper = XunFeiAsrHelper() + xunFeiAsrHelper = XunFeiAsrHelper( + appId: xunfeiAppId, + accessKeyId: xunfeiAccessKeyId, + accessKeySecret: xunfeiAccessKeySecret + ) + } else { + xunFeiAsrHelper?.updateConfig( + appId: xunfeiAppId, + accessKeyId: xunfeiAccessKeyId, + accessKeySecret: xunfeiAccessKeySecret + ) } // 启动讯飞识别 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 194208bd0..c0f88a996 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 @@ -336,13 +336,19 @@ private func sendAstEvent(_ event: [String: Any]) { } let supportedLanguages = args["supportedLanguages"] as? [String] ?? ["zh-CN"] + let xunfeiAppId = args["xunfeiAppId"] as? String ?? "" + let xunfeiAccessKeyId = args["xunfeiAccessKeyId"] as? String ?? "" + let xunfeiAccessKeySecret = args["xunfeiAccessKeySecret"] as? String ?? "" // 初始化ASR引擎 let success = azureAsrHelper.initialize( subscriptionKey: subscriptionKey, region: region, supportedLanguages: supportedLanguages, - audioSourceType: .microphone + audioSourceType: .microphone, + xunfeiAppId: xunfeiAppId, + xunfeiAccessKeyId: xunfeiAccessKeyId, + xunfeiAccessKeySecret: xunfeiAccessKeySecret ) result(success) diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/XunFeiAsrHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/XunFeiAsrHelper.swift index e2ddd5915..bc63bcaee 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/XunFeiAsrHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/XunFeiAsrHelper.swift @@ -1,12 +1,16 @@ import Foundation import CryptoKit +// 讯飞语音识别助手 +//调用讯飞开放平台的实时语音转写大模型接口 +// 参考文档:https://www.xfyun.cn/doc/spark/asr_llm/rtasr_llm.html + class XunFeiAsrHelper: NSObject, URLSessionWebSocketDelegate { private let tag = "XunFeiAsrHelper" - private let appId = "43d87d22" - private let accessKeyId = "2bd01ce06ac35957fc0c082f9b6284ec" - private let accessKeySecret = "ODU3OTUyYjhjMjY5NDhkMmMxYWY2Zjlm" + private var appId: String + private var accessKeyId: String + private var accessKeySecret: String private var webSocketTask: URLSessionWebSocketTask? private lazy var session: URLSession = { @@ -26,6 +30,23 @@ class XunFeiAsrHelper: NSObject, URLSessionWebSocketDelegate { private var callback: AzureAsrHelper.ContinuousRecognizeCallback? + init(appId: String, accessKeyId: String, accessKeySecret: String) { + self.appId = appId + self.accessKeyId = accessKeyId + self.accessKeySecret = accessKeySecret + super.init() + } + + func updateConfig(appId: String, accessKeyId: String, accessKeySecret: String) { + self.appId = appId + self.accessKeyId = accessKeyId + self.accessKeySecret = accessKeySecret + } + + private func isConfigValid() -> Bool { + return !appId.isEmpty && !accessKeyId.isEmpty && !accessKeySecret.isEmpty + } + func start(callback: AzureAsrHelper.ContinuousRecognizeCallback, language: String, isAutoDetect: Bool, isRemoveFirstPunctuation: Bool) { stop() @@ -37,6 +58,11 @@ class XunFeiAsrHelper: NSObject, URLSessionWebSocketDelegate { self.lastIntermediateResult = "" self.sessionId = "" + if !isConfigValid() { + callback.onError(sessionid: self.currentSessionId, 1007, "Xunfei config missing") + return + } + let urlString = getWebSocketUrl() guard let url = URL(string: urlString) else { print("\(tag): Invalid URL")