- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
导读
本文以 TEN Framework 仓库中的示例扩展web_audio_control_go为主线,讲解如何用 Go 编写一个"浏览器 ↔ 扩展 ↔ TEN 图"三层联动的 Web 音频控制扩展:它内置 HTTP 服务器与 WebSocket 通道,让用户直接在浏览器里输入音频文件路径、点击按钮触发播放,并把 ASR 转写结果实时推回页面展示。读完本文,你将掌握该扩展的配置属性、Graph 接线方式、HTTP/WebSocket API 协议、Go 源码级实现原理,以及如何在 transcriber_demo 示例应用中把它与音频播放、ASR、VAD 等扩展串成一条完整链路。
扩展定位:Web 端与 TEN 运行时之间的桥
web_audio_control_go是一个用 Go 编写的 TEN Framework 扩展(extension),其核心作用是为 TEN 应用提供一个基于浏览器的控制界面。它的双向职责非常清晰:
- 控制方向:浏览器通过 HTTP 请求(
POST /api/start_play)把用户的播放意图交给扩展,扩展将其封装为 TEN 命令(start_play)通过SendCmd发给图内的音频播放扩展; - 回传方向:图内 ASR 扩展产出的转写结果(
asr_result数据)到达扩展后,由BroadcastAsrResult通过 WebSocket 推送给所有连接的浏览器页面实时展示。
扩展的官方描述(manifest.json)将其定位为 "Web-based audio control extension with real-time transcription display",标签为go、web、audio、websocket,并声明依赖系统包ten_runtime_go(版本0.11)。它属于 transcriber_demo 示例应用的ten_packages/extension目录,可作为示例直接参考或复制到自己的 TEN 项目中。
架构链路
原文档给出的整体数据流如下:
User Browser <--HTTP/WebSocket--> Web Server (Go Extension) <--TEN Protocol--> TEN Framework | v Audio Player Extension | v ASR Extension (Transcription)结合仓库源码,可以进一步细化这条链路(server/server.go 中的路由注册):
- 浏览器加载扩展内嵌的静态页面
index.html; - 用户提交音频文件路径与循环播放选项,页面以表单方式
POST /api/start_play; - 扩展解析表单,构造
start_play命令并tenEnv.SendCmd发送给图内的音频播放扩展; - 音频播放扩展输出
pcm_frame音频帧,送往 ASR 扩展; - ASR 扩展输出
asr_result数据,扩展在OnData中解析出text与final字段,并通过 WebSocket 广播; - 浏览器
onmessage收到消息后,把文本渲染为卡片并自动滚动到最新内容。
从源码结构看,该扩展还通过audio_frame_in(pcm_frame)接收 VAD 输出的音频帧,并在OnAudioFrame中检测is_speech属性,调用BroadcastVadStatus将说话状态推送到前端,实现"当前是否有人在说话"的实时可视化(详见后文"实际源码中的扩展能力")。
配置属性:http_port与http_host
扩展在 property.json 中声明了两个属性:
{ "http_host": "0.0.0.0", "http_port": 8001 }| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
http_port | int64 | 8001 | HTTP/WebSocket 服务器监听端口 |
http_host | string | 0.0.0.0 | 监听地址,默认绑定所有网卡 |
原文档仅介绍了http_port;实际源码(main.go)在OnStart中同时读取这两个属性:
host, err := tenEnv.GetPropertyString("http_host") if err != nil { host = "0.0.0.0" // 获取失败时回退默认值 } port, err := tenEnv.GetPropertyInt64("http_port") if err != nil { port = 8001 // 获取失败时回退默认值 }也就是说,即便属性缺失或类型不匹配,扩展也会静默回退到默认值,不会导致启动失败。需要注意的是http_port被声明为int64,在 JSON 配置中应写成数字而非字符串。若需监听指定网卡,可在 Graph 节点属性中覆盖http_host;在 transcriber_demo 的 property.json 中该节点未显式配置属性,因此实际运行时使用默认的0.0.0.0:8001。
扩展 API 契约:命令与数据
manifest.json 的api字段声明了完整的对外契约:
- cmd_out:
start_play(对外发送的播放命令); - audio_frame_in / audio_frame_out:
pcm_frame(接收/转发音频帧); - data_in:
asr_result(接收转写数据)。
发送的命令start_play
由 HTTP 请求触发,扩展构造 TEN 命令并发送。命令属性:
file_path(string):要播放的音频文件路径,必填;loop_playback(bool):是否循环播放,可选。
对应的构造逻辑见 server/server.go 的handleStartPlay:先校验file_path非空,再依次cmd.SetPropertyString("file_path", ...)、cmd.SetProperty("loop_playback", ...),最后tenEnv.SendCmd(cmd, nil)(fire-and-forget,不等待响应)。
接收的数据asr_result
扩展通过OnData接收图内 ASR 扩展发送的转写数据(main.go)。原文档称接收的数据名为display_text,但实际源码按asr_result匹配:
if dataName == "asr_result" { text, _ := data.GetPropertyString("text") final, _ := data.GetPropertyBool("final") e.server.BroadcastAsrResult(text, final) }数据属性:
text(string):转写的文本内容;final(bool):是否为最终结果(false表示中间结果/interim)。
这一点以源码为准:在 transcriber_demo 的 property.json 中,azure_asr_python的data_out名称就是asr_result,与OnData的匹配逻辑一致。文档编写时若仍沿用display_text命名,需注意与源码保持一致,否则数据无法被接收。
实战接线:把扩展串进 TEN Graph
通用 Graph 配置(原文档示例)
原文档给出了在predefined_graphs中组合 web_control、audio_player、asr 三个扩展的最小配置,要点如下:
web_control节点(addon 为web_audio_control_go)通过property设置http_port;- 命令流:
web_control的cmd_out名start_play指向audio_player; - 音频流:
audio_player的audio_frame_out名pcm_frame指向asr; - 数据流:
asr的data_out名display_text指向web_control。
{ "nodes": [ { "type": "extension", "name": "web_control", "addon": "web_audio_control_go", "property": { "http_port": 8001 } }, { "type": "extension", "name": "audio_player", "addon": "audio_file_player_python" }, { "type": "extension", "name": "asr", "addon": "your_asr_extension" } ], "connections": [ { "extension": "web_control", "cmd_out": [ { "name": "start_play", "dest": [{ "extension": "audio_player" }] } ] }, { "extension": "audio_player", "audio_frame_out": [ { "name": "pcm_frame", "dest": [{ "extension": "asr" }] } ] }, { "extension": "asr", "data_out": [ { "name": "display_text", "dest": [{ "extension": "web_control" }] } ] } ] }仓库中的真实接线:transcriber_demo
仓库中 transcriber_demo 的 property.json 给出了该扩展的真实落地方案,比原文档示例更完整,包含四个节点与多条数据流:
- 节点:
azure_asr_python(ASR,属性用${env:AZURE_STT_KEY|}、${env:AZURE_STT_REGION|}、${env:AZURE_STT_LANGUAGE|en-US}做环境变量注入)、web_audio_control_go、audio_file_player_python(音频播放)、vtt_nodejs(录音/VTT 会话); - 命令流:
web_audio_control_go的start_play→audio_file_player_python;start_recording/stop_recording→vtt_nodejs; - 音频帧流:
web_audio_control_go与audio_file_player_python的pcm_frame同时分发到azure_asr_python和vtt_nodejs; - 数据流:
azure_asr_python的asr_result分发到web_audio_control_go与vtt_nodejs。
这也解释了扩展为何同时具备audio_frame_in(接收来自播放器的pcm_frame)与audio_frame_out(把音频帧转发给 ASR)。此外,start_recording/stop_recording命令虽然未在扩展文档的 API 章节列出,但已由 server/server.go 的/api/start_recording、/api/stop_recording路由实现,属于文档之外的真实能力。
访问与操作 Web 界面
应用启动后,浏览器访问:
http://localhost:8001根路径/会在 server/server.go 中被重定向到/static/index.html。界面操作流程:
- 在 "Audio File Path" 输入框中填写音频文件路径(也可通过文件选择器上传,见下);
- 可选:勾选 "Loop Playback" 启用循环播放;
- 点击 "▶️ Start Transcription" 按钮开始播放与转写;
- 转写文本实时显示在下方 "Transcription Results" 区域,每条文本以卡片形式呈现并自动滚动到最新内容。
前端页面还提供了两个附加能力(index.html 中的元素可印证):fileSelector文件选择器与selectFileBtn按钮对应/api/upload上传接口;页面底部还提供跳转到/static/recordings.html(录音回放页)与/static/microphone.html(麦克风采集页)的入口。
连接状态与自动重连
页面顶部有连接状态指示器(connectionStatus/statusText):
- 🟢Connected:与服务器连接正常;
- 🔴Disconnected:与服务器断开连接。
前端通过connectWebSocket()建立ws://连接,断线后在onclose回调中以 3 秒间隔自动重连(setInterval(connectWebSocket, 3000)),无需刷新页面即可恢复实时转写推送。
错误提示
- 文件不存在等播放失败场景:HTTP 接口返回错误 JSON,前端
statusMessage区域给出明确提示; - 网络错误:
ws.onerror/onclose被触发,页面进入断开状态并提示。
WebSocket 消息协议
服务器 → 客户端
原文档给出的简化格式:
{ "type": "text", "data": "Transcribed text content" }实际源码(server/server.go)中WebSocketMessage结构体定义的字段更丰富,type取值包括asr_result、audio_data、vad_status、error:
{ "type": "asr_result", "text": "转写文本", "final": true, "is_speech": false, "sample_rate": 16000, "channels": 1, "samples_per_channel": 320 }- 转写推送走
asr_result类型(含text与final),前端据此区分中间结果与最终结果; - VAD 状态走
vad_status类型(含is_speech),用于实时显示说话状态; - 浏览器向服务器上传麦克风音频时,先发送一个
audio_data类型的 JSON 元数据(声明sample_rate、channels、samples_per_channel),随后紧跟二进制音频帧;服务器收到后调用audioDataHandler封装为pcm_frame音频帧发回 TEN 图。
客户端 → 服务器
- 文本消息:JSON,用于上传音频元数据(
type: "audio_data"及采样参数); - 二进制消息:PCM 音频数据,服务器解析元数据后以
16-bit PCM、interleave 格式、mono封装成 TEN 音频帧。
HTTP API
POST /api/start_play
启动音频播放。请求参数(Form Data):
file_path:音频文件路径(必填);loop_playback:是否循环播放(true/false,可选)。
该接口同时支持multipart/form-data与application/x-www-form-urlencoded(源码先尝试ParseMultipartForm,失败则回退ParseForm)。
成功响应:
{ "status": "ok", "message": "Playback started" }错误响应:
{ "status": "error", "message": "file_path is required" }注意:file_path为空时返回 HTTP 400;命令构造或发送失败时返回 HTTP 500。由于是 fire-and-forget 发送,接口并不等待播放器确认,立即返回成功。
POST /api/upload(源码补充)
上传音频文件(multipart/form-data,字段名file,上限 100MB)。文件以时间戳_原文件名命名保存到系统临时目录audio_uploads,成功返回:
{ "status": "ok", "message": "File uploaded successfully", "file_path": "/tmp/audio_uploads/..." }前端可将返回的file_path回填到输入框,再调用start_play。
POST /api/start_recording 与 /api/stop_recording(源码补充)
分别构造start_recording/stop_recording命令发送给录音扩展(如vtt_nodejs),用于控制会话录制。
GET /api/list_sessions(源码补充)
读取./recordings目录下各会话的metadata.json,返回会话列表,供录音回放页使用。
技术栈与依赖
- 后端:Go;
- Web 框架:
net/http标准库; - WebSocket:
github.com/gorilla/websocket; - 前端:HTML5 + CSS3 + 原生 JavaScript(无框架);
- TEN 运行时:Go binding(
ten_framework/ten_runtime)。
依赖声明见 go.mod:
module ten_packages/extension/web_audio_control_go go 1.20 replace ten_framework => ../../../ten_packages/system/ten_runtime_go/interface require ( github.com/gorilla/websocket v1.5.1 ten_framework v0.0.0-00010101000000-000000000000 )其中replace指令把ten_framework指向仓库内ten_packages/system/ten_runtime_go/interface,这是 TEN 项目内扩展的标准做法,无需联网拉取运行时源码。
项目结构
packages/example_apps/transcriber_demo/ten_packages/extension/web_audio_control_go/ ├── main.go # 扩展入口:属性读取、OnStart/OnStop、OnData、OnAudioFrame、handleAudioData ├── server/ │ ├── server.go # Web 服务器实现:HTTP 路由、WebSocket、上传、命令转发、广播 │ └── static/ │ ├── index.html # 主控制页面(含文件选择、录音回放入口) │ ├── microphone.html # 麦克风音频采集页面 │ └── recordings.html # 录音会话回放页面 ├── manifest.json # 扩展清单:API 契约、依赖、显示名与描述 ├── property.json # 默认配置(http_host / http_port) ├── go.mod / go.sum # Go 模块定义与校验和 ├── LICENSE # Apache License 2.0 └── docs/ # README.en-US.md / README.zh-CN.md前端静态文件通过//go:embed static/*直接编译进二进制,运行期无需额外部署静态资源目录(见 server/server.go)。
实际源码中的扩展能力(文档之外的实现细节)
生命周期管理
OnStart:读取属性 →server.NewWebServer(host, port, tenEnv)→ 设置音频数据处理回调 →go e.server.Start()异步启动 HTTP 服务 →OnStartDone();OnStop:调用server.Stop()关闭所有 WebSocket 连接并关闭 HTTP 服务器,再OnStopDone()。
浏览器音频上传 → TEN 音频帧
handleAudioData是浏览器麦克风/上传音频进入 TEN 图的入口:用ten.NewAudioFrame("pcm_frame")创建音频帧,设置采样率、channel layout 0(mono)、每样本 2 字节(16-bit PCM)、AudioFrameDataFmtInterleave交织格式与每通道采样数,AllocBuf+LockBuf+copy+UnlockBuf填充数据后tenEnv.SendAudioFrame发送。该路径与 Graph 中audio_frame_out: pcm_frame的声明一一对应。
VAD 状态回传
OnAudioFrame检查帧上的is_speech属性(VAD 扩展加注),有该属性则BroadcastVadStatus把布尔状态推给所有前端;无该属性(未经 VAD)则直接返回。这使得浏览器页面可以在人开始/停止说话时实时切换状态显示。
广播与并发安全
clients表以sync.RWMutex保护;BroadcastAsrResult/BroadcastVadStatus遍历所有客户端WriteJSON,写失败即关闭并移除该连接,避免向失效连接反复写入。
常见问题排查
- 浏览器打不开
http://localhost:8001:确认扩展所在应用已启动,且http_port未被占用;若在远端部署,需改用主机 IP 或配置端口转发。 - 点击 Start 后提示
file_path is required:file_path为空,需填写音频文件在运行环境的绝对路径,或先用/api/upload上传后回填返回路径。 - ASR 结果不显示:核对 Graph 中 ASR 的
data_out名称是否为asr_result(与OnData匹配),并确认data流已指向web_audio_control_go。 - 日志查看:扩展使用
key_point日志类别输出关键事件(如服务启动、WebSocket 连接、最终 ASR 结果);可在 transcriber_demo 的 property.json 的log配置基础上,将key_point级别的日志输出到控制台或logs/debug.log文件,便于定位问题。
许可证
Apache License 2.0(LICENSE)。该扩展由 TEN Framework Team 维护,随仓库以 Apache 2.0 协议开源。
- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
相关推荐
TEN Framework 实战:用 Go 编写 Web 音频控制扩展(web_audio_control_go)实现浏览器驱动的实时转写
TEN Framework 实战:用 Go 编写 Web 音频控制扩展(web_audio_control_go)实现浏览器驱动的实时转写 导读 本文以 TEN
人工智能AI Agent多模态语音AI 应用TEN Framework 音频文件播放器扩展(audio_file_player_python)实战指南:多格式音频转 16kHz PCM 与 10ms 帧级播放
TEN Framework 音频文件播放器扩展(audio_file_player_python)实战指南:多格式音频转 16kHz PCM 与 10ms 帧级
人工智能AI Agent多模态语音AI 应用TEN Framework 讯飞实时转写扩展 iflytek_asr_python 集成指南
TEN Framework 讯飞实时转写扩展 iflytek_asr_python 集成指南 本指南以 iflytek_asr_python 扩展为主线,介绍如
人工智能AI Agent多模态语音AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考