- tags
- [WebRTC, 实时音视频, 边缘网络, 基础设施, 低延迟, OpenAI, 语音AI]
- created
- 2026-05-07
- updated
- 2026-05-07
- sources
- [raw/notes/openai-webrtc-relay-transceiver-2026-05-04.md]
定义¶
OpenAI 2026-05-04 公开的 WebRTC 重架构:把"包路由"与"协议终止"切成两层——无状态 relay(轻量 UDP 转发 + 全球 PoP + 小公网 footprint)+ 有状态 transceiver(持有 ICE / DTLS / SRTP / session lifecycle 全套 WebRTC 协议状态 + 与推理后端的内部协议转换)。设计目标 = 在不改客户端 WebRTC 行为的前提下,让 WebRTC 媒体能跑在 Kubernetes 上,且 first-hop 延迟够低支撑 ChatGPT voice / Realtime API / 研究项目的全球 900M+ WAU 规模。
驱动业务 = ChatGPT voice、Realtime API 的 WebRTC endpoint、若干研究项目。作者 Yi Zhang + William McDonald(OpenAI Members of Technical Staff)。
三个 collide at scale 的约束¶
原文:"three constraints that started to collide at scale":
- One-port-per-session media termination 不适合 OpenAI 基础设施 — 大段公网 UDP 端口范围在云 LB / Kubernetes service / 防火墙 / 滚动发布里都难管理
- 有状态 ICE 与 DTLS session 需要稳定所有权 — single-port-per-server 会让同会话的包打到不同进程,DTLS 握手或 SRTP 解密直接失败
- 全球路由必须保持 first-hop 低延迟 — first hop 决定 setup 与媒体的下限,跨公网到远端 region 不可接受
三条同时成立的解 = stateless forwarder + stateful terminator,是表 4 个候选方案中唯一同时满足三约束的。
为什么不是 SFU:1:1 latency-sensitive 选 transceiver 模型¶
SFU(Selective Forwarding Unit)= 多方场景标准 WebRTC media server,把 AI 当成"另一个参与者"加入。适合 group call / classroom / collaborative meeting。即便在 client-to-AI 产品里 SFU 也常是 default 起点,因为可以复用一套成熟系统做 signaling / media 路由 / 录制 / 可观测性 / 未来扩展(人接管 / 多参与者)。
OpenAI 的 workload 不同:most sessions are 1:1(一用户对一模型 / 一 application 对一 real-time agent),每个 turn 都对延迟敏感。所以选 transceiver 模型——一个 WebRTC edge service 终止客户端连接,再把 media 与 events 转换成更简单的内部协议给推理 / 转写 / 语音生成 / 工具调用 / 编排服务用。
transceiver 是唯一拥有 WebRTC session state 的服务(ICE 连通性检查、DTLS 握手、SRTP 加密密钥、session lifecycle)。把状态压在一个地方让会话所有权可推理,让后端服务能像普通服务那样 scale,而不是表现得像 WebRTC peer——这与 agora-rtsa-sdk "把 SDK 边界设计成只做不可省的事,把设备相关的留给应用"是同构的边界设计判断。
最初实现 = 单一 Go service 基于 Pion(Sean DuBois 创建并维护的开源 WebRTC Go 实现,DuBois 现在 OpenAI),同时处理 signaling 与 media termination。
候选方案对照(OpenAI 表)¶
| 方案 | 优点 | 缺点 |
|---|---|---|
| Unique IP:port per session(native direct UDP) | 直接 client-to-server,无转发层 | 每会话一公网 UDP 端口;大端口范围难暴露/难安全;K8s/云 LB 不适配 |
| Unique IP:port per server | 公网 UDP 比 per-session 小很多;单 socket 应用层多路复用 | 单主机干净,跨 LB fleet 不行;首包仍可能落错实例 |
| TURN relay(协议终止式) | client 只需知道 TURN 地址 + 端口;可在边缘集中策略 | TURN allocation 加 setup RTT;跨 TURN server 迁移/恢复 allocation 难 |
| Stateless forwarder + stateful terminator(OpenAI 选) | 公网 UDP footprint 小;transceiver 仍持完整 WebRTC session | 媒体到 owning transceiver 多一跳转发;需要 relay 与 transceiver 之间的自定义协调 |
首包路由:协议原生字段做路由 hint¶
首包路由是这套设计的核心难点——relay 必须在 packet path 本身没有 session 之前就路由这个包,不能停下来查外部 lookup service。
WebRTC session 自带一个协议原生路由钩子:ICE username fragment(ufrag)——session setup 时交换、STUN 连通性检查里 echo 回来的短标识符。OpenAI 在服务端生成 ufrag 时把路由元数据编进去,刚好够 relay 推断 destination cluster + owning transceiver。
完整流程:
- signaling 时,transceiver 分配 session state,在 SDP answer 返回 shared relay VIP + UDP port(如
203.0.113.10:3478)——VIP 是虚拟 IP 前置整个 relay fleet - 客户端首个 media-path 包通常是 STUN binding request——ICE 用它验证可达
- relay 解析这第一个 STUN 包刚好够读出 server ufrag、解码路由 hint、转发到 owning transceiver
- 每个 transceiver 监听一个 shared UDP socket(一个 OS 端点 bind 到内部 IP:port,不是 per-session 一个 socket)
- relay 创建 client source IP:port → transceiver destination 的 session 之后,后续 DTLS / RTP / RTCP 包不再重新解 ufrag
- relay session 故意最小化:只有 in-memory session(驱动转发)+ 监控计数器 + session 过期清理 timer
- relay 重启丢 session 时,下一个 STUN 包从 ufrag 重建 session
- 进一步用 Redis cache 持久化
<client IP:port, transceiver IP:port>映射——relay 重启或新流可以从 Redis 恢复,不用等 STUN 重建
这是 agent-tool-design "在标准协议里塞自己语义、不引入新字段"的网络版——不查外部服务、不做应用层握手,把 ICE 协议已有字段(ufrag)重新解读出路由含义。
Global Relay:地理化部署¶
公网 UDP surface 收敛到一小批稳定地址 + 端口后,可以把同一个 relay 模式全球部署——Global Relay = 地理分布的 relay ingress 点,全部实现同样的包转发行为。
Broad geographic ingress shortens the first client-to-OpenAI hop because a packet can enter our network at a relay close to the user, in both geography and network topology, instead of crossing the public internet to a distant region first.
信令侧:Cloudflare 做 geo / proximity steering¶
OpenAI 用 Cloudflare geo + proximity steering 让初始 HTTP / WebSocket 请求落到附近 transceiver cluster:
- request context 决定 session location 以及对客户端 advertise 哪个 Global Relay ingress point
- SDP answer 给出 Global Relay 地址
- ufrag 让 Global Relay 把媒体路由到指定 cluster,relay 再路由到目标 transceiver
geo-steered signaling + Global Relay 把 setup 与 media 都放到附近 entry path 上,同时保持 session 锚定在一个 transceiver 上——直接缩短用户从开口到服务可响应的等待时间。
cloudflare-mesh 已记录 Cloudflare 是 Agent 时代私有组网首选;本架构给"Cloudflare 是前沿 AI 公司基础设施 default"加新证据形态——不是 Mesh / Workers,而是边缘 DNS / proximity routing 这一层。
relay 实现:Go + Linux 调优、不上 kernel bypass¶
写在 Go 里,故意保持窄实现。Linux 内核 networking stack 收 UDP 包并交给 socket,relay 在用户态:普通 Go 进程从 socket 读包头、更新少量 flow state、转发——不终止 WebRTC。没有用 kernel-bypass 框架(如 DPDK / AF_XDP)。
关键设计选择¶
- No protocol termination:relay 只解析 STUN 头 / ufrag;DTLS / RTP / RTCP 用缓存 state,包对 relay 是不透明的
- Ephemeral state:小型短超时的 in-memory map,仅记 client address → transceiver destination
- Horizontal scalability:多 relay 实例并行跑在 LB 后面。state 不是 hard WebRTC state,重启只丢极少流量、流恢复快
效率措施¶
SO_REUSEPORT(Linux socket 选项):同机多 relay worker bind 同一 UDP 端口,内核分发包到 worker,避免单读取循环瓶颈runtime.LockOSThread:把每个读 UDP 的 goroutine 钉到具体 OS 线程;配合SO_REUSEPORT,倾向于把同一 flow 的包留在同一 CPU core,提升缓存局部性 + 减少上下文切换- 预分配 buffer + 最小拷贝:减少解析与分配开销,避免 Go GC
"This implementation handled our global real-time media traffic with a relatively small relay footprint, so we kept the simpler design instead of taking on a kernel bypass route." —— first-principles-deletion "必须这么久 / 必须这么重吗" 的工程化范例:常见 case 优化够用就先不上更复杂的方案。
四条核心选择(原文 takeaways)¶
- Preserve protocol semantics at the edge — 客户端仍说标准 WebRTC,浏览器 + 移动端互通不变
- Keep hard session states in one place — transceiver 持 ICE / DTLS / SRTP / session lifecycle,relay 只转发
- Route on information already present in setup — ICE ufrag 给了 first-packet 路由 hook,无需热路径外部 lookup
- Optimize for the common case before reaching for kernel bypass — 窄 Go +
SO_REUSEPORT+ thread pinning + 低分配解析对当前 workload 已经够用
与 Sentino 路径的对照¶
Sentino 走 Agora SD-RTN(agora-rtsa-sdk / agora-rtc-voice / sentino-iot),把"全球低延迟实时媒体到云端 AI"问题外包给多租户基础设施供应商。OpenAI 自建 Global Relay = 同一类问题的 in-house 解。两者形成同源问题、不同规模阶段、不同自建/外包姿态的对照:
| 维度 | OpenAI(in-house Global Relay) | Sentino(Agora SD-RTN) |
|---|---|---|
| 全球可达 | 自建 PoP + Cloudflare geo steering | Agora SD-RTN(多租户共享) |
| WebRTC 终止 | 自家 transceiver(Pion + Go) | Agora 服务端 + Agora ConvoAI Agent |
| 与推理后端连接 | "simpler internal protocols"(未披露) | MQTT 信令 + Agora ConvoAI HTTP callback / Sentino LLM endpoint |
| Kubernetes 适配 | 需要重新架构(split relay) | 由 Agora 屏蔽,Sentino 不感知 |
| 模型与媒体的耦合 | gpt-realtime 走自家栈 | Agora ConvoAI Agent + Sentino LLM endpoint 半自定义模式 |
| 数据控制 / 合规自由度 | 完整(自家 fleet) | 受 Agora 数据治理边界约束(agora-rtsa-sdk E2E 加密段) |
| 触发自建的规模阈值 | 900M+ WAU | Sentino 远低于此 |
含义:对 Sentino 当前规模,自建无意义;但本文是"为什么 SD-RTN 这类服务有价值"的反向证据——OpenAI 在 900M WAU 的规模下重新做这件事的工程量极大(split relay + ufrag 编路由 + Cloudflare 编排 + Go 调优 + Pion 维护)。Agora 屏蔽掉的就是这套底层路由 / 状态粘性 / 端口管理问题。
watching point:Sentino 当前用 Agora ConvoAI 的"AI as participant in SFU"模型(Agent 在频道里以参与者身份加入),OpenAI 明确说这是大多数客户的 default 起点,但 1:1 latency-sensitive workload 应该走 transceiver 模型。当某个 Sentino 客户的并发量 / 延迟敏感度大幅升高时(比如未来的实时 barge-in 重度场景),是否需要考虑迁移到 transceiver 模型 = 待跟踪开放问题。
SFU 迁移到 transceiver 模型:Sentino 视角的具体对比¶
OpenAI 选 transceiver 不是偶然——是 1:1 latency-sensitive workload 的最优形态。Sentino 当前走 SFU 形态(Agora ConvoAI Agent 作为参与者加入频道),与 OpenAI 的 transceiver 形态在协议状态归属、路径跳数、多租户隔离、客户改造成本几个维度上有结构性差异。下面把"如果 Sentino 未来要迁移"的代价 / 收益拆出来,作为长期 watching point 的具体抓手。
维度对比¶
| 维度 | 当前 SFU 形态(Agora ConvoAI) | 迁移后 transceiver 形态(OpenAI 范式) |
|---|---|---|
| WebRTC session 终止点 | Agora 服务端 SFU;Agent 是另一个 WebRTC peer | 自家 transceiver;推理后端走简化内部协议(不是 WebRTC peer) |
| Agent 角色 | 在 Agora 频道里以"参与者"身份加入,与设备同等地位 | Agent 不感知 WebRTC,只看 transceiver 转过来的"已解码音频帧 + 事件" |
| 媒体路径跳数 | 设备 → Agora SD-RTN edge → SFU → Agent 出口 → ASR/LLM/TTS | 设备 → relay → transceiver(一处终止)→ 推理后端 |
| 路径上的协议解码次数 | 至少 2 次(设备入口 + Agent 出口) | 1 次(transceiver) |
| 会话所有权管理 | Agora 服务端透明处理 | 自家 transceiver 持有 ICE/DTLS/SRTP;relay 用 ufrag 路由 |
| 公网 UDP footprint | Agora SD-RTN(与 Sentino 无关) | 自家小而稳定的 VIP 集合(要自己管) |
| Kubernetes 适配复杂度 | Agora 屏蔽,Sentino 不感知 | 自己解决(split relay + ufrag 路由 + Redis cache + SO_REUSEPORT …) |
| 首包路由到 owning instance | Agora 内部解决 | 必须自建(ICE ufrag 编路由元数据 + relay 解析) |
| Geo steering 信令 | Agora 边缘网络自动就近 | 自己接 Cloudflare geo + proximity steering |
| AI 进入对话的延迟 | Agent 走 join channel 路径 + 一段冷启动;首字延迟受 Agora SFU 调度影响 | transceiver 直接对接推理后端;首字延迟受自家栈控制 |
| Barge-in / turn-taking 控制权 | 受 Agora ConvoAI v2 join API 的 turn_detection 配置约束(agora-convoai-join-api) | 完全在自家 transceiver + 推理后端协调,无中间 SFU 调度 |
| E2E 加密 vs 服务端 ASR 互斥(agora-rtsa-sdk) | 启用 E2E → ConvoAI 服务端 ASR 不可用 | transceiver 在自家 trust boundary 内解密 → ASR 与 E2E 不互斥(trust 边界从 Agora 收回到 Sentino) |
| 数据治理 / 合规自由度 | 受 Agora 数据治理边界约束 | 完整自家控制 |
| 多租户隔离机制 | Agora 频道 + token | 自家 transceiver 做租户隔离设计 |
| 客户端改造 | 无(设备已跑 Agora RTSA / RTC SDK) | 设备改走纯 WebRTC,丢弃 Agora SDK 依赖(这是最大破坏点) |
| 运维负担 | 几乎只看 Agora 控制台 | 自家 PoP / relay 健康 / Pion 跟进 / 跨 region 故障切换全自担 |
| 触发自建的规模阈值 | 低(任何规模都能跑) | 极高(OpenAI 900M+ WAU;Discord 250 万并发语音;Sentino 远低于此) |
| 协议互通失去什么(agora-rtsa-sdk "RTSA ↔ RTC ↔ Web 协议互通") | 设备 / Web / 移动端跨形态在同一频道互通 | 不同形态需要各自接入 transceiver;天然失去多端在同一会话的能力(对 Sentino 当前 1:1 设备 ↔ Agent 形态影响小,但对未来"人 + 设备 + AI 三方在同一会话"场景是失去) |
触发迁移的三类场景(Sentino 视角)¶
- 单客户并发 + 延迟敏感度同时升高到 Agora 多租户调度成为瓶颈 — 例如某客户的实时 barge-in 重度场景(用户 / Agent 几乎不停打断对方)发现 Agora SFU 的调度延迟对体感影响明显。但这要求单客户量级足够大让 Agora 公允分配的边缘资源不够用——Sentino 远未到这个规模
- E2E 加密 + 服务端 ASR 同时强需求 — 像 sentino-tenga 这种性健康对话场景,如果未来同时需要"端到端加密 + 用户语音必须经 ASR 转写",当前 Agora ConvoAI 服务端 ASR 与 E2E 加密互斥(agora-rtsa-sdk "反直觉副作用"段)。迁移到自家 transceiver 可以把 trust 边界从 Agora 收回到 Sentino,让 ASR 在自家 trust boundary 内做。但这条有更便宜的中间方案——设备端做 ASR / 在 Agora ConvoAI 之外接私有 ASR endpoint,不需要重做 RTC 栈
- 数据主权 / 合规要求强到 Agora 数据治理 unacceptable — 例如某 enterprise 客户合规要求"音频 metadata 不出 Sentino 控制平面"。同样有更便宜的方案——选择 Agora 的私有化部署 / Region 隔离方案,不需要重做 RTC 栈
三类场景的共性:每一种"必须迁移"的硬触发点都有更便宜的中间方案可以缓解。OpenAI 走自建是因为 900M WAU + 全球分布的边际成本曲线让自建变成正向 ROI;Sentino 短中期没有任何场景能让自建 ROI 成立。
渐进式迁移路径(如果未来真要走)¶
如果某一天硬触发点出现,迁移不是一步到位,而是分阶段:
- 第一步:保留 Agora SD-RTN 做 client 侧入口,但把 Agora ConvoAI Agent 替换成自家 transceiver pod—— Agent 不再"作为参与者加入",而是设备的 RTC stream 在 Agora 出口被引流到自家服务做"ConvoAI 兼容协议"接入。这一步不需要客户端改造。Agora 这个层面的能力是否对外暴露需要确认
- 第二步:把"Agora SD-RTN 入口 → 自家 transceiver"路径换成"自家 relay PoP → 自家 transceiver",但仍接受 Agora SDK 客户端(设备 / Web 端兼容)。需要在 SDP / ICE 协议层做兼容工作,跨度大
- 第三步:完全去 Agora 化——设备 / Web 端改走纯 WebRTC(Pion / 浏览器原生),自家 transceiver 接管所有协议状态。需要客户端 SDK 替换,对已出货设备是 OTA 升级风险
每一步的代价都比上一步大一个量级。对 Sentino 当前阶段,第一步以前的所有事情都不用做——保持现状即可,把这条路径作为长期 watching point。
真正的预警信号(不是"现在该迁移",是"该开始评估")¶
Sentino 应该在以下任一信号出现时启动评估,而不是等到不得不迁移:
- 单客户日活超过 10 万级且要求实时 barge-in(不是回合制对话)
- 多个客户场景同时要求 E2E 加密 + 服务端 ASR(sentino-tenga + 类似敏感场景客户)
- enterprise 客户合规要求 "音频不经第三方"(sentino-agent enterprise 模式的硬约束)
- Agora 商务条款 / 价格 / SLA 出现结构性恶化(这是商务侧的迁移压力,不是技术侧)
- Agora 战略路径明确收缩对 IoT / AI 语音的支持(与 sentino-iot "Sentino 是 Agora 合作伙伴" 关系产生张力)
Watch 而不 Act 是当前正确姿态——本节存在的意义是给"什么时候该 act" 一个明确清单,不是建议现在就动。
反常识 / 工程细节¶
- Pion 是 Go 写的开源 WebRTC 实现,OpenAI 的 transceiver 直接基于它 — 印证开源协议栈选 Go 实现的可行性,不是必须 C++(agora-rtsa-sdk 走 C 是嵌入式约束,OpenAI 服务端走 Go 是 K8s + 工程效率约束)
- Pion 的创建者 Sean DuBois + WebRTC 原始架构师 Justin Uberti 现在都在 OpenAI — 标准协议生态侧的关键人物物理上集中到一家公司,是"协议层的人才聚拢"信号;与 Anthropic 在 mcp-protocol 协议层的姿态对照,OpenAI 在 WebRTC 协议层有同等深度的人才布局
- 不上 kernel bypass 是有意识的选择,不是技术不行 — DPDK / AF_XDP 这种方案常被互联网公司视为高并发 UDP 必备;OpenAI 明确说"窄 Go 实现 + SO_REUSEPORT + thread pinning + 低分配解析对我们 workload 够用",是 first-principles-deletion "必须用最快方案吗"的反向选择
- Redis 不是 transceiver 的协议状态存储,只是 relay 路由 cache — 真正的 WebRTC 协议状态在 transceiver 内存里。Redis 让 relay 重启更平滑而已。这与"Redis = session 持久化"的常见架构假设是反向的——OpenAI 选择把协议状态留在 transceiver 内存里、靠"重启从 ufrag 重建"做容错
- 客户端无感知 — 整个架构的复杂度对客户端不可见,浏览器 / 移动端跑标准 WebRTC、不需要任何 SDK 特殊改动。这是 harness-engineering "标准开放、最佳实现封闭"在网络协议层的应用
待办与开放问题¶
- relay 实例数 / Global Relay PoP 数 / transceiver cluster 数 — 文章未披露,竞品对照需要数字
- transceiver 与推理后端的 "simpler internal protocols" 是什么 — 私有 binary?gRPC over QUIC?protobuf over HTTP/2?文章未披露
- 媒体面 RTT p50 / p95 数字 — 文章只定性"low and stable",无具体数字
- Pion upstream 是否会受益 — Sean DuBois 在 OpenAI 内部对 Pion 的修改是否反向贡献回开源版本,决定 Sentino / 其他 WebRTC 玩家能否搭便车
- gpt-realtime 模型对此架构有无特定假设 — 文章未提模型层耦合
- Realtime API WebSocket vs WebRTC endpoint 客户分布 — 只说"WebRTC endpoint"被驱动,WebSocket 接入未提
相关概念¶
- agora-rtsa-sdk — Agora RTSA C SDK 是 IoT 嵌入式设备的对偶选择;同一类 "WebRTC 化媒体 + AI 后端" 问题的多租户 SDN 解 vs OpenAI 自建 in-house 解的对照
- agora-rtc-voice — Agora RTC AI 语音对话设备端视角;OpenAI Global Relay 对应 SD-RTN 的角色
- voice-presence — 语音临场感 / 对话动态(自然时机 / 停顿 / barge-in)的产品体感下限被网络栈决定,不是被模型决定
- cloudflare-mesh — Cloudflare 在 Agent / AI 基础设施的 default 化;本架构给加新证据形态(geo / proximity steering)
- harness-engineering — Environment 层的网络版:"thin routing layer + thick stateful terminator" 是 Harness "分层处理不同保质期信息"原则在 RTC 媒体平面的应用
- first-principles-deletion — relay 故意只读 STUN ufrag、不解密、不上 kernel bypass,是"必须这一层做这个吗 / 必须用最复杂的方案吗"的双重应用
- agent-tool-design — ICE ufrag 编路由元数据是"在标准协议里塞自己语义、不引入新字段"的范例
- voice-ai-companion-market — 900M WAU + first-hop 优化是 voice AI 设备 / 应用市场体感门槛的工程对照
- mcp-protocol — Anthropic 在 MCP 协议层的人才布局 vs OpenAI 在 WebRTC 协议层(Sean DuBois + Justin Uberti)的人才布局对照
- sentino-iot — Sentino IoT 走 Agora SD-RTN 路线;本架构是"自建路线"的反向参照样本
- sentino-agent — 当 Sentino 客户的并发与延迟敏感度大幅升高时,是否需要从 "AI as participant in SFU" 迁移到 transceiver 模型 = 长期 watching point