Flutter三方库适配OpenHarmony【flutter_speech】— 语音识别启动与参数配置

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

引擎创建好了,监听器也设置好了,现在终于可以开始识别 了。startListening方法是用户按下"开始"按钮后真正触发的动作,也是整个语音识别流程中参数最多的一个环节。

Core Speech Kit的startListening需要传入一个StartParams对象,里面包含了音频参数、扩展参数、会话ID等配置。这些参数直接影响识别的质量和行为------采样率设错了识别不出来,VAD参数设错了要么截断用户的话,要么等半天才返回结果。

我在调试这些参数的时候,反复试了十几种组合才找到比较平衡的配置。今天把这些经验分享出来。

💡 本文对应源码FlutterSpeechPlugin.etsstartListening方法,第180-222行。

一、StartParams 参数结构详解

1.1 整体结构

typescript 复制代码
interface StartParams {
  sessionId: string;                    // 会话ID
  audioInfo: AudioInfo;                 // 音频参数
  extraParams: Record<string, Object>;  // 扩展参数
}

三个字段,每个都有讲究:

字段 类型 必填 说明
sessionId string 标识本次识别会话
audioInfo AudioInfo 音频采集参数
extraParams Record<string, Object> 识别行为控制参数

1.2 flutter_speech中的完整构建

typescript 复制代码
private startListening(result: MethodResult): void {
  // ... 前置检查 ...

  const audioParam: speechRecognizer.AudioInfo = {
    audioType: 'pcm',
    sampleRate: 16000,
    soundChannel: 1,
    sampleBit: 16
  };

  const extraParam: Record<string, Object> = {
    "recognitionMode": 0,
    "vadBegin": 2000,
    "vadEnd": 3000,
    "maxAudioDuration": 60000
  };

  const recognizerParams: speechRecognizer.StartParams = {
    sessionId: this.sessionId,
    audioInfo: audioParam,
    extraParams: extraParam
  };

  this.asrEngine.startListening(recognizerParams);
  result.success(true);
}

二、AudioInfo 音频参数:PCM 格式、采样率、声道、位深

2.1 AudioInfo 结构

typescript 复制代码
interface AudioInfo {
  audioType: string;    // 音频格式
  sampleRate: number;   // 采样率(Hz)
  soundChannel: number; // 声道数
  sampleBit: number;    // 位深(bit)
}

2.2 各参数详解

audioType - 音频格式
typescript 复制代码
audioType: 'pcm'
说明 flutter_speech的选择
'pcm' 原始PCM音频 ✅ 使用这个

目前Core Speech Kit只支持PCM格式。PCM(Pulse Code Modulation,脉冲编码调制)是最基础的音频格式,没有压缩,数据量大但延迟低。

📌 为什么只支持PCM:语音识别引擎需要原始音频数据来做特征提取。如果传入压缩格式(如MP3、AAC),引擎还需要先解码,增加延迟。PCM直接就能用,效率最高。

sampleRate - 采样率
typescript 复制代码
sampleRate: 16000
采样率 说明 适用场景
8000 Hz 电话质量 低带宽场景
16000 Hz 语音识别标准 推荐
44100 Hz CD质量 音乐录制
48000 Hz 专业音频 视频制作

16kHz是语音识别领域的事实标准。大多数语音识别模型都是用16kHz的数据训练的,使用其他采样率可能导致识别准确率下降。

💡 我的测试结果:我试过用8000Hz,识别率明显下降,尤其是声母相近的字容易混淆。用44100Hz倒是没问题,但数据量大了将近3倍,浪费带宽和内存。16000Hz是最佳平衡点。

soundChannel - 声道数
typescript 复制代码
soundChannel: 1
声道数 说明 适用场景
1 单声道 语音识别(推荐)
2 双声道(立体声) 音乐播放

语音识别只需要单声道。人说话的声音是单一音源,双声道不会带来额外的信息量,只会增加数据量。

