☰
TEN Framework vosk_asr_cpp 扩展深入解析:用 C++ 构建基于 Vosk 的本地实时语音识别
2026/9/25 7:50:02 网站建设 项目流程
  • 人工智能
  • AI Agent
  • 多模态
  • 语音
  • AI 应用

【免费下载链接】ten-framework

Open-source framework for conversational voice AI agents

项目地址:https://gitcode.com/TEN-framework/ten-framework
点击查看免费下载

本文以 TEN Framework 仓库中的vosk_asr_cpp示例扩展为主线,系统讲解如何将开源语音识别引擎 Vosk 封装为一个标准的 TEN Framework C++ 扩展:从 VOSK SDK 与识别模型的手工部署,到扩展属性(property.json)配置、扩展生命周期回调(on_init/on_audio_frame/on_deinit)的源码走读,再到基于 googletest 的独立测试与tman安装集成流程。读完本文,你可以完整复现该扩展的部署与构建过程,并理解 TEN Framework 音频扩展"帧进、结果出"的数据流设计。

一、扩展定位:TEN Framework 中的 C++ ASR 扩展

vosk_asr_cpp是一个为 TEN Framework 用 C++ 编写的 Vosk 自动语音识别(ASR)扩展,其定位是:接收上游(如音频文件播放器、麦克风采集模块)送入的音频帧,逐帧送入 Vosk 识别器进行解码,并以data消息的形式输出识别文本——既包括说话过程中的部分结果(partial result),也包括语句结束时的最终结果(final result),从而天然适配实时转写场景。

从 manifest.json 可以看到该扩展的元信息:

  • type为extension,包名为vosk_asr_cpp,当前版本0.11.73;
  • tags标注为cpp,表明这是一个 C++ 实现的扩展;
  • 依赖两个系统包:ten_runtime(扩展运行时,版本0.11.73)与googletest(版本1.7.0-rc2,用于独立测试)。

manifest 中还定义了两个常用脚本,体现了该包的日常开发方式:

"scripts": { "test": "bin/vosk_asr_cpp_test", "build": "sh -c cd .ten/app && tgn gen linux x64 debug -- ten_enable_standalone_test=true && tgn build linux x64 debug" }

其中build脚本通过tgn gen生成构建文件并显式开启ten_enable_standalone_test=true,再执行tgn build,这与第六节介绍的独立测试机制直接对应。

二、环境准备:手工部署 VOSK SDK

为什么 SDK 不随扩展自带

VOSK SDK(包含头文件与动态库)体积较大,因此vosk_asr_cpp默认不内置SDK,需要使用者自行下载并部署。官方文档(README.en-US.md、README.ja-JP.md 等多语言版本)给出的做法是:从 vosk-api 的官方发行页(alphacep/vosk-api 的 releases)下载与当前平台匹配的预编译包,然后按以下两个位置放置文件:

文件放置位置作用
vosk_api.h扩展根目录下的include/目录编译期头文件,src/main.cc中#include "vosk_api.h"直接依赖它
libvosk.so扩展根目录下的lib_private/目录运行期动态库,构建时链接、运行时通过 rpath 查找

rpath:lib_private/是如何被找到的

这一目录约定并非随意规定,BUILD_release.gn 中有明确的构建配置佐证:

