Browse Source

fix(azure_speech): 修正讯飞语音识别支持的语言代码

将支持的语言代码中的错误值"en-TW"更正为"zh-TW",以正确匹配台湾中文的语言标识。同时为语音识别入口添加登录和积分检查逻辑,并在相关方法上补充注释说明。
newdev_shunjiawei
lxm 8 months ago
parent
commit
5bb6930768
  1. 2
      lib/modules/translation/controllers/translation_controller.dart
  2. 2
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  3. 160
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift

2
lib/modules/translation/controllers/translation_controller.dart

@ -964,8 +964,10 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
}
/// 开始语音识别
/// 同声翻译、通话翻译、多媒体翻译的语音识别调用入口
Future<void> startRecognition() async {
Logger.info('开始语音识别');
//检查用户是否登录且积分是否足够,不足则提示充值
if (User.isLoggedIn() && User.instance.tradeintegral <= 0) {
showRechargeDialog('translation');
return;

2
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -130,7 +130,7 @@ class AzureAsrHelper(private val context: Context) {
val xunfeiConfigReady = this.xunfeiAppId.isNotBlank() &&
this.xunfeiAccessKeyId.isNotBlank() &&
this.xunfeiAccessKeySecret.isNotBlank()
this.useXunfei = supportedLanguages.all { it == "zh-CN" || it == "en-US" || it == "zh-HK"|| it == "en-TW" } && xunfeiConfigReady
this.useXunfei = supportedLanguages.all { it == "zh-CN" || it == "en-US" || it == "zh-HK"|| it == "zh-TW" } && xunfeiConfigReady
// 根据支持的语言数量决定是否启用自动语言检测
this.isAutoDetectLanguage = supportedLanguages.size >= 2

160
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift

@ -1,18 +1,17 @@
import Foundation
import AVFoundation
import Foundation
import MicrosoftCognitiveServicesSpeech
import speech
import os.log
import speech
// 移除 Network 导入,因为我们将使用独立的 NetworkStateMonitor
// import Network
// MARK: - 基于微软Azure语音服务的ASR实现
/**
* Azure ASR Helper
*
* 基于微软Azure语音服务的ASR实现
* 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-recognize-speech
*/
/// Azure ASR Helper
///
/// 基于微软Azure语音服务的ASR实现
/// 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-recognize-speech
public class AzureAsrHelper: NSObject {
private let tag = "AzureAsrHelper"
// 日志对象
@ -49,7 +48,6 @@ public class AzureAsrHelper: NSObject {
private var currsessionId = ""
// 配置参数
private var currentLanguage = "zh-CN"
private var supportedLanguages = ["zh-CN"]
@ -81,10 +79,6 @@ public class AzureAsrHelper: NSObject {
// 音频处理
public var audioStream: SimpleAudioReceiver?
public func asrProvider() -> String {
return useXunfei ? "xunfei" : "azure"
}
@ -134,10 +128,13 @@ public class AzureAsrHelper: NSObject {
}
// 检查是否只包含中文或英文,如果是则使用讯飞
let xunfeiConfigReady = !self.xunfeiAppId.isEmpty &&
!self.xunfeiAccessKeyId.isEmpty &&
!self.xunfeiAccessKeySecret.isEmpty
self.useXunfei = supportedLanguages.allSatisfy { $0 == "zh-CN" || $0 == "en-US" || $0 == "zh-HK" || $0 == "en-TW" } && xunfeiConfigReady
let xunfeiConfigReady =
!self.xunfeiAppId.isEmpty && !self.xunfeiAccessKeyId.isEmpty
&& !self.xunfeiAccessKeySecret.isEmpty
self.useXunfei =
supportedLanguages.allSatisfy {
$0 == "zh-CN" || $0 == "en-US" || $0 == "zh-HK" || $0 == "zh-TW"
} && xunfeiConfigReady
// 根据支持的语言数量决定是否启用自动语言检测
self.isAutoDetectLanguage = supportedLanguages.count >= 2
@ -152,9 +149,12 @@ public class AzureAsrHelper: NSObject {
// 针对翻译场景的性能优化
// optimizeForTranslation()
if isAutoDetectLanguage {
speechConfig?.setPropertyTo("Continuous", by: SPXPropertyId.speechServiceConnectionLanguageIdMode)
speechConfig?.setPropertyTo(
"Continuous", by: SPXPropertyId.speechServiceConnectionLanguageIdMode)
// 不在这里设置语言,让SPXAutoDetectSourceLanguageConfiguration处理
os_log("启用语言检测模式,支持语言: %{public}@", log: log, type: .info, supportedLanguages.joined(separator: ","))
os_log(
"启用语言检测模式,支持语言: %{public}@", log: log, type: .info,
supportedLanguages.joined(separator: ","))
} else {
speechConfig?.speechRecognitionLanguage = currentLanguage
os_log("固定语言模式,当前语言: %{public}@", log: log, type: .info, currentLanguage)
@ -188,7 +188,6 @@ public class AzureAsrHelper: NSObject {
private func handleNetworkAvailable() {
os_log("网络已恢复", log: log, type: .info)
if !self.isNetworkRecovering {
self.isNetworkRecovering = true
os_log("网络恢复,准备重新启动识别", log: self.log, type: .info)
@ -196,7 +195,6 @@ public class AzureAsrHelper: NSObject {
}
/**
* 处理网络连接丢失(带防抖机制)
*/
@ -205,7 +203,9 @@ public class AzureAsrHelper: NSObject {
// 防抖机制:如果距离上次触发时间小于防抖间隔,则忽略本次触发
if currentTime - lastNetworkLostTime < networkLostDebounceInterval {
os_log("网络丢失事件被防抖过滤,距离上次触发仅%{public}dms", log: log, type: .debug, currentTime - lastNetworkLostTime)
os_log(
"网络丢失事件被防抖过滤,距离上次触发仅%{public}dms", log: log, type: .debug,
currentTime - lastNetworkLostTime)
return
}
@ -233,7 +233,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 预初始化音频组件
* 在初始化阶段就准备好音频流和相关组件,减少首次启动延迟
@ -251,7 +250,6 @@ public class AzureAsrHelper: NSObject {
}
/**
* 异步启动音频录制(带防抖与状态二次校验)
*
@ -279,7 +277,9 @@ public class AzureAsrHelper: NSObject {
// 防抖:距离上次启动过短,取消旧任务
if currentTime - lastAudioStartTime < audioOperationDebounceInterval {
audioStartWorkItem?.cancel()
os_log("音频启动操作被防抖过滤,距离上次启动仅%{public}dms", log: log, type: .debug, currentTime - lastAudioStartTime)
os_log(
"音频启动操作被防抖过滤,距离上次启动仅%{public}dms", log: log, type: .debug,
currentTime - lastAudioStartTime)
} else {
// 即使未触发防抖,也保证旧任务不会残留
audioStartWorkItem?.cancel()
@ -312,7 +312,9 @@ public class AzureAsrHelper: NSObject {
// 准备启动
self.isAudioStarting = true
self.pendingStopRequest = false // 启动前清空旧的停止请求
let success = self.performAudioStart(mod: mod, audioSourceType: audioSourceType, audioDataCallback: audioDataCallback)
let success = self.performAudioStart(
mod: mod, audioSourceType: audioSourceType, audioDataCallback: audioDataCallback
)
self.isAudioStarting = false
self.isAudioStarted = success
@ -350,7 +352,7 @@ public class AzureAsrHelper: NSObject {
audioDataCallback: SimpleAudioReceiver.AudioDataCallback?
) -> Bool {
do {
if(!audioStream!.isContinuousRecognitionActive){
if !audioStream!.isContinuousRecognitionActive {
audioStream?.startAudioRecord(
mod: mod,
audioSourceType: audioSourceType == .microphone ? .microphone : .external,
@ -469,15 +471,20 @@ public class AzureAsrHelper: NSObject {
}
// 启动讯飞识别
xunFeiAsrHelper?.start(callback: callback, language: currentLanguage, isAutoDetect: isAutoDetectLanguage, isRemoveFirstPunctuation: isRemoveFirstPunctuation)
xunFeiAsrHelper?.start(
callback: callback, language: currentLanguage, isAutoDetect: isAutoDetectLanguage,
isRemoveFirstPunctuation: isRemoveFirstPunctuation)
// 创建代理回调,将音频数据转发给讯飞
let proxyCallback = AudioDataCallbackProxy(original: audioDataCallback) { [weak self] data in
let proxyCallback = AudioDataCallbackProxy(original: audioDataCallback) {
[weak self] data in
self?.xunFeiAsrHelper?.sendAudio(frameBuffer: data)
}
// 异步启动音频处理
startAudioRecordAsync(mod: mod, audioSourceType: audioSourceType, audioDataCallback: proxyCallback) { [weak self] (success: Bool) in
startAudioRecordAsync(
mod: mod, audioSourceType: audioSourceType, audioDataCallback: proxyCallback
) { [weak self] (success: Bool) in
guard let self = self else { return }
if success {
@ -506,20 +513,28 @@ public class AzureAsrHelper: NSObject {
// self.audioStream?.isContinuousRecognitionActive = true
// 异步启动音频处理
startAudioRecordAsync(mod: mod, audioSourceType: audioSourceType, audioDataCallback: audioDataCallback) { [weak self] (success: Bool) in
startAudioRecordAsync(
mod: mod, audioSourceType: audioSourceType, audioDataCallback: audioDataCallback
) { [weak self] (success: Bool) in
guard let self = self else { return }
if success {
// 只有在音频启动成功后才设置状态为 true
self.audioStream?.isContinuousRecognitionActive = true
print("startContinuousRecognition:\(self.audioStream?.isContinuousRecognitionActive)")
print(
"startContinuousRecognition:\(self.audioStream?.isContinuousRecognitionActive)"
)
// 启动语音识别
do {
try self.recognizer?.startContinuousRecognition()
os_log("连续识别已启动,音频源: %{public}@", log: self.log, type: .info, audioSourceType == .microphone ? "麦克风" : "外部")
os_log(
"连续识别已启动,音频源: %{public}@", log: self.log, type: .info,
audioSourceType == .microphone ? "麦克风" : "外部")
} catch {
os_log("启动连续识别失败: %{public}@", log: self.log, type: .error, error.localizedDescription)
os_log(
"启动连续识别失败: %{public}@", log: self.log, type: .error,
error.localizedDescription)
self.stopContinuousRecognition()
self.audioStream?.isContinuousRecognitionActive = false
}
@ -537,8 +552,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 停止连续语音识别(优化:确保取消未执行的启动任务)
* - 返回: 停止操作是否成功
@ -566,7 +579,6 @@ public class AzureAsrHelper: NSObject {
return false
}
do {
// 关键:取消尚未执行的启动任务,防止 stop 后音频又被启动
@ -698,7 +710,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 设置识别器
*/
@ -734,7 +745,10 @@ public class AzureAsrHelper: NSObject {
// 创建识别器
if isAutoDetectLanguage {
// 使用正确的初始化方法
guard let autoDetectSourceLanguageConfig = try? SPXAutoDetectSourceLanguageConfiguration(supportedLanguages) else {
guard
let autoDetectSourceLanguageConfig =
try? SPXAutoDetectSourceLanguageConfiguration(supportedLanguages)
else {
print("创建自动语言检测配置失败,使用默认语言配置")
// 如果创建失败,使用默认的单语言配置
recognizer = try SPXSpeechRecognizer(
@ -764,7 +778,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 设置麦克风流 - 使用推流方式
* 优化:避免重复初始化已存在的音频流
@ -787,8 +800,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 从语音识别结果中获取检测到的语言
* @param result 语音识别结果
@ -803,8 +814,10 @@ public class AzureAsrHelper: NSObject {
// 备用方案:从属性中获取语言
if let properties = result.properties,
let language = properties.getPropertyByName("speechServiceResponse_RecognitionLanguage"),
!language.isEmpty {
let language = properties.getPropertyByName(
"speechServiceResponse_RecognitionLanguage"),
!language.isEmpty
{
print("从属性中检测到语言: \(language)")
return language
}
@ -823,8 +836,6 @@ public class AzureAsrHelper: NSObject {
return currentLanguage
}
/**
* 动态切换语言(翻译场景中的语言切换)
*
@ -857,7 +868,9 @@ public class AzureAsrHelper: NSObject {
}
}
os_log("语言已切换: %{public}@ -> %{public}@", log: log, type: .info, validatedSourceLang, validatedTargetLang)
os_log(
"语言已切换: %{public}@ -> %{public}@", log: log, type: .info, validatedSourceLang,
validatedTargetLang)
return true
}
/**
@ -870,7 +883,7 @@ public class AzureAsrHelper: NSObject {
self.continuousCallback = callback
// 重设识别器
if (!setupRecognizer()) {
if !setupRecognizer() {
return false
}
guard let recognizer = recognizer else { return false }
@ -888,7 +901,8 @@ public class AzureAsrHelper: NSObject {
print("正在识别事件=检测到语言: \(detectedLanguage), 识别中: \(text)")
DispatchQueue.main.async {
callback.onRecognizing(sessionid: self.currsessionId,result.text ?? "", detectedLanguage)
callback.onRecognizing(
sessionid: self.currsessionId, result.text ?? "", detectedLanguage)
}
}
@ -897,7 +911,8 @@ public class AzureAsrHelper: NSObject {
guard let self = self else { return }
let result = event.result
guard result.reason == .recognizedSpeech,
!(result.text?.isEmpty ?? true) else { return }
!(result.text?.isEmpty ?? true)
else { return }
// 使用现有的 getDetectedLanguage 方法替代 result.language
let detectedLanguage = self.getDetectedLanguage(from: result)
@ -907,7 +922,8 @@ public class AzureAsrHelper: NSObject {
// 在主线程回调结果
DispatchQueue.main.async {
callback.onResult(sessionid: self.currsessionId,result.text ?? "", detectedLanguage)
callback.onResult(
sessionid: self.currsessionId, result.text ?? "", detectedLanguage)
self.currsessionId = UUID().uuidString
}
@ -939,14 +955,12 @@ public class AzureAsrHelper: NSObject {
os_log("识别取消: %{public}@", log: self.log, type: .error, errorDetails)
callback.onCanceled(sessionid: self.currsessionId, reason, errorDetails)
}
return true
}
/**
* 禁用耳机进行拾音
*/
@ -980,10 +994,13 @@ public class AzureAsrHelper: NSObject {
* @param filePath 录音文件路径
* @param audioDataCallback 音频数据回调接口
*/
public func enableRecord( audioSourceType: AudioSourceType = .microphone,filePath: String, audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = nil) {
public func enableRecord(
audioSourceType: AudioSourceType = .microphone, filePath: String,
audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = nil
) {
os_log("开启录音: %{public}@", log: log, type: .info, filePath)
self.audioSourceType = audioSourceType
if(audioSourceType == .external){
if audioSourceType == .external {
os_log("外部音频源不在这里录音", log: log, type: .info)
return
}
@ -997,12 +1014,14 @@ public class AzureAsrHelper: NSObject {
audioStream?.recordfile = RecordFile()
}
// 修复:移除多余的 audioDataCallback 参数
// 修复:添加类型转换
if(!audioStream!.isContinuousRecognitionActive){
if !audioStream!.isContinuousRecognitionActive {
// 异步启动音频处理
startAudioRecordAsync(mod: "normal", audioSourceType: audioSourceType, audioDataCallback: audioDataCallback) { [weak self] (success: Bool) in
startAudioRecordAsync(
mod: "normal", audioSourceType: audioSourceType,
audioDataCallback: audioDataCallback
) { [weak self] (success: Bool) in
guard let self = self else { return }
}
@ -1017,7 +1036,7 @@ public class AzureAsrHelper: NSObject {
*/
public func moveFile(sourcePath: String, destPath: String) -> Bool {
if(audioSourceType == .external){
if audioSourceType == .external {
os_log(" 外部音频源不在这里移动文件", log: log, type: .info)
return true
}
@ -1025,12 +1044,11 @@ public class AzureAsrHelper: NSObject {
return true
}
/**
* 重命名指定路径的音频文件
*/
public func renameFile(filePath: String, newName: String) -> Bool {
if(audioSourceType == .external){
if audioSourceType == .external {
os_log(" 外部音频源不在这里重命名文件", log: log, type: .info)
return true
}
@ -1038,13 +1056,11 @@ public class AzureAsrHelper: NSObject {
return true
}
/**
* 暂停录音
*/
public func pauseRecord() {
if(audioSourceType == .external){
if audioSourceType == .external {
os_log(" 外部音频源不在这里暂停录音", log: log, type: .info)
return
}
@ -1059,7 +1075,7 @@ public class AzureAsrHelper: NSObject {
* 继续录音
*/
public func resumeRecord() {
if(audioSourceType == .external){
if audioSourceType == .external {
os_log(" 外部音频源不在这里继续录音", log: log, type: .info)
return
}
@ -1074,7 +1090,7 @@ public class AzureAsrHelper: NSObject {
* 关闭录音
*/
public func stopRecord(isSave: Bool) {
if(audioSourceType == .external){
if audioSourceType == .external {
os_log(" 外部音频源不在这里关闭录音", log: log, type: .info)
return
}
@ -1082,17 +1098,13 @@ public class AzureAsrHelper: NSObject {
return
}
audioStream.isRecord = false
if (!audioStream.isContinuousRecognitionActive) {
if !audioStream.isContinuousRecognitionActive {
audioStream.stopMicrophoneCapture()
}
audioStream.recordfile?.isPause = false //
audioStream.recordfile?.closeFile(isSave: true) //
}
// MARK: - 回调函数
/**
* 连续识别回调接口
@ -1164,7 +1176,7 @@ private func isNetworkRelatedError(_ errorDetails: String) -> Bool {
let networkErrorKeywords = [
"network", "connection", "timeout", "unreachable",
"网络", "连接", "超时", "不可达",
"ConnectionFailure", "NetworkError", "TimeoutError"
"ConnectionFailure", "NetworkError", "TimeoutError",
]
let lowercaseError = errorDetails.lowercased()
@ -1184,8 +1196,6 @@ extension AzureAsrHelper: NetworkStateMonitor.NetworkStateListener {
}
func onNetworkLost() {
os_log("网络连接丢失", log: log, type: .error)
@ -1216,5 +1226,3 @@ private class AudioDataCallbackProxy: SimpleAudioReceiver.AudioDataCallback {
}
}

Loading…
Cancel
Save