☰
使用 Candle 运行 Silero VAD:基于 ONNX 的 Rust 流式语音活动检测实战
2026/10/2 2:02:07 网站建设 项目流程
  • 人工智能
  • 大模型
  • 机器学习
  • 深度学习
  • 本地部署
  • 模型推理服务

【免费下载链接】candle

Minimalist ML framework for Rust

项目地址:https://gitcode.com/GitHub_Trending/ca/candle
点击查看免费下载

语音活动检测(Voice Activity Detection, VAD)是音频处理中最基础也最关键的环节之一,它决定了"这段音频里人到底有没有在说话"。本文基于当前仓库中的 candle-examples/examples/silero-vad/README.md 与对应示例源码,完整讲解如何用 Rust 与 Candle 加载 Silero VAD v5 的 ONNX 模型,通过命令行管道(stdin)消费流式 PCM 音频,逐帧输出说话概率预测。读完本文,你将掌握该示例的全部命令行参数、两种麦克风采集方案(arecord 与 SoX)、帧/上下文(frame/context)机制的源码级原理,以及如何在 Candle 的 ONNX 推理链路中维护 VAD 的循环状态(state)。

Silero VAD 是什么,为什么选择在 Candle 中运行

Silero VAD v5 是一个开源的语音活动检测模型,专为流式音频场景设计:它按固定长度的帧(chunk)接收音频,对每一帧输出一个 0 到 1 之间的"说话概率",并维护一个跨帧传递的内部状态(state),从而让模型能感知历史音频上下文,在噪声、静音与人声交界处做出更稳定的判断。由于模型以 ONNX 格式分发,恰好与 Candle 的 ONNX 推理能力(candle-onnx)天然契合——不需要训练或转换权重,直接从 Hugging Face Hub 拉取 ONNX 文件即可在纯 Rust 程序中完成推理。

当前仓库中的示例使用 onnx-community/silero-vad 可以看到,默认情况下它会通过 Candle 的 hub 封装(candle-examples/src/hub.rs)自动下载该模型;也支持通过--model-id直接指定本地模型文件路径,绕过在线下载。

构建与运行前的准备

该示例并不是默认 feature,需要显式启用onnx特性,这一约束在 candle-examples/Cargo.toml 中有明确声明:

[[example]] name = "silero-vad" required-features = ["onnx"]

因此构建命令必须带上--features onnx:

cargo run --example silero-vad --release --features onnx -- --sample-rate 16000

几点补充说明:

  • onnx特性对应candle-onnx依赖(见 candle-examples/Cargo.toml),它负责解析 ONNX protobuf 并执行计算图求值。
  • 如果你打算用 CUDA 加速,可以再加上--features cuda(在 candle-examples/Cargo.toml 中,cuda特性会联动启用candle/cuda与cudaforge);macOS 上则可选用--features metal。
  • 编译candle-onnx时要求系统中有protoc可用,如果报 "Could not findprotocinstallation",需要先安装 protoc 并加入PATH,详见 candle-onnx/README.md。
  • 首次运行会自动从 Hugging Face Hub 下载模型(缓存机制与进度显示见 candle-examples/src/hub.rs),之后会命中本地缓存,不再重复下载。

命令行参数逐一解析

主程序使用clap解析参数(见 main.rs),全部参数如下表:

参数类型默认值说明
--cpu布尔false强制在 CPU 上运行,而非 GPU
--tracing布尔false开启 Chrome tracing,生成trace-timestamp.json性能分析文件
--input字符串无指定输入源(当前实现中音频从 stdin 读取)
--sample-rate枚举无(必填)采样率,仅支持8000或16000
--model-id字符串无本地 ONNX 模型文件路径;不传则自动从 Hub 下载
--config-file字符串无配置文件路径
--which枚举silero选择模型,目前仅有silero一个取值

其中最关键的是--sample-rate。它直接决定了两组核心参数(见 main.rs):

  • 8 kHz:帧大小(frame_size)= 256,上下文大小(context_size)= 32;
  • 16 kHz:帧大小 = 512,上下文大小 = 64。

--which枚举中保留了一个量化的SileroQuantized分支,但源码中以注释明确标注 "candle-onnx doesn't support Int8 dtype"(main.rs),因此当前只有 FP32 的silero模型可以实际运行。

方式一:用 arecord 采集麦克风

arecord是 ALSA(Advanced Linux Sound Architecture)自带的命令行录音工具。原文档给出的命令:

