libsherpacore - 基于 sherpa-onnx 的 C++ 语音交互静态库
基于 sherpa-onnx 的 C++ 语音交互静态库,封装 ASR(语音识别)、KWS(关键词唤醒)、VAD(人声检测)、TTS(语音合成)四大功能,提供统一的唤醒→识别→合成交互流程。
系统架构
┌─────────────────────────────────────────────────┐
│ 应用层(demo / 用户代码) │
│ connect 信号 → 回调处理结果 │
├─────────────────────────────────────────────────┤
│ SherPaOnnxManager(公共接口) │
│ loadModels / start / stop / speak / setWakeMode │
├──────────────┬──────────────┬────────────────────┤
│ mic 回调线程 │ AsrWorker │ TtsWorker 线程 │
│ VAD+KWS 检测 │ 超时+ASR解码 │ 合成+播放 │
│ 音频累积 │ │ │
├──────────────┴──────────────┴────────────────────┤
│ sherpa-onnx 引擎(OfflineRecognizer / │
│ OnlineRecognizer / KeywordSpotter / VAD / TTS) │
├───────────────────────────────────────────────────┤
│ miniaudio(音频采集/播放,header-only) │
├───────────────────────────────────────────────────┤
│ sigslot(信号槽 + EventLoop 跨线程投递) │
└─────────────────────────────────────────────────┘
设计要点
- 纯 C++17,无 Qt 等第三方框架依赖,跨平台(Windows / Linux)
- PIMPL 模式:公共头文件不暴露 sherpa-onnx / miniaudio 类型
- 静态库:无符号导出宏,直接链接
- sigslot 信号槽 + EventLoop:替代 Qt 信号槽 +
QMetaObject::invokeMethod(Qt::QueuedConnection),任意线程postToMainThread→ 主线程processAll执行 - 线程模型:mic 回调线程(miniaudio 内部)+ AsrWorker 线程(超时检测+ASR解码)+ TtsWorker 线程(合成+播放)+ 主线程(事件循环)
核心功能
| 功能 | 引擎 | 说明 |
|---|---|---|
| ASR (Offline) | OfflineRecognizer (SenseVoice) | 非流式语音识别,累积音频后一次性解码(默认) |
| ASR (Online) | OnlineRecognizer (Zipformer) | 流式语音识别,支持增量解码 |
| KWS | KeywordSpotter | 关键词唤醒,持续检测 |
| VAD | VoiceActivityDetector (silero) | 人声检测,实时判断是否有人说话 |
| TTS | OfflineTts (kokoro/matcha/zipvoice) | 语音合成 + miniaudio 播放,三种模型可选 |
ASR 模式
通过 setAsrMode() 设置,支持两种 ASR 引擎:
| 模式 | 值 | 引擎 | 模型 | 说明 |
|---|---|---|---|---|
Offline | 1 | OfflineRecognizer | SenseVoice | 非流式,累积音频后一次性解码,准确率高(默认) |
Online | 2 | OnlineRecognizer | Zipformer | 流式,一次性送入 + InputFinished + while Decode |
唤醒模式
通过 setWakeMode() 设置,定义 3 种工作模式:
| 模式 | 值 | 唤醒方式 | 流程 |
|---|---|---|---|
Kws | 1 | 关键词唤醒 | 检测到关键词 → 等待静音+预缓冲 → 累积音频 → 静默2秒 → 送ASR |
Vad | 2 | 人声检测唤醒(默认) | 预缓冲2秒音频 → VAD实时检测到人声 → 唤醒并继续累积 → 静默2秒 → 送ASR |
Tts | 3 | 纯语音输出 | 不监听麦克风,仅做TTS语音合成播放 |
VAD 模式预缓冲机制:未唤醒时也持续累积每帧音频(保留最近 2 秒),VAD 实时检测到人声(vadSpeechNow=true)时立即唤醒,此时缓冲区已包含语音开头,避免丢失前段语音。
KWS 唤醒后音频处理策略:KWS 检测到关键词后,进入"等待静音"状态并同时预缓冲音频:
- 若检测到静音(用户说完唤醒词后停顿)→ 清空预缓冲(丢弃唤醒词尾音)→ 再等 0.3 秒确保静音稳定 → 开始累积命令音频
- 若 0.5 秒内未检测到静音(用户说完唤醒词后立刻说命令)→ 保留预缓冲(不丢失命令开头)→ 立即开始累积
KWS 持续运行:KWS 始终处理音频(AcceptWaveform + Decode)保持 stream 状态流动,但仅在睡眠模式下检查 GetResult,避免已唤醒后误触发清空 ASR 缓冲区。
超时语义:唤醒后持续说话时不会超时(lastSpeechTime 每帧刷新),只有静默超过2秒才触发 flush 送 ASR。flush 后重置 VAD 和 KWS 状态,避免长期累积导致检测退化。
目录结构
libsherpacore/
├── includes/ # 公共头文件
│ ├── sherpa_core_manager.h # 管理类公共接口
│ ├── signal.hpp # sigslot 信号槽 + EventLoop
│ └── ringbuffer.h # 环形缓冲区(预留)
├── srcs/ # 内部实现
│ ├── sherpa_core_manager.cpp # 核心实现
│ ├── sherpa_core_manager_p.h # PIMPL(含 sherpa-onnx / miniaudio 头)
│ └── miniaudio.h # header-only 音频库
├── demos/
│ └── main.cpp # 测试程序
├── models/ # 模型文件
│ ├── asr/
│ │ ├── sherpa-onnx-sense-voice-small/ # Offline ASR 模型
│ │ └── sherpa-onnx-streaming-zipformer-zh/ # Online ASR 模型
│ ├── kws/
│ ├── silero_vad.onnx
│ └── tts/ # TTS 模型
│ ├── sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/ # ZipVoice 中英双语
│ ├── matcha-icefall-zh-baker/ # Matcha 中文 TTS(单音色,默认)
│ └── kokoro-multi-lang-v1_0/ # Kokoro 多语言 TTS(53 音色)
├── third_party/sherpa-onnx/ # sherpa-onnx SDK
├── CMakeLists.txt # 构建脚本
└── build.bat # Windows 一键构建
模型下载
模型文件未包含在仓库中,请单独下载:
models.7z — 链接: https://pan.baidu.com/s/1-uetgD3w5OJvJ__whK6ISg?pwd=rmqh 提取码: rmqh
下载后解压到项目根目录,确保 models/ 目录结构与上述一致。
构建
依赖
- CMake ≥ 3.15
- C++17 编译器(MSVC 2019+ / GCC 9+)
- sherpa-onnx SDK(已包含在
third_party/)
Windows
# 方式一:一键脚本
build.bat
# 方式二:手动 cmake
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
产物:
libs/sherpacore.lib— 静态库bin/demo_test.exe— 测试程序
Linux
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
使用方法
1. 创建管理类并连接信号
#include "sherpa_core_manager.h"
SherpaCoreManager manager;
// 连接信号(继承 sigslot::has_slots<>)
class MyListener : public sigslot::has_slots<> {
public:
void onWakedUp() { /* 唤醒回调 */ }
void onAsrFinal(std::string text) { /* 识别结果 */ }
void onAsleep() { /* 超时进入睡眠 */ }
void onError(std::string msg) { /* 错误 */ }
};
MyListener listener;
manager.wakedUp.connect(&listener, &MyListener::onWakedUp);
manager.asrFinalResult.connect(&listener, &MyListener::onAsrFinal);
manager.asleep.connect(&listener, &MyListener::onAsleep);
manager.errorOccurred.connect(&listener, &MyListener::onError);
2. 配置模型路径并加载
manager.setAsrModelDir("./models/asr/sherpa-onnx-sense-voice-small");
manager.setKwsModelDir("./models/kws");
manager.setVadModelPath("./models/silero_vad.onnx");
manager.setTtsModelDir("./models/tts/matcha-icefall-zh-baker"); // TTS(Matcha,默认)
manager.setAsrMode(AsrMode::Offline); // 默认 Offline (SenseVoice)
manager.setWakeMode(WakeMode::Vad); // 默认 VAD 模式
manager.setProvider("auto"); // 推理后端:auto(自动检测CUDA)/cpu/cuda,默认 auto
manager.setFilterResult(true); // 过滤无效结果(纯标点/空白/单字),默认开启
manager.setFilteredResults({"我。", "啊。"}); // 自定义过滤:完全匹配则丢弃
if (!manager.loadModels()) {
// 加载失败处理
}
3. 启动流水线
manager.start();
// mic 开始采集 → VAD/KWS 检测 → 唤醒 → 累积音频 → 静默2秒 → ASR识别 → asleep
4. 主线程事件循环
while (running) {
sigslot::EventLoop::instance().processAll(); // 处理跨线程事件
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
5. TTS 语音合成
manager.speak("你好,今天天气怎么样?", 0, 1.0f); // text, speakerId, speed
manager.setTtsVolume(2.0f); // TTS 播放音量(0.0-2.0,默认 1.0)
manager.pauseTts(); // 暂停播放
manager.resumeTts(); // 恢复播放
manager.stopTts(); // 停止播放
6. 停止
manager.stop();
demo_test 命令行参数
demo_test [选项]
| 选项 | 说明 | 默认 |
|---|---|---|
--online / -online | 使用流式 Zipformer ASR 模型 | Offline SenseVoice |
--wake <mode> / -wake <mode> | 唤醒模式:vad / kws / tts | vad |
--tts <model> / -tts <model> | TTS 模型:kokoro / matcha / zipvoice | zipvoice |
--provider <p> / -provider <p> | 推理后端:auto / cpu / cuda | auto |
示例:
demo_test # VAD 模式 + Offline SenseVoice + ZipVoice + auto provider
demo_test --online # VAD 模式 + Online Zipformer
demo_test --wake kws # KWS 唤醒词模式
demo_test --wake tts # TTS 纯语音输出模式
demo_test --wake vad --online # VAD 模式 + Online Zipformer
demo_test --tts matcha # 使用 Matcha TTS 模型
demo_test --provider cpu # 强制使用 CPU 推理
demo_test --provider cuda # 强制使用 CUDA 推理(需 GPU 版 sherpa-onnx)
运行时命令(stdin):
| 命令 | 说明 |
|---|---|
t <text> | TTS 合成并播放文本 |
p | 暂停 TTS 播放 |
r | 恢复 TTS 播放 |
s | 停止 TTS 播放 |
q | 退出程序 |
信号列表
| 信号 | 参数 | 触发时机 | 执行线程 |
|---|---|---|---|
modelLoadProgress | string | 模型加载进度 | loadModels 调用线程 |
started | — | 流水线启动 | start 调用线程 |
stopped | — | 流水线停止 | stop 调用线程 |
wakedUp | — | 唤醒(KWS命中/VAD检测到人声) | 主线程 |
asleep | — | 静默超时,进入睡眠 | 主线程 |
voiceDetected | — | VAD 检测到人声(边沿触发:无人声→有人声),不受唤醒模式限制 | 主线程 |
asrFinalResult | string | ASR 识别最终结果 | 主线程 |
ttsStarted | — | TTS 播放开始 | 主线程 |
ttsFinished | — | TTS 播放完成 | 主线程 |
ttsStopped | — | TTS 被停止 | 主线程 |
errorOccurred | string | 错误信息 | 主线程或调用线程 |
结果过滤
通过 setFilterResult(bool) 控制,默认开启。开启后,ASR 识别结果在发送 asrFinalResult 信号前会统计有效内容数量(汉字 U+4E00-U+9FFF、字母 a-z/A-Z、数字 0-9)。若结果有效内容数 ≤ 1(如纯标点 "。"、单字 "我。"),则判定为无效结果,不发送信号。
manager.setFilterResult(true); // 开启过滤(默认)
manager.setFilterResult(false); // 关闭过滤,输出原始结果
自定义过滤列表:通过 setFilteredResults() 设置一组字符串,ASR 结果若完全匹配列表中的任一字符串则丢弃:
manager.setFilteredResults({"我。", "啊。", "嗯。"});
该过滤同时适用于 Offline (SenseVoice) 和 Online (Zipformer) 两种 ASR 路径。
模型配置
ASR — Offline (SenseVoice)
models/asr/sherpa-onnx-sense-voice-small/
├── model_q8.onnx # 量化模型
└── tokens.txt # 词表
配置参数(代码内固定):
language = "zh"— 中文use_itn = true— 逆文本归一化num_threads = 2
ASR — Online (Zipformer)
models/asr/sherpa-onnx-streaming-zipformer-zh/
├── encoder-epoch-99-avg-1.int8.onnx # encoder(int8 量化)
├── decoder-epoch-99-avg-1.int8.onnx # decoder(int8 量化)
├── joiner-epoch-99-avg-1.int8.onnx # joiner(int8 量化)
└── tokens.txt # 词表
配置参数(代码内固定):
feat_config.sample_rate = 16000feat_config.feature_dim = 80decoding_method = "greedy_search"enable_endpoint = falsenum_threads = 1
VAD(silero)
| 参数 | 值 | 说明 |
|---|---|---|
| threshold | 0.3 | 检测阈值(越低越敏感) |
| min_silence_duration | 0.5s | 判定语音结束的静音时长 |
| min_speech_duration | 0.25s | 最短语音时长 |
| max_speech_duration | 15s | 最大语音时长(超时强制切段) |
| window_size | 512 | 处理窗口大小 |
KWS(关键词唤醒)
| 参数 | 值 | 说明 |
|---|---|---|
| max_active_paths | 8 | 搜索路径数(越大越灵活) |
| num_trailing_blanks | 3 | 关键词后 trailing blank 帧数 |
| keywords_score | 2.0 | 关键词加分(越高越易匹配) |
| keywords_threshold | 0.1 | 匹配阈值(越低越易触发,快语速友好) |
| num_threads | 2 | 推理线程数 |
注:
keywords_threshold从默认 0.25 降至 0.1,keywords_score从 1.0 升至 2.0,以支持快语速唤醒。
TTS 模型选择(三选一)
通过 setTtsModelType() 选择 TTS 引擎(需在 loadModels() 前设置):
manager.setTtsModelType(TtsModelType::Kokoro); // 多语言多音色
manager.setTtsModelType(TtsModelType::Matcha); // 中文单音色(默认)
manager.setTtsModelType(TtsModelType::Zipvoice); // 中英双语 flow-matching
demo 运行时通过 --tts <kokoro|matcha|zipvoice> 参数选择(默认 matcha)。
推理后端(Provider)选择
通过 setProvider() 设置推理后端(需在 loadModels() 前设置):
manager.setProvider("auto"); // 自动检测:CUDA 可用则用 CUDA,否则 CPU(默认)
manager.setProvider("cpu"); // 强制 CPU
manager.setProvider("cuda"); // 强制 CUDA
- auto(默认):通过 onnxruntime C API
GetAvailableProviders查询当前构建是否支持CUDAExecutionProvider,支持则用 CUDA,否则回退 CPU - cpu:强制 CPU 推理
- cuda:强制 CUDA 推理。需自行替换 GPU 版 sherpa-onnx/onnxruntime 到
third_party/目录,并安装 CUDA Toolkit + cuDNN。若 CUDA 运行时缺失,模型加载会失败
demo 运行时通过 --provider <auto|cpu|cuda> 参数选择(默认 auto)。
2. ZipVoice 中英双语蒸馏
models/tts/sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/
├── encoder.int8.onnx # 编码器
├── decoder.int8.onnx # 解码器(flow-matching)
├── vocos_24khz.onnx # 声码器(Vocos 24kHz,需单独下载)
├── tokens.txt # 词表
├── lexicon.txt # 词典
├── espeak-ng-data/ # 音素数据
└── test_wavs/ # 参考音频(零样本克隆用)
配置参数(代码内固定):
zipvoice.encoder = "encoder.int8.onnx"zipvoice.decoder = "decoder.int8.onnx"zipvoice.vocoder = "vocos_24khz.onnx"zipvoice.tokens = "tokens.txt"zipvoice.lexicon = "lexicon.txt"zipvoice.data_dir = "espeak-ng-data"
零样本语音克隆:ZipVoice 合成时必须提供参考音频 + 参考文本(代码内自动加载):
- 参考音频:
test_wavs/leijun-1.wav(雷军音色,24kHz 单声道 f32) - 参考文本:
"那还是三十六年前, 一九八七年. 我呢考上了武汉大学的计算机系." num_steps = 4(flow-matching 步数,质量/速度折中)- 如需更换音色,替换参考 wav 和对应文本(必须与音频内容完全一致,否则音质明显劣化)
- vocoder 下载地址:https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos_24khz.onnx
1. Matcha 中文 Baker(默认)
models/tts/matcha-icefall-zh-baker/
├── model-steps-3.onnx # 声学模型
├── vocos-22khz-univ.onnx # 声码器(Vocos)
├── tokens.txt # 词表
├── lexicon.txt # 中文词典
├── dict/ # 中文分词词典
├── date.fst # 日期正则
├── number.fst # 数字正则
└── phone.fst # 电话正则
配置参数(代码内固定):
matcha.acoustic_model = "model-steps-3.onnx"matcha.vocoder = "vocos-22khz-univ.onnx"matcha.lexicon = "lexicon.txt"matcha.tokens = "tokens.txt"matcha.dict_dir = "dict"rule_fsts = "number.fst,date.fst,phone.fst"(文本规范化:数字/日期/电话→中文读法)num_threads = hardware_concurrency()- 单音色(speakerId = 0,DataBaker 中文女声)
3. Kokoro 多语言
models/tts/kokoro-multi-lang-v1_0/
├── model.onnx # 声学模型
├── voices.bin # 多音色声音库(53 音色,ID 0-52)
├── tokens.txt # 词表
├── lexicon-zh.txt # 中文词典
└── espeak-ng-data/ # 多语言音素数据
配置参数(代码内固定):
kokoro.model = "model.onnx"kokoro.voices = "voices.bin"kokoro.tokens = "tokens.txt"kokoro.data_dir = "espeak-ng-data"kokoro.lexicon = "lexicon-zh.txt"- 多音色(中文音色 45-52,如 45=小贝、50=云希)
数字读法模式:通过 setTtsNumberMode() 控制(三种模型通用):
manager.setTtsNumberMode(TtsNumberMode::Integer); // 整数读法:1234 → 一千二百三十四
manager.setTtsNumberMode(TtsNumberMode::Digit); // 逐位读法(默认):1234 → 一二三四
Integer模式依赖number.fst规则文件,按中文整数读法规范化Digit模式在代码中预处理,将每个数字逐位映射为汉字(适合验证码、电话号码等场景)
音频参数
| 参数 | 值 |
|---|---|
| 采样率 | 16000 Hz |
| 声道数 | 1(单声道) |
| 格式 | int16 PCM |
| 输入增益 | 1.5x(放大软语音) |
| 唤醒超时 | 2 秒静默 |
| VAD 预缓冲 | 2 秒(VAD 模式) |
| 累积上限 | 30 秒 |
项目主页
产品案例
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐




所有评论(0)