适合谁看正在做鸿蒙语音识别 Flutter 对接的人纠结该返回中间结果还是最终结果的人想理解语音识别回传策略对 AI 体验影响的人问题背景鸿蒙 CoreSpeechKit 的SpeechRecognitionEngine在识别过程中会产生多个回调回调触发时机数据onStart引擎开始识别sessionId, eventMessageonEvent识别过程中的事件eventCode, eventMessageonResult识别到文本片段result.result, result.isLastonComplete识别完成eventMessageonError出错errorCode, errorMessage关键点在于onResult回调中的result.isLast字段isLast false这是中间结果文本可能还会变比如我想吃 → 我想吃鸡 → 我想吃鸡蛋isLast true这是最终结果文本不会再变Flutter 侧该消费哪一种这是一个需要根据场景权衡的设计选择。项目中的真实场景食界探味当前选择了只返回最终文本。鸿蒙侧的处理逻辑// SpeechRecognitionPlugin.ets onResult: (sessionId, result) { console.info(TAG, onResult: ${JSON.stringify(result)}); if (result.isLast this.pendingResult) { this.pendingResult.success(result.result); // 只在 isLast 时回传 this.pendingResult null; this.shutdownEngine(); } }Flutter 侧的接收方式// speech_recognition_channel.dart static FutureString startListening({String language zh-CN}) async { final result await _channel.invokeMethodString( startListening, {language: language}, ); return result ?? ; // 返回的是完整的最终文本 }协调器拿到最终文本后直接提交给 AI// ai_explore_coordinator.dart Futurevoid startVoiceInput() async { state state.copyWith(status: AiSessionStatus.listening); final text await SpeechRecognitionChannel.startListening(); if (text.isNotEmpty) { await submitQuery(text); // 最终文本直接作为 AI 输入 } }整个链路是鸿蒙识别 → 等待最终结果 → 一次性回传 Flutter → 直接提交 AI。核心实现先说结论在 AI 助手场景下返回最终文本比流式片段更合适。因为 AI 需要的是完整的语义而不是识别过程中的碎片。一、为什么食界探味选择了最终文本当前设计选择最终文本的原因有 3 个1. AI 需要完整语义用户说想吃鸡蛋做的清淡一点AI 需要理解的是完整句子的含义。如果返回流式片段想 → 想吃 → 想吃鸡 → 想吃鸡蛋 → 想吃鸡蛋做的 → 想吃鸡蛋做的清淡一点每一片段的语义都不完整。AI 拿到想吃时根本不知道用户想吃什么。只有拿到最终文本想吃鸡蛋做的清淡一点AI 才能正确提取意图。2. 避免中间状态干扰如果返回流式片段协调器需要处理每个片段都更新 UI 状态片段之间文本会变化想吃鸡 → 想吃鸡蛋需要判断什么时候片段稳定了和 AI 的流式输出可能产生冲突当前设计完全避免了这些问题识别过程中页面只显示正在聆听...识别完成后才显示最终文本并提交 AI。3. 简化状态管理// 当前设计只有两种状态 // 1. listening识别中→ 显示正在聆听... // 2. 拿到最终文本 → 直接 submitQuery() // 如果用流式片段需要更多状态 // 1. listening识别中 // 2. partialText中间文本不断变化 // 3. finalText最终文本 // 4. 需要判断 partialText 什么时候稳定最终文本设计让状态管理更简单也更不容易出 bug。二、鸿蒙侧的引擎生命周期管理当前设计还有一个好处识别完成后立即 shutdown 引擎。onResult: (sessionId, result) { if (result.isLast this.pendingResult) { this.pendingResult.success(result.result); this.pendingResult null; this.shutdownEngine(); // ← 立即释放鸿蒙 ASR 引擎 } }如果用流式片段引擎需要在整个识别过程中保持存活直到用户手动停止或 VAD 检测到结束。这会增加鸿蒙端的资源占用。当前设计中引擎只在识别期间存活用户按住语音按钮 → createEngine() → startListening() → 用户说话... → onResult(isLast: true) → shutdownEngine() ← 立即释放在鸿蒙设备上语音识别引擎是比较重的资源。及时释放能减少内存占用和电量消耗。三、流式片段在什么场景下才有价值虽然当前场景不适合流式片段但有些场景确实需要场景为什么需要流式片段实时字幕/会议记录用户需要看到正在识别的内容语音输入框实时预览用户边说边看确认识别是否正确长段落口述用户说很长一段话需要实时反馈多人对话转写需要实时区分不同说话人在这些场景中用户需要看到正在识别什么所以流式片段是必要的。但在 AI 助手场景中用户不需要看到中间过程——他只需要知道系统在听和识别完了。中间的想吃鸡 → 想吃鸡蛋 → 想吃鸡蛋做的变化对用户没有价值。四、如果要改成流式片段需要改什么假设以后需要支持流式识别比如做实时字幕需要改 3 层鸿蒙侧在每个onResult回调中都回传片段// 改造前只在 isLast 时回传 onResult: (sessionId, result) { if (result.isLast this.pendingResult) { this.pendingResult.success(result.result); } } // 改造后每次 onResult 都回传 onResult: (sessionId, result) { // 通过 EventChannel 或多次 invokeMethod 回传片段 this.channel?.invokeMethod(onPartialResult, { text: result.result, isLast: result.isLast, }); if (result.isLast) { this.shutdownEngine(); } }Flutter 侧从 MethodChannel 改为 EventChannel// 改造前MethodChannel单次返回 static FutureString startListening() async { return await _channel.invokeMethodString(startListening); } // 改造后EventChannel流式返回 static StreamSpeechFragment listen() { return _eventChannel.receiveBroadcastStream().map((event) { return SpeechFragment( text: event[text], isLast: event[isLast], ); }); }协调器侧需要处理流式片段// 改造后需要处理每个片段 SpeechRecognitionChannel.listen().listen((fragment) { if (fragment.isLast) { submitQuery(fragment.text); } else { // 更新 UI 显示中间文本 state state.copyWith(partialText: fragment.text); } });这会显著增加复杂度。所以当前选择最终文本是一个务实的决定。五、VAD语音端点检测如何配合最终文本设计鸿蒙 ASR 引擎的 VAD 参数const extraParam: Recordstring, Object { recognitionMode: 0, vadBegin: 2000, // 2秒静音开始检测结束 vadEnd: 3000, // 3秒静音确认结束 maxAudioDuration: 20000 // 最长20秒 };VAD 和最终文本设计的配合用户说话 → 引擎识别中间结果不回传 Flutter → 用户停顿 2 秒 → VAD 开始检测 → 用户继续说话 → VAD 重置 → 用户停顿 3 秒 → VAD 确认结束 → 引擎返回 isLast: true → 回传最终文本给 FlutterVAD 自动处理了用户什么时候说完的判断所以 Flutter 侧不需要手动调stopListening()。用户说完话后鸿蒙引擎自动结束识别并返回最终结果。六、手动停止 vs 自动停止的设计当前支持两种停止方式1. VAD 自动停止— 用户说完话后 3 秒静音引擎自动结束2. 用户手动停止— 松开语音按钮时调用stopListening()private handleStopListening(result: MethodResult): void { if (this.asrEngine) { this.asrEngine.finish(this.sessionId); // 手动结束识别 } result.success(null); }两种方式都会触发onResult(isLast: true)最终文本都会回传给 Flutter。在 AI 助手场景中VAD 自动停止是主要方式——用户说完话后自动识别完成不需要手动操作。手动停止只是备选用于用户提前松开按钮的情况。七、最终文本设计对 AI 推荐质量的影响最终文本设计间接提升了 AI 推荐质量因为AI 拿到的是完整句子— 不是碎片能正确理解意图识别质量更高— 引擎在最终结果时会做全局优化中间片段可能有错误没有误触发— 不会因为一个碎片就触发 AI 调用如果用流式片段可能出现片段1: 想吃 → AI 不知道想吃什么 片段2: 想吃鸡 → AI 可能理解为想吃鸡肉 片段3: 想吃鸡蛋做的 → AI 重新理解为鸡蛋料理每次片段变化都可能触发不同的工具调用导致混乱。最终文本避免了这个问题。关键代码位置文件作用app/ohos/entry/src/main/ets/plugins/SpeechRecognitionPlugin.ets鸿蒙 ASR 插件isLast 判断app/lib/core/platform/speech_recognition_channel.dartFlutter 侧语音识别通道app/lib/core/ai/ai_explore_coordinator.dart协调器接收最终文本并提交 AIapp/lib/features/ai_assistant/screens/ai_assistant_screen.dartAI 页面语音按钮交互app/lib/features/ai_assistant/widgets/ai_input_bar.dart输入栏语音按钮 UI最终文本 vs 流式片段对比维度最终文本当前流式片段AI 理解质量✅ 拿到完整语义⚠️ 中间片段语义不完整状态管理复杂度✅ 简单listening → final⚠️ 复杂listening → partial → final鸿蒙引擎生命周期✅ 识别完立即释放⚠️ 需要保持存活更久用户反馈⚠️ 只有正在聆听...✅ 能看到实时识别文本适用场景AI 助手、推荐、搜索实时字幕、会议记录、口述实现复杂度✅ 低MethodChannel⚠️ 高EventChannel资源占用✅ 低⚠️ 高引擎存活时间长常见坑中间片段直接提交 AI— 语义不完整AI 可能误解意图每个片段都更新 UI— 文本频繁变化用户看着眼花没有处理 isLast— 引擎不会自动结束需要手动 finish没有 shutdown 引擎— 鸿蒙 ASR 引擎一直占用内存没有 VAD 配置— 引擎不知道用户什么时候说完需要手动停止没有处理 onComplete— 识别完成但没有结果时Flutter 会一直等待没有错误处理— 麦克风权限被拒绝、引擎创建失败时页面卡死可复用模板最终文本模式推荐用于 AI 助手// 鸿蒙侧 onResult: (sessionId, result) { if (result.isLast this.pendingResult) { this.pendingResult.success(result.result); this.pendingResult null; this.shutdownEngine(); } }// Flutter 侧 final text await SpeechRecognitionChannel.startListening(); if (text.isNotEmpty) { await submitQuery(text); // 直接提交 AI }流式片段模式用于实时字幕等场景// 鸿蒙侧通过 EventChannel 回传 onResult: (sessionId, result) { this.eventChannel?.send({ text: result.result, isLast: result.isLast, }); if (result.isLast) { this.shutdownEngine(); } }// Flutter 侧消费流式片段 SpeechRecognitionChannel.listen().listen((fragment) { if (fragment.isLast) { submitQuery(fragment.text); } else { updatePartialText(fragment.text); // 更新 UI } });VAD 参数配置模板const vadParams { recognitionMode: 0, // 在线识别 vadBegin: 2000, // 2秒静音开始检测 vadEnd: 3000, // 3秒静音确认结束 maxAudioDuration: 20000 // 最长20秒 };语音识别状态机模板idle → listening用户按住语音按钮 → [鸿蒙 ASR 识别中] → onResult(isLast: true) → 回传最终文本 → submitQuery(text) → parsing → searching → responding → idle idle → listening → [用户松开按钮] → stopListening() → onResult(isLast: true) → 回传最终文本 → submitQuery(text) idle → listening → [识别出错] → onError() → state error → 用户点击重试本篇总结在鸿蒙 Flutter 下做语音识别回传选择最终文本还是流式片段取决于场景AI 助手场景→ 最终文本更合适。AI 需要完整语义中间片段没有价值而且最终文本设计更简单、资源占用更低、识别质量更高实时字幕/会议记录场景→ 流式片段更合适。用户需要看到正在识别的内容中间反馈是必要的食界探味当前选择最终文本是一个务实的决定。鸿蒙 ASR 引擎在result.isLast时才回传文本给 Flutter中间片段只用于日志。这让整个链路更简单用户说话 → 鸿蒙识别 → 等待最终结果 → 一次性提交 AI。如果以后需要支持流式识别需要从 MethodChannel 改为 EventChannel协调器也需要处理每个片段。但对当前 AI 助手场景来说最终文本已经足够好用。