ten_package("vosk_asr_cpp") { package_kind = "extension" enable_build = true sources = [ "src/main.cc" ] include_dirs = [ "include", "include/nlohmann_json", ] # Add rpath to find vosk library. if (is_mac) { ldflags = [ "-Wl,-rpath,@loader_path/../lib_private" ] } else if (is_linux) { ldflags = [ "-Wl,-rpath=\$ORIGIN/../lib_private" ] } lib_dirs = [ "lib_private" ] libs = [ "vosk" ] }

可以看到:include目录被加入头文件搜索路径(vosk_api.h由此被找到);lib_dirs指向lib_private(链接期找到libvosk);同时通过平台相关的-rpath(Linux 下$ORIGIN/../lib_private,macOS 下@loader_path/../lib_private)确保扩展编译出的动态库在运行期也能从相对自身路径的lib_private/目录加载libvosk.so,而不依赖系统库搜索路径或LD_LIBRARY_PATH。这就是"头文件放include/、动态库放lib_private/"这一约定的底层原因。

注意:仓库中include/目录当前已内置了nlohmann(JSON 库)头文件,说明该扩展在构建配置层面预留了第三方头文件扩展能力;而 vosk 相关头文件仍需使用者按上述说明手工补充。

三、安装 VOSK 识别模型

SDK 只解决"怎么跑识别引擎"的问题,真正的识别能力来自 Vosk 模型。文档要求:从 Vosk 模型官网(alphacephei.com/vosk/models)下载所需模型,解压后放到扩展根目录的models/目录下。

模型加载逻辑在源码中非常直接。src/main.cc 的on_init回调中:

void on_init(ten::ten_env_t &ten_env) override { auto model_name = ten_env.get_property_string("model_name"); // Open the specified model. vosk_model = vosk_model_new((std::string("models/") + model_name).c_str()); if (vosk_model == nullptr) { TEN_LOGE("Failed to load model, check if exists in the folder"); exit(EXIT_FAILURE); } auto sample_rate = ten_env.get_property_float32("sample_rate"); TEN_ENV_LOG_INFO(ten_env, (std::string("Specify sample rate: ") + std::to_string(sample_rate)).c_str()); vosk_recognizer = vosk_recognizer_new(vosk_model, sample_rate); if (vosk_recognizer == nullptr) { TEN_LOGE("Failed to create recognizer"); exit(EXIT_FAILURE); } ten_env.on_init_done(); }

要点:

  1. 模型名不是硬编码的,而是从扩展属性model_name读取,再以"models/" + model_name拼接成相对路径调用vosk_model_new加载——这就是"模型必须放在models/目录下"的强制约定;
  2. 加载失败会打印Failed to load model, check if exists in the folder并直接exit(EXIT_FAILURE),排查部署问题时可优先检查日志中这条信息;
  3. 识别器vosk_recognizer_new的第二个参数是采样率,来自属性sample_rate(float32 类型),识别器的解码行为会严格基于该采样率设计。

四、属性配置:property.json 的两个核心参数

扩展根目录下的 property.json 定义了该扩展的全部可配置属性,默认内容如下:

{ "model_name": "vosk-model-small-en-us-0.15", "sample_rate": 16000.0 }
属性类型默认值含义与约束
model_namestringvosk-model-small-en-us-0.15models/目录下的模型子目录名,对应从官网下载的英文小模型;更换模型(如中文模型)时同步修改此项
sample_ratefloat3216000.0送入识别器的音频采样率;Vosk 官方小模型基于 16 kHz 设计,修改该值时须与模型要求及上游音频流一致

sample_rate在on_init中会以TEN_ENV_LOG_INFO打印(日志形如Specify sample rate: 16000),便于运行时核对配置是否生效。测试代码 tests/basic.cc 的注释也再次强调:测试音频需为16 kHz 采样率的 PCM 格式。

五、核心实现走读:音频帧进、识别结果出

数据流总览

从源码结构看,整个扩展的数据流是一个清晰的单向管线:

上游音频帧 (audio_frame) └─ on_audio_frame ├─ frame->lock_buf() 锁定 PCM 数据 ├─ vosk_recognizer_accept_waveform() 送入 Vosk 解码 ├─ 判断 is_final(0=仍在说话,非0=一句话结束) │ ├─ 非最终句:vosk_recognizer_partial_result() │ └─ 最终句:vosk_recognizer_result() └─ send_data("recognition_result") 输出 data 消息 ├─ property: result (string) └─ property: is_final (int)

on_audio_frame:逐帧解码与部分/最终结果

关键实现在 src/main.cc:

void on_audio_frame(ten::ten_env_t &ten_env, std::unique_ptr<ten::audio_frame_t> frame) override { std::string frame_name = frame->get_name(); TEN_ENV_LOG_INFO( ten_env, (std::string("Received audio frame '") + frame_name + "'").c_str()); ten::buf_t locked_in_buf = frame->lock_buf(); int is_final = vosk_recognizer_accept_waveform( vosk_recognizer, reinterpret_cast<const char *>(locked_in_buf.data()), static_cast<int>(locked_in_buf.size())); frame->unlock_buf(locked_in_buf); const char *result = nullptr; if (is_final != 0) { result = vosk_recognizer_result(vosk_recognizer); } else { result = vosk_recognizer_partial_result(vosk_recognizer); } auto recognition_result = ten::data_t::create("recognition_result"); recognition_result->set_property("result", result); recognition_result->set_property("is_final", is_final); bool rc = ten_env.send_data(std::move(recognition_result)); TEN_ASSERT(rc, "Should not happen."); }

几个实现细节值得注意:

  • 缓冲区生命周期管理:lock_buf()/unlock_buf()是 TEN Framework 音频帧的加锁/解锁配对操作。Vosk 的accept_waveform是同步调用,解码完成并取走结果后即可解锁,保证帧内存可被框架复用;
  • is_final的双义性:vosk_recognizer_accept_waveform的返回值(0 或非 0)本身表示"这一帧是否触发了语句结束",扩展原样把它透传给下游is_final属性,下游节点(如 TTS、日志节点)可据此区分"流式部分结果"与"完整句子结果";
  • 消息命名:输出的data消息固定命名为recognition_result,携带result(识别文本)与is_final两个属性,这与测试代码的读取方式完全对称。

on_deinit 与扩展注册

资源释放与注册分别见 src/main.cc:on_deinit中先释放识别器、再释放模型(vosk_recognizer_free→vosk_model_free)并置空指针,最后调用ten_env.on_deinit_done()通知框架;文件末尾通过宏TEN_CPP_REGISTER_ADDON_AS_EXTENSION(vosk_asr_cpp, vosk_asr_cpp_t)将 C++ 类注册为 TEN 扩展,扩展名与包名保持一致。

六、独立测试:用 test.wav 验证端到端识别

该扩展自带一个基于 googletest 的独立(standalone)测试,可在不启动完整 TEN 应用的情况下验证"音频进、文本出"的完整链路。

前置条件:安装 googletest 系统包

BUILD_release.gn 中的注释给出了明确的命令:

tman install system googletest

对应manifest.json中声明的googletest依赖。之后通过tgn gen ... -- ten_enable_standalone_test=true生成构建时,才会启用如下测试目标:

if (ten_enable_standalone_test) { ten_package_test("vosk_asr_cpp_test") { package_kind = "extension" sources = [ ".ten/app/ten_packages/system/googletest/src/gtest-all.cc", ".ten/app/ten_packages/system/googletest/src/gtest_main.cc", "tests/basic.cc", ] ... } }

测试流程解析

tests/basic.cc 的实现思路是模拟一个真实音频源:

  1. 构造函数打开./tests/test.wav(注释明确要求:PCM 格式、16 kHz 采样率),该样例音频随包提供(tests/test.wav);
  2. on_start中按 4096 字节为块循环fread,每块数据封装成一个名为recognize的audio_frame_t,通过lock_buf/memcpy/unlock_buf写入 PCM 数据后send_audio_frame送入扩展,模拟"持续灌入音频流";
  3. on_data回调读取扩展输出的recognition_result消息,打印result与is_final;一旦收到is_final > 0的最终结果,即调用ten_env.stop_test()结束测试——这隐含了 test.wav 中应包含一段完整语句的假设;
  4. TEST(Test, Basic)入口通过set_test_mode_single("vosk_asr_cpp")声明被测扩展,随后run()执行整个生命周期。

提醒:独立测试要求models/目录中已按property.json的model_name配置好模型,否则扩展会在on_init阶段直接失败退出。

七、安装与集成到 TEN 应用

官方文档说明安装方式为"遵循 TEN Framework 的包安装指南"。结合仓库快速上手文档 docs/getting-started/quick-start.md 中介绍 C++ 扩展的标准流程(以webrtc_vad_cpp为例),vosk_asr_cpp的集成路径为:

  1. 安装扩展:在应用目录(如示例应用transcriber_demo)下执行
    tman install extension vosk_asr_cpp

    将扩展(连同本文第二节、第三节要求手工放置的 SDK 与模型文件)拉入应用的.ten包环境;

  2. 重新构建:执行tman run build,构建系统会依据BUILD_release.gn把src/main.cc编译成扩展动态库并链接libvosk;
  3. 在应用中编排:按 TEN Framework 的应用规范,将该扩展挂入音频处理链路,让上游节点(如音频文件播放器)的音频帧发往vosk_asr_cpp,下游节点消费recognition_result数据消息即可。

此外,C++ 扩展的编译要求宿主机具备 C++ 工具链(gcc/g++ 或 clang),快速上手文档中给出了各平台的安装示例;manifest.json的build脚本则展示了在 Linux x64 debug 模式下手工驱动tgn gen+tgn build的等价做法。

八、目录结构与许可证小结

综合 BUILD.gn 的打包规则与仓库实际内容,该扩展的目录职责如下:

路径内容说明
src/main.cc扩展唯一源码生命周期回调与 Vosk 调用(本文第五节)
include/头文件内置 nlohmann 头文件;vosk 头文件需手工放入
lib_private/私有动态库需手工放入libvosk.so,由 rpath 定位
models/识别模型需手工下载解压放置,名称与model_name属性一致
property.json属性默认值model_name与sample_rate
tests/basic.cc、tests/test.wav独立测试与样例音频16 kHz PCM
BUILD.gn/BUILD_release.gn构建定义开发态打包 / 发布态编译(含 rpath 与链接配置)
docs/README.*.md多语言文档en-US、zh-CN、zh-TW、ja-JP、ko-KR 五个版本

关于许可证,官方文档声明:该包是 TEN Framework 项目的一部分,遵循仓库顶层 LICENSE 所约定的 Apache License 2.0 许可。

九、小结

vosk_asr_cpp虽然代码量很小,但它是理解 TEN Framework C++ 扩展机制的一个理想样本:手工部署第三方 SDK(include/+lib_private/+ rpath)、通过property.json声明可配置项、在on_init/on_audio_frame/on_deinit三个回调中完成资源初始化、流式解码与释放,最后用 googletest 独立测试验证端到端行为。掌握这套模式后,将其他本地推理引擎封装成 TEN Framework 音频扩展,只需替换解码核心、保持"音频帧进、data 消息出"的边界约定即可。

  • 人工智能
  • AI Agent
  • 多模态
  • 语音
  • AI 应用

【免费下载链接】ten-framework

Open-source framework for conversational voice AI agents

项目地址:https://gitcode.com/TEN-framework/ten-framework
点击查看免费下载

相关推荐

上一篇:微信聊天记录永久保存完全指南:如何让珍贵对话永不丢失
下一篇:如何永久保存微信聊天记录?WeChatMsg导出工具完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询