Browse Source

修复安卓网络异常导致冒上一句的问题。修复网络异常导致一直有。。。,取消不了识别问题

weicu
fdp 1 year ago
parent
commit
cfe765f5df
  1. 31
      lib/modules/agent/controllers/agent_controller.dart
  2. 4
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  3. 221
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  4. 31
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  5. 21
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt

31
lib/modules/agent/controllers/agent_controller.dart

@ -577,7 +577,38 @@ class AgentController extends GetxController {
// messages.refresh(); // messages.refresh();
} }
break; break;
//识别取消(网络波动会触发)
case AgentServiceEventType.recognitionCanceled:
// 查找是否有正在识别中的消息
final index =
messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
if (currentText.value.isNotEmpty) {
if (index >= 0) {
// 将识别中的消息转换为最终消息
messages[index] = Message(
isUser: true,
text: currentText.value,
isRecognizing: false,
timestamp: messages[index].timestamp,
);
// messages.refresh();
} else {
// 如果没有找到识别中的消息,添加新消息
messages.add(Message(
isUser: true, text: currentText.value, isRecognizing: false));
}
isProcessing.value = true;
// 标记为新的AI回复
_isNewAssistantResponse = true;
// 使用reverse:true时通常不需要手动滚动
} else if (index >= 0) {
// 如果识别结果为空,移除临时的识别消息
messages.removeAt(index);
// messages.refresh();
}
break;
case AgentServiceEventType.ttsStarted: case AgentServiceEventType.ttsStarted:
isSpeaking.value = true; isSpeaking.value = true;
print('TTS开始播放'); print('TTS开始播放');

4
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -588,7 +588,7 @@ object AgentService : CoroutineScope {
_hasSpeechDetected.set(true) _hasSpeechDetected.set(true)
Log.d(TAG, "ai识别中重置定时器")
restartIdleCheck() restartIdleCheck()
@ -672,7 +672,6 @@ object AgentService : CoroutineScope {
"details" to errorDetails "details" to errorDetails
) )
) )
recognizeCallback()
} }
override fun onError(code: Int, error: String) { override fun onError(code: Int, error: String) {
@ -686,7 +685,6 @@ object AgentService : CoroutineScope {
"message" to error "message" to error
) )
) )
recognizeCallback()
} }
}) })
return true return true

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