sampleBit - 位深
typescript 复制代码
sampleBit: 16
位深 动态范围 说明
8 bit 48 dB 质量较低
16 bit 96 dB 标准质量(推荐)
24 bit 144 dB 专业录音
32 bit 192 dB 极高精度

16bit是语音识别的标准位深,能提供足够的动态范围来区分语音中的细微差异。

2.3 音频参数的数据量计算

复制代码
数据量 = 采样率 × 位深 × 声道数 × 时长

flutter_speech的配置:
16000 Hz × 16 bit × 1 channel = 256,000 bits/s = 32 KB/s

录制60秒的数据量:
32 KB/s × 60s = 1,920 KB ≈ 1.9 MB

每秒32KB的数据量对于在线传输来说完全可以接受。

2.4 参数错误的后果

错误配置 后果 表现
audioType不是'pcm' 引擎无法处理 startListening抛异常
sampleRate设为8000 识别率下降 识别结果不准确
soundChannel设为2 可能不兼容 引擎可能报错
sampleBit设为8 音质太差 识别率严重下降

三、extraParams 扩展参数:recognitionMode、VAD 配置

3.1 extraParams 结构

typescript 复制代码
const extraParam: Record<string, Object> = {
  "recognitionMode": 0,
  "vadBegin": 2000,
  "vadEnd": 3000,
  "maxAudioDuration": 60000
};

3.2 recognitionMode - 识别模式

typescript 复制代码
"recognitionMode": 0
模式 说明 适用场景
0 短语音模式 识别短句,VAD自动停止 语音指令、搜索
1 长语音模式 持续识别,不自动停止 会议记录、听写

flutter_speech使用短语音模式(0),适合"说一句话就停"的交互场景。

两种模式的行为差异:

复制代码
短语音模式 (recognitionMode=0):
用户说话 → 停顿 → VAD检测到静音 → 自动停止 → 返回结果

长语音模式 (recognitionMode=1):
用户说话 → 停顿 → 继续等待 → 用户继续说 → ... → 手动调用finish停止

🎯 选择建议:如果你的App是"按住说话"的交互模式,用短语音模式。如果是"持续听写"的模式(比如语音笔记),用长语音模式。

3.3 vadBegin - VAD开始超时

typescript 复制代码
"vadBegin": 2000  // 毫秒

含义 :开始监听后,如果在vadBegin毫秒内没有检测到语音,就认为用户没有说话,自动停止识别。

复制代码
startListening
    │
    ├── 0ms: 开始等待语音
    │
    ├── 500ms: 还没检测到语音...
    │
    ├── 1000ms: 还没检测到语音...
    │
    ├── 1500ms: 还没检测到语音...
    │
    └── 2000ms: vadBegin超时!→ onError(无语音输入)
vadBegin值 效果 适用场景
1000ms 等待1秒 快速响应场景
2000ms 等待2秒 平衡(推荐)
5000ms 等待5秒 用户可能需要思考
10000ms 等待10秒 极端宽松

💡 调优经验:2秒是比较合理的值。太短的话,用户还没来得及说话就超时了;太长的话,用户不说话时要等很久才能收到反馈。

3.4 vadEnd - VAD结束超时

typescript 复制代码
"vadEnd": 3000  // 毫秒

含义 :检测到语音后,如果连续vadEnd毫秒没有新的语音输入(静音),就认为用户说完了,自动停止识别。

复制代码
用户说话
    │
    ├── "今天天气" ← 正在说
    │
    ├── 停顿 0.5秒 ← 还在vadEnd范围内,继续等待
    │
    ├── "怎么样" ← 继续说
    │
    ├── 停顿 1秒 ← 还在范围内
    │
    ├── 停顿 2秒 ← 还在范围内
    │
    └── 停顿 3秒 ← vadEnd超时!→ 认为说完了 → 返回最终结果
vadEnd值 效果 适用场景
1000ms 1秒静音就停 快速指令
2000ms 2秒静音就停 短句识别
3000ms 3秒静音就停 平衡(推荐)
5000ms 5秒静音就停 长句/思考型

