欢迎光临
我们一直在努力

Flutter三方库适配OpenHarmony【flutter_speech】— 持续语音识别与长录音

前言

欢迎加入开源鸿蒙跨平台社区: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、内存、网络、电量消耗
  • 下一篇我们讲语音识别结果的后处理——标点符号、文本格式化、纠错等话题。

    如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


    相关资源:

    • Core Speech Kit长语音识别
    • VAD语音活动检测
    • 指数退避算法
    • Flutter App生命周期
    • flutter_speech OpenHarmony源码
    • 开源鸿蒙跨平台社区

    请添加图片描述

    赞(0)
    未经允许不得转载:171主机测评 » Flutter三方库适配OpenHarmony【flutter_speech】— 持续语音识别与长录音
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址