自定义语音唤醒词:从训练到部署的完整链路实践
为什么你需要一个自定义唤醒词?
"小爱同学"、"Hey Siri"、"小度小度"------这些唤醒词你可能每天都会用。但如果你是嵌入式开发者、智能硬件创业者,或者只是想给自己的 DIY 项目加个语音唤醒,你会发现一个问题:
市面上的方案要么太贵,要么太封闭。
百度、思必驰的商业方案需要商务对接;Snowboy 早已停止维护;Porcupine 虽然能自定义唤醒词,但需要在线激活授权,按设备数量收费,小团队难以承受。想自定义一个词?要么花钱,要么自己造轮子。
本文带你走通一条完整的开源链路:训练模型 → 导出 ONNX → 端侧推理 → 实际部署。
整体架构
javascript
┌──────────────────────┐
│ 训练阶段 │
│ OpenWakeWord / 听词 │ → ONNX 模型文件
│ 自定义数据 / 纯合成 │
└──────────────────────┘
↓
┌──────────────────────┐
│ 推理阶段 │
│ onnx-wakeword │ → 跨平台 C++/Android/Linux/Web/ESP32
│ 轻量级 ONNX Runtime │
└──────────────────────┘
↓
┌──────────────────────┐
│ 部署阶段 │
│ ESP32 / Android │ → 离线运行,无需联网
│ Linux / Web │
└──────────────────────┘
第一步:从哪里搞到模型?
方案 A:自己训练(OpenWakeWord)
OpenWakeWord 是目前最活跃的开源唤醒词训练框架。它用合成数据(TTS 生成)+ 真实噪音混合,不需要你录制任何音频。
bash
pip install openwakeword
# 准备一个包含唤醒词的文本文件
# 运行训练脚本,指定你的唤醒词
优点是完全可控,缺点是调参门槛不低,GPU 训练时间较长。
另外 nanoWakeWord 和 WeKws 也是不错选择:前者轻量级、专注嵌入式,后者基于 WeNet 生态、中文支持好。模型格式各有不同,但都能导出 ONNX。
方案 B:在线平台生成
像听词这类平台,输入关键词直接拿 ONNX 模型。底层用合成语音 + 自研训练管线,几分钟出结果。
python
输入:"你好曼波"
输出:nihaomanbo_basic_dcnn_v9.0.zip (约 94KB)
├── dscnn_multiscale_nihaomanbo.onnx
└── model_info.json
不用管线、不用 GPU、不用调参,适合快速验证和小批量项目。
第二步:推理引擎选型
拿到 ONNX 模型后,如果要自己写推理代码,你需要处理:
- Mel-Spectrogram 特征提取(C++ 实现 STFT/FFT/Mel 滤波器组)
- ONNX Runtime 加载和推理
- 后处理:sigmoid → softmax → 滑动窗口去抖
- 多层级防误触发逻辑
这些写起来大概 2000 行 C++。不过轮子已经有人造好了:
🔧 onnx-wakeword:一套代码,四端运行
github.com/voicute/onn... 是一个专门为唤醒词场景设计的轻量级推理引擎。
bash
onnx-wakeword/
├── onnx-wakeword.cpp # C++ 核心引擎(Mel 特征提取 + 推理 + 防误触发)
├── android/ # Android AAR,支持 ort-android + NNAPI
├── esp32/ # ESP-IDF Component,适配 ESP32-S3
├── linux/ # Linux 可执行文件 + Python bindings
├── web/ # WebAssembly,浏览器端运行
└── models/ # 预训练模型示例(曼波、来福、咕咕嘎嘎等)
核心设计
Mel 特征提取内置。 不需要单独加载 melspectrogram 模型,引擎内部用高效的 C++ 实现了完整的音频处理管线:
c
PCM 16kHz → 分帧加窗 → STFT → Mel 滤波器组 → log-mel 特征 → 模型输入
支持两种模型结构:
| 模型类型 | 特点 | 模型大小 |
|---|---|---|
| DSCNN (Multiscale) | 速度快,适合 MCU | ~130KB |
| Embedding-based | 精度高,适合 CPU | ~500KB+ |
内置五层防误触发(DetectionLogic):
| 层级 | 策略 | 说明 |
|---|---|---|
| L1 | 连续帧确认 | 连续 N 帧都检测到才初步确认 |
| L2 | 峰值/背景比 | 唤醒信号必须显著高于背景噪音 |
| L3 | 冷却时间 | 触发后一段时间内不再触发 |
| L4 | 多关键词互斥 | 多个唤醒词同时激活时做 softmax 决策 |
| L5 | 前后静音检测 | 真正的人声唤醒前后会有能量跳变 |
这些逻辑在引擎内部闭环,调用方只需要注册一个回调:
cpp
engine.setCallback([](const std::string& word, float prob) {
printf("检测到唤醒词: %s (置信度: %.2f)\n", word.c_str(), prob);
});
engine.start();
C++ 集成(一行搞定)
cpp
#include "onnx-wakeword.h"
auto engine = OnnxWakeWord::create(
"model.onnx", // 唤醒词模型
"mel.onnx" // melspectrogram 模型(可选,DSCNN 模式不需要)
);
engine->setThreshold(0.5);
engine->start();
Android 集成
kotlin
// build.gradle
implementation(files("libs/onnx-wakeword.aar"))
// Kotlin
val engine = OnnxWakeWord.create(assetManager, "model.onnx", "mel.onnx")
engine.setCallback { word, prob ->
Log.i("WakeWord", "检测到 $word ($prob)")
}
engine.start()
// 输入麦克风音频
engine.feedPcm(audioBuffer)
ESP32 集成
c
// 作为 ESP-IDF component 引入
// CMakeLists.txt
set(EXTRA_COMPONENT_DIRS components/onnx-wakeword-esp32)
// main.c
wakeword_handle_t handle;
wakeword_init(&handle, model_data, model_size);
wakeword_set_threshold(handle, 0.5f);
wakeword_start(handle);
// 在 I2S 回调中喂音频
wakeword_feed_pcm(handle, samples, sample_count);
Web 浏览器端
javascript
import { createDetector } from 'onnx-wakeword-web'
const detector = createDetector()
await detector.loadModels('model.zip', 'melspectrogram.onnx')
await detector.startMic((word, prob) => {
console.log(`检测到: ${word} (${prob})`)
})
第三步:实际部署踩坑记录
Android 上的实测数据
| 指标 | 数值 |
|---|---|
| 模型大小 | ~130KB(未压缩) |
| 内存占用 | ~20MB(含 ONNX Runtime) |
| 单次推理耗时 | < 5ms(高通骁龙 8 Gen2) |
| 实时率 | 0.05-0.1(远超实时) |
| 误触发率 | < 1次/小时(五层检测全开) |
测试设备:小米 14,Android 14。低端机型(如骁龙 660)推理耗时约 15-20ms,仍远低于实时要求。推荐使用 ort-android 包,支持 CPU + NNAPI 双后端。
唤醒词设计原则
别随便取一个词就当唤醒词。好的唤醒词能帮你把召回率从 80% 拉到 95%:
- 3-5 个字,4 个字最佳
- 有爆破音(b/p/d/t/k),能量足、好检测
- 避免叠词("叮叮"、"咕咕"),识别难度翻倍
- 避免混音/近音("小希" vs "小西")
- 不要太常见,否则日常对话中误触发满天飞
别问为什么知道------我们训练过"咕咕嘎嘎"和"优米优米",惨痛教训。
ONNX Runtime 版本兼容
不同平台需要不同版本的 ONNX Runtime:
| 平台 | ORT 版本 | 备注 |
|---|---|---|
| Linux x86_64 | 1.18+ | 最新版即可 |
| Android arm64 | 1.17-1.18 | 需交叉编译 |
| Web (WASM) | 1.20.1 | jsDelivr CDN 加载 |
DSCNN Multiscale 算子兼容性最好,基本所有版本都能跑。Embedding 模型需要较新的 Runtime。
总结
一条完整链路走下来:
选词 → 训练/生成 ONNX 模型 → onnx-wakeword 引擎加载 → 回调里点个灯/发个 HTTP → 完事
整个流程可以控制在 10 分钟内 (如果你用现成平台生成模型),或者半月到一个月(如果你自己训练,很折腾但是很锻炼人)。
开源方案已经足够能打了。想要更低的误触发、更快的响应速度?去看代码然后调参吧。
相关链接:
- 🔧 推理引擎:github.com/voicute/onn...
- 🎓 训练框架:OpenWakeWord · nanoWakeWord · WeKws
- 🎯 在线生成:www.voicute.com