Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
liwei1dao 8 months ago
parent
commit
e0d6abd444
  1. 2
      lib/modules/translation/controllers/translation_controller.dart
  2. 30
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  3. 7
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  4. 76
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt
  5. 229
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift
  6. 10
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift
  7. 47
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/XunFeiAsrHelper.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;

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

@ -127,10 +127,24 @@ class AzureAsrHelper(private val context: Context) {
this.supportedLanguages = supportedLanguages
}
Log.i(
tag,
"lxm---ASR initialize: supportedLanguages=${supportedLanguages.joinToString(",")}, audioSourceType=$audioSourceType"
)
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
val onlySupportedByXunfei = supportedLanguages.all {
it == "zh-CN" || it == "en-US" || it == "zh-HK" || it == "zh-TW"
}
val chineseVariantsCount = supportedLanguages
.map { it.trim().lowercase() }
.filter { it == "zh" || it.startsWith("zh-") }
.distinct()
.size
val hasMultipleChineseVariants = chineseVariantsCount >= 2
this.useXunfei = onlySupportedByXunfei && xunfeiConfigReady && !hasMultipleChineseVariants
// 根据支持的语言数量决定是否启用自动语言检测
this.isAutoDetectLanguage = supportedLanguages.size >= 2
@ -140,6 +154,11 @@ class AzureAsrHelper(private val context: Context) {
this.currentLanguage = supportedLanguages[0]
}
Log.i(
tag,
"lxm---ASR initialize derived: isAutoDetectLanguage=$isAutoDetectLanguage, currentLanguage=$currentLanguage, provider=${getAsrProvider()}, xunfeiConfigured=$xunfeiConfigReady"
)
// 创建语音配置
speechConfig = SpeechConfig.fromSubscription(subscriptionKey, region).apply {
if (isAutoDetectLanguage) {
@ -314,7 +333,14 @@ class AzureAsrHelper(private val context: Context) {
}
try {
xunFeiAsrHelper?.start(callback, currentLanguage, isAutoDetectLanguage, isRemoveFirstPunctuation)
Log.d(tag, "startContinuousRecognition: ${audioStream.isContinuousRecognitionActive}")
xunFeiAsrHelper?.start(
callback,
currentLanguage,
supportedLanguages.toList(),
isAutoDetectLanguage,
isRemoveFirstPunctuation
)
this.audioSourceType = audioSourceType
if (!audioStream.isContinuousRecognitionActive) {

7
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -423,6 +423,13 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
val xunfeiAccessKeySecret = call.argument<String>("xunfeiAccessKeySecret") ?: ""
try {
val isXunfeiConfigured = xunfeiAppId.isNotBlank() &&
xunfeiAccessKeyId.isNotBlank() &&
xunfeiAccessKeySecret.isNotBlank()
FileLogger.i(
tag,
"lxm---ASR initialize args: supportedLanguages=${supportedLanguages.joinToString(",")}, useExternalAudio=$useExternalAudio, region=$region, xunfeiConfigured=$isXunfeiConfigured"
)
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {

76
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt

@ -34,6 +34,7 @@ class XunFeiAsrHelper(private val context: Context) {
private var lastIntermediateResult = ""
private var currentLanguage = "zh-CN"
private var supportedLanguages: List<String> = emptyList()
private var isAutoDetectLanguage = false
// 是否第去除一个文字的标点符号
@ -58,7 +59,13 @@ class XunFeiAsrHelper(private val context: Context) {
return appId.isNotBlank() && accessKeyId.isNotBlank() && accessKeySecret.isNotBlank()
}
fun start(callback: AzureAsrHelper.ContinuousRecognizeCallback, language: String, isAutoDetect: Boolean, isRemoveFirstPunctuation: Boolean) {
fun start(
callback: AzureAsrHelper.ContinuousRecognizeCallback,
language: String,
supportedLanguages: List<String>,
isAutoDetect: Boolean,
isRemoveFirstPunctuation: Boolean
) {
try {
webSocket?.cancel()
webSocket = null
@ -70,6 +77,7 @@ class XunFeiAsrHelper(private val context: Context) {
lastIntermediateResult = ""
currentLanguage = language
this.supportedLanguages = supportedLanguages
isAutoDetectLanguage = isAutoDetect
removeFirstPunctuation = isRemoveFirstPunctuation
@ -99,15 +107,7 @@ class XunFeiAsrHelper(private val context: Context) {
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
Log.d(tag, "WebSocket Closed: $code / $reason")
if (currentSessionId.isNotEmpty() && lastIntermediateResult.isNotEmpty()) {
val detectedLanguage = if (isAutoDetectLanguage) {
if (lastIntermediateResult.contains(Regex("[\\u4e00-\\u9fa5]"))) {
"zh-CN"
} else {
"en-US"
}
} else {
currentLanguage
}
val detectedLanguage = selectDetectedLanguage(lastIntermediateResult)
callback.onResult(currentSessionId, lastIntermediateResult, detectedLanguage)
}
callback.onSessionStopped(currentSessionId)
@ -219,15 +219,7 @@ class XunFeiAsrHelper(private val context: Context) {
return
}
val detectedLanguage = if (isAutoDetectLanguage) {
if (resultTextTemp.contains(Regex("[\\u4e00-\\u9fa5]"))) {
"zh-CN"
} else {
"en-US"
}
} else {
currentLanguage
}
val detectedLanguage = selectDetectedLanguage(resultTextTemp)
if (st.optInt("type") == 0) {
callback.onResult(currentSessionId, resultTextTemp, detectedLanguage)
@ -254,6 +246,52 @@ class XunFeiAsrHelper(private val context: Context) {
}
}
private fun normalizeLanguageCode(code: String): String {
val trimmed = code.trim()
if (trimmed.isEmpty()) return trimmed
val parts = trimmed.split("-")
val lang = parts.getOrNull(0)?.lowercase() ?: trimmed.lowercase()
val region = parts.getOrNull(1)?.uppercase()
return if (region.isNullOrEmpty()) lang else "$lang-$region"
}
private fun findSupportedExact(target: String): String? {
val targetNorm = normalizeLanguageCode(target)
return supportedLanguages.firstOrNull { normalizeLanguageCode(it) == targetNorm }
}
private fun findSupportedPrefix(prefix: String): String? {
val p = prefix.lowercase()
return supportedLanguages.firstOrNull { normalizeLanguageCode(it).startsWith(p) }
}
private fun pickPreferredChineseLanguage(): String {
return findSupportedExact("zh-CN")
?: findSupportedExact("zh-HK")
?: findSupportedExact("zh-TW")
?: findSupportedPrefix("zh")
?: currentLanguage
}
private fun pickPreferredEnglishLanguage(): String {
return findSupportedExact("en-US")
?: findSupportedPrefix("en")
?: currentLanguage
}
// 选择检测到的语言
// 如果开启了自动检测语言,根据文本内容判断是否为中文
// 如果是中文,选择中文语言;否则选择英文语言
// 如果未开启自动检测语言,返回当前语言
private fun selectDetectedLanguage(text: String): String {
if (!isAutoDetectLanguage) return currentLanguage
val hasChinese = text.contains(Regex("[\\u4e00-\\u9fa5]"))
return if (hasChinese) pickPreferredChineseLanguage() else pickPreferredEnglishLanguage()
}
private fun getWebSocketUrl(): String {
val baseWsUrl = "wss://office-api-ast-dx.iflyaisol.com/ast/communicate/v1"

229
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,14 +128,42 @@ 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
let onlySupportedByXunfei = supportedLanguages.allSatisfy {
$0 == "zh-CN" || $0 == "en-US" || $0 == "zh-HK" || $0 == "zh-TW"
}
let chineseVariants = Set(
supportedLanguages
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() }
.filter { $0 == "zh" || $0.hasPrefix("zh-") }
)
let hasMultipleChineseVariants = chineseVariants.count >= 2
self.useXunfei =
onlySupportedByXunfei && xunfeiConfigReady && !hasMultipleChineseVariants
os_log(
"ASR helper initialize: supportedLanguages=%{public}@, audioSourceType=%{public}@, xunfeiConfigured=%{public}@, provider=%{public}@",
log: log,
type: .info,
supportedLanguages.joined(separator: ","),
String(describing: audioSourceType),
String(xunfeiConfigReady),
asrProvider()
)
// 根据支持的语言数量决定是否启用自动语言检测
self.isAutoDetectLanguage = supportedLanguages.count >= 2
os_log(
"ASR helper initialize derived: isAutoDetectLanguage=%{public}@, currentLanguage=%{public}@",
log: log,
type: .info,
String(self.isAutoDetectLanguage),
self.currentLanguage
)
// 如果只有一种语言,设置为当前语言
if !isAutoDetectLanguage && !supportedLanguages.isEmpty {
self.currentLanguage = supportedLanguages[0]
@ -152,9 +174,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 +213,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 +220,6 @@ public class AzureAsrHelper: NSObject {
}
/**
* 处理网络连接丢失(带防抖机制)
*/
@ -205,7 +228,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
}
@ -225,7 +250,7 @@ public class AzureAsrHelper: NSObject {
self.lastNetworkLostTime = 0
self.isNetworkRecovering = true
} else {
self.continuousCallback?.onError(sessionid: currsessionId,1000, "网络连接不可用,请检查网络设置")
self.continuousCallback?.onError(sessionid: currsessionId, 1000, "网络连接不可用,请检查网络设置")
os_log("网络仍未恢复,继续检测", log: self.log, type: .debug)
self.networkLostCount = 0
self.stopContinuousRecognition()
@ -233,7 +258,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 预初始化音频组件
* 在初始化阶段就准备好音频流和相关组件,减少首次启动延迟
@ -251,7 +275,6 @@ public class AzureAsrHelper: NSObject {
}
/**
* 异步启动音频录制(带防抖与状态二次校验)
*
@ -273,13 +296,15 @@ public class AzureAsrHelper: NSObject {
audioSourceType: AudioSourceType,
audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = nil,
completion: @escaping (Bool) -> Void
) {
) {
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
// 防抖:距离上次启动过短,取消旧任务
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 +337,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
@ -339,7 +366,7 @@ public class AzureAsrHelper: NSObject {
deadline: .now() + .milliseconds(Int(audioOperationDebounceInterval)),
execute: workItem
)
}
}
/**
* 执行音频启动操作
@ -350,7 +377,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,
@ -423,7 +450,7 @@ public class AzureAsrHelper: NSObject {
audioSourceType: AudioSourceType = .microphone,
audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = nil,
isRemoveFirstPunctuation: Bool = true
) -> Bool {
) -> Bool {
guard speechConfig != nil else {
print("语音服务未初始化")
return false
@ -469,15 +496,23 @@ public class AzureAsrHelper: NSObject {
}
// 启动讯飞识别
xunFeiAsrHelper?.start(callback: callback, language: currentLanguage, isAutoDetect: isAutoDetectLanguage, isRemoveFirstPunctuation: isRemoveFirstPunctuation)
xunFeiAsrHelper?.start(
callback: callback,
language: currentLanguage,
supportedLanguages: supportedLanguages,
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 +541,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
}
@ -535,9 +578,7 @@ public class AzureAsrHelper: NSObject {
audioStream?.isContinuousRecognitionActive = false
return false
}
}
}
/**
* 停止连续语音识别(优化:确保取消未执行的启动任务)
@ -566,7 +607,6 @@ public class AzureAsrHelper: NSObject {
return false
}
do {
// 关键:取消尚未执行的启动任务,防止 stop 后音频又被启动
@ -698,7 +738,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 设置识别器
*/
@ -734,7 +773,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 +806,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 设置麦克风流 - 使用推流方式
* 优化:避免重复初始化已存在的音频流
@ -787,8 +828,6 @@ public class AzureAsrHelper: NSObject {
}
}
/**
* 从语音识别结果中获取检测到的语言
* @param result 语音识别结果
@ -803,8 +842,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 +864,6 @@ public class AzureAsrHelper: NSObject {
return currentLanguage
}
/**
* 动态切换语言(翻译场景中的语言切换)
*
@ -857,7 +896,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 +911,7 @@ public class AzureAsrHelper: NSObject {
self.continuousCallback = callback
// 重设识别器
if (!setupRecognizer()) {
if !setupRecognizer() {
return false
}
guard let recognizer = recognizer else { return false }
@ -888,7 +929,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 +939,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 +950,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 +983,12 @@ public class AzureAsrHelper: NSObject {
os_log("识别取消: %{public}@", log: self.log, type: .error, errorDetails)
callback.onCanceled(sessionid: self.currsessionId,reason, errorDetails)
callback.onCanceled(sessionid: self.currsessionId, reason, errorDetails)
}
return true
}
/**
* 禁用耳机进行拾音
*/
@ -980,10 +1022,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 +1042,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 }
}
@ -1015,9 +1062,9 @@ public class AzureAsrHelper: NSObject {
/**
* 移动文件到新路径
*/
public func moveFile(sourcePath: String, destPath: String)-> Bool {
public func moveFile(sourcePath: String, destPath: String) -> Bool {
if(audioSourceType == .external){
if audioSourceType == .external {
os_log(" 外部音频源不在这里移动文件", log: log, type: .info)
return true
}
@ -1025,12 +1072,11 @@ public class AzureAsrHelper: NSObject {
return true
}
/**
* 重命名指定路径的音频文件
*/
public func renameFile(filePath: String, newName: String)-> Bool {
if(audioSourceType == .external){
public func renameFile(filePath: String, newName: String) -> Bool {
if audioSourceType == .external {
os_log(" 外部音频源不在这里重命名文件", log: log, type: .info)
return true
}
@ -1038,13 +1084,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 +1103,7 @@ public class AzureAsrHelper: NSObject {
* 继续录音
*/
public func resumeRecord() {
if(audioSourceType == .external){
if audioSourceType == .external {
os_log(" 外部音频源不在这里继续录音", log: log, type: .info)
return
}
@ -1074,7 +1118,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 +1126,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: - 回调函数
/**
* 连续识别回调接口
@ -1104,7 +1144,7 @@ public class AzureAsrHelper: NSObject {
* @param text 识别的文本
* @param detectedLanguage 检测到的语言
*/
func onResult(sessionid:String,_ text: String, _ detectedLanguage: String)
func onResult(sessionid: String, _ text: String, _ detectedLanguage: String)
/**
* 识别进行中调用
@ -1112,17 +1152,17 @@ public class AzureAsrHelper: NSObject {
* @param recognizing 正在识别的文本
* @param detectedLanguage 检测到的语言
*/
func onRecognizing(sessionid:String ,_ recognizing: String, _ detectedLanguage: String)
func onRecognizing(sessionid: String, _ recognizing: String, _ detectedLanguage: String)
/**
* 会话开始时调用
*/
func onSessionStarted(sessionid:String)
func onSessionStarted(sessionid: String)
/**
* 会话结束时调用
*/
func onSessionStopped(sessionid:String)
func onSessionStopped(sessionid: String)
/**
* 识别取消时调用
@ -1130,46 +1170,46 @@ public class AzureAsrHelper: NSObject {
* @param reason 取消原因
* @param errorDetails 错误详情
*/
func onCanceled(sessionid:String ,_ reason: String, _ errorDetails: String)
func onCanceled(sessionid: String, _ reason: String, _ errorDetails: String)
/**
* 识别出错时调用
*
* @param error 错误信息
*/
func onError(sessionid:String ,_ code: Int,_ error: String)
func onError(sessionid: String, _ code: Int, _ error: String)
}
/**
* 检查网络状态
* @return 网络是否可用
*/
private func checkNetworkStatus() -> Bool {
private func checkNetworkStatus() -> Bool {
// 直接使用 NetworkStateMonitor 的 checkNetworkStatus 方法
let isAvailable = networkMonitor.checkNetworkStatus()
if !isAvailable {
os_log("网络不可用,无法启动语音识别", log: log, type: .error)
continuousCallback?.onError(sessionid: self.currsessionId,1000,"网络连接不可用,请检查网络设置")
continuousCallback?.onError(sessionid: self.currsessionId, 1000, "网络连接不可用,请检查网络设置")
}
return isAvailable
}
}
/**
/**
* 判断是否为网络相关错误
* @param errorDetails 错误详情
* @return 是否为网络错误
*/
private func isNetworkRelatedError(_ errorDetails: String) -> Bool {
private func isNetworkRelatedError(_ errorDetails: String) -> Bool {
let networkErrorKeywords = [
"network", "connection", "timeout", "unreachable",
"网络", "连接", "超时", "不可达",
"ConnectionFailure", "NetworkError", "TimeoutError"
"ConnectionFailure", "NetworkError", "TimeoutError",
]
let lowercaseError = errorDetails.lowercased()
return networkErrorKeywords.contains { lowercaseError.contains($0.lowercased()) }
}
}
}
// MARK: - NetworkStateListener实现
@ -1184,8 +1224,6 @@ extension AzureAsrHelper: NetworkStateMonitor.NetworkStateListener {
}
func onNetworkLost() {
os_log("网络连接丢失", log: log, type: .error)
@ -1215,6 +1253,3 @@ private class AudioDataCallbackProxy: SimpleAudioReceiver.AudioDataCallback {
xunfeiCallback?(data)
}
}

10
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift

@ -343,6 +343,16 @@ private func sendAstEvent(_ event: [String: Any]) {
let xunfeiAppId = args["xunfeiAppId"] as? String ?? ""
let xunfeiAccessKeyId = args["xunfeiAccessKeyId"] as? String ?? ""
let xunfeiAccessKeySecret = args["xunfeiAccessKeySecret"] as? String ?? ""
let isXunfeiConfigured = !xunfeiAppId.isEmpty && !xunfeiAccessKeyId.isEmpty && !xunfeiAccessKeySecret.isEmpty
os_log(
"ASR initialize args: supportedLanguages=%{public}@, region=%{public}@, xunfeiConfigured=%{public}@",
log: log,
type: .info,
supportedLanguages.joined(separator: ","),
region,
String(isXunfeiConfigured)
)
// 初始化ASR引擎
let success = azureAsrHelper.initialize(

47
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/XunFeiAsrHelper.swift

@ -23,6 +23,7 @@ class XunFeiAsrHelper: NSObject, URLSessionWebSocketDelegate {
private var lastIntermediateResult = ""
private var currentLanguage = "zh-CN"
private var supportedLanguages: [String] = []
private var isAutoDetectLanguage = false
// 是否第去除一个文字的标点符号
@ -47,11 +48,12 @@ class XunFeiAsrHelper: NSObject, URLSessionWebSocketDelegate {
return !appId.isEmpty && !accessKeyId.isEmpty && !accessKeySecret.isEmpty
}
func start(callback: AzureAsrHelper.ContinuousRecognizeCallback, language: String, isAutoDetect: Bool, isRemoveFirstPunctuation: Bool) {
func start(callback: AzureAsrHelper.ContinuousRecognizeCallback, language: String, supportedLanguages: [String], isAutoDetect: Bool, isRemoveFirstPunctuation: Bool) {
stop()
self.callback = callback
self.currentLanguage = language
self.supportedLanguages = supportedLanguages
self.isAutoDetectLanguage = isAutoDetect
self.removeFirstPunctuation = isRemoveFirstPunctuation
self.currentSessionId = UUID().uuidString
@ -268,16 +270,43 @@ class XunFeiAsrHelper: NSObject, URLSessionWebSocketDelegate {
}
}
private func detectLanguage(from text: String) -> String {
if isAutoDetectLanguage {
if text.range(of: "[\\u4e00-\\u9fa5]", options: .regularExpression) != nil {
return "zh-CN"
} else {
return "en-US"
private func normalizeLanguageCode(_ code: String) -> String {
let trimmed = code.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty { return trimmed }
let parts = trimmed.split(separator: "-")
let lang = parts.first?.lowercased() ?? trimmed.lowercased()
let region = parts.count > 1 ? parts[1].uppercased() : ""
return region.isEmpty ? lang : "\(lang)-\(region)"
}
} else {
return currentLanguage
private func findSupportedExact(_ target: String) -> String? {
let targetNorm = normalizeLanguageCode(target)
return supportedLanguages.first { normalizeLanguageCode($0) == targetNorm }
}
private func findSupportedPrefix(_ prefix: String) -> String? {
let p = prefix.lowercased()
return supportedLanguages.first { normalizeLanguageCode($0).hasPrefix(p) }
}
private func pickPreferredChineseLanguage() -> String {
return findSupportedExact("zh-CN")
?? findSupportedExact("zh-HK")
?? findSupportedExact("zh-TW")
?? findSupportedPrefix("zh")
?? currentLanguage
}
private func pickPreferredEnglishLanguage() -> String {
return findSupportedExact("en-US")
?? findSupportedPrefix("en")
?? currentLanguage
}
private func detectLanguage(from text: String) -> String {
guard isAutoDetectLanguage else { return currentLanguage }
let hasChinese = text.range(of: "[\\u4e00-\\u9fa5]", options: .regularExpression) != nil
return hasChinese ? pickPreferredChineseLanguage() : pickPreferredEnglishLanguage()
}
private func getWebSocketUrl() -> String {

Loading…
Cancel
Save