From ba73d6d780c661d40ed19f2f752f1b1f6d15ac8b Mon Sep 17 00:00:00 2001 From: "guangfei.zhao" Date: Mon, 29 Sep 2025 14:11:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=94=A4=E9=86=92=E6=B5=81=E7=A8=8B=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=EF=BC=8C=E4=BF=AE=E5=A4=8D=E9=87=8D=E5=A4=8D=E5=94=A4?= =?UTF-8?q?=E9=86=92=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/manager.dart | 12 ++++---- lib/widgets/chat_popup.dart | 6 +++- .../flutter_vosk_wakeword/AudioRecorder.kt | 30 +++++++++++++++---- .../flutter_vosk_wakeword/WakewordDetector.kt | 2 +- .../flutter_vosk_wakeword/WakewordService.kt | 11 +------ 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/lib/manager.dart b/lib/manager.dart index 0540ba4..0525c14 100644 --- a/lib/manager.dart +++ b/lib/manager.dart @@ -89,7 +89,7 @@ class AIChatAssistantManager { Future startVoskWakeword() async { FlutterVoskWakeword.instance.initialize( - wakeWords: ['你好众众', '你好', '众众'], // 唤醒词列表 + wakeWords: ['你好众众', '你好', '众众', '测试', '哈喽', '唤醒'], // 唤醒词列表 ); debugPrint('Starting Vosk Wakeword detection...'); @@ -99,12 +99,10 @@ class AIChatAssistantManager { debugPrint('Vosk Wakeword detected: ${event.text}'); _wakeWordDetected = true; // 通知所有监听者 - // _wakeWordController.add(null); - - // 可选:一段时间后自动重置状态 - Future.delayed(const Duration(seconds: 2), () { - _wakeWordDetected = false; - }); + _wakeWordController.add(null); + // 监听到之后就停止,这个时候已经弹出了对话框 + // 对话框消失就可以继续监听 recognitionStream 的订阅不会断开 + FlutterVoskWakeword.instance.stop(); }); } diff --git a/lib/widgets/chat_popup.dart b/lib/widgets/chat_popup.dart index bfd6082..8f9cff8 100644 --- a/lib/widgets/chat_popup.dart +++ b/lib/widgets/chat_popup.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:ui'; import 'package:ai_chat_assistant/manager.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_vosk_wakeword/flutter_vosk_wakeword.dart'; import 'package:provider/provider.dart'; import '../pages/full_screen.dart'; import '../services/message_service.dart'; @@ -44,6 +45,7 @@ class _ChatPopupState extends State with SingleTickerProviderStateMix // 订阅唤醒词事件 _wakeWordSubscription = AIChatAssistantManager.instance.onWakeWordDetected.listen((_) async { + await FlutterVoskWakeword.instance.stop(); // 在这里处理唤醒事件,例如显示聊天窗口 debugPrint("唤醒词被检测到,执行UI操作!"); final messageService = MessageService.instance; @@ -59,8 +61,9 @@ class _ChatPopupState extends State with SingleTickerProviderStateMix super.dispose(); } - void _insertOverlay() { + Future _insertOverlay() async { if (_isShowingPopup) return; + await FlutterVoskWakeword.instance.stop(); // 停止唤醒词监听,避免误触发 setState(() { _isShowingPopup = true; }); @@ -69,6 +72,7 @@ class _ChatPopupState extends State with SingleTickerProviderStateMix void _removeOverlay() { if (!_isShowingPopup) return; + FlutterVoskWakeword.instance.start(); // 重新开始监听唤醒词 _partScreenAnimationController.reverse().then((_) { if (mounted) { setState(() { diff --git a/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/AudioRecorder.kt b/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/AudioRecorder.kt index 2c5c3ae..ee11870 100644 --- a/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/AudioRecorder.kt +++ b/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/AudioRecorder.kt @@ -51,13 +51,31 @@ class AudioRecorder(private val recognizer: Recognizer, while (isRecording()) { val nread = audioRecord?.read(buffer, 0, buffer.size) ?: 0 if (nread > 0) { - Log.i(TAG, "Read $nread bytes from AudioRecord") + // 判断是否有完整的识别结果 val ac = recognizer.acceptWaveForm(buffer, nread) - Log.i(TAG, "Recognizer acceptWaveForm returned: $ac") - if (ac) { - listener.onResult(recognizer.result) - } else { - listener.onPartialResult(recognizer.partialResult) + val partialResultText = recognizer.partialResult + val resultText = recognizer.result + val pRes1 = VoskRecognizer.parsePartialResult(partialResultText) + val pRes2 = VoskRecognizer.parseFinalResult(partialResultText) + val finalResult = VoskRecognizer.parseFinalResult(resultText) + val partialResult = pRes1.ifEmpty { pRes2.ifEmpty { "" } } +// Log.i(TAG, "Partial: $partialResultText, Parsed: $partialResult; Final: $resultText, Parsed: $finalResult; Accepted: $ac") + if (partialResult.isNotBlank() || finalResult.isNotBlank()) { + Log.i( + TAG, + "Partial result: ${partialResult}, Final result: $finalResult" + ) + // 去除 [unk] 的影响 + if (partialResult == "[unk]" || finalResult == "[unk]") { + continue + } + if (ac) { + listener.onResult(resultText) + recognizer.reset() + } else { + listener.onPartialResult(partialResultText) +// recognizer.reset() + } } } } diff --git a/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/WakewordDetector.kt b/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/WakewordDetector.kt index ba25ec4..a9cb3c7 100644 --- a/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/WakewordDetector.kt +++ b/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/WakewordDetector.kt @@ -21,7 +21,7 @@ class WakewordDetector(private val wakeWords: ArrayList) { val jsonArray = JSONArray() wakeWords.forEach { jsonArray.put(it) } // 添加 [unk] 允许识别唤醒词之外的其他词语 -// jsonArray.put("[unk]") + jsonArray.put("[unk]") return jsonArray.toString() } } \ No newline at end of file diff --git a/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/WakewordService.kt b/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/WakewordService.kt index f67d3d6..b3f7731 100644 --- a/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/WakewordService.kt +++ b/packages/flutter_vosk_wakeword/android/src/main/kotlin/com/tsystems/flutter_vosk_wakeword/WakewordService.kt @@ -16,7 +16,6 @@ import org.vosk.Recognizer import org.vosk.android.RecognitionListener import org.vosk.android.StorageService import java.io.IOException -import kotlin.collections.contentToString class WakewordService: Service(), RecognitionListener { @@ -231,18 +230,10 @@ class WakewordService: Service(), RecognitionListener { private fun broadcastWake(word: String, now: Long, source: String) { lastWakeWord = word lastDetectTs = now + recognizer?.reset() Log.i(TAG, "Wake word detected($source): $word") EventReceiver.broadcastWakeWord(this, word) // 重置识别器状态,为下一次唤醒做准备 - recognizer?.reset() - // 可选:重建 recognizer 减少同一发音尾部抖动: - // try { - // val grammar = wakeWordDetector?.getVoskGrammar() - // recognizer?.close() - // recognizer = Recognizer(model, 16000.0f, grammar) - // } catch (e: Exception) { - // Log.w(TAG, "Recognizer reset failed: ${e.message}") - // } } override fun onError(e: Exception?) {