$ arecord -t raw -f S16_LE -r 16000 -c 1 -d 5 - | cargo run --example silero-vad --release --features onnx -- --sample-rate 16000

命令拆解:

  • -t raw:输出原始(无容器)PCM 数据;
  • -f S16_LE:16 位有符号小端整数(signed 16-bit little-endian),这与示例中I16Frames的解析逻辑一一对应;
  • -r 16000:采样率 16 kHz;
  • -c 1:单声道;
  • -d 5:录制 5 秒;
  • 末尾的-:表示将音频写到 stdout,通过管道|送入示例程序的 stdin。

注意示例程序约定 stdin 输入的采样率必须与--sample-rate一致:如果录音用了 8 kHz,运行参数就应改为--sample-rate 8000,程序会按 256 帧长处理。

方式二:用 SoX 采集并重采样

如果声卡/麦克风不支持直接输出 16 kHz,或者你想用 48 kHz 的通用采集配置,原文档提供的 SoX 双段管道可以把采样率转换为模型所需的 16 kHz:

$ rec -t raw -r 48000 -b 16 -c 1 -e signed-integer - trim 0 5 | sox -t raw -r 48000 -b 16 -c 1 -e signed-integer - -t raw -r 16000 -b 16 -c 1 -e signed-integer - | cargo run --example silero-vad --release --features onnx -- --sample-rate 16000

命令拆解:

  • 第一段rec(SoX 的录音命令):以 48 kHz、16 bit、单声道、signed-integer 格式录制原始 PCM,trim 0 5表示截取开头 5 秒,输出到 stdout;
  • 第二段sox:把 48 kHz 的原始 PCM 流读入(-t raw -r 48000 -b 16 -c 1 -e signed-integer -),重采样到 16 kHz 后以同样的原始格式写出(-t raw -r 16000 -b 16 -c 1 -e signed-integer -);
  • 最后送入cargo run --example silero-vad ... --sample-rate 16000。

这套写法完全兼容示例程序的输入约定:无论采集端是多少采样率,最终喂给模型的都是 16 kHz / S16_LE / 单声道的原始 PCM。

源码剖析:从 stdin 字节流到说话概率

理解了命令后,我们再深入 main.rs 看它内部是如何一步步完成推理的,这既是理解该示例的关键,也是后续将其嵌入自有音频管线的基础。

1. 字节流 → i16 → f32 的帧迭代器

I16Frames(main.rs)是一个自定义迭代器,负责从任意std::io::Read(这里是 stdin)中按帧读取数据。它的工作方式:

  • 初始化时按frame_size * 2字节(每个 i16 占 2 字节)分配缓冲区;
  • 每次读取累积到一帧完整大小后,把每 2 个字节按小端序还原为i16(i16::from_le_bytes),再除以i16::MAX归一化到[-1.0, 1.0]的 f32 范围;
  • 若音频流提前结束且不足一帧,则直接丢弃不足部分(对应 main.rs 中chunk.len() < frame_size时的continue)。

2. 模型加载与设备选择

模型加载分两步:

  • 若未指定--model-id,通过candle_examples::hub::Api从onnx-community/silero-vad仓库获取onnx/model.onnx(main.rs);
  • 用candle_onnx::read_file读取并解码 ONNX protobuf,得到ModelProto(对应 candle-onnx/src/lib.rs 中的 prost 解码逻辑)。

设备选择则交给candle_examples::device(args.cpu)(candle-examples/src/lib.rs):--cpu强制 CPU;否则按 CUDA → Metal → CPU 的优先级自动选择可用设备,并在回退到 CPU 时打印提示(如 "Running on CPU, to run on GPU(metal), build this example with--features metal")。

3. 循环状态(state)与上下文(context)的维护

VAD 的核心在于跨帧的循环状态。示例用State结构体封装了三样东西(main.rs):

  • sample_rate:作为输入张量传给 ONNX 图的sr输入;
  • state:形状为(2, 1, 128)的 F32 张量,初始化为全零,承载模型的循环记忆;
  • context:形状为(1, context_size)的张量,保存上一帧末尾的音频样本,用于帧间平滑衔接。

