- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
本文以 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(); }要点:
- 模型名不是硬编码的,而是从扩展属性
model_name读取,再以"models/" + model_name拼接成相对路径调用vosk_model_new加载——这就是"模型必须放在models/目录下"的强制约定; - 加载失败会打印
Failed to load model, check if exists in the folder并直接exit(EXIT_FAILURE),排查部署问题时可优先检查日志中这条信息; - 识别器
vosk_recognizer_new的第二个参数是采样率,来自属性sample_rate(float32 类型),识别器的解码行为会严格基于该采样率设计。
四、属性配置:property.json 的两个核心参数
扩展根目录下的 property.json 定义了该扩展的全部可配置属性,默认内容如下:
{ "model_name": "vosk-model-small-en-us-0.15", "sample_rate": 16000.0 }| 属性 | 类型 | 默认值 | 含义与约束 |
|---|---|---|---|
model_name | string | vosk-model-small-en-us-0.15 | models/目录下的模型子目录名,对应从官网下载的英文小模型;更换模型(如中文模型)时同步修改此项 |
sample_rate | float32 | 16000.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 的实现思路是模拟一个真实音频源:
- 构造函数打开
./tests/test.wav(注释明确要求:PCM 格式、16 kHz 采样率),该样例音频随包提供(tests/test.wav); on_start中按 4096 字节为块循环fread,每块数据封装成一个名为recognize的audio_frame_t,通过lock_buf/memcpy/unlock_buf写入 PCM 数据后send_audio_frame送入扩展,模拟"持续灌入音频流";on_data回调读取扩展输出的recognition_result消息,打印result与is_final;一旦收到is_final > 0的最终结果,即调用ten_env.stop_test()结束测试——这隐含了 test.wav 中应包含一段完整语句的假设;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的集成路径为:
- 安装扩展:在应用目录(如示例应用
transcriber_demo)下执行tman install extension vosk_asr_cpp将扩展(连同本文第二节、第三节要求手工放置的 SDK 与模型文件)拉入应用的
.ten包环境; - 重新构建:执行
tman run build,构建系统会依据BUILD_release.gn把src/main.cc编译成扩展动态库并链接libvosk; - 在应用中编排:按 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
相关推荐
TEN Framework vosk_asr_cpp 扩展实战:Vosk C++ 语音识别扩展的 SDK 部署、属性配置与源码解析
TEN Framework vosk_asr_cpp 扩展实战:Vosk C++ 语音识别扩展的 SDK 部署、属性配置与源码解析 本文以 TEN Framew
人工智能AI Agent多模态语音AI 应用TEN Framework 集成指南:基于 AWS Transcribe 的异步实时语音识别扩展 aws_asr_python
TEN Framework 集成指南:基于 AWS Transcribe 的异步实时语音识别扩展 aws_asr_python 导读 本文深入讲解 TEN Fr
人工智能AI Agent多模态语音AI 应用TEN Framework 中的 Soniox 实时语音识别扩展(soniox_asr_python)使用与源码解析
TEN Framework 中的 Soniox 实时语音识别扩展(soniox_asr_python)使用与源码解析 本指南以 TEN Framework 仓库
人工智能AI Agent多模态语音AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考