前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
flutter_speech默认配置的最大录音时长是60秒,识别模式是短语音(recognitionMode=0)。对于语音搜索、语音指令这类场景完全够用。但如果你要做语音笔记、会议记录、实时字幕这类需要长时间识别的功能,就需要突破这个限制。
今天我们来探讨如何基于flutter_speech的架构实现持续语音识别。
一、maxAudioDuration 参数扩展
1.1 当前限制
const extraParam: Record<string, Object> = {
"recognitionMode": 0, // 短语音模式
"vadBegin": 2000,
"vadEnd": 3000,
"maxAudioDuration": 60000 // 60秒
};
短语音模式下,VAD检测到静音就会自动停止。即使把maxAudioDuration设到很大,用户一停顿就结束了。
1.2 长语音模式
要实现持续识别,需要切换到长语音模式:
const extraParam: Record<string, Object> = {
"recognitionMode": 1, // 长语音模式
"vadBegin": 5000, // 5秒等待开口
"vadEnd": 5000, // 5秒静音才停
"maxAudioDuration": 300000 // 5分钟
};
| 参数 | 短语音模式 | 长语音模式 | 说明 |
|---|---|---|---|
| recognitionMode | 0 | 1 | 长语音不会因静音自动停止 |
| vadBegin | 2000 | 5000 | 给用户更多思考时间 |
| vadEnd | 3000 | 5000 | 允许更长的停顿 |
| maxAudioDuration | 60000 | 300000 | 5分钟 |
1.3 通过Dart层传递模式参数
当前flutter_speech的Dart API不支持传递识别模式。可以扩展:
// 扩展后的Dart API(建议改进)
Future listen({bool continuous = false}) =>
_channel.invokeMethod("speech.listen", {
'continuous': continuous,
});
// 原生端接收参数
case "speech.listen":
const args = call.args as Record<string, Object> | null;
const continuous = args?.['continuous'] as boolean ?? false;
this.startListening(result, continuous);
break;
二、长时间语音识别的会话管理
2.1 单次长会话
最简单的方案——一个会话持续到用户手动停止:
startListening(recognitionMode=1, maxAudioDuration=300000)
│
├── onResult("你好", isLast=false)
├── onResult("你好今天", isLast=false)
├── (用户停顿5秒)
├── onResult("你好今天开会", isLast=false)
├── ...持续识别...
│
└── 用户点击Stop → finish(sessionId) → onResult(最终结果, isLast=true)
优点:实现简单。
缺点:maxAudioDuration有上限,超长会话可能被系统中断。
2.2 分段续接方案
更健壮的方案——将长会话拆分为多个短会话,自动续接:
第1段:startListening → 识别 → onComplete → 保存结果
↓ 自动
第2段:startListening → 识别 → onComplete → 拼接结果
↓ 自动
第3段:startListening → 识别 → onComplete → 拼接结果
↓
... 直到用户手动停止
2.3 分段续接的实现
// 原生端实现(扩展方案)
private continuousMode: boolean = false;
private accumulatedText: string = '';
private setupContinuousListener(): void {
if (!this.asrEngine) return;
const channel = this.channel;
const plugin = this;
this.asrEngine.setListener({
onResult(sessionId, result) {
const fullText = plugin.accumulatedText + result.result;
channel?.invokeMethod('speech.onSpeech', fullText);
if (result.isLast) {
plugin.accumulatedText = fullText;
if (plugin.continuousMode) {
// 自动开始下一段
plugin.startNextSegment();
} else {
plugin.isListening = false;
channel?.invokeMethod('speech.onRecognitionComplete', fullText);
}
}
},
onComplete(sessionId, eventMessage) {
if (plugin.continuousMode && plugin.isListening) {
plugin.startNextSegment();
}
},
onError(sessionId, errorCode, errorMessage) {
console.error(TAG, `onError in continuous mode: ${errorCode}`);
if (plugin.continuousMode && errorCode === 5) {
// 无语音输入,继续等待
plugin.startNextSegment();
} else {
plugin.isListening = false;
channel?.invokeMethod('speech.onError', errorCode);
}
},
// ... onStart, onEvent
});
}
private startNextSegment(): void {
try {
const params = this.buildStartParams(true);
this.asrEngine?.startListening(params);
} catch (e) {
console.error(TAG, `startNextSegment error: ${JSON.stringify(e)}`);
}
}
2.4 分段间的无缝衔接
分段续接的最大挑战是衔接处的文本连贯性。两段之间可能会有重复或遗漏:
第1段结果:"今天的会议主要讨论"
第2段结果:"讨论三个议题"
拼接结果:"今天的会议主要讨论讨论三个议题" ← "讨论"重复了
解决方案:
- 简单拼接:直接拼接,接受少量重复(最简单)
- 重叠检测:检测两段结尾和开头的重叠部分,去重
- 标点分隔:在每段结尾加标点符号分隔
// 简单的重叠去重
private mergeSegments(prev: string, next: string): string {
// 检查prev的结尾是否和next的开头重叠
const maxOverlap = Math.min(prev.length, next.length, 10);
for (let i = maxOverlap; i > 0; i--) {
if (prev.endsWith(next.substring(0, i))) {
return prev + next.substring(i);
}
}
return prev + next;
}
三、自动重连与断点续识策略
3.1 网络中断的处理
在线识别模式下,网络中断会导致识别失败。对于长时间识别,需要自动重连:
onError(sessionId, errorCode, errorMessage) {
if (plugin.continuousMode) {
if (errorCode === 1 || errorCode === 2) {
// 网络错误,延迟重试
console.warn(TAG, 'network error, retrying in 3 seconds...');
setTimeout(() => {
if (plugin.continuousMode && plugin.isListening) {
plugin.startNextSegment();
}
}, 3000);
return;
}
}
// 其他错误正常处理
plugin.isListening = false;
channel?.invokeMethod('speech.onError', errorCode);
}
3.2 重连策略
| 策略 | 实现 | 适用场景 |
|---|---|---|
| 立即重试 | 错误后立即startListening | 临时网络抖动 |
| 延迟重试 | 等待3秒后重试 | 网络短暂中断 |
| 指数退避 | 1s→2s→4s→8s… | 网络持续不稳定 |
| 放弃 | 超过N次重试后停止 | 网络完全不可用 |
3.3 断点续识
网络恢复后,已经说过的话不会重新识别。需要在UI上提示用户:
// Dart层提示
void onNetworkRecovery() {
_showSnackBar('网络已恢复,请继续说话');
// 之前的识别结果已保存在accumulatedText中
}
四、识别结果拼接与文本累积
4.1 文本累积策略
// Dart层的文本累积
class ContinuousRecognitionState {
final List<String> segments = []; // 每段的最终结果
String currentSegment = ''; // 当前段的实时结果
String get fullText {
final completed = segments.join('');
return completed + currentSegment;
}
void onPartialResult(String text) {
currentSegment = text;
}
void onSegmentComplete(String text) {
segments.add(text);
currentSegment = '';
}
}
4.2 UI显示
// 显示累积的完整文本
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Text(
recognitionState.fullText,
style: TextStyle(fontSize: 16),
),
);
}
4.3 文本格式化
长文本识别需要考虑格式化:
String formatRecognitionText(String raw) {
// 1. 添加标点(如果识别结果没有标点)
// 2. 分段落
// 3. 首字母大写(英文)
return raw;
}
📌 Core Speech Kit的中文识别通常会自带标点,所以格式化的工作量不大。但分段续接时,段与段之间的标点可能需要手动处理。
五、电量与性能消耗的权衡
5.1 长时间识别的资源消耗
| 资源 | 短语音(10秒) | 长语音(5分钟) | 长语音(1小时) |
|---|---|---|---|
| CPU | 低 | 中 | 高 |
| 内存 | ~20MB | ~25MB | ~30MB+ |
| 网络流量 | ~320KB | ~9.6MB | ~115MB |
| 电量 | 忽略不计 | 可感知 | 显著 |
5.2 优化建议
降低CPU消耗:
- 使用长语音模式而不是反复创建短会话
- 避免在onResult回调中做复杂计算
降低网络消耗:
- 考虑离线识别模式(准确率会降低)
- 弱网环境下降级到离线
降低电量消耗:
- 在用户不说话时暂停识别(但这会增加延迟)
- 提供"省电模式"选项
5.3 用户提示
长时间识别应该在UI上提示用户资源消耗:
// 显示录音时长和预估消耗
Text('已录制 ${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}'),
Text('预估流量:${(duration.inSeconds * 32 / 1024).toStringAsFixed(1)} MB'),
5.4 后台识别的限制
OpenHarmony对后台音频采集有限制。如果App进入后台,麦克风可能被系统回收:
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
// App进入后台
if (_isListening) {
_speech.stop(); // 保存当前结果
_showNotification('语音识别已暂停');
}
} else if (state == AppLifecycleState.resumed) {
// App回到前台
if (_wasContinuousListening) {
_speech.listen(); // 恢复识别
}
}
}
六、实现方案对比
| 方案 | 复杂度 | 最大时长 | 文本连贯性 | 推荐场景 |
|---|---|---|---|---|
| 单次长会话 | 低 | 5分钟 | 好 | 语音笔记 |
| 分段续接 | 中 | 无限制 | 中等 | 会议记录 |
| 分段+重叠去重 | 高 | 无限制 | 好 | 实时字幕 |
对于大多数场景,单次长会话(recognitionMode=1 + 较大的maxAudioDuration)就够了。只有需要超过5分钟的场景才需要分段续接。
总结
本文探讨了基于flutter_speech实现持续语音识别的方案:
- 长语音模式:recognitionMode=1,不会因静音自动停止
- 分段续接:多个短会话自动衔接,突破时长限制
- 自动重连:网络中断后延迟重试,保证识别连续性
- 文本累积:分段结果拼接,处理重叠和格式化
- 资源权衡:长时间识别需要关注CPU、内存、网络、电量消耗
下一篇我们讲语音识别结果的后处理——标点符号、文本格式化、纠错等话题。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:

转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_37695089/article/details/158265089



