创建日期:2026-10-13 | 最近更新:2026-10-13 事实核对基于 sherpa-onnx 1.13.8(
sherpa-onnx-node原生 addon 与sherpa-onnxWebAssembly 包;node v24.19.0、Chrome 149、Linux x64)。文中所有耗时、内存、体积均为本机实测,脚本与完整日志留档于source/sherpa-onnx-lab/(bash run.sh | tee run.log可复现)。
sherpa-onnx 101(第 0 篇 · 入门):把语音能力搬进前端工程
一句话:sherpa-onnx 是一个纯本地的语音推理引擎——语音识别、语音合成、说话人、唤醒词、降噪全都在你自己的进程里跑,不联网、不调 API、不花 token。对前端来说,它的意义是:浏览器或 Node 里终于有了一套不依赖云服务的语音能力。
这个系列按「一个前端工程师真会用到什么」来组织:先把两条 Node 路线和成本记账讲清楚(本篇),再挑模型(第 1 篇)、落地识别链路(第 2 篇)、做领域定制(第 3 篇)、加 TTS 和唤醒词(第 4 篇),最后进浏览器(第 5 篇)。
0. 先给结论
| # | 结论 | 证据 |
|---|---|---|
| 1 | Node 里有两套互不相同的包:sherpa-onnx-node(原生 addon,多线程)和 sherpa-onnx(WebAssembly,单线程)。API 都不一样,选错了要重写代码 | §2 |
| 2 | 同一模型同一段音频:WASM 版解码慢 3.4~3.5 倍(RTF 0.085 对 0.299;两次运行 3.41 / 3.53),文本还不完全一致 | §2、第 5 篇 |
| 3 | 最小的能力只要 630 KB:Silero VAD 模型 0.6 MB,加载后常驻内存 74 MB,处理速度是实时的 117 倍 | §3 |
| 4 | 成本差异极大:唤醒词模型加载后 93 MB,中英双语流式识别 324 MB,TTS(Kokoro)496 MB | §4 |
| 5 | 全部能力都能离线跑,模型不需要联网校验;下载一次之后断网可用 | §4 |
| 6 | 真正要花时间的地方不是 API,是选模型和裁词表——这两件事决定「它能不能听懂你们组的黑话」 | 第 1、3 篇 |
1. 它是什么
sherpa-onnx 是 k2-fsa(下一代 Kaldi 团队,小米)做的离线语音推理引擎,Apache-2.0,C++ 写成,底层跑 ONNX Runtime。它自己不训练模型,只负责"把训练好的模型高效地跑起来",然后给几十种语言/平台发绑定:C/C++、Python、Java/Kotlin、Swift、Go、Rust、C#、Pascal、Node.js、WebAssembly、Android、iOS、鸿蒙、Flutter、Tauri……
把它和常见的两条路对比一下就清楚它的位置:
| 方案 | 音频出不出你的机器 | 成本 | 延迟 | 离线可用 |
|---|---|---|---|---|
| 云厂商 ASR/TTS API | 出(要上传) | 按量计费 | 网络往返 + 排队 | 否 |
浏览器原生 SpeechRecognition | 出(Chrome 走云端) | 免费但有配额 | 不可控 | 否 |
| sherpa-onnx | 不出 | 一次性下载模型 + 你的 CPU | 本地算力决定 | 是 |
代价也很直白:模型要自己下、要自己挑、要自己算体积和内存,识别准确率也不会比大厂云端模型好。它换来的东西是隐私、离线、零边际成本和完全可控的延迟。
1.1 它能做什么
这个清单不用查文档——sherpa-onnx-node 导出的就是全部能力,跑一行就看得到:
$ node -e "console.log(Object.keys(require('sherpa-onnx-node')).sort().join(', '))"
AudioTagging, CircularBuffer, Display, GenerationConfig, KeywordSpotter,
LinearResampler, OfflinePunctuation, OfflineRecognizer, OfflineSpeakerDiarization,
OfflineSpeechDenoiser, OfflineTts, OnlinePunctuation, OnlineRecognizer,
OnlineSpeechDenoiser, SpeakerEmbeddingExtractor, SpeakerEmbeddingManager,
SpokenLanguageIdentification, Vad, gitDate, gitSha1, onnxruntimeVersion,
readWave, version, writeWave
翻译成人话:
| 类 | 干什么 | 典型用途 |
|---|---|---|
OnlineRecognizer | 流式语音识别(边说边出字) | 实时字幕、语音输入 |
OfflineRecognizer | 非流式识别(整段音频一次出结果) | 转录音频文件 |
Vad | 语音活动检测(把静音剪掉、按句切分) | 长音频预处理、省算力 |
KeywordSpotter | 唤醒词 / 关键词检测 | 「打开控制台」这种语音命令 |
OfflineTts | 语音合成 | 朗读、语音播报 |
OfflinePunctuation / OnlinePunctuation | 加标点 | 识别结果可读化 |
SpeakerEmbeddingExtractor / Manager | 声纹(说话人识别) | 谁在说话 |
OfflineSpeakerDiarization | 说话人分离 | 会议记录分角色 |
SpokenLanguageIdentification | 语种识别 | 多语种路由 |
OfflineSpeechDenoiser / OnlineSpeechDenoiser | 降噪 | 嘈杂环境前置处理 |
AudioTagging | 音频事件打标(狗叫、警报…) | 环境感知 |
LinearResampler / CircularBuffer | 重采样、环形缓冲 | 麦克风采集必备胶水 |
注意 Online = 流式,Offline = 非流式,这个命名贯穿整个库。
2. 两条 Node 路线:先选对包
这是最容易踩的第一个坑。npm 上两个名字很像的包是两套完全不同的东西:
sherpa-onnx-node | sherpa-onnx | |
|---|---|---|
| 实现 | 原生 addon(node-addon-api + .so/.dll) | WebAssembly |
| 多线程 | 支持 | 不支持 |
| 最低 Node | 16 | 18 |
| 安装体积 | 约 32 MB(含 libonnxruntime.so 27 MB) | 约 15 MB(单个 .wasm) |
| 浏览器能跑 | 不能 | 能(但要另一套构建产物,见第 5 篇) |
官方 README 原文就是这么一句话对照的:
||Method|Support multiple threads|Minimum required node version| |---|---|---| |this package| https://github.com/nodejs/node-addon-api | Yes | v16| |https://www.npmjs.com/package/sherpa-onnx| WebAssembly | No | v18|
(摘自 sherpa-onnx-node 包内 README.md,留档见 source/sherpa-onnx-lab/official-sources/nodejs-addon-examples-README.md)
它们连 API 签名都不一样,这不是风格差异,是实打实的迁移成本。举个最小例子:
// 原生 addon:参数是一个对象
stream.acceptWaveform({ sampleRate: wave.sampleRate, samples: wave.samples });
// WebAssembly:两个位置参数,而且顺序是 (采样率, 样本)
stream.acceptWaveform(wave.sampleRate, wave.samples);
而且 WASM 版还要手动 free():
stream.free();
recognizer.free();
同一个模型(2023 版中英双语 mobile、int8)、同一段 10.05 秒音频、同样 100 ms 一包喂进去:
| 实现 | 加载模型 | 解码 | RTF | 识别结果 |
|---|---|---|---|---|
| 原生 addon(2 线程) | 0.923 s | 0.852 s | 0.085 | 昨天是 MONDAY TODAY IS LIBRAR THE DAY AFTER TOMORROW是星期三 |
| WebAssembly(单线程,SIMD) | 1.059 s | 3.010 s | 0.299 | 昨天是 MONDAY TODAY IS LIBR THE DAY AFTER TOMORROW是星期三 |
慢三倍多,文本还差一个字母。 这个差距主要来自线程数(WASM 版据其 CI 构建脚本 build-wasm-simd-nodejs.sh 是带 SIMD 的,但不带 pthread)。所以:
- 服务端 / 桌面端 / Electron 主进程 → 用
sherpa-onnx-node,把numThreads加上去; - 必须在浏览器里跑 → 只能 WASM,先算清楚性能预算(第 5 篇有一份实测账)。
两种实现都装在一个工程里不冲突(我这份实验就同时装了),但代码里最好包一层适配器,别让上层感知到差异。
3. 五分钟跑起来:从 630 KB 的 VAD 开始
建议第一个上手的能力不是识别,是 VAD。 理由:模型最小(0.6 MB)、不需要词表、不需要调参、输出是数字(好断言),而且它是后面所有链路的第一个环节。
mkdir sherpa-lab && cd sherpa-lab
npm i sherpa-onnx-node
curl -sSL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
const sherpa = require('sherpa-onnx-node');
const vad = new sherpa.Vad({
sileroVad: {
model: './silero_vad.onnx',
threshold: 0.5,
minSpeechDuration: 0.25, // 短于 250ms 的不算语音
minSilenceDuration: 0.5, // 静音超过 500ms 才断开
windowSize: 512, // 16kHz 下 = 32ms 一窗,这个值是模型定死的
},
sampleRate: 16000,
numThreads: 1,
debug: false,
}, 60); // 第二个参数:内部环形缓冲 60 秒
const wave = sherpa.readWave('test.wav');
const win = vad.config.sileroVad.windowSize;
for (let i = 0; i + win <= wave.samples.length; i += win) {
vad.acceptWaveform(wave.samples.subarray(i, i + win));
while (!vad.isEmpty()) {
const seg = vad.front();
vad.pop();
console.log(`语音段 ${(seg.start / wave.sampleRate).toFixed(3)}s 起,` +
`长 ${(seg.samples.length / wave.sampleRate).toFixed(3)}s`);
}
}
本机实测(10.05 秒中文音频):
VAD 切出段数 : 3
端到端耗时 : 0.086 s (RTF = 0.0085 )
段 0: start=0.678s dur=1.606s end=2.284s
段 1: start=3.814s dur=2.118s end=5.932s
段 2: start=6.534s dur=3.366s end=9.900s
能量包络起点(参照) : 0.710s(原始音频,判据 = RMS > 10% 峰值)
与能量参照之差 : -0.032 s
三件事值得记下来:
- 快得离谱:RTF 0.0085,即 1 秒音频花 8.5 毫秒,约 117 倍实时。VAD 在前端链路里几乎不占预算,可以放心每帧都跑。
- 起点精度约 ±30 ms。我用「RMS 超过峰值 10%」这个粗糙判据做参照,VAD 报的起点比它早 32 毫秒——这个量级对切分足够,但不要拿它当逐字对齐用(那个要用识别结果自带的
timestamps)。 windowSize: 512不是随便填的,是 Silero 模型的固定输入长度(16 kHz 下 32 ms)。喂进去的块必须刚好这个大小。
4. 记账:体积、内存、速度
选型真正要看的三个数。下面全部是本机(Linux x64、28 核、DDR4)实测,体积按解包后的模型目录算(含官方附带的测试音频和 fp32/int8 两套权重,所以比单个 .onnx 大):
| 能力 | 模型 | 磁盘体积 | 加载后常驻 RSS | RTF(越低越快) |
|---|---|---|---|---|
| VAD | Silero VAD | 0.6 MB | 74 MB | 0.0085 |
| 唤醒词(中文) | KWS zipformer 3.3M | 36 MB | — | 0.030 |
| 唤醒词(中英) | KWS zipformer 3M(2025-12) | 39 MB | 93 MB | 0.029 |
| 英文识别(非流式) | Moonshine v2 tiny 量化 | 43 MB | 162 MB | 0.026 |
| 中英识别(流式) | X-ASR 480ms int8(2026-06) | 163 MB | 324 MB | 0.112 ~ 0.130 |
| 中英识别(流式,老) | zipformer bilingual mobile(2023) | 360 MB | 253 MB | 0.088 ~ 0.099 |
| 语音合成 | Kokoro 多语言(54 音色) | 384 MB | 496 MB | 1.00 |
读数说明:
- RSS 是"加载完模型、还没开始推理"时的进程常驻内存,每个模型在独立进程里量的。基础 Node 进程本身约 50 MB。
- RTF = 处理耗时 ÷ 音频时长,小于 1 就是比实时快。RTF 0.09 表示「能同时盯 11 路实时音频」,RTF 1.00 表示「合成 1 秒语音要花 1 秒 CPU」。
- 这里的内存是全量权重常驻。前端场景下这是硬约束:Kokoro 那 496 MB 在浏览器里就是实打实的内存占用,手机浏览器上要慎重。
两条直接可用的结论:
- TTS 是最贵的一环。 RTF 约等于 1 意味着它刚好实时,没有余量——这就是为什么前端做语音播报要「分句流式合成 + 边合成边播」,而不是等整段合成完(第 4 篇细说)。
- 识别和唤醒词是可以叠加的:VAD(74 MB)+ 唤醒词(93 MB)不到 170 MB,能先在本地待机听唤醒词,唤醒了再加载大模型——这是前端最省内存的架构。
5. 模型从哪儿下
全部在 GitHub Releases 上,按用途分成不同的 tag:
| tag | 内容 |
|---|---|
asr-models | 语音识别(zipformer / paraformer / whisper / moonshine / sensevoice / dolphin…),498 个资产 |
kws-models | 唤醒词 |
tts-models | 语音合成(kokoro / matcha / vits / zipvoice / pocket…) |
punctuation-models | 标点 |
speaker-recongition-models | 声纹(官方 tag 名就是这个拼写,少了个 i) |
speaker-segmentation-models | 说话人分离 |
speech-enhancement-models | 降噪 |
hr-files | 同音词替换用的拼音词典与规则(第 3 篇) |
audio-tagging-models | 音频事件打标 |
下载就是一个 curl:
curl -sSL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
包内一般自带 test_wavs/,先用它跑通链路再换自己的音频——这一步能省掉大量「到底是模型不行还是我的音频不对」的排查。本实验为了方便复现,把用到的模型整理成了一份 download-models.sh(约 1.1 GB)。
6. 什么时候不该用它
说清楚边界比堆优点有用:
- 要识别得又准又省心 → 用云端大模型。sherpa-onnx 的本地模型准确率有明确上限,尤其嘈杂环境、方言、远场。
- 要跑在低端安卓机上做实时字幕 → 先做主频和内存预算,int8 小模型(唤醒词、14M/20M 级 zipformer)之外都容易掉帧。
- 要做「理解」而不是「转写」 → 它只输出文字,语义理解是另一层的事。
- 要多人实时会议转写 → 说话人分离能跑,但那是"先录完整段再离线分"的模式,不是流式。
反过来说,它特别适合这些场景:内网/离线环境、隐私敏感(音频不能出设备)、要零边际成本地跑量(批量转写几千小时音频)、要给语音命令做本地唤醒、要把语音能力打进浏览器做 demo。
7. 本系列路线图
| 篇 | 主题 | 回答的问题 |
|---|---|---|
| 0 | 入门(本篇) | 两条 Node 路线怎么选?成本怎么算? |
| 1 | 模型选型 | 程序员说的是「把 useEffect 的依赖数组补上」,哪个模型听得懂? |
| 2 | 落地识别链路 | 流式怎么用?长音频怎么切?端点检测的坑在哪? |
| 3 | 听懂你的黑话 | 热词和同音词替换怎么把术语救回来?为什么有时候救不回来? |
| 4 | 说话与听见 | 本地 TTS 什么水平?自定义唤醒词要几步? |
| 5 | 前端集成 | 浏览器里怎么跑?体积和性能代价是多少? |
| 6 | 视频转字幕 | 手里一堆金融/编程长视频要字幕,选哪个模型、CPU 还是 GPU、能到多准? |
| 7 | 词表工程 | 左边那列「模型听成的样子」,你是怎么知道的?——词表不是写出来的,是挖出来的 |
| 8 | 并发与吞吐 | 做成服务后能同时转多少路?瓶颈其实在切分(VAD),不在识别 |
全部实验脚本在
source/sherpa-onnx-lab/,bash run.sh | tee run.log一次跑完,输出即文中引用的原始日志。模型不进仓库,用download-models.sh拉。