每帧推理的流程(main.rs):

  1. 将当前帧chunk转为形状(1, frame_size)的张量;
  2. 用Tensor::cat把上一帧的context拼到当前帧前面,得到(1, frame_size + context_size)的输入;
  3. 构造输入字典{"input": ..., "sr": ..., "state": ...},调用candle_onnx::simple_eval执行 ONNX 计算图;
  4. 从图输出中取出两个结果:第一个输出作为本帧说话概率(标量),第二个输出作为新的state写入下一轮;
  5. 取当前帧末尾context_size个样本作为下一轮的context。

simple_eval的实现见 candle-onnx/src/eval.rs,它会先把图中的 initializer 常量填充进值表,再按输入张量校验形状与 dtype,最后顺序执行算子。从代码注释(eval.rs)可以看出,当前实现是"每次求值直接跑一遍 proto 计算图"的直译模式,尚未引入中间表示缓存优化,因此在实时场景中单次推理的开销主要来自图求值本身。

4. 输出解读

每帧推理后程序打印一行vad chunk prediction: {output},其中 output 是 0~1 的说话概率(程序用assert_eq!(output.len(), 1)确认模型输出确实是标量)。全部帧处理完后,还会打印所有帧预测的算术平均:

vad chunk prediction: 0.0143 vad chunk prediction: 0.9876 ... vad average prediction: 0.5123

如果你的应用只需要"某段时间是否有人说话"这类粗粒度判断,可以直接看平均预测;如果需要逐帧门限(比如帧概率 > 0.5 判定为语音段),可以基于每帧输出自行叠加阈值逻辑——示例本身只负责输出原始概率,不做门限判定。

另外,程序启动时还会打印一行 SIMD 能力摘要:

avx: true, neon: false, simd128: false, f16c: false

这是通过candle::utils::with_avx()等查询编译时启用的指令集特性(见 main.rs),可用于确认当前构建是否启用了 AVX 等 CPU 加速。

5. Tracing 与性能定位

带--tracing运行时,程序会通过tracing_chrome::ChromeLayerBuilder构建 Chrome tracing 层,输出trace-timestamp.json性能分析文件(main.rs)。在排查"哪一步耗时高"(模型下载、模型加载、逐帧推理)时,可以借助它逐段分析;代码中已经用Instant分别记录了下载、加载、推理三段耗时并打印。

运行结果预期与常见问题

  • 输出流:程序从 stdin 读入原始 PCM,每处理完一帧输出一行预测,音频结束后输出平均预测。5 秒 @ 16 kHz 对应 160000 个样本,按 512 帧长约产生 312 帧预测输出。
  • 静音与说话概率:Silero VAD 对静音帧通常输出接近 0 的概率,对人声帧输出接近 1 的概率;数值表现取决于具体音频与噪声环境,示例不提供阈值调节参数。
  • 量化模型暂不可用:onnx-community/silero-vad同时提供了model_quantized.onnx,但受限于 candle-onnx 暂不支持 Int8 权重(main.rs 中的 TODO 注释),示例只使用 FP32 模型。
  • 管道断裂:如果在运行中提前结束录音程序(如 Ctrl+C),stdin EOF 后迭代器会正常终止并输出平均预测,不会产生错误。

小结

通过这个示例,你可以看到 Candle 处理流式音频模型的一条完整通路:hf-hub拉取 ONNX 权重 →candle_onnx::read_file解析模型 →simple_eval逐帧求值 → 手动维护 state/context 实现跨帧记忆。它既是一个开箱即用的 VAD 命令行工具,也是一个很好的"如何用 Rust + Candle 跑带循环状态的 ONNX 模型"的参考模板。如果你想进一步改造,可以把I16Frames换成任意来源的音频块(文件、网络流、语音引擎回调),只需保持 S16_LE 单声道与--sample-rate对应的帧长即可无缝接入。

延伸阅读:完整的示例代码见 candle-examples/examples/silero-vad/main.rs,运行说明见 candle-examples/examples/silero-vad/README.md;ONNX 推理引擎的解析与求值实现见 candle-onnx/src/lib.rs 与 candle-onnx/src/eval.rs;模型下载与缓存逻辑见 candle-examples/src/hub.rs。

  • 人工智能
  • 大模型
  • 机器学习
  • 深度学习
  • 本地部署
  • 模型推理服务

【免费下载链接】candle

Minimalist ML framework for Rust

项目地址:https://gitcode.com/GitHub_Trending/ca/candle
点击查看免费下载

相关推荐

上一篇:Markn:重新定义Markdown预览体验的实时渲染解决方案
下一篇:终极指南:5分钟掌握IDM激活脚本的完整使用方法

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询