本项目把 ten-vad-ggml 参考项目完整移植为 C++17、零第三方依赖 的实现,并产出可供其他项目链接使用的 DLL。
文档涵盖:构建方法、C API 参考、如何链接进其他项目、以及性能基准数据(与 Python ONNX 实现对比)。
| 产物 | 说明 |
|---|---|
ten_vad_ggml.dll |
共享库(Release x64),导出 4 个 C 函数 |
ten_vad_ggml_shared.lib |
MSVC 导入库,供其他项目链接 |
ten_vad.h |
公共 C 头文件,仅依赖 <stddef.h> / <stdint.h> |
ten-vad-ggml.bin |
GGML 格式模型文件(296 KB,运行时按路径加载,不内嵌) |
ten_vad_ggml.lib |
可选:静态库(同源码构建的另一个 target) |
DLL 内实现完整 VAD 流水线:STFT → Mel 滤波组 → 对数归一化 → LPC 残差自相关基音估计(Viterbi 追踪)→ 3 层可分离卷积 → 2 层 LSTM → 2 层全连接 → Sigmoid。不含 onnxruntime / ggml / FFTW 等任何外部库。
- Visual Studio 2022(MSVC,C++17)
- CMake ≥ 3.15
- 无需其他第三方库
cmake -S cpp -B cpp/build -G "Visual Studio 17 2022" -A x64
cmake --build cpp/build --config Releasecpp/build/Release/ten_vad_ggml_shared.dll # DLL
cpp/build/Release/ten_vad_ggml_shared.lib # 导入库
cpp/build/Release/ten_vad_ggml.lib # 静态库(可选,同源构建)
cpp/build/Release/ten_vad_ggml.exe 等 # CLI / 基准 / 测试工具
| CMake 目标 | 类型 | 说明 |
|---|---|---|
ten_vad_ggml |
STATIC | 静态库,供本仓库 CLI/测试/基准链接 |
ten_vad_ggml_shared |
SHARED | DLL + 导入库,供外部项目链接 |
ten_vad_cli |
EXE | 命令行 VAD(WAV → 逐帧概率) |
ten_vad_tests |
EXE | 单元测试(4745 项断言,ctest 可跑) |
ten_vad_dllexample |
EXE | DLL 消费者示例(验证导出 API) |
ten_vad_bench |
EXE | 性能基准(峰值内存 / 帧耗时 / RTF,仅 Windows) |
可选 CMake 开关:-DTEN_VAD_BUILD_TESTS=OFF、-DTEN_VAD_BUILD_CLI=OFF。
头文件:ten_vad.h
所有函数以 C 链接(extern "C"),默认 __cdecl 调用约定,可从 C / C++ 直接调用。
#define TEN_VAD_SAMPLE_RATE 16000 // 采样率 Hz
#define TEN_VAD_HOP_SIZE 256 // 每次 process() 处理的采样数(16 ms)
#define TEN_VAD_FRAME_BYTES (TEN_VAD_HOP_SIZE * 2) // 16-bit 采样字节数(512 B)typedef struct ten_vad_ctx ten_vad_ctx; // 不透明句柄ten_vad_ctx *ten_vad_create(const char *model_path);加载 GGML 模型并创建 VAD 上下文。失败(文件不存在、magic 错误、超参数不匹配、张量截断)返回 NULL。
float ten_vad_process(ten_vad_ctx *ctx, const int16_t *samples, size_t n_samples);处理一个 hop(16 ms)的音频,返回语音概率,范围 [0, 1]。
samples:16 kHz 的 int16 采样,至少n_samples个。n_samples:允许少于TEN_VAD_HOP_SIZE(不足部分按静音补齐),便于流式收尾。- 内部失败返回
0。
void ten_vad_reset(ten_vad_ctx *ctx);复位全部状态:LSTM 隐/细胞状态、特征提取状态、30 秒重置计数器。
void ten_vad_destroy(ten_vad_ctx *ctx);释放上下文。ctx 为 NULL 时安全。
#include "ten_vad/ten_vad.h"
ten_vad_ctx *vad = ten_vad_create("ten-vad-ggml.bin");
if (!vad) { /* 处理加载失败 */ }
int16_t buf[TEN_VAD_HOP_SIZE];
while (读取到 TEN_VAD_HOP_SIZE 个采样) {
float prob = ten_vad_process(vad, buf, TEN_VAD_HOP_SIZE);
/* prob > 0.5 视为语音 */
}
ten_vad_destroy(vad);ten_vad_ggml_shared.dll(部署时与可执行文件同目录,或加入PATH)ten_vad_ggml_shared.lib(链接时使用)ten_vad.h(编译时使用)ten-vad-ggml.bin(运行时按路径加载)
cl /std:c++17 /EHsc /O2 /I <include目录> main.cpp <build>\Release\ten_vad_ggml_shared.libten_vad.h 中 TEN_VAD_API 宏:定义了 TEN_VAD_SHARED 时按 __declspec(dllimport) 声明,不定义则按普通声明(两种都能链接,建议定义以获得更优的调用代码)。
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE <path-to-ten_vad_ggml_shared.lib>)
target_include_directories(my_app PRIVATE <path-to-include>)
target_compile_definitions(my_app PRIVATE TEN_VAD_SHARED) # 可选,见上运行时保证 DLL 可被找到(VS 调试会把 DLL 拷到 exe 目录,或用 $<TARGET_FILE:ten_vad_ggml_shared> 配合 add_custom_command(TARGET ... POST_BUILD) 拷贝)。
导出的是标准 C ABI,可用 ctypes / CFFI / P/Invoke / FFI 直接绑定:
import ctypes
dll = ctypes.CDLL("ten_vad_ggml_shared.dll")
dll.ten_vad_create.restype = ctypes.c_void_p
dll.ten_vad_create.argtypes = [ctypes.c_char_p]
dll.ten_vad_process.restype = ctypes.c_float
dll.ten_vad_process.argtypes = [ctypes.c_void_p,
ctypes.POINTER(ctypes.c_int16), ctypes.c_size_t]- 平台:Windows x64,MSVC Release(
/O2),单线程。 - 输入:30 秒合成类语音信号(220 Hz + 440 Hz 双音,幅度包络调制),16 kHz / int16,共 1875 帧(每帧 16 ms)。
- C++ 指标:
QueryPerformanceCounter计时;峰值工作集(Peak Working Set,psapi)。 - Python ONNX 指标:
time.perf_counter分段计时(特征提取 / onnxruntime 推理);同样用 psapi 测峰值工作集。 - RTF = 处理耗时 / 音频时长。RTF < 1 表示快于实时。
- C++ 基准工具:ten_vad_bench.cpp,Python:bench_onnx.py。
| 指标 | C++ (DLL/静态同源) | Python ONNX | 倍率 |
|---|---|---|---|
| 全流程耗时 | 334 ms | 74 087 ms | C++ 快 222× |
| 全流程 RTF | 0.0111 | 2.470 | — |
| 每帧耗时(全流程) | 178 µs | 39 513 µs | C++ 快 222× |
| 推理 RTF(LSTM+Dense,不含特征) | 0.00489 | 0.0288 | C++ 快 5.9× |
| 推理每帧 | 78 µs | 461 µs | — |
| 峰值工作集 | 5.49 MB | 57.14 MB | C++ 低 10.4× |
| 会话/模型初始化 | 一次性开销(仅读 296 KB 模型文件) | 133 ms | — |
说明:
- Python 特征提取为 numpy 参考实现(逐帧 Python 循环,未向量化),是全流程 RTF 2.47 的主因;生产级 Python 实现可显著改善。纯推理环节(同网络)C++ 仍快 5.9×。
- C++ 侧峰值工作集 5.49 MB 已含模型(296 KB)、全部 DSP 状态与 LSTM 权重。
- 输出概率与 Python ONNX / 参考项目实现一致(最大偏差 < 0.001,4745 项测试通过)。
- 单线程 C++ 实现 RTF ≈ 0.011,即处理 1 秒音频仅需约 11 ms,有约 90× 实时余量,可运行在低端嵌入式/移动 CPU 上。
- 每帧 178 µs 中,特征提取约占 100 µs、推理约 78 µs;LSTM 门运算为当前主要计算热点。
- 线程安全:
ten_vad_ctx持有全部可变状态,单个上下文不可并发调用。多路音频请每路创建独立上下文;多个上下文可在多线程并行使用。 - 调用约定:C API 为
__cdecl(x64 上统一)。 - 运行时依赖:DLL 依赖 MSVC 运行库(VCRuntime)。目标机器需装有对应的 VC++ Redistributable(Release 下为 x64)。除此之外无其他动态依赖。
- 30 秒 LSTM 重置:为复刻原生库行为,每处理 1875 帧(30 s)自动清零 LSTM 状态(内部处理,调用方无需干预);
ten_vad_reset()可随时手动清零。 - 模型文件:模型为 GGML 格式(magic
0x67676d6c),版本需与本库超参数匹配(2 层 LSTM、隐层 64、40 Mel 带)。 - 静态链接选项:若不需要 DLL,可直接链接
ten_vad_ggml.lib(静态库)并包含ten_vad.h(此时不定义TEN_VAD_SHARED),产物零运行时依赖(除 VC 运行库)。