@ -50,8 +50,17 @@ class AzureAsrHelper(private val context: Context) {
// 网络状态监听 // 网络状态监听
private var networkMonitor: NetworkStateMonitor = NetworkStateMonitor(context) private var networkMonitor: NetworkStateMonitor = NetworkStateMonitor(context)
private var isNetworkRecovering = false private var isNetworkRecovering = false
// 网络丢失计数器
private var networkLostCount = 0
// 最大网络丢失次数(3秒检测)
private val maxNetworkLostCount = 3
// 防抖机制相关变量
private var lastNetworkLostTime = 0L
private val networkLostDebounceInterval = 3000L // 2秒防抖间隔
/** /**
* 音频来源类型 * 音频来源类型
@ -221,61 +230,6 @@ class AzureAsrHelper(private val context: Context) {
} }
/**
* 执行一次性语音识别
*
* @param callback 识别结果回调
*/
fun recognizeOnce(
callback: RecognizeCallback,
audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE
) {
if (speechConfig == null) {
callback.onError(1002,"语音服务未初始化")
return
}
this.audioSourceType = audioSourceType
// 确保不在连续识别中
if (isContinuousRecognitionActive) {
stopContinuousRecognition()
}
try {
// 启动音频处理(若已启动则跳过)
if (!audioStream!!.isWriting()) {
audioStream!!.startAudioRecord(
when (audioSourceType) {
AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE
AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL
},
null
)
} else {
Log.d(tag, "音频已启动,跳过重复启动")
}
// 执行同步识别
val result = recognizer?.recognizeOnceAsync()?.get()
// 停止音频处理
stopAudioProcessing()
if (result != null && result.reason == ResultReason.RecognizedSpeech) {
val detectedLanguage = AutoDetectSourceLanguageResult.fromResult(result)?.language
?: supportedLanguages[0]
callback.onResult(result.text, detectedLanguage)
} else {
callback.onError(1004,"未能识别语音")
}
} catch (e: Exception) {
stopAudioProcessing()
callback.onError(1003,"识别异常: ${e.message}")
}
}
/** /**
* 开始连续语音识别 * 开始连续语音识别
* @param audioSourceType 音频源类型 * @param audioSourceType 音频源类型
@ -300,11 +254,14 @@ class AzureAsrHelper(private val context: Context) {
return true return true
} }
if (!isRecognizerValid()) { if (!isRecognizerValid()) {
Log.w(tag, "识别器已失效,正在重新创建...") Log.d(tag, "重新启动连续识别")
if (!setupRecognizer()) { val callback = continuousCallback
Log.e(tag, "重新创建识别器失败") if (callback == null) {
Log.e(tag, "无法重新启动连续识别:回调为空")
continuousCallback?.onError(1001,"回调为空")
return false return false
} }
setupEventListeners(callback)
} }
this.audioSourceType = audioSourceType this.audioSourceType = audioSourceType
@ -330,7 +287,7 @@ class AzureAsrHelper(private val context: Context) {
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "开始连续识别失败: ${e.message}") Log.e(tag, "开始连续识别失败: ${e.message}")
audioStream!!.stopMicrophoneCapture() stopContinuousRecognition()
isContinuousRecognitionActive = false isContinuousRecognitionActive = false
return false return false
} }
@ -399,10 +356,8 @@ class AzureAsrHelper(private val context: Context) {
// 直接在当前线程调用回调 // 直接在当前线程调用回调
Log.d(tag, "会话结束事件") Log.d(tag, "会话结束事件")
if (audioSourceType == AudioSourceType.EXTERNAL) { if (audioSourceType == AudioSourceType.EXTERNAL) {
callback.onSessionStopped() callback.onSessionStopped()
isContinuousRecognitionActive = false isContinuousRecognitionActive = false
// stopAudioProcessing()
} }
} }
) )
@ -414,20 +369,7 @@ class AzureAsrHelper(private val context: Context) {
val reason = event.reason.toString() val reason = event.reason.toString()
Log.d(tag, "识别被取消: reason=$reason, details=$errorDetails") Log.d(tag, "识别被取消: reason=$reason, details=$errorDetails")
callback.onCanceled(reason,errorDetails)
// 检查是否是网络相关错误
if (isNetworkRelatedError(reason, errorDetails)) {
Log.d(tag, "检测到网络相关错误,停止音频处理")
stopAudioProcessingImmediately()
// 如果不是正在恢复网络,通知上层网络错误
if (!isNetworkRecovering) {
callback.onError(1000,"启动连续识别失败,网络连接已断开")
}
} else {
// 非网络错误,正常处理
callback.onCanceled(reason, errorDetails)
}
} }
) )
} catch (e: Exception) { } catch (e: Exception) {
@ -448,41 +390,28 @@ class AzureAsrHelper(private val context: Context) {
Log.e(tag, "语音服务未初始化") Log.e(tag, "语音服务未初始化")
return false return false
} }
// if (!isContinuousRecognitionActive) {
// return true
// }
try { try {
isContinuousRecognitionActive = false
if (audioSourceType == AudioSourceType.EXTERNAL) {
//pushAudioData(ByteArray(0))
}
Log.d(tag, "停止连续语音识别: ") Log.d(tag, "停止连续语音识别: ")
// 停止音频处理
audioStream!!.stopMicrophoneCapture()
// 直接停止连续识别(SDK内部已是异步操作) // 直接停止连续识别(SDK内部已是异步操作)
recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS) recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS)
// 停止音频处理 recognizer?.close()
audioStream!!.stopMicrophoneCapture() recognizer = null
// 会话结束事件会设置isContinuousRecognitionActive = false isContinuousRecognitionActive = false
return true return true
} catch (e: Exception) { } catch (e: Exception) {
// 强制重置状态 // 强制重置状态
isContinuousRecognitionActive = false isContinuousRecognitionActive = false
Log.e(tag, "停止连续识别失败: ${e.message}") Log.e(tag, "停止连续识别失败: ${e.message}")
recognizer?.stopContinuousRecognitionAsync() // 停止音频处理
// 停止音频处理
audioStream!!.stopMicrophoneCapture() audioStream!!.stopMicrophoneCapture()
// 尝试强制关闭识别器 recognizer?.stopContinuousRecognitionAsync()
// try {
// recognizer?.close() recognizer?.close()
// recognizer = null recognizer = null
// } catch (ex: Exception) { isContinuousRecognitionActive = false
// Log.e(tag, "关闭识别器失败: ${ex.message}")
// }
return false return false
} }
} }
@ -503,12 +432,10 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.stopContinuousRecognitionAsync() recognizer?.stopContinuousRecognitionAsync()
isContinuousRecognitionActive = false isContinuousRecognitionActive = false
} }
// recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS)
// 停止音频处理 // 停止音频处理
stopAudioProcessing() stopAudioProcessing()
// 清理网络监听 // 清理网络监听
networkMonitor.dispose() clearNetworkDetection()
// 停止录音 // 停止录音
if (audioStream!!.recordfile != null) { if (audioStream!!.recordfile != null) {
audioStream!!.recordfile!!.closeFile(true) audioStream!!.recordfile!!.closeFile(true)
@ -521,24 +448,15 @@ class AzureAsrHelper(private val context: Context) {
speechConfig?.close() speechConfig?.close()
speechConfig = null speechConfig = null
// 释放音频配置 // 释放音频配置
audioConfig?.close() audioConfig?.close()
audioConfig = null audioConfig = null
// 确保状态被重置 // 确保状态被重置
isContinuousRecognitionActive = false isContinuousRecognitionActive = false
//microphoneStream = null
audioConfig = null
recognizer = null
speechConfig = null
} catch (e: Exception) { } catch (e: Exception) {
// 确保状态被重置 // 确保状态被重置
isContinuousRecognitionActive = false isContinuousRecognitionActive = false
//microphoneStream = null
audioConfig = null audioConfig = null
recognizer = null recognizer = null
speechConfig = null speechConfig = null
@ -743,23 +661,48 @@ class AzureAsrHelper(private val context: Context) {
* 处理网络连接可用 * 处理网络连接可用
*/ */
private fun handleNetworkAvailable() { private fun handleNetworkAvailable() {
if (!isNetworkRecovering) { // 网络恢复时重置计数器
networkLostCount = 0
lastNetworkLostTime = 0L
if (!isNetworkRecovering) {
isNetworkRecovering = true isNetworkRecovering = true
Log.d(tag, "网络恢复,准备重新启动识别") Log.d(tag, "网络恢复,准备重新启动识别")
// 如果之前在进行连续识别,重新启动
Handler(Looper.getMainLooper()).postDelayed({
restartContinuousRecognition()
}, 1000) // 延迟1秒确保网络稳定
} }
} }
/** /**
* 处理网络连接丢失 * 处理网络连接丢失(带防抖机制)
*/ */
private fun handleNetworkLost() { private fun handleNetworkLost() {
Log.d(tag, "网络断开,停止音频处理") val currentTime = System.currentTimeMillis()
// 防抖机制:如果距离上次触发时间小于防抖间隔,则忽略本次触发
if (currentTime - lastNetworkLostTime < networkLostDebounceInterval) {
Log.d(tag, "网络丢失事件被防抖过滤,距离上次触发仅${currentTime - lastNetworkLostTime}ms")
return
}
// 更新最后触发时间
lastNetworkLostTime = currentTime
//这里启动一个3秒定时器,3秒后检查网络是否恢复
// 启动3秒定时器,3秒后检查网络是否恢复
Handler(Looper.getMainLooper()).postDelayed({
Log.e(tag, "网络不可用")
val isNetworkAvailable = checkNetworkStatus()
if (isNetworkAvailable) {
// 如果网络恢复,重置计数器和防抖时间
Log.d(tag, "网络已恢复,重置计数器")
lastNetworkLostTime = 0L
isNetworkRecovering = true
} else {
continuousCallback?.onError(1000,"网络连接不可用,请检查网络设置")
Log.d(tag,"网络仍未恢复,继续检测")
networkLostCount = 0
stopAudioProcessingImmediately() stopAudioProcessingImmediately()
}
}, 3000L) // 3秒延时
} }
/** /**
@ -774,30 +717,16 @@ class AzureAsrHelper(private val context: Context) {
Log.e(tag, "立即停止音频处理失败: ${e.message}") Log.e(tag, "立即停止音频处理失败: ${e.message}")
} }
} }
/**
/** * 清理网络检测相关资源
* 重新启动连续识别(网络恢复时使用) */
*/ private fun clearNetworkDetection() {
private fun restartContinuousRecognition() {
try { networkLostCount = 0
Log.d(tag, "重新启动连续识别") lastNetworkLostTime = 0L
// 检查continuousCallback是否为空
val callback = continuousCallback
if (callback == null) {
Log.e(tag, "无法重新启动连续识别:回调为空")
return
}
setupEventListeners(callback)
isNetworkRecovering = false
Log.d(tag, "连续识别重新启动成功")
} catch (e: Exception) {
Log.e(tag, "重新启动连续识别失败: ${e.message}")
isNetworkRecovering = false
}
}
} }
}

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

