news 2026/9/14 2:46:45

sherpa-onnx Dart 实战:Silero VAD 端点检测 + 非流式 ASR 完整识别方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
sherpa-onnx Dart 实战:Silero VAD 端点检测 + 非流式 ASR 完整识别方案

sherpa-onnx Dart 实战:Silero VAD 端点检测 + 非流式 ASR 完整识别方案

【免费下载链接】sherpa-onnxSpeech-to-text, text-to-speech, speaker diarization, speech enhancement, source separation, and VAD using next-gen Kaldi with onnxruntime without Internet connection. Support embedded systems, Android, iOS, HarmonyOS, Raspberry Pi, RISC-V, RK NPU, Axera NPU, Ascend NPU, x86_64 servers, websocket server/client, support 12 programming languages项目地址: https://gitcode.com/GitHub_Trending/sh/sherpa-onnx

本文围绕 sherpa-onnx 的 Dart API 示例dart-api-examples/vad-with-non-streaming-asr/展开,讲解如何用 Silero VAD 模型对整段录音做端点检测(Voice Activity Detection),再将检测出的每段语音分别送入 Paraformer、SenseVoice、Whisper、Zipformer Transducer 等非流式(offline)识别模型转写成文本。读完后你将掌握:VoiceActivityDetectorOfflineRecognizer的标准协作流程、Silero VAD 各参数的含义与默认值、8 种模型类型各自的命令行参数与运行脚本用法,以及示例中逐窗喂入音频(windowSize切片)、flush()收尾、显式释放资源等关键细节。

一、这个示例解决什么问题

对于一段较长的离线录音(会议录音、采访音频等),直接整段丢给非流式 ASR 模型有两个问题:一是多数模型对输入时长有限制,二是无法得到“每句话出现在哪个时间段”的时间戳。sherpa-onnx 给出的标准解法是先 VAD 分句、再逐句识别

  1. Silero VAD 按固定窗口(默认 512 个采样点)逐帧检测语音活动,输出一个个带起始时间戳的语音片段;
  2. 每个片段交给一个OfflineRecognizer流创建、喂入、解码,得到文本;
  3. 起始时间 -- 结束时间 : 文本的格式打印带时间戳的结果。

目录 dart-api-examples/vad-with-non-streaming-asr/README.md 中列出的示例及对应脚本如下(bin/下实际还有 README 未收录的moonshine.dartzipformer-ctc.dartdolphin-ctc.dart等,下文一并覆盖):

Dart 程序使用的模型运行脚本
bin/paraformer.dartParaformerrun-paraformer.sh
bin/sense-voice.dartSenseVoice CTCrun-sense-voice-zh.sh、run-sense-voice-en.sh
bin/sense-voice-2.dartSenseVoice(另一实现入口)run-sense-voice-zh-2.sh
bin/telespeech-ctc.dartTeleSpeech CTCrun-telespeech-ctc.sh
bin/whisper.dartWhisperrun-whisper.sh
bin/zipformer-transducer.dartZipformer Transducerrun-zipformer-transducer.sh
bin/zipformer-ctc.dartZipformer CTCrun-zipformer-ctc.sh
bin/moonshine.dartMoonshinerun-moonshine.sh
bin/dolphin-ctc.dartDolphin CTCrun-dolphin-ctc.sh

二、环境依赖与前置条件

由 pubspec.yaml 可见示例的依赖声明:

environment: sdk: ">=3.2.0 <4.0.0" dependencies: sherpa_onnx: 1.13.7 path: ^1.9.0 args: ^2.5.0
  • 需要 Dart SDK 3.2.0 及以上版本;
  • 依赖sherpa_onnx包(示例锁定版本 1.13.7),它是仓库中 flutter/sherpa_onnx 包的纯 Dart 封装,通过 FFI 调用底层 C API,无需原生插件即可在桌面/服务器端运行;
  • args用于解析命令行参数,path用于路径处理。

每个模型的运行脚本都遵循同一套流程:先执行dart pub get安装依赖,再按需下载三样东西——模型压缩包、Silero VAD 模型silero_vad.onnx、一条 16 kHz 的测试音频(中文用lei-jun-test.wav,英文用Obama.wav),最后以dart run启动对应的 Dart 程序。这些资源均来自项目自身的 Releases 发布渠道,脚本内已写好完整的下载与解压命令,可直接运行bash run-paraformer.sh之类的脚本。

三、核心流程拆解(以 paraformer.dart 为例)

所有 8 个示例的结构几乎一致,以 bin/paraformer.dart 为主线逐步说明。

