# libsherpacorecore **Repository Path**: akinggw/libsherpacorecore ## Basic Information - **Project Name**: libsherpacorecore - **Description**: 基于 sherpa-onnx 的 C++ 语音交互静态库,封装 ASR(语音识别)、KWS(关键词唤醒)、VAD(人声检测)、TTS(语音合成)四大功能,提供统一的唤醒→识别→合成交互流程。 - **Primary Language**: C++ - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-22 - **Last Updated**: 2026-10-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # libsherpacore 基于 [sherpa-onnx](https://github.com/k2-fsa/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 (matcha) | 语音合成 + 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 模型 │ └── matcha-icefall-zh-baker/ # Matcha 中文 TTS(单音色) ├── third_party/sherpa-onnx/ # sherpa-onnx SDK ├── CMakeLists.txt # 构建脚本 └── build.bat # Windows 一键构建 ``` ## 模型下载 模型文件未包含在仓库中,请单独下载: > **models.7z** — 链接: https://pan.baidu.com/s/1s-mQ0ukviSjCss5quEbORw?pwd=dvrt 提取码: dvrt 下载后解压到项目根目录,确保 `models/` 目录结构与上述一致。 ## 构建 ### 依赖 - CMake ≥ 3.15 - C++17 编译器(MSVC 2019+ / GCC 9+) - sherpa-onnx SDK(已包含在 `third_party/`) ### Windows ```bash # 方式一:一键脚本 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 ```bash cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build ``` ## 使用方法 ### 1. 创建管理类并连接信号 ```cpp #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. 配置模型路径并加载 ```cpp 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.setFilterResult(true); // 过滤无效结果(纯标点/空白/单字),默认开启 manager.setFilteredResults({"我。", "啊。"}); // 自定义过滤:完全匹配则丢弃 if (!manager.loadModels()) { // 加载失败处理 } ``` ### 3. 启动流水线 ```cpp manager.start(); // mic 开始采集 → VAD/KWS 检测 → 唤醒 → 累积音频 → 静默2秒 → ASR识别 → asleep ``` ### 4. 主线程事件循环 ```cpp while (running) { sigslot::EventLoop::instance().processAll(); // 处理跨线程事件 std::this_thread::sleep_for(std::chrono::milliseconds(20)); } ``` ### 5. TTS 语音合成 ```cpp 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. 停止 ```cpp manager.stop(); ``` ## demo_test 命令行参数 ``` demo_test [选项] ``` | 选项 | 说明 | 默认 | |------|------|------| | `--online` / `-online` | 使用流式 Zipformer ASR 模型 | Offline SenseVoice | | `--wake ` / `-wake ` | 唤醒模式:`vad` / `kws` / `tts` | `vad` | 示例: ```bash demo_test # VAD 模式 + Offline SenseVoice demo_test --online # VAD 模式 + Online Zipformer demo_test --wake kws # KWS 唤醒词模式 demo_test --wake tts # TTS 纯语音输出模式 demo_test --wake vad --online # VAD 模式 + Online Zipformer ``` 运行时命令(stdin): | 命令 | 说明 | |------|------| | `t ` | 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(如纯标点 `"。"`、单字 `"我。"`),则判定为无效结果,不发送信号。 ```cpp manager.setFilterResult(true); // 开启过滤(默认) manager.setFilterResult(false); // 关闭过滤,输出原始结果 ``` **自定义过滤列表**:通过 `setFilteredResults()` 设置一组字符串,ASR 结果若完全匹配列表中的任一字符串则丢弃: ```cpp 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 = 16000` - `feat_config.feature_dim = 80` - `decoding_method = "greedy_search"` - `enable_endpoint = false` - `num_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(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 中文女声) **数字读法模式**:通过 `setTtsNumberMode()` 控制: ```cpp 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 秒 |