鸿蒙语音识别结果回传 Flutter 时应该返回最终文本还是流式片段
2026/6/14 21:21:00 网站建设 项目流程

适合谁看

  • 正在做鸿蒙语音识别 + Flutter 对接的人

  • 纠结该返回中间结果还是最终结果的人

  • 想理解语音识别回传策略对 AI 体验影响的人

问题背景

鸿蒙 CoreSpeechKit 的SpeechRecognitionEngine在识别过程中会产生多个回调:

回调

触发时机

数据

onStart

引擎开始识别

sessionId, eventMessage

onEvent

识别过程中的事件

eventCode, eventMessage

onResult

识别到文本片段

result.result, result.isLast

onComplete

识别完成

eventMessage

onError

出错

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 Future<String> startListening({String language = 'zh-CN'}) async { final result = await _channel.invokeMethod<String>( 'startListening', {'language': language}, ); return result ?? ''; // 返回的是完整的最终文本 }

协调器拿到最终文本后直接提交给 AI:

// ai_explore_coordinator.dart Future<void> 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 Future<String> startListening() async { return await _channel.invokeMethod<String>('startListening'); } // 改造后:EventChannel(流式返回) static Stream<SpeechFragment> 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: Record<string, Object> = { 'recognitionMode': 0, 'vadBegin': 2000, // 2秒静音开始检测结束 'vadEnd': 3000, // 3秒静音确认结束 'maxAudioDuration': 20000 // 最长20秒 };

VAD 和最终文本设计的配合:

用户说话 → 引擎识别(中间结果,不回传 Flutter) → 用户停顿 2 秒 → VAD 开始检测 → 用户继续说话 → VAD 重置 → 用户停顿 3 秒 → VAD 确认结束 → 引擎返回 isLast: true → 回传最终文本给 Flutter

VAD 自动处理了"用户什么时候说完"的判断,所以 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 推荐质量,因为:

  1. AI 拿到的是完整句子— 不是碎片,能正确理解意图

  2. 识别质量更高— 引擎在最终结果时会做全局优化,中间片段可能有错误

  3. 没有误触发— 不会因为一个碎片就触发 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.dart

Flutter 侧语音识别通道

app/lib/core/ai/ai_explore_coordinator.dart

协调器,接收最终文本并提交 AI

app/lib/features/ai_assistant/screens/ai_assistant_screen.dart

AI 页面,语音按钮交互

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 助手场景来说,最终文本已经足够好用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询