1. 初始化绑定与命令行参数

void main(List<String> arguments) async { await sherpa_onnx.initBindingsAsync(); ... }

initBindingsAsync()负责加载底层动态库,是所有 sherpa_onnx Dart 调用的前置步骤。随后用ArgParser定义 4 个必选参数:--silero-vad--model--tokens--input-wav,缺任何一项都会打印 usage 并退出。

2. 配置并创建 Silero VAD

final sileroVadConfig = sherpa_onnx.SileroVadModelConfig( model: sileroVad, minSilenceDuration: 0.25, minSpeechDuration: 0.5, maxSpeechDuration: 5.0, ); final vadConfig = sherpa_onnx.VadModelConfig( sileroVad: sileroVadConfig, numThreads: 1, debug: true, ); final vad = sherpa_onnx.VoiceActivityDetector( config: vadConfig, bufferSizeInSeconds: 10);

各参数的含义(默认值来自 flutter/sherpa_onnx/lib/src/vad_config.dart):

参数示例取值默认值含义
model./silero_vad.onnx必填Silero VAD 模型路径
threshold未显式设置0.5语音概率判定阈值,越高越严格
minSilenceDuration0.250.5段内连续静音超过该秒数则视为一句话结束
minSpeechDuration0.5语音段最短时长,短于此值的片段被丢弃
maxSpeechDuration5.0语音段最长时长(秒),防止长段不切分
windowSize未显式设置512Silero VAD 单帧采样点数(对应 32 ms @16 kHz),也是喂入 VAD 的步长
numThreads11VAD 推理线程数
bufferSizeInSeconds1010检测器内部缓存缓冲,单位秒

从源码结构看,VoiceActivityDetector构造时会把SileroVadModelConfig的字段逐一映射到 C 层结构体(见 flutter/sherpa_onnx/lib/src/vad.dart 中minSilenceDurationwindowSize等赋值),因此示例中的取值最终作用于底层 Silero 模型推理逻辑。

3. 配置并创建非流式识别器

final paraformer = sherpa_onnx.OfflineParaformerModelConfig(model: model); final modelConfig = sherpa_onnx.OfflineModelConfig( paraformer: paraformer, tokens: tokens, debug: true, numThreads: 1, modelType: 'paraformer', ); final config = sherpa_onnx.OfflineRecognizerConfig(model: modelConfig); final recognizer = sherpa_onnx.OfflineRecognizer(config);

关键点:非流式识别器通过OfflineModelConfig中的字段组合来区分模型类型——Paraformer 填paraformer字段并设modelType: 'paraformer';Whisper 填whisper字段(内含encoder/decoder两个路径);Zipformer Transducer 填zipformer2字段(内含encoder/decoder/joiner三个路径);SenseVoice 填senseVoice字段。tokens指向词表文件,numThreads控制 ONNX Runtime 推理线程数。

4. 逐窗喂入 VAD,边检测边解码

final waveData = sherpa_onnx.readWave(inputWav); if (waveData.sampleRate != 16000) { print('Only 16000 Hz is supported. Given: ${waveData.sampleRate}'); exit(1); } int numSamples = waveData.samples.length; int numIter = numSamples ~/ vadConfig.sileroVad.windowSize; for (int i = 0; i != numIter; ++i) { int start = i * vadConfig.sileroVad.windowSize; vad.acceptWaveform(Float32List.sublistView( waveData.samples, start, start + vadConfig.sileroVad.windowSize)); while (!vad.isEmpty()) { final samples = vad.front().samples; final startTime = vad.front().start.toDouble() / waveData.sampleRate; final endTime = startTime + samples.length.toDouble() / waveData.sampleRate; final stream = recognizer.createStream(); stream.acceptWaveform(samples: samples, sampleRate: waveData.sampleRate); recognizer.decode(stream); final result = recognizer.getResult(stream); stream.free(); print( '${startTime.toStringAsPrecision(5)} -- ${endTime.toStringAsPrecision(5)} : ${result.text}'); vad.pop(); } } vad.flush();

