- 人工智能
- 语音
- 音频
- 深度学习
【免费下载链接】DeepSpeech
DeepSpeech is an open source embedded (offline, on-device) speech-to-text engine which can run in real time on devices ranging from a Raspberry Pi 4 to high power GPU servers.
本文以 DeepSpeech 官方文档 doc/NodeJS-Examples.rst 为骨架,围绕其引用的完整示例程序 native_client/javascript/client.ts 展开,系统讲解如何在 Node.js 与 Electron 环境中完成模型加载、外部 Scorer 配置、WAV 音频预处理、批式(一次性)与流式(增量)语音识别推理,并结合 native_client/javascript/index.ts 的源码级 API 定义与 ci_scripts/node-tests.sh 的测试流程,帮助读者掌握可复制、可运行的端到端调用方案。
一、环境准备与项目结构
官方示例全部来自native_client/javascript/client.ts这一命令行客户端,它在编译打包后通过 package.json.in 中的bin字段以deepspeech命令对外暴露。其运行时依赖如下:
| 依赖 | 版本 | 用途 |
|---|---|---|
deepspeech | 项目自身 | Node.js 原生绑定(含 TypeScript 类型声明index.d.ts) |
node-pre-gyp | 0.15.x | 下载/定位预编译原生绑定 |
argparse | 1.0.x | 命令行参数解析 |
sox-stream | 2.0.x | 调用 SoX 做音频重采样与格式转换 |
memory-stream | 1.0.x | 将转换后的音频流收集进内存 Buffer |
node-wav | 0.0.2 | 解析 WAV 文件头与采样数据 |
该客户端使用 TypeScript 编写,通过tsc编译为 JavaScript(见 native_client/javascript/Makefile 中的npm-pack目标)。原生部分则由 SWIG 接口文件 deepspeech.i 将 C API native_client/deepspeech.h 包装为 Node 模块,并负责把传入的Buffer转换为 C 层的short*音频数组(要求 Buffer 长度为偶数,即 16-bit 采样)。
在开始前,请确认已安装 Node.js 与 npm,并已通过 npm 安装好deepspeech包及其依赖。示例程序运行需要三样输入:PB 格式的冻结模型文件(--model)、可选的 KenLM 外部 Scorer(--scorer)以及 16 kHz 的 WAV 音频(--audio)。
二、命令行客户端参数一览
client.ts使用argparse定义了一组与运行 DeepSpeech 推理直接相关的参数,是理解后续代码入口的关键:
| 参数 | 必填 | 说明 |
|---|---|---|
--model | 是 | 模型文件路径(protocol buffer 二进制冻结图) |
--scorer | 否 | 外部 Scorer 文件路径 |
--audio | 是 | 待识别的 WAV 音频文件路径 |
--version | 否 | 打印 DeepSpeech 版本号与运行时(Node/Electron)后退出 |
--extended | 否 | 输出扩展元数据(逐 token 时间戳、置信度) |
--stream | 否 | 使用流式推理代码路径(测试用) |
--hot_words | 否 | 热词及其加成,词:加成对以逗号分隔 |
--beam_width | 否 | 解码束宽(代码中通过model.setBeamWidth生效) |
--lm_alpha/--lm_beta | 否 | Scorer 的语言模型权重与词插入权重 |
其中--version通过自定义VersionAction实现,它会同时区分运行环境:若process.versions.electron存在则打印Runtime: Electron,否则打印Runtime: Node,这对排查 Electron 下原生绑定加载问题很有帮助。
三、创建模型实例并加载模型
文档的第一个代码块(js_ref_model_start/js_ref_model_stop标记之间)展示了模型加载的核心逻辑:
console.error('Loading model from file %s', args['model']); const model_load_start = process.hrtime(); let model = new Ds.Model(args['model']); const model_load_end = process.hrtime(model_load_start); console.error('Loaded model in %ds.', totalTime(model_load_end)); if (args['beam_width']) { model.setBeamWidth(args['beam_width']); }new Ds.Model(modelPath)对应 index.ts 中的Model构造函数:它调用原生绑定CreateModel,若返回状态码非 0 则抛出包含错误码与错误信息的异常(错误码可进一步通过ErrorCodeToErrorMessage转义,参见 doc/Error-Codes.rst)。因此务必用try/catch或进程级错误处理包裹模型加载。
模型加载成功后,可立即调用:
model.sampleRate():获取模型期望的采样率(示例代码用它决定音频重采样目标,DeepSpeech 默认模型为 16000 Hz);model.beamWidth()/model.setBeamWidth(w):查询或设置解码束宽。束宽越大,结果越好但解码耗时越长(见 index.ts 的注释说明);model.enableExternalScorer(path)与model.disableExternalScorer():启用/禁用外部语言模型;model.setScorerAlphaBeta(alpha, beta):调整 CTC 解码器的语言模型权重(alpha)与词插入权重(beta)。
示例代码中 scorer 的加载流程是:先enableExternalScorer,随后若同时提供了--lm_alpha与--lm_beta则调用setScorerAlphaBeta微调解码参数。
四、音频读取与采样率校验
在推理之前,客户端用node-wav解码音频文件,并做了一次关键的一致性检查:
const buffer = Fs.readFileSync(args['audio']); const result = Wav.decode(buffer); if (result.sampleRate < desired_sample_rate) { console.error(`Warning: original sample rate ( ${result.sampleRate})` + `is lower than ${desired_sample_rate} Hz. ` + `Up-sampling might produce erratic speech recognition.`); }注意:当原始采样率低于模型期望值(如 16 kHz)时,程序仅输出警告而不中断——因为后续 SoX 转换流会统一将音频重采样到desired_sample_rate,但低采样率升采样可能造成识别质量波动。若 WAV 采样率高于模型期望值则无此警告。
随后通过sox-stream构建转换管道,将任意采样率的 WAV 统一转为模型所需格式的裸 PCM:
let conversionStream = bufferToStream(buffer). pipe(Sox({ global: { 'no-dither': true, 'replay-gain': 'off', }, output: { bits: 16, rate: desired_sample_rate, channels: 1, encoding: 'signed-integer', endian: 'little', compression: 0.0, type: 'raw' } }));这套参数明确了 DeepSpeech 输入音频的硬性约束:16-bit、单声道、小端、有符号整数、采样率与模型一致、raw 裸 PCM。client.ts依赖系统中安装的 SoX 完成实际转换,若需在无 SoX 环境下运行,应在调用前自行完成等价转换。
五、批式推理:stt 与 sttWithMetadata
文档的第二个代码块(js_ref_inference_start/js_ref_inference_stop标记之间)是批式推理部分。所谓批式,是指将整段音频一次性送入模型:
if (args['extended']) { let metadata = model.sttWithMetadata(audioBuffer, 1); console.log(candidateTranscriptToString(metadata.transcripts[0])); Ds.FreeMetadata(metadata); } else { console.log(model.stt(audioBuffer)); }两条路径分别对应 index.ts 中的两个方法:
model.stt(aBuffer: Buffer): string:返回纯文本识别结果,对应原生SpeechToText;model.sttWithMetadata(aBuffer, aNumResults = 1): Metadata:返回Metadata对象,包含多条候选转录(数量不超过aNumResults),每条候选转录由逐 token 组成,带置信度与时间信息。
Metadata/CandidateTranscript/TokenMetadata三个 TypeScript 接口(见 index.ts)分别表示:
TokenMetadata:单个 token 的文本、timestep(以 20ms 为单位的位置)与start_time(秒);CandidateTranscript:tokens数组与confidence(近似为该转录各 timestep 声学模型 logit 之和,用于粗略比较候选质量);Metadata:transcripts候选数组。
candidateTranscriptToString辅助函数将一条候选转录的 token 文本依次拼接为最终字符串:
function candidateTranscriptToString(transcript: Ds.CandidateTranscript): string { var retval = "" for (var i = 0; i < transcript.tokens.length; ++i) { retval += transcript.tokens[i].text; } return retval; }内存管理是使用元数据路径的关键:sttWithMetadata、intermediateDecodeWithMetadata、finishStreamWithMetadata返回的Metadata必须显式调用Ds.FreeMetadata(metadata)释放。这是因为 deepspeech.i 中的 SWIG 注释明确指出:Node.js 不保证 finalizer 会被调用,因此必须由应用层负责释放,否则会造成内存泄漏。
六、流式推理:createStream 与增量解码
当传入--stream时,示例走流式代码路径,将音频分块喂给模型,支持边录音边出中间结果:
let stream = model.createStream(); conversionStream.on('data', (chunk: Buffer) => { stream.feedAudioContent(chunk); if (args['extended']) { let metadata = stream.intermediateDecodeWithMetadata(); console.error('intermediate: ' + candidateTranscriptToString(metadata.transcripts[0])); } else { console.error('intermediate: ' + stream.intermediateDecode()); } }); conversionStream.on('end', () => { if (args['extended']) { let metadata = stream.finishStreamWithMetadata(); console.log(candidateTranscriptToString(metadata.transcripts[0])); } else { console.log(stream.finishStream()); } });StreamImpl类(index.ts)不能被直接实例化,必须通过model.createStream()创建(底层调用原生CreateStream)。其方法含义如下:
| 方法 | 说明 |
|---|---|
feedAudioContent(aBuffer) | 喂入一段 16-bit 单声道原始 PCM 样本,采样率须与模型匹配 |
intermediateDecode() | 返回当前累积音频的中间解码文本 |
intermediateDecodeWithMetadata(n = 1) | 返回含元数据的中间结果,需FreeMetadata |
finishStream() | 结束流并返回最终结果;调用后流即被释放,不能再使用 |
finishStreamWithMetadata(n = 1) | 结束流并返回含元数据结果,同样会释放流且需FreeMetadata |
配套的还有模块级导出函数FreeStream(stream)(不执行解码直接销毁流状态,适用于不再需要结果、想省去昂贵解码开销的场景)与FreeModel(model)(销毁模型并释放资源)。示例代码在批式推理结束后调用Ds.FreeModel(model),再通过setTimeout(handleExit, 1000)等待资源充分释放后退出进程;在 Electron 环境下handleExit会调用app.quit()。
七、热词(Hot Words)定制
--hot_words参数以词:加成的逗号分隔形式传入,示例代码逐对解析并调用model.addHotWord:
for (let word_boost of args['hot_words'].split(',')) { let word = word_boost.split(':'); model.addHotWord(word[0], parseFloat(word[1])); }根据 index.ts,addHotWord的正向加成会提高词出现在转录中的概率,负向则降低;但过大的正向加成可能导致该热词后的字母被拆散。同时需要注意:未出现在 Scorer 词表中的词(如专有名词)或包含空格的字符串不会被生效。相关接口还包括eraseHotWord(word)与clearHotWords()。完整示例可参考 doc/HotWordBoosting-Examples.rst。
八、完整源码与测试验证
示例程序约 170 行,除上述核心逻辑外还包含totalTime计时工具函数(基于process.hrtime输出秒数)、bufferToStream(将Buffer包装成可管道传输的Duplex流)以及整段推理耗时统计:
const audioLength = (audioBuffer.length / 2) * (1 / desired_sample_rate); const inference_start = process.hrtime(); // ... 推理 ... const inference_stop = process.hrtime(inference_start); console.error('Inference took %ds for %ds audio file.', totalTime(inference_stop), audioLength.toPrecision(4));注意这里audioBuffer.length / 2是因为 16-bit 采样每样本占 2 字节,换算得到音频秒数。完整代码可直接查看 native_client/javascript/client.ts。
在 CI 侧,ci_scripts/node-tests.sh 会依次执行run_all_inference_tests(覆盖批式推理与--extended元数据路径)、run_js_streaming_inference_tests(覆盖--stream流式路径)与run_hotword_tests(覆盖热词功能),测试辅助实现见 ci_scripts/all-utils.sh。因此读者若想快速验证本地 Node.js 环境是否工作正常,可参照该脚本使用data/smoke_test目录下的样例音频(如 data/smoke_test/LDC93S1.wav)与data/alphabet.txt配套模型进行冒烟测试。
九、构建与打包(进阶)
若需从源码构建 Node.js 绑定,native_client/javascript/Makefile 提供了完整流水线:
make package.json:由package.json.in模板注入项目名与版本(版本取自 training/deepspeech_training/VERSION);make deepspeech_wrap.cxx:调用 SWIG 将 deepspeech.i 编译为 C++ 包装层;make build:通过node-pre-gyp配置并编译原生模块,产出lib/binding/*/下的deepspeech.node;make npm-pack:tsc编译 TypeScript 后执行npm pack生成发布用 tgz 包。
index.ts在加载时通过node-pre-gyp的binary.find定位预编译绑定;针对 Windows 平台,代码会临时修改PATH以帮助动态链接器找到依赖库,且在 Electron 下会处理app.asar到app.asar.unpacked的路径替换——这是 Electron 场景特有的坑,值得使用者留意。
十、常见问题与要点小结
- 输入音频格式:必须是 16-bit、单声道、小端、有符号整数的 PCM,采样率与模型一致(通常 16000 Hz),批式
stt接受整段 Buffer,流式feedAudioContent接受分块 Buffer; - 元数据必须释放:所有返回
Metadata的调用(sttWithMetadata、intermediateDecodeWithMetadata、finishStreamWithMetadata)之后都要调用Ds.FreeMetadata; - 流生命周期:
finishStream/finishStreamWithMetadata会释放流对象,之后不可复用;不需要结果时可用FreeStream提前销毁; - 错误处理:
Model构造、setBeamWidth、addHotWord、enableExternalScorer等 API 在出错时均会抛出包含十六进制错误码的异常,生产代码应统一捕获; - 运行时差异:
Version()可返回库版本,结合process.versions.electron可区分 Node 与 Electron 运行环境,便于定位原生绑定加载问题。
以上示例与 API 的完整对应关系可继续查阅 doc/NodeJS-API.rst(Model、StreamImpl、Metadata等类型的成员文档)以及 native_client/javascript/client.ts(完整可运行源码)。
- 人工智能
- 语音
- 音频
- 深度学习
【免费下载链接】DeepSpeech
DeepSpeech is an open source embedded (offline, on-device) speech-to-text engine which can run in real time on devices ranging from a Raspberry Pi 4 to high power GPU servers.
相关推荐
DeepSpeech C API 实战指南:模型加载与语音识别推理示例解析
DeepSpeech C API 实战指南:模型加载与语音识别推理示例解析 本篇技术指南围绕 DeepSpeech 仓库 doc/C Examples.rst
人工智能语音音频深度学习DeepSpeech .NET API 实战指南:从模型加载到语音识别推理的 C 完整示例
DeepSpeech .NET API 实战指南:从模型加载到语音识别推理的 C 完整示例 导读 本文以 DeepSpeech 仓库中的官方 .NET 示例文档
人工智能语音音频深度学习DeepSpeech C API 完全指南:模型管理、外部评分器与流式语音识别
DeepSpeech C API 完全指南:模型管理、外部评分器与流式语音识别 本指南以 DeepSpeech 仓库的官方 C API 文档( doc/C AP
人工智能语音音频深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考