news 2026/9/25 7:15:47

TEN Framework 中的 Web Audio Control Go 扩展:浏览器驱动的音频播放与实时转写实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TEN Framework 中的 Web Audio Control Go 扩展:浏览器驱动的音频播放与实时转写实战指南
  • 人工智能
  • AI Agent
  • 多模态
  • 语音
  • AI 应用

【免费下载链接】ten-framework

Open-source framework for conversational voice AI agents

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

导读

本文以 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 中的路由注册):

  1. 浏览器加载扩展内嵌的静态页面index.html;
  2. 用户提交音频文件路径与循环播放选项,页面以表单方式POST /api/start_play;
  3. 扩展解析表单,构造start_play命令并tenEnv.SendCmd发送给图内的音频播放扩展;
  4. 音频播放扩展输出pcm_frame音频帧,送往 ASR 扩展;
  5. ASR 扩展输出asr_result数据,扩展在OnData中解析出text与final字段,并通过 WebSocket 广播;
  6. 浏览器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_portint648001HTTP/WebSocket 服务器监听端口
http_hoststring0.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。界面操作流程:

  1. 在 "Audio File Path" 输入框中填写音频文件路径(也可通过文件选择器上传,见下);
  2. 可选:勾选 "Loop Playback" 启用循环播放;
  3. 点击 "▶️ Start Transcription" 按钮开始播放与转写;
  4. 转写文本实时显示在下方 "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,写失败即关闭并移除该连接,避免向失效连接反复写入。

常见问题排查

  1. 浏览器打不开http://localhost:8001:确认扩展所在应用已启动,且http_port未被占用;若在远端部署,需改用主机 IP 或配置端口转发。
  2. 点击 Start 后提示file_path is required:file_path为空,需填写音频文件在运行环境的绝对路径,或先用/api/upload上传后回填返回路径。
  3. ASR 结果不显示:核对 Graph 中 ASR 的data_out名称是否为asr_result(与OnData匹配),并确认data流已指向web_audio_control_go。
  4. 日志查看:扩展使用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

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

相关推荐

上一篇:HamsterBase完全指南:如何用这款本地优先的知识收集工具构建你的私人网页档案馆
下一篇:PowerJob Zookeeper注册中心终极指南:替代默认数据库方案

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 7:15:34

Qt+MySQL教务系统毕业设计:从数据库设计到驱动避坑全指南

简介&#xff1a;这是一套基于Qt框架与MySQL数据库的教务系统完整源码&#xff0c;包含学生、教师、管理员三种身份模块&#xff0c;覆盖课程管理、成绩录入与查询、用户权限区分等典型业务场景&#xff0c;面向计算机相关专业学生开展课程设计、毕业设计或项目初期演示使用&am…

作者头像 李华
网站建设 2026/9/25 7:10:18

treg CLI Agent 实战:OpenRouter 与 MCP 协议驱动的本地 AI 工作流

1. 从“treg”这个标题说起&#xff1a;一个被低估的CLI Agent入口第一次看到“treg”这个标题&#xff0c;很多人会一头雾水。它不像“codex cli”或者“claude cli”那样一眼能看出用途&#xff0c;也不像“openrouter”那样自带流量标签。但如果你最近在折腾AI Agent、MCP协…

作者头像 李华
网站建设 2026/9/25 7:10:15

使用 API Blueprint 描述超媒体 API:Polls Hypermedia API 实战范本

文档API设计教程 【免费下载链接】api-blueprint API Blueprint 项目地址&#xff1a; https://gitcode.com/gh_mirrors/ap/api-blueprint 点击查看 免费下载 API Blueprint 是一套建立在 Markdown 语义之上的 Web API 描述语言&#xff0c;而超媒体&#xff08;Hypermedia&am…

作者头像 李华
网站建设 2026/9/25 7:09:38

AI小说生成器快速上手教程:如何自动生成多章节长篇并衔接上下文

AI小说生成器快速上手教程&#xff1a;如何自动生成多章节长篇并衔接上下文 【免费下载链接】AI_NovelGenerator 使用ai生成多章节的长篇小说&#xff0c;自动衔接上下文、伏笔 项目地址: https://gitcode.com/GitHub_Trending/ai/AI_NovelGenerator 写长篇有个绕不开的…

作者头像 李华