这段主循环体现了 VAD + ASR 协作的核心模式:

  • 采样率限制:示例只接受 16 kHz 单声道 WAV,其他采样率直接退出(各模型运行脚本提供的测试音频均为 16 kHz)。
  • 切片步长 =windowSizevad.acceptWaveform每次喂入windowSize(默认 512)个采样点,即模拟 32 ms 一帧的准实时输入,这也是 VAD 模型设计的帧长;用Float32List.sublistView做零拷贝切片。
  • isEmpty/front/pop队列语义:VAD 内部维护一个语音段队列。每当检测到一段完整语音(满足minSilenceDuration/maxSpeechDuration切分条件),该片段就进入队列;主循环用while (!vad.isEmpty())依次取出front()片段、解码、打印,再pop()移除。
  • 时间戳计算vad.front().start是片段起始采样点序号,除以采样率得起始秒数;start + samples.length / sampleRate得结束秒数。
  • vad.flush()收尾:音频喂完后调用flush(),把 VAD 内部残留的语音(结尾没有跟随足够静音的段)强制吐出队列,再用同样的 while 循环解码,避免丢失结尾的语音。

每次解码都是“一次一片段”的独立生命周期:createStream() → acceptWaveform → decode → getResult → stream.free(),片段之间互不依赖,这也意味着片段级错误不会相互污染。

5. 资源释放

程序末尾显式调用vad.free()recognizer.free()。这两个对象持有底层 C++ 实例与 ONNX Runtime 会话,示例的写法明确了 Dart 侧所有 native 资源都遵循“用完即释放”的约定。

四、各模型的命令行参数与运行方式

