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)识别模型转写成文本。读完后你将掌握:VoiceActivityDetector与OfflineRecognizer的标准协作流程、Silero VAD 各参数的含义与默认值、8 种模型类型各自的命令行参数与运行脚本用法,以及示例中逐窗喂入音频(windowSize切片)、flush()收尾、显式释放资源等关键细节。
一、这个示例解决什么问题
对于一段较长的离线录音(会议录音、采访音频等),直接整段丢给非流式 ASR 模型有两个问题:一是多数模型对输入时长有限制,二是无法得到“每句话出现在哪个时间段”的时间戳。sherpa-onnx 给出的标准解法是先 VAD 分句、再逐句识别:
- Silero VAD 按固定窗口(默认 512 个采样点)逐帧检测语音活动,输出一个个带起始时间戳的语音片段;
- 每个片段交给一个
OfflineRecognizer流创建、喂入、解码,得到文本; - 按
起始时间 -- 结束时间 : 文本的格式打印带时间戳的结果。
目录 dart-api-examples/vad-with-non-streaming-asr/README.md 中列出的示例及对应脚本如下(bin/下实际还有 README 未收录的moonshine.dart、zipformer-ctc.dart、dolphin-ctc.dart等,下文一并覆盖):
| Dart 程序 | 使用的模型 | 运行脚本 |
|---|---|---|
bin/paraformer.dart | Paraformer | run-paraformer.sh |
bin/sense-voice.dart | SenseVoice CTC | run-sense-voice-zh.sh、run-sense-voice-en.sh |
bin/sense-voice-2.dart | SenseVoice(另一实现入口) | run-sense-voice-zh-2.sh |
bin/telespeech-ctc.dart | TeleSpeech CTC | run-telespeech-ctc.sh |
bin/whisper.dart | Whisper | run-whisper.sh |
bin/zipformer-transducer.dart | Zipformer Transducer | run-zipformer-transducer.sh |
bin/zipformer-ctc.dart | Zipformer CTC | run-zipformer-ctc.sh |
bin/moonshine.dart | Moonshine | run-moonshine.sh |
bin/dolphin-ctc.dart | Dolphin CTC | run-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 | 语音概率判定阈值,越高越严格 |
minSilenceDuration | 0.25 | 0.5 | 段内连续静音超过该秒数则视为一句话结束 |
minSpeechDuration | 0.5 | — | 语音段最短时长,短于此值的片段被丢弃 |
maxSpeechDuration | 5.0 | — | 语音段最长时长(秒),防止长段不切分 |
windowSize | 未显式设置 | 512 | Silero VAD 单帧采样点数(对应 32 ms @16 kHz),也是喂入 VAD 的步长 |
numThreads | 1 | 1 | VAD 推理线程数 |
bufferSizeInSeconds | 10 | 10 | 检测器内部缓存缓冲,单位秒 |
从源码结构看,VoiceActivityDetector构造时会把SileroVadModelConfig的字段逐一映射到 C 层结构体(见 flutter/sherpa_onnx/lib/src/vad.dart 中minSilenceDuration、windowSize等赋值),因此示例中的取值最终作用于底层 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)。
- 切片步长 =
windowSize:vad.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:取值为auto、zh、en、ja、ko、yue,留空则自动检测(默认自动);--use-itn:true时启用逆文本规范化(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.wavZipformer 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.dart、zipformer-ctc.dart、dolphin-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.wavmoonshine.dart参数最多,需要--preprocessor、--encoder、--uncached-decoder、--cached-decoder四个模型文件加--tokens,完整命令见 run-moonshine.sh。
五、输出格式与结果解读
所有示例的输出统一为如下格式(时间单位:秒,保留 5 位有效数字):
0.40000 -- 2.32000 : 你好,这里是 sherpa-onnx 5.10000 -- 7.00000 : 第二句话的内容每一行对应 VAD 切出的一个语音段:左端是片段起始时间,右端是结束时间,冒号后是非流式识别器输出的文本。由于 VAD 参数(minSilenceDuration、maxSpeechDuration等)决定切分粒度,调整这些值会直接影响行数与每行文本的长短: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 两套实现)。
实际集成时可以按需调整的点:
- 切分策略:修改
SileroVadModelConfig的minSilenceDuration/minSpeechDuration/maxSpeechDuration与threshold,平衡句级时间戳精度与切分稳定性; - 性能:调大
VadModelConfig.numThreads与OfflineModelConfig.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),仅供参考