@ -365,37 +365,6 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
} }
} }
"recognizeOnce" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
// 直接使用同步方式进行一次性识别
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
// 直接返回结果,不发送事件
result.success(
mapOf(
"text" to text,
"detectedLanguage" to detectedLanguage
)
)
}
override fun onError(code: Int, error: String) {
result.error(code.toString(), "错误码: $code, 错误信息: $error", null)
}
}, audioSourceType)
} catch (e: Exception) {
result.error("RECOGNITION_ERROR", "识别过程异常: ${e.message}", null)
}
}
"disableBluetoothAudio" -> { "disableBluetoothAudio" -> {
azureAsrHelper.audioStream?.disableBluetoothAudio(); azureAsrHelper.audioStream?.disableBluetoothAudio();

21
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt

@ -85,8 +85,7 @@ class SimpleAudioReceiver(private val context: Context) {
* 初始化音频录制 * 初始化音频录制
*/ */
fun initAudioRecord() { fun initAudioRecord() {
val format = getOptimalAudioFormat()
pushAudioStream = AudioInputStream.createPushStream(format)
isRunning.set(true) isRunning.set(true)
Log.d(TAG, "initAudioRecord: ${isRunning}") Log.d(TAG, "initAudioRecord: ${isRunning}")
startWriteThread() // 再启动数据读取线程 startWriteThread() // 再启动数据读取线程
@ -143,8 +142,9 @@ class SimpleAudioReceiver(private val context: Context) {
if(audioDataCallback!=null){ if(audioDataCallback!=null){
audioDataCallback?.onAudio(finalData) audioDataCallback?.onAudio(finalData)
} }
if(pushAudioStream!=null){
pushAudioStream?.write(finalData) pushAudioStream?.write(finalData)
}
if(recordfile!=null){ if(recordfile!=null){
recordfile?.saveAudioDataToWav(finalData) recordfile?.saveAudioDataToWav(finalData)
} }
@ -184,6 +184,8 @@ class SimpleAudioReceiver(private val context: Context) {
* @param callback 音频数据回调,可为空 * @param callback 音频数据回调,可为空
*/ */
fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallback?) { fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallback?) {
val format = getOptimalAudioFormat()
pushAudioStream = AudioInputStream.createPushStream(format)
this.audioSourceType = audioSourceType this.audioSourceType = audioSourceType
this.audioDataCallback = callback this.audioDataCallback = callback
writeQueue.clear() writeQueue.clear()
@ -238,6 +240,8 @@ class SimpleAudioReceiver(private val context: Context) {
try { try {
// 首先设置通话音频模式 // 首先设置通话音频模式
//setupCommunicationAudioMode() //setupCommunicationAudioMode()
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100)
val sampleRate = supportedSampleRates.firstOrNull { rate -> val sampleRate = supportedSampleRates.firstOrNull { rate ->
val bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat) val bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat)
@ -274,7 +278,7 @@ class SimpleAudioReceiver(private val context: Context) {
} }
audioRecord?.startRecording() audioRecord?.startRecording()
Log.d("TAG", "通话模式录音开始,采样率: $sampleRate Hz") Log.d("TAG", "通话模式录音开始,采样率: $sampleRate Hz")
} catch (e: Exception) { } catch (e: Exception) {
@ -312,8 +316,7 @@ class SimpleAudioReceiver(private val context: Context) {
//setupCommunicationAudioMode() //setupCommunicationAudioMode()
// 停止录音 // 停止录音
audioRecord?.startRecording() audioRecord?.startRecording()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "继续录音失败: ${e.message}") Log.e(TAG, "继续录音失败: ${e.message}")
} }
@ -328,7 +331,11 @@ class SimpleAudioReceiver(private val context: Context) {
// 停止录音 // 停止录音
audioRecord?.stop() audioRecord?.stop()
if(pushAudioStream != null){
pushAudioStream?.close()
pushAudioStream = null
}
// 恢复音频模式 这里会导致关闭麦克风卡住待优化 // 恢复音频模式 这里会导致关闭麦克风卡住待优化
//restoreCommunicationAudioMode() //restoreCommunicationAudioMode()

Loading…
Cancel
Save