Paraformer(中文,paraformer.dart

dart run \ ./bin/paraformer.dart \ --silero-vad ./silero_vad.onnx \ --model ./sherpa-onnx-paraformer-zh-2023-09-14/model.int8.onnx \ --tokens ./sherpa-onnx-paraformer-zh-2023-09-14/tokens.txt \ --input-wav ./lei-jun-test.wav

对应 run-paraformer.sh,使用官方发布的 Paraformer 中文模型 int8 量化版。

SenseVoice(多语言,sense-voice.dart/sense-voice-2.dart

SenseVoice 示例比 Paraformer 多两个可选参数(见 bin/sense-voice.dart):

dart run \ ./bin/sense-voice.dart \ --silero-vad ./silero_vad.onnx \ --model ./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/model.onnx \ --tokens ./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/tokens.txt \ --use-itn true \ --input-wav ./lei-jun-test.wav
  • --language:取值为autozhenjakoyue,留空则自动检测(默认自动);
  • --use-itntrue时启用逆文本规范化(inverse text normalization),把“二零二五年”这类读法还原为数字等书面形式。中文、英文两个脚本(run-sense-voice-zh.sh、run-sense-voice-en.sh)均传--use-itn true,分别配合lei-jun-test.wav(中文)与Obama.wav(英文)演示。

Whisper(whisper.dart

Whisper 需要 encoder 与 decoder 两个模型文件,因此多一个--decoder参数(见 bin/whisper.dart):

dart run \ ./bin/whisper.dart \ --silero-vad ./silero_vad.onnx \ --encoder ./sherpa-onnx-whisper-tiny.en/tiny.en-encoder.int8.onnx \ --decoder ./sherpa-onnx-whisper-tiny.en/tiny.en-decoder.int8.onnx \ --tokens ./sherpa-onnx-whisper-tiny.en/tiny.en-tokens.txt \ --input-wav ./Obama.wav

Zipformer Transducer(zipformer-transducer.dart

Transducer 架构还需要 joiner 模型,是三件套参数最多的一类:

dart run \ ./bin/zipformer-transducer.dart \ --silero-vad ./silero_vad.onnx \ --encoder ./sherpa-onnx-zipformer-gigaspeech-2023-12-12/encoder-epoch-30-avg-1.int8.onnx \ --decoder ./sherpa-onnx-zipformer-gigaspeech-2023-12-12/decoder-epoch-30-avg-1.onnx \ --joiner ./sherpa-onnx-zipformer-gigaspeech-2023-12-12/joiner-epoch-30-avg-1.int8.onnx \ --tokens ./sherpa-onnx-zipformer-gigaspeech-2023-12-12/tokens.txt \ --input-wav ./Obama.wav

单文件 CTC / 其他模型

telespeech-ctc.dartzipformer-ctc.dartdolphin-ctc.dart均只需--model单文件加--tokens,与 Paraformer 的参数形态一致,例如 run-telespeech-ctc.sh:

dart run \ ./bin/telespeech-ctc.dart \ --silero-vad ./silero_vad.onnx \ --model ./sherpa-onnx-telespeech-ctc-int8-zh-2024-06-04/model.int8.onnx \ --tokens ./sherpa-onnx-telespeech-ctc-int8-zh-2024-06-04/tokens.txt \ --input-wav ./lei-jun-test.wav

moonshine.dart参数最多,需要--preprocessor--encoder--uncached-decoder--cached-decoder四个模型文件加--tokens,完整命令见 run-moonshine.sh。

五、输出格式与结果解读

所有示例的输出统一为如下格式(时间单位:秒,保留 5 位有效数字):

0.40000 -- 2.32000 : 你好,这里是 sherpa-onnx 5.10000 -- 7.00000 : 第二句话的内容

每一行对应 VAD 切出的一个语音段:左端是片段起始时间,右端是结束时间,冒号后是非流式识别器输出的文本。由于 VAD 参数(minSilenceDurationmaxSpeechDuration等)决定切分粒度,调整这些值会直接影响行数与每行文本的长短:maxSpeechDuration调小可得到更细的时间戳,minSilenceDuration调大则更不容易把一句话截断。

六、小结与扩展方向

dart-api-examples/vad-with-non-streaming-asr/演示的是 sherpa-onnx 处理离线长音频的标准范式:Silero VAD 做端点检测 → 每段独立创建 stream 送入 OfflineRecognizer → flush 收尾 → 显式释放资源。这套模式在仓库的其他语言示例(如 python-api-examples/vad-with-non-streaming-asr.py)中同样成立,Dart 版本可直接用于命令行批处理、桌面应用与 Web(flutter/sherpa_onnx包同时提供 FFI 与 Web 两套实现)。

实际集成时可以按需调整的点:

  • 切分策略:修改SileroVadModelConfigminSilenceDuration/minSpeechDuration/maxSpeechDurationthreshold,平衡句级时间戳精度与切分稳定性;
  • 性能:调大VadModelConfig.numThreadsOfflineModelConfig.numThreads,示例为保持跨平台结果一致固定为 1;
  • 模型替换:只需替换OfflineModelConfig中对应模型类型的子配置(如换成 Whisper 或 Zipformer CTC),VAD 部分与主循环代码完全复用;
  • 实时场景:本示例按文件整段处理;若要从麦克风准实时处理,思路不变,只是把“逐窗读文件”换成“逐窗读音频输入设备”,仓库中其他 Dart 示例(如 dart-api-examples/streaming-asr/)展示了流式侧的写法。

【免费下载链接】sherpa-onnxSpeech-to-text, text-to-speech, speaker diarization, speech enhancement, source separation, and VAD using next-gen Kaldi with onnxruntime without Internet connection. Support embedded systems, Android, iOS, HarmonyOS, Raspberry Pi, RISC-V, RK NPU, Axera NPU, Ascend NPU, x86_64 servers, websocket server/client, support 12 programming languages项目地址: https://gitcode.com/GitHub_Trending/sh/sherpa-onnx

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 2:46:44

STM32嵌入式开发:从Keil迁移到VS Code+GCC的工程实践

1. 为什么STM32开发者正在集体“逃离”Keil&#xff0c;转向VS Code&#xff1f; 我第一次在客户现场看到工程师用VS Code调试STM32F407时&#xff0c;他正把一个断点打在FreeRTOS的 vTaskDelay() 函数里&#xff0c;同时开着三个终端窗口&#xff1a;一个跑OpenOCD&#xff…

作者头像 李华
网站建设 2026/9/14 2:46:42

工控单板Linux存储、系统升级与恢复出厂实战指南

工控单板跑 Linux&#xff0c;最怕的不是性能不够&#xff0c;而是系统在客户现场出了乱子没人能管。前两篇聊完系统裁剪和启动流程&#xff0c;这篇把存储、升级和恢复出厂这三块一次性讲透。这三件事在开发阶段往往被当成“杂活”&#xff0c;可真上了产线、进了项目&#xf…

作者头像 李华
网站建设 2026/9/14 2:44:42

基于SSM的酒店管理系统:三层架构与并发事务实践

简介&#xff1a;基于SSM框架的酒店管理系统设计与实现项目资源&#xff0c;面向Java Web课程设计、毕业设计及SSM框架初学者。系统采用Spring、SpringMVC、MyBatis三层整合架构&#xff0c;覆盖登录注册、客房管理、订单管理、客户信息维护、财务结算等典型业务场景&#xff0…

作者头像 李华
网站建设 2026/9/14 2:43:52

MATLAB调用GoogLeNet图片分类实战:Inception模块与迁移学习详解

简介&#xff1a;本资源是基于MATLAB实现的GoogLeNet深度卷积神经网络完整工程包&#xff0c;面向具备基础深度学习与MATLAB编程能力的高校学生、科研人员及图像分类实践者&#xff0c;用于快速掌握Inception模块构建、批量归一化应用及端到端图像分类模型训练流程。压缩包共25…

作者头像 李华