@anov/sherpa-web
v1.1.1
Published
Next-gen Sherpa-onnx Web/Wasm audio toolkit (Production-ready STT & VAD; TTS & Diarization in Roadmap)
Readme
@anov/sherpa-web
浏览器端轻量高性能 Sherpa-onnx WebAssembly 语音音频工具箱。提供生产级流式 ASR(Zipformer INT8)、端侧人声活动检测(Silero-VAD)与 Two-Pass 离线高精终审(SenseVoice INT8),支持热词动态加权、近音自愈与纯离线推理。
Context Pointers
- 资产清单与离线部署: 详见
ASSETS.md(获取全量模型 SHA-256 校验码、CDN 镜像直链与自动化下载脚本)。 - 意图与决策引擎: 搭配使用
@anov/intent-matcher构建四环离线语义理解与多轮对话系统。 - 架构术语规范: 遵循
GLOSSARY.md。
⚠️ 关键前置要求:跨域隔离 (COOP / COEP)
底层多线程 WebAssembly 强依赖 SharedArrayBuffer。网页服务器必须配置两项响应头,否则浏览器将拦截内存分配:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp- Vite 开发环境:配置
vite-plugin-cross-origin-isolation插件。 - Nginx 生产环境:在相应
location /块中添加上述两条add_header指令。
🚀 核心工作流 (In-File Workflow)
步骤 1:安装依赖
pnpm add @anov/sherpa-web步骤 2:实例化识别器并启动录音
import { OnlineRecognizer } from '@anov/sherpa-web';
const asr = new OnlineRecognizer({
hotwords: ['打开控制台', '播放音乐'],
hotwordsScore: 2.5,
callbacks: {
onPartial: (text) => console.log('流式字幕:', text),
onFinal: (text, meta) => {
console.log('句子终审定稿:', text);
console.log('来源阶段:', meta?.pass === 2 ? 'SenseVoice 终审' : 'Zipformer 流式');
},
onStatusChange: (status) => console.log('引擎状态:', status),
},
});
// 1. 初始化运行时并预热核心模型
await asr.init();
// 2. 请求麦克风并开启实时识别
await asr.start();
// 3. 停止识别并注销音频管道
// await asr.stop();验收判据 (Completion Criterion):调用 await asr.start() 后麦克风指示灯点亮;对着麦克风发音时,onPartial 输出递增文本,停顿后 onFinal 返回定稿文本。
🔀 核心分支场景 (Branch Recipes)
分支 1:运行时无损动态更新热词 (Dynamic Hotwords)
支持在录音过程中动态更新业务热词。若正在收音,识别器自动排队至当前语音段结束后安全重构,绝不打断当前录音:
// 格式支持字符串、带权值分数字符串或对象
await asr.setHotwords([
{ word: '控制中心', score: 3.0 },
'静音模式 :2.5',
'返回主页',
]);分支 2:启用 Two-Pass 离线高精终审 (SenseVoice Rescoring)
第一阶段流式 Zipformer 保证 50ms 级首字响应;句子结束时自动触发第二阶段 SenseVoice INT8 模型(239MB)修正文本与回填富标点:
const asr = new OnlineRecognizer({
twoPass: {
mode: 'auto', // 'auto' 根据设备 CPU 核心与内存自适应启用
modelType: 'sense-voice',
modelPaths: {
modelPath: '/sense-voice.onnx',
tokensPath: '/sense-voice-tokens.txt',
},
},
callbacks: {
onFinal: (text, meta) => {
// meta.isRescored 为 true 表示经过第二阶段修正
console.log(`[Pass ${meta?.pass}] 最终结果: ${text}`);
},
},
});
await asr.init();分支 3:独立使用 Silero-VAD 人声活动检测 (Standalone VAD)
无需初始化 ASR 即可独立运行人声起止点检测,原生支持多播订阅与异常隔离:
import { VadDetector } from '@anov/sherpa-web';
const vad = new VadDetector({
threshold: 0.015,
minSpeechDurationMs: 60,
minSilenceDurationMs: 700,
});
await vad.init();
// 多播事件订阅(返回取消订阅函数)
const unsubscribe = vad.subscribe({
onSpeechStart: () => console.log('检测到人声起始'),
onSpeechEnd: (segment) => console.log('人声结束,时长:', segment.durationSec),
});
// 向 VAD 推送 16kHz PCM 单声道切片
vad.acceptWaveform(pcmChunk);
// 注销
// unsubscribe();
// vad.dispose();分支 4:纯离线 / 专网环境加载 (Air-gapped Deployment)
在无法连通外网的专网环境中,指定 localBase: '/' 即可强行阻断所有 CDN 外部请求:
const asr = new OnlineRecognizer({
modelPaths: {
localBase: '/', // 从站点 public 静态根目录加载 wasm 与 data
},
twoPass: {
mode: 'auto',
modelType: 'sense-voice',
modelPaths: {
modelPath: '/sense-voice.onnx',
tokensPath: '/sense-voice-tokens.txt',
},
},
});分支 5:脱离 Wasm / 浏览器的单元测试 (Zero-Wasm Headless Testing)
在 Node.js 或 CI 环境中,利用 FakeSherpaRuntime 与 BufferAudioSource 运行毫秒级单测,无需加载数十兆二进制:
import { describe, it, expect } from 'vitest';
import { OnlineRecognizer, FakeSherpaRuntime, BufferAudioSource } from '@anov/sherpa-web';
describe('ASR 业务流单测', () => {
it('验证音频切片消费与定稿输出', async () => {
let finalOutput = '';
const fakeRuntime = new FakeSherpaRuntime();
fakeRuntime.setMockResult('测试语音指令');
const asr = new OnlineRecognizer({
runtime: fakeRuntime,
audioSource: new BufferAudioSource(new Float32Array(16000)),
callbacks: {
onFinal: (text) => { finalOutput = text; },
},
});
await asr.init();
await asr.start();
await asr.stop();
expect(finalOutput).toBe('测试语音指令');
});
});分支 6:离散 PCM 音频识别与两阶段评测 (recognizeSamples)
无需开启麦克风或音频流,直接传入 16kHz 单声道 Float32Array PCM 音频数组进行识别。方法内部自动穿透 Pass 1 流式粗筛与 Pass 2 离线终审重打分(若已配置 Two-Pass),常用于音频回归评测、音频文件识别与服务端/离线批处理:
// samples 为 16000Hz、单声道、Float32Array PCM 采样数据
const result = await asr.recognizeSamples(pcmSamples);
console.log('定稿文本:', result.text);
console.log('产出阶段:', result.pass); // 1: Pass 1 流式, 2: Pass 2 SenseVoice 终审
console.log('是否经过纠偏:', result.isRescored); // boolean
console.log('流式粗筛文本:', result.rawStreamingText); // 第一阶段原始文本
if (result.fusionSource) {
console.log('融合策略:', result.fusionSource); // 'pass1' | 'pass2' | 'merged'
console.log('融合原因:', result.fusionReason); // 如 'pass2-empty', 'hotword-restored:...'
}分支 7:独立使用端侧中文逆文本规整 (Chinese ITN)
SDK 导出了纯函数式的端侧零依赖中文 ITN 引擎,可直接用于汉字数字、序数词、百分比、温度及度量衡的规范化:
import { normalizeChineseNumbers, parseChineseInteger } from '@anov/sherpa-web';
// 1. 中文数字、序数、百分比、温度、度量衡规整
const text1 = normalizeChineseNumbers('把空调调到二十六度,风速调到第三档');
// => "把空调调到26度,风速调到第3档"
const text2 = normalizeChineseNumbers('电量还剩百分之八十五,预计还能行驶一百二十公里');
// => "电量还剩85%,预计还能行驶120公里"
// 2. 解析中文整数(支持 0 ~ 9999 规范中文数词或阿拉伯数字,非法返回 null)
const count = parseChineseInteger('一百二十五'); // => 125
const zero = parseChineseInteger('零'); // => 0📋 配置契约与类型速查 (Reference)
OnlineRecognizerOptions 核心参数
| 配置项 | 类型 | 默认值 | 作用与约束 |
| :--- | :--- | :---: | :--- |
| hotwords | Array<string \| HotwordItem> | [] | 声学偏置热词列表(支持 word :score 语法) |
| hotwordsScore | number | 2.0 | 热词全局基准声学加权得分 |
| modelingUnit | 'cjkchar' \| 'bpe' \| 'cjkchar+bpe' | 'cjkchar' | 识别建模单元,中文指令建议选用 'cjkchar' |
| profile | 'fast' \| 'balanced' \| 'accurate' | 'balanced' | 解码性能配置文件(balanced: maxActivePaths=16, blankPenalty=1.25) |
| enableItn | boolean | true | 是否启用端侧通用中文逆文本规整 (ITN,汉字数字/度量衡转阿拉伯数字) |
| twoPass | TwoPassOptions | undefined | 第二阶段 SenseVoice 离线重打分配置 |
| audioSource | AudioSource | BrowserMicAudioSource | 音频输入源适配器,默认采集麦克风输入 |
| vadDetector | VadDetector | 内部自动创建 | 负责驱动流切片的人声端点检测器 |
| vadMode | 'hybrid' \| 'adaptive' \| 'silero' | 'hybrid' | VAD 运行模式(非 Fake 环境下默认为 'hybrid') |
| enableVadGating | boolean | true | 是否启用 VAD 门禁切片与录音段分割 |
| tailPaddingMs | number | 350 | 语音段尾部静音缓冲时长(毫秒),防止发音截断丢字 |
| localBase | string | undefined | 静态资产根路径(如 '/') |
| homophoneReplacer | HomophoneReplacerOptions | undefined | 应用层注入的业务别名/同音词纠偏表(单次正则替换,杜绝连锁污染) |
| textPostProcessor | (text: string) => string | undefined | 自定义文本后处理管道函数 |
| callbacks | SttCallbacks | {} | 流式中间结果、定稿文本及生命周期回调 |
OnlineRecognizer 核心公共方法
| 方法 | 返回类型 | 说明 |
| :--- | :--- | :--- |
| init() | Promise<void> | 初始化运行时环境,预热加载核心模型与 VAD |
| start() | Promise<void> | 开启音频输入源并进入实时收音与识别流 |
| stop() | Promise<void> | 停止收音,冲刷待裁决切片并注销输入音频源 |
| setHotwords(hotwords, score?) | Promise<void> | 运行时动态热词更新(收音中自动排队至当前语音段后安全生效) |
| recognizeSamples(samples) | Promise<RecognizeResult> | 离散 16kHz Float32Array PCM 音频直认(自动打通 Pass 1 与 Pass 2) |
| getRecordedPcm() | Float32Array | 获取当前录音会话累积的 16kHz PCM 单声道音频数据 |
| getRecordedWavBlob() | Blob | 获取当前录音会话编码生成的标准 WAV 格式 Blob |
| dispose() | void | 彻底停止录音、销毁音频源、控制器、两阶段打分器与 Wasm 运行时 |
SttCallbacks 回调定义
interface SttCallbacks {
onPartial?: (text: string) => void;
onFinal?: (text: string, meta?: SttFinalMeta) => void;
onSpeechStart?: () => void;
onSpeechEnd?: (segment?: VadSegment) => void;
onStatusChange?: (status: string) => void;
onProgress?: (info: { loaded: number; total: number; percent: number }) => void;
onLog?: (msg: string, level?: 'info' | 'ok' | 'warn' | 'err') => void;
onPcmChunk?: (chunk: Float32Array) => void;
}工具函数导出速查
import {
normalizeChineseNumbers, // (text: string) => string: 中文数字/度量/温度通用 ITN 规整
parseChineseInteger, // (raw: string) => number | null: 解析 0~9999 汉字或阿拉伯整数
stripPunctuation, // (text: string) => string: 剔除文本标点符号
computeConfidence, // (ysProbs?: number[]) => number | undefined: 从 token 对数概率导出 [0, 1] 置信度
serializeHotwords, // 序列化热词列表为 Sherpa 格式
extractPureHotwords, // 提取热词纯词干
} from '@anov/sherpa-web';⚠️ 开发注意细节 (Gotchas)
- 输入音频规格:底层模型严格只接收 16000Hz 采样率、单声道、32位浮点 PCM。使用自定义
AudioSource时需经过重采样转换(可使用内置WavResampler)。 - 解码算法限制:若
decoderOptions.decodingMethod设为'greedy_search',热词加权机制将不生效(需保持默认'modified_beam_search')。 - 命名别名兼容:本库遵循统一领域语言规范,
AsrEngine与OnlineRecognizer互为等价导出别名。
📄 License
MIT © 2026 Anov Contributors
