中文语音关键词检测(KWS)+ 一次性语音转写(ASR)+ 语音活动检测(VAD)引擎,打包成可独立发布的 Rust 库。 引擎只输出事件(识别到哪个关键词、转写出什么文本、什么时候在说话),"该做什么"由调用方决定 —— 可接入智能家居 API、游戏宏、IoT 网关、辅助功能工具等任意场景。
-
中文优先,预训练模型开箱可用。 内置 WenetSpeech 10k 小时训练的 zipformer KWS 权重(~6 MB int8 量化版),clone 完立刻能识别任意中文关键词。拼音 token 表和
keywords_invoker.txt开箱匹配,不用像通用 Whisper / Vosk 那样自行做汉化适配。 -
三种能力自由拆装。 KWS(关键词触发)/ ASR(自由语音转写)/ VAD(语音活动检测)是三个独立 Cargo feature:智能家居只用 KWS,会议记录只用 ASR,"唤醒词 + 自由指令" 组合用 KWS+ASR+VAD。关掉的能力在编译期就消失,不留运行时开销。
-
纯事件输出,永远不替你决定。 API 产出
KeywordEvent/SpeechSegment/RecognitionResult就收工 —— 敲键盘、调 HTTP、发 MQTT、播 TTS 回复,全是你的业务代码。对比行业里常见的"语音库捆绑 enigo/autoit/PyAutoGUI",voicekit 只做识别,上层永远在你手里。 -
跨平台 / 跨线程 / 跨语言的工程化封装。 cpal stream 关在专属 OS 线程,
Send安全,FFI 跨线程free不 UB(macOS CoreAudio 的历史坑已规避)。除 Rust crate 外同步产出libvoicekit_ffi.{dylib,so,dll}+ cbindgen 自动生成的voicekit.h,Python / Node / Go / C++ 一致接入。 -
Rust 性能下限 + sherpa-onnx 工程上限。 无 Python runtime、无 GIL、无异步调度开销;音频回调立即入通道,模型推理在 worker 线程,回调路径从不阻塞 OS 音频线程。int8 量化权重 + cmake 原生编译的 sherpa-onnx,纯 CPU 环境持续监听不发热。
-
Clone 即跑,也已为独立发布做好准备。 必要资产(KWS 权重 + Silero VAD + tokens + 测试词条)全在
assets/下,cargo run --example recognize_loop就能验证全链路。crates/voicekit/Cargo.toml已通过exclude把大型 ONNX 从 crates.io 打包中剔除。
use voicekit::KeywordDetector;
let mut det = KeywordDetector::from_model_dir("./assets/model/kws")?;
det.start(|ev| println!("matched: {} (score {:.2})", ev.keyword, ev.score))?;
std::thread::park();use voicekit::{SpeechRecognizer, VoiceActivityDetector};
let asr = SpeechRecognizer::from_model_dir("./assets/model/asr")?;
let vad = VoiceActivityDetector::from_model_file("./assets/model/vad/silero_vad.onnx")?;
for utt in vad.utterances_from_mic()? {
println!("{}", asr.transcribe(&utt.samples)?.text);
}就这么多。utterances_from_mic 返回一个阻塞 Iterator,说话间隔 ≥0.5s 就切一句。Drop 掉或调 stopper().stop() 停止。
两种风格都行:
// 回调
det.start(|ev| handle(ev))?;
// channel(更适合 select! / recv_timeout / 主线程控制)
let rx = det.start_channel()?;
for ev in rx {
handle(ev);
}use voicekit::{KeywordDetector, SpeechRecognizer, VoiceActivityDetector};
let mut kws = KeywordDetector::from_model_dir("./assets/model/kws")?;
let asr = SpeechRecognizer::from_model_dir("./assets/model/asr")?;
let vad = VoiceActivityDetector::from_model_file("./assets/model/vad/silero_vad.onnx")?;
// 示意:kws 命中后由你的业务逻辑把后续 VAD 段丢给 asr.transcribe(...)完整方式由应用层协调(怎么共享一个麦克风流、怎么处理打断等都是产品决策);voicekit 只提供三个原语。
| feature | 默认 | 作用 |
|---|---|---|
kws |
✅ | 启用 KeywordDetector |
asr |
✅ | 启用 SpeechRecognizer |
vad |
✅ | 启用 VoiceActivityDetector |
builtin-audio |
✅ | 启用 cpal 麦克风采集;关掉就只能用 push_samples |
[dependencies]
# 全套(默认)
voicekit = "0.1"
# 只要 KWS + 麦克风
voicekit = { version = "0.1", default-features = false, features = ["kws", "builtin-audio"] }
# 只要 VAD 做分段,音频自己管
voicekit = { version = "0.1", default-features = false, features = ["vad"] }voicekit/
├── Cargo.toml / DESIGN.md / README.md / LICENSE
├── .cargo/config.toml # sherpa-onnx dylib 的 rpath
├── assets/
│ ├── model/
│ │ ├── kws/ # 已 bundle,17 MB,KWS 开箱即用
│ │ │ ├── encoder/decoder/joiner-epoch-12-*.onnx (+ int8)
│ │ │ ├── tokens.txt
│ │ │ ├── keywords_invoker.txt / keywords_test.txt
│ │ │ └── README.md
│ │ ├── asr/ # 空槽位,放你下载的 offline zipformer
│ │ │ └── README.md # 下载指引(一条 curl 命令)
│ │ └── vad/ # Silero VAD(2.2 MB,已 bundle)
│ │ └── silero_vad.onnx
│ └── skills_example.json # game_macro 示例数据
└── crates/
├── voicekit/ # 核心库
│ └── examples/
│ ├── smart_home.rs # KWS → HTTP
│ ├── game_macro.rs # KWS → 键盘动作
│ ├── recognize_once.rs # ASR 3 秒单次录音
│ └── recognize_loop.rs # ASR + VAD 持续监听(<50 行)
├── voicekit-ffi/ # C ABI(KWS only)
│ ├── cbindgen.toml
│ └── include/voicekit.h
└── voicekit-cli/ # KWS 调试 CLI
# 首次编译:sherpa-onnx 原生库经 cmake 拉起,约 3–5 分钟
cargo build --workspace --all-targets
# KWS CLI:对麦克风说 测试 / 小爱同学 / 开灯 / 关灯 / 打开空调 / 打开电视
./target/debug/voicekit ./assets/model/kws ./assets/model/kws/keywords_test.txt
# ASR 持续监听(需要先按 assets/model/asr/README.md 下载 offline 模型)
cargo run --example recognize_loop -p voicekit -- \
./assets/model/asr ./assets/model/vad/silero_vad.onnx- KWS 误触 →
KwsModelConfig.keywords_threshold调高(默认 0.25,可到 0.35) - KWS 漏识别 → 调低到 0.15,或确认关键词 ≥ 4 音节且含特色音素(zh / sh / q / x 等)
- KWS 双触发 →
BehaviorConfig.min_trigger_interval_ms调大(默认 200,智能家居建议 1000) - VAD 句子被切碎 →
VadConfig.min_silence_duration调大(默认 0.5s → 0.8s) - VAD 太晚出结果 → 调小到 0.3s
- 采样率不是 16kHz →
voicekit::Resampler自己做(VAD 内部已自动处理)
cargo build --release -p voicekit-ffi
# 产物:target/release/libvoicekit_ffi.{dylib,so,dll} + .a头文件:crates/voicekit-ffi/include/voicekit.h
当前 FFI 仅暴露 KWS;ASR 的 C 接口因为底层 sherpa-onnx 可能抛 C++ 异常不易安全包装,需要时再加。
from ctypes import CDLL, CFUNCTYPE, Structure, c_char_p, c_uint64, c_float, c_void_p, POINTER
lib = CDLL("./target/release/libvoicekit_ffi.dylib")
class VkEvent(Structure):
_fields_ = [("keyword", c_char_p), ("timestamp_ms", c_uint64), ("score", c_float)]
CB = CFUNCTYPE(None, POINTER(VkEvent), c_void_p)
def on_kw(ev, _): print("got:", ev.contents.keyword.decode("utf-8"))
lib.vk_detector_new.restype = c_void_p
det = lib.vk_detector_new(b"./assets/model/kws")
lib.vk_detector_start(c_void_p(det), CB(on_kw), None)
input("press enter to stop")
lib.vk_detector_stop(c_void_p(det)); lib.vk_detector_free(c_void_p(det))编辑 assets/model/kws/keywords_test.txt(或 keywords_invoker.txt),每行:
<拼音 tokens 空格分隔> @<显示名>
拼音 token 必须来自 assets/model/kws/tokens.txt(227 个,带声调)。运行中改完 reload_keywords 即可热加载,不用重启。
- voicekit 源代码:MIT,见 LICENSE
- 打包的 KWS 模型:Apache 2.0,见 assets/model/kws/README.md
- 打包的 Silero VAD:MIT(snakers4/silero-vad)
- 你自己下载的 ASR 模型:license 自查