🤔 vadEnd的取舍:设太短,用户说话中间停顿一下就被截断了(比如"我想...嗯...去北京"中间的停顿)。设太长,用户说完后要等好几秒才能看到最终结果。3秒是我测试下来的最佳平衡点。

3.5 vadBegin和vadEnd的配合

复制代码
vadBegin=2000, vadEnd=3000 的完整时序:

t=0s:     startListening → 开始等待语音
t=0-2s:   等待用户开口(vadBegin倒计时)
t=0.8s:   用户开始说话 → vadBegin停止计时
t=0.8-3s: 用户持续说话
t=3s:     用户停止说话 → vadEnd开始计时
t=3-6s:   等待用户继续说(vadEnd倒计时)
t=6s:     vadEnd超时 → 返回最终结果

四、sessionId 会话管理机制

4.1 sessionId的作用

typescript 复制代码
private sessionId: string = '10000';

sessionId是识别会话的唯一标识,用于:

  1. 区分不同的识别会话:虽然flutter_speech只有一个会话
  2. 在回调中关联请求:每个回调都会带上sessionId
  3. 停止/取消指定会话finish(sessionId)cancel(sessionId)需要指定

4.2 flutter_speech的sessionId策略

flutter_speech使用了固定的sessionId'10000'),因为:

  • 同一时间只有一个识别会话
  • 不需要区分多个并发会话
  • 简化了代码逻辑
typescript 复制代码
// 所有操作都用同一个sessionId
this.asrEngine.startListening({ sessionId: this.sessionId, ... });
this.asrEngine.finish(this.sessionId);
this.asrEngine.cancel(this.sessionId);

4.3 如果需要动态sessionId

如果你的应用需要管理多个识别会话(flutter_speech不需要),可以用时间戳生成唯一ID:

