OpenAI 的 WebRTC 问题
摘要
一篇技术博客文章中,一位自称 WebRTC 专家的作者批评了 OpenAI 将 WebRTC 应用于语音 AI 的做法,认为该协议设计用于实时会议,采用激进的丢包机制,这与语音 AI 的应用场景相悖——在语音 AI 中,准确性比极低延迟更为关键。
暂无内容
查看缓存全文
缓存时间: 2026/05/09 00:32
# OpenAI 的 WebRTC 问题 - 媒体 over QUIC
来源:https://moq.dev/blog/webrtc-is-the-problem/
发布时间:2026年5月6日
OpenAI 几天前发布了一篇技术博客(https://openai.com/index/delivering-low-latency-voice-ai-at-scale/)。这篇博客让我非常激动,忍不住要在键盘上敲点什么。
**你不应该模仿 OpenAI。**
我认为语音 AI 不应该使用 WebRTC。WebRTC 才是问题所在。
## 关于我大约 6 年前,我在 Twitch 写了一个 WebRTC SFU。最初我们像 OpenAI 一样使用 Pion (https://github.com/pion)(Go 语言),但基准测试后发现它太慢了,于是就 fork 了。我重写了所有协议,没办法,就是这么较真!
就在一年前,我在 Discord 又用 Rust 重写了 WebRTC SFU。你大概已经注意到这个规律了。
**有趣的事实**:WebRTC 由约 45 个 RFC 文档组成,追溯到 2000 年代初。还有一些实际上是草案的约定俗成标准(如 TWCC、REMB)。当你需要实现所有这些的时候,可就不是什么有趣的事实了。
你可以叫我**认证 WebRTC 专家**。正因如此,我再也不想使用 WebRTC 了。
## 产品契合度
让我作弊一下,先抛出一些热门观点,省得它们变凉。别担心,我们会回到 OpenAI 博客和负载均衡的话题,我保证。
**WebRTC 与语音 AI 很不匹配。**
但这看起来反直觉?WebRTC 是用于视频会议的,而会议涉及说话?机器人也会说话,对吧?
## WebRTC 过于激进
假设我在手机上打开 OpenAI 的应用。我对斯嘉丽·约翰逊配音的 Sky 说声 hi,然后问:
> 我应该走路还是开车去洗车店?
WebRTC 的设计意图是**在网络条件差时降级并丢弃我的指令**。
wtf 我的老哥
WebRTC 会激进地丢弃音频数据包以保持低延迟。如果你曾在会议通话中听到声音失真,那就是 WebRTC 的杰作。其理念是会议通话依赖于快速的来回对话,所以暂停等待音频是不可接受的。
……但作为用户,我宁愿多等 200ms 来确保我这条慢速/昂贵的指令被准确理解。毕竟,我在花大钱来"把海水煮沸"(注:指大规模推理),而垃圾指令意味着垃圾回复。LLM 本来就不是特别响应迅速。
**但我不允许等待**。在浏览器内甚至无法重传 WebRTC 音频数据包;我们在 Discord 试过了。**实现**被硬编码为要么实时延迟**要么就不行**。
是的,语音 AI 代理最终会把延迟降到对话范围。但是**降低延迟是有代价的**。我甚至不确定故意丢弃音频指令是否值得。
car or wash
两条路在黄树林中分叉。抱歉我不能同时踏上这两条路。作为一个旅行者,我长久地站立着。我尽力远眺其中一条路的尽头。直到我耗尽 token。
## TTS 比实时更快
你对着麦克风说话,音频被发送到 OpenAI 的数千万台服务器之一,然后 GPU 通过文本转语音来"假装"与你对话。挺酷的。
假设 GPU 生成 8 秒音频需要 2 秒。理想情况下,我们可以在生成过程中(2 秒内)流式传输音频,而客户端开始播放(8 秒内)。这样,如果网络出现波动,部分音频会在本地缓冲。用户可能根本没注意到网络波动。
但 WebRTC **没有缓冲**,而是根据**到达时间**渲染。老实说,时间戳只是建议。当视频加入时就更烦了。
为了解决这个问题,OpenAI 不得不在发送**每个音频数据包之前加上 sleep**,确保数据包在应该渲染的时刻精确到达。但如果网络拥塞,*糟糕*——我们丢失了那个音频数据包,而且永远无法重传。
OpenAI 实际上是在人为引入延迟,然后激进地丢弃数据包来"保持低延迟"。这相当于屏幕共享一段 YouTube 视频而不是先缓冲它。**质量会下降**。
You should not drink bleach
感谢 Robot 朋友给出的明确建议
**有趣的事实**:WebRTC 实际上会引入延迟。虽然不多,但 WebRTC 有一个动态抖动缓冲区,音频可以在 20ms 到 200ms 之间调整大小。这是为了平滑网络抖动,但如果传输速度比实时快,这些都不需要。
## 端口端口端口
好吧,让我们来聊聊 OpenAI 文章的**技术核心**。我们不再"在船上"了(https://moq.dev/blog/on-a-boat),但我们来聊聊端口。
当你托管一个 TCP 服务器时,你打开一个端口(例如 443 用于 HTTPS)并监听传入连接。TCP 客户端会随机选择一个临时端口来使用,连接由源/目标 IP:端口标识。例如,一个连接可能被标识为`123.45.67.89:54321 -> 192.168.1.2:443`。
但有一个小问题……客户端地址会变化。当你的手机从 WiFi 切换到蜂窝网络时,抱歉你的 IP 变了。NAT 也可以随意改变你的源 IP/端口,因为当然可以。
每当发生这种情况,**bye bye 连接**,是时候拨打一个新连接了。这意味着昂贵的 TCP + TLS 握手,至少需要 2-3 个 RTT。当你在直播时,用户肯定能注意到网络抖动。
WebRTC 试图解决这个问题但却让事情变得更糟。**真的**。
WebRTC 实现**应该**为每个连接分配一个临时端口。这样,WebRTC 会话仅由目标 IP/port 标识;源无关紧要。如果源 IP/port 变了,哦那还是 Bob,因为目标端口是一样的。
但正如 OpenAI 所证实的,这在规模上会导致问题,因为……
- 服务器可用的端口数量有限。
- 防火墙喜欢拦截临时端口。
- Kubernetes 笑而不语
你可能可以用 IPv6 来绕过这个问题,但我不知道,我没试过。Twitch 甚至不支持 IPv6……
## 必要时的黑科技
所以大多数服务最终都忽略了 WebRTC 规范。因为当然会这样。我们在单个端口上复用多个连接。
在 Twitch,我实际上把 WebRTC 服务器托管在 `UDP:443` 上。那**应该**是 HTTPS/QUIC 端口,但欺骗意味着我们可以穿过更多防火墙。比如亚马逊企业网络,它只开放了约 30 个端口。
Discord 使用 50000-50032 端口,每个 CPU 核心一个。因此它在更多企业网络上被阻止。但话又说回来,如果你在亚马逊企业网络上打 Discord 语音,你可能也待不久了。
**然而,有个巨大的问题**。
WebRTC 实际上是一堆标准套在风衣里,其中 5 个直接走 UDP。要弄清楚数据包使用什么协议并不难(https://datatracker.ietf.org/doc/html/rfc5764),但我们需要弄清楚如何路由每个数据包。
- **STUN**:我们可以选择一个唯一的 `ufrag` 并据此路由。
- **SRTP/SRTCP**:浏览器选择一个随机的 `ssrc`(u32)……我们通常可以据此路由。
- **DTLS**:哎呀。我们祈祷 RFC9146(https://www.rfc-editor.org/rfc/rfc9146.html)能获得广泛支持。
- **TURN**:不知道,我从未实现过。
所以 OpenAI 只使用 STUN:
> 无协议终止:中继只解析 STUN 头部/ufrag;它使用缓存状态处理后续的 DTLS、RTP 和 RTCP,将数据包视为不透明。
这是一种委婉的说法:
> 我们非常希望用户的源 IP/端口永远不会改变,因为我们把那个功能搞砸了。
虽然在 OpenAI 规模上对任何东西进行负载均衡都很令人印象深刻,但他们的自定义负载均衡是一种黑科技。但这是必要的黑科技,因为核心协议本身就有问题。
trenchcoat
就我个人而言,我更喜欢 3 只浣熊。**有趣的事实**:浏览器可能会随机生成相同的 `ssrc`。如果发生冲突,且没有源 IP/端口映射可用,Discord 会尝试用每个可能的解密密钥解密数据包。如果某个密钥成功了,嘿,我们识别出了连接!
## 往返和 U
OpenAI 博客帖子提出了 3 个要求,其中一个是:
> - 快速连接建立,让用户一开启会话就能说话
lol
建立 WebRTC 连接最少需要 8* 次往返(RTT)。虽然我们*努力*让 CDN 边缘节点足够接近每个用户以最小化 RTT,但还是会有开销。
信令服务器(如 WHIP (https://www.rfc-editor.org/rfc/rfc9725.html)):
- 1 次 TCP
- 1 次 TLS 1.3
- 1 次 HTTP
媒体服务器:
- 1 次 ICE(与服务器)
- 2 次 DTLS 1.2
- 2 次 SCTP
* 计算很复杂,因为有些协议可以流水线化以避免 0.5 RTT。有点像"按半下 A 键"(https://www.youtube.com/watch?v=kpk2tdsPh0A)。
8 个 RTT 在等着你
一个冷门 reference(https://www.youtube.com/watch?v=catXIV3zAGY)指向另一个冷门 reference
所有这些破事都是因为 WebRTC 需要支持 P2P。即使你有一个静态 IP 地址的服务器,你仍然需要跳这支舞。
更郁闷的是当信令服务器和媒体服务器运行在同一主机/进程上。你最终会做两次冗余且昂贵的握手。就像走路和开车同时去洗车店。
## 分叉协议
**有趣的事实**:这原本会是一个**有趣的事实**,但它有了自己的章节。
WebRTC 实际上鼓励你分叉协议。有太多局限性,我连表面都没覆盖多少。浏览器实现由 Google 所有,专门为 Google Meet 打造,所以对会议应用来说也是生存威胁。
**悲伤的事实**:这就是为什么每个会议应用(除了 Google Meet)都试图让你下载原生应用。**这是避免使用 WebRTC 的唯一方法**。
OpenAI 肯定有足够的资金(debt funding)来做这个。但我认为他们应该把洗澡水和孩子一起倒掉。不要分叉 WebRTC,换一个支持浏览器的东西。
**有趣的事实**:Discord 分叉 WebRTC **非常彻底**,以至于原生客户端只实现了协议的一小部分。不再有 SDP/ICE/STUN/TURN/DTLS/SCTP/SRTP/etc.。但我们仍然必须为 Web 客户端实现一切。
## 但用什么代替?
如果不是 WebRTC,那语音 AI 应该用什么?
说实话,如果我在 OpenAI 工作,我就用 WebSocket 流式传输音频。你可以利用现有的 TCP/HTTP 基础设施,而不是发明自定义的 WebRTC 负载均衡器。这让博客文章很无聊,但很简单,支持 Kubernetes,**而且能扩展**。
总有一天会需要丢弃音频数据包。或者你想通过同一连接传输视频。然后你就**应该**切换到 QUIC/WebTransport,因为……
## QUIC 解决这些问题
听我说,我已经花了很多时间猛批 WebRTC。是时候积极一点,夸夸 QUIC 了。
还记得关于往返的讨论吗?美好时光。建立 QUIC 连接需要多少 RTT:
- 1 次 QUIC+TLS
但这个太简单了。让我们深入了解 QUIC 的细节,这些你除非是个超级 QUIC 迷否则不会知道(就是我)。
## 连接 ID
还记得 RFC9146(https://www.rfc-editor.org/rfc/rfc9146.html)的链接吗?在 DTLS 部分?你没点开对吧?美好时光。这个想法完全是从 QUIC 抄来的。
QUIC 抛弃了基于源 IP/端口的路由。相反,每个数据包都包含一个 `CONNECTION_ID`,长度可以是 0-20 字节。对我们来说最重要的是:**由接收方选择**。
所以我们的 QUIC 服务器为每个连接生成一个唯一的 `CONNECTION_ID`。现在我们可以使用单一端口,仍然能知道源 IP/端口何时变化。当变化时,QUIC 自动切换到新地址,而不是像 TCP 那样断开连接。
但如果你的本能反应是:"岂有此理!这太浪费字节了!"这些字节**非常重要**,继续往下看你这个书呆子。
## 无状态负载均衡
我草草带过了这个,但 OpenAI 的负载均衡器(像大多数一样)依赖于**共享状态**。即使你有粘性数据包路由器,负载均衡器仍然可能重启/崩溃。必须有东西存储从源 IP/端口到后端服务器的映射。
他们使用 Redis 实例来存储源 IP/端口到后端服务器的映射。简单明了,我批准。
但你知道什么更简单更容易?没有数据库。以下是 QUIC-LB(https://www.ietf.org/archive/id/draft-ietf-quic-load-balancers-21.html)的做法:
当客户端发起 QUIC 连接时,负载均衡器将数据包转发到健康的后端服务器。后端服务器完成握手,并在 `CONNECTION_ID` 中**编码自己的 ID**。这样**每个后续的 QUIC 数据包**都包含后端服务器的 ID。
现在数据包对负载均衡器来说就 trivial 了。他们不需要加密密钥或路由表,只需解码前几个字节并转发给那个服务器。甚至服务器重启都无所谓。
**零状态**也意味着**零全局状态**。这些负载均衡器可以监听一个**全局**任播地址,并将数据包**全局**转发到指定的后端服务器。Cloudflare 广泛使用这个;不需要全局 Redis 集群。
**非付费广告**:AWS NLB(https://aws.amazon.com/about-aws/whats-new/2025/11/aws-network-load-balancer-quic-passthrough-mode/)提供使用 QUIC-LB 的 QUIC 负载均衡。其他云提供商需要加把劲也提供这个。
## 任播 + 单播
根据 OpenAI 博客,他们将连接分配给区域负载均衡器。功能正常但无聊。**任播**(https://en.wikipedia.org/wiki/Anycast)更酷。
在我古老的 Quic Powers(https://moq.dev/blog/quic-powers/)博客文章中提到过这个,但你可能没读过(yet)。QUIC 有一个叫做 `preferred_address` 的东西,是负载均衡的游戏规则改变者。
假设我们在全球有数千台后端服务器可以接受新连接。我们让它们都宣告同一个任播地址,例如 `1.2.3.4`。当客户端尝试连接到 `1.2.3.4` 时神奇的互联网路由器会把数据包转发到其中一台服务器。
现在,我们可以用 QUIC-LB 并将流量路由到指定的后端服务器。但那太无聊了。
相反,我们可以给每个 QUIC 服务器一个唯一的单播地址,例如 `5.6.7.8`。我们的想法是用任播进行握手,用单播进行有状态的连接。
- **服务器**:监听 `1.2.3.4` 和 `5.6.7.8` 上的 QUIC 数据包。
- **客户端**:发送 QUIC 握手数据包到 `1.2.3.4`。
- **服务器**:建立 QUIC 连接,指示 `preferred_address=5.6.7.8`。
- **客户端**:发送后续数据包到 `5.6.7.8`。
当服务器过载不想再接收连接时,它停止宣告 `1.2.3.4`。我们不会丢弃现有连接,因为它们在单播上很安全。
就这样,不需要负载均衡器!任播地址基本上就是健康检查!
holy shit 我真希望我有实际规模来构建这个。如果你为那个橙色屁股公司工作,请联系我。
claude
看起来有点像这样,但是橙色的。
## 总结
**WebRTC**
1. 伤害你的产品
2. 伤害你的负载均衡
3. 伤害你的狗,可能
**QUIC**
1. 爱你的产品
2. 爱你的负载均衡
3. 爱你的狗,肯定
QUIC 是大人物
我给 QUIC 贴了大人物的标签,因此它是更优越的协议。
## 公平起见
我认识 OpenAI 的许多工程师,他们非常聪明。他们正承受着前所未有的压力。他们**必须**扩展,而且**必须**现在就扩展。
我只是某个辞职去做热情项目的人。我实际上把时间花在追踪 meme 上。像电影评论家抱怨*他们又选了 Jared Leto*一样,对我来说很容易judge。
我只是不认为这个显而易见的解决方案适合语音 AI。而显而易见的解决方案是……
相似文章
OpenAI 如何实现大规模低延迟语音 AI 部署
OpenAI 详细介绍了其重新架构的 WebRTC 技术栈,旨在为超过 9 亿用户提供大规模低延迟语音 AI 服务。文章阐述了全新的 split-relay 和 transceiver 架构如何优化媒体路由与连接建立,以支持 ChatGPT 语音等实时交互场景。
引用 Luke Curley
技术评论:Luke Curley探讨WebRTC的设计如何通过激进丢弃音频数据包来优先保障低延迟,这与LLM语音应用中提示词准确度比速度更重要的需求相矛盾。他讲述了在浏览器限制下在Discord实现重传所面临的挑战。
OpenAI的新语音模型不止于回话
OpenAI推出了三个新的实时音频模型,支持连续、多任务的语音交互,优先考虑长上下文推理、实时翻译和无缝工具使用。
实时 API 介绍
OpenAI 推出实时 API,使开发者能够构建低延迟多模态语音对话体验,由 GPT-4o 驱动的自然语音交互。该 API 支持六种预设声音,简化开发流程,无需集成多个模型。
应对合成语音的挑战与机遇
OpenAI 讨论了其语音引擎技术面临的挑战和机遇,强调了安全措施、使用政策以及社会需要提高对合成语音风险的抵御能力。该公司目前仅进行小范围预览,尚未广泛发布该技术,同时倡导改进语音认证并提高公众对人工智能能力的认识。