typescript 复制代码
// 动态生成sessionId(flutter_speech未使用)
private generateSessionId(): string {
  return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

4.4 sessionId与回调的关联

每个监听器回调都会带上sessionId参数:

typescript 复制代码
onStart(sessionId: string, eventMessage: string): void { ... }
onResult(sessionId: string, result: SpeechRecognitionResult): void { ... }
onError(sessionId: string, errorCode: number, errorMessage: string): void { ... }

flutter_speech在回调中只用sessionId做日志记录,不做逻辑判断:

typescript 复制代码
onStart(sessionId: string, eventMessage: string): void {
  console.info(TAG, `onStart: sessionId=${sessionId}`);  // 仅日志
  channel?.invokeMethod('speech.onRecognitionStarted', null);
},

五、maxAudioDuration 最大录音时长控制

5.1 参数含义

typescript 复制代码
"maxAudioDuration": 60000  // 毫秒 = 60秒

这个参数限制了单次识别的最大录音时长。超过这个时长,引擎会自动停止识别并返回结果。

5.2 为什么需要限制

  1. 防止资源浪费:无限制的录音会持续占用麦克风和网络
  2. 用户体验:超长的识别通常意味着用户忘记停止了
  3. 系统限制:Core Speech Kit可能有内部的时长限制

5.3 不同场景的建议值

场景 建议值 说明
语音搜索 15000 (15秒) 搜索词通常很短
语音指令 10000 (10秒) 指令更短
通用场景 60000 (60秒) flutter_speech的选择
语音笔记 300000 (5分钟) 长文本听写
会议记录 无限制 需要特殊处理

flutter_speech选择了60秒,对于大多数语音识别场景来说足够了。

5.4 超时后的行为

复制代码
t=0s:     startListening
t=0-60s:  正常识别
t=60s:    maxAudioDuration超时
          → onResult(最终结果, isLast=true)
          → onComplete

超时后引擎会自动停止,和用户主动调用stop的效果类似。

六、startListening 方法的完整实现

6.1 完整源码(带注释)

typescript 复制代码
private startListening(result: MethodResult): void {
  // ========== 前置检查 ==========
  if (!this.asrEngine) {
    result.error('ERROR_ENGINE_NOT_INITIALIZED',
      'Speech engine not initialized. Call activate first.', null);
    return;
  }

  try {
    // ========== 防重入处理 ==========
    if (this.isListening) {
      this.asrEngine.cancel(this.sessionId);
      this.isListening = false;
    }

    // ========== 重置状态 ==========
    this.lastTranscription = '';
    this.isListening = true;

    // ========== 构建音频参数 ==========
    const audioParam: speechRecognizer.AudioInfo = {
      audioType: 'pcm',
      sampleRate: 16000,
      soundChannel: 1,
      sampleBit: 16
    };

    // ========== 构建扩展参数 ==========
    const extraParam: Record<string, Object> = {
      "recognitionMode": 0,
      "vadBegin": 2000,
      "vadEnd": 3000,
      "maxAudioDuration": 60000
    };

    // ========== 构建启动参数 ==========
    const recognizerParams: speechRecognizer.StartParams = {
      sessionId: this.sessionId,
      audioInfo: audioParam,
      extraParams: extraParam
    };

    // ========== 开始识别 ==========
    this.asrEngine.startListening(recognizerParams);
    result.success(true);

  } catch (e) {
    console.error(TAG, `startListening error: ${JSON.stringify(e)}`);
    this.isListening = false;
    result.error('ERROR_SPEECH_LISTEN',
      `Failed to start listening: ${JSON.stringify(e)}`, null);
  }
}

6.2 流程图

复制代码
startListening(result)
    │
    ├── asrEngine == null?
    │   └── 是 → error('ERROR_ENGINE_NOT_INITIALIZED') → return
    │
    ├── isListening == true? (防重入)
    │   └── 是 → cancel当前会话 → isListening = false
    │
    ├── 重置状态
    │   ├── lastTranscription = ''
    │   └── isListening = true
    │
    ├── 构建参数
    │   ├── audioParam: PCM/16kHz/单声道/16bit
    │   ├── extraParam: 短语音/VAD 2s+3s/最长60s
    │   └── recognizerParams: sessionId + audio + extra
    │
    ├── asrEngine.startListening(recognizerParams)
    │   └── 失败 → catch → isListening = false → error
    │
    └── result.success(true)

6.3 前置检查:引擎未初始化

typescript 复制代码
if (!this.asrEngine) {
  result.error('ERROR_ENGINE_NOT_INITIALIZED',
    'Speech engine not initialized. Call activate first.', null);
  return;
}

如果用户没有先调用activate就直接调用listen,引擎还没创建,需要返回明确的错误提示。

6.4 防重入处理

typescript 复制代码
if (this.isListening) {
  this.asrEngine.cancel(this.sessionId);
  this.isListening = false;
}

如果上一次识别还没结束,用户又点了"开始",需要先取消上一次再开始新的。不做这个处理的话,可能会出现两个识别会话冲突的问题。

🤦 踩坑经历:我一开始没做防重入,测试时快速连续点击"开始"按钮,引擎就报错了。加了这个逻辑后就稳定了。

6.5 状态重置

typescript 复制代码
this.lastTranscription = '';
this.isListening = true;

每次开始新的识别前,清空上次的识别结果,并标记为"正在监听"状态。

七、与Android startListening的对比

7.1 代码对比

Android

java 复制代码
private void startListening(MethodChannel.Result result) {
    Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
        RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
    intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
    intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, locale);

    speechRecognizer.startListening(intent);
    result.success(true);
}

OpenHarmony

typescript 复制代码
private startListening(result: MethodResult): void {
    const recognizerParams: speechRecognizer.StartParams = {
      sessionId: this.sessionId,
      audioInfo: { audioType: 'pcm', sampleRate: 16000, soundChannel: 1, sampleBit: 16 },
      extraParams: { "recognitionMode": 0, "vadBegin": 2000, "vadEnd": 3000, "maxAudioDuration": 60000 }
    };

    this.asrEngine.startListening(recognizerParams);
    result.success(true);
}

7.2 参数对比

功能 Android (Intent) OpenHarmony (StartParams)
语言 EXTRA_LANGUAGE 创建引擎时指定
部分结果 EXTRA_PARTIAL_RESULTS=true 默认支持
最大结果数 EXTRA_MAX_RESULTS 不支持
音频格式 系统自动 audioInfo手动指定
VAD控制 系统默认 vadBegin/vadEnd可配
最大时长 系统默认 maxAudioDuration可配
识别模式 LANGUAGE_MODEL_* recognitionMode

📌 OpenHarmony的参数控制更精细:Android的很多参数是系统默认的,开发者无法控制。OpenHarmony允许开发者精确配置音频参数和VAD参数,灵活性更高。

八、参数调优建议

8.1 不同场景的推荐配置

语音搜索场景

typescript 复制代码
const extraParam = {
  "recognitionMode": 0,      // 短语音
  "vadBegin": 1500,           // 1.5秒等待开口
  "vadEnd": 2000,             // 2秒静音就停
  "maxAudioDuration": 15000   // 最长15秒
};

语音指令场景

typescript 复制代码
const extraParam = {
  "recognitionMode": 0,      // 短语音
  "vadBegin": 2000,           // 2秒等待
  "vadEnd": 1500,             // 1.5秒静音就停(指令通常很短)
  "maxAudioDuration": 10000   // 最长10秒
};

听写/笔记场景

typescript 复制代码
const extraParam = {
  "recognitionMode": 1,       // 长语音
  "vadBegin": 5000,           // 5秒等待(用户可能在思考)
  "vadEnd": 5000,             // 5秒静音才停(允许长停顿)
  "maxAudioDuration": 300000  // 最长5分钟
};

8.2 调优原则

  1. vadBegin:根据用户从点击按钮到开始说话的平均时间来设置。通常1.5-3秒
  2. vadEnd:根据用户说话中停顿的平均时长来设置。短句1.5-2秒,长句3-5秒
  3. maxAudioDuration:根据业务场景的最大合理时长来设置。宁可设大一点
  4. recognitionMode:一句话的交互用0,持续听写用1

总结

本文详细讲解了flutter_speech语音识别启动的参数配置:

  1. AudioInfo:PCM格式、16kHz采样率、单声道、16bit位深------语音识别的标准配置
  2. recognitionMode:0=短语音(自动停止),1=长语音(手动停止)
  3. VAD参数:vadBegin控制等待开口时间,vadEnd控制静音停止时间
  4. maxAudioDuration:最大录音时长,防止无限录音
  5. sessionId:会话标识,flutter_speech使用固定值'10000'
  6. 防重入:重复调用时先cancel旧会话再开始新会话

下一篇我们讲语音识别的停止与取消 ------stopcancel两个方法的语义区别和实现细节。

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


相关资源:

相关推荐
软件算法开发1 小时前
基于火烈鸟搜索算法的LSTM网络模型(FSA-LSTM)的一维时间序列预测matlab仿真
人工智能·rnn·matlab·lstm·一维时间序列预测·火烈鸟搜索算法·fsa-lstm
空空潍7 小时前
鸿蒙HarmonyOS入门-音乐app开发项目(含源码)
华为·harmonyos
永霖光电_UVLED10 小时前
NUBURU启动Q1阶段,实现40套高功率蓝光激光系统的量产
大数据·人工智能
RFG201210 小时前
20、详解Dubbo框架:消费方如何动态获取服务提供方地址?【微服务架构入门】
java·人工智能·后端·微服务·云原生·架构·dubbo
紫微AI10 小时前
适用于代理Agents的语言
人工智能·agents·新语言
CCPC不拿奖不改名10 小时前
虚拟机基础:在VMware WorkStation上安装Linux为容器化部署打基础
linux·运维·服务器·人工智能·milvus·知识库搭建·容器化部署
这是个栗子10 小时前
AI辅助编程工具(六) - CodeGeeX
人工智能·ai·codegeex
vortesnail11 小时前
超详细的云服务部署 OpenClaw 并接入飞书全流程,别再趟坑了
人工智能·程序员·openai
紫微AI11 小时前
Anthropic Claude Code 工程博客精读:构建可靠长时运行AI代理的有效框架实践
人工智能