最近被那个“从 WebRTC 创造者到 OpenAI Realtime AI 负责人”的话题刷屏了。Justin Uberti 这个名字,做过实时通信的人不可能不认识——2011 年他在 Google 牵头搞出 WebRTC,后来又去微软折腾过一阵子,再后来直接去了 OpenAI 负责 Realtime API。这条职业轨迹本身就很有意思:一边是让浏览器原生支持音视频通话的祖师爷,一边是2025年最火的人机实时语音交互底座。我花了差不多一周时间,把他公开分享里聊到的架构思路、WebRTC 的老底子,以及 OpenAI Realtime API 的实际表现放在一起做了个系统梳理,这篇笔记就是这次调研的记录。

如果你正在做 Voice Agent、语音客服、实时语音助手这类东西,或者只是好奇“为什么 2025 年的语音 AI 不直接用 WebRTC 反而用 WebSocket”,这篇内容应该能给你省下不少弯路。我会尽量用实践经验去拆解背后的取舍,而不是停留在“用这个好、用那个好”的口水对比上。

1. 为什么实时语音AI不等于WebRTC通话,前提从“通路”变成了“端点”

1.1 传统WebRTC解决的核心问题:让人和人之间“把话说清楚”

要理解 Justin Uberti 后来在 OpenAI 做的事情,先得搞清楚 WebRTC 当初存在的意义。2011 年前后的现实是:你想在浏览器里和另一个人视频通话,需要装插件、开额外的客户端,或者依赖 Flash 这种已经快被抛弃的东西。Google 想做的是把“两个真人终端之间低延迟音视频互通”变成浏览器原生能力,于是 WebRTC 出现了。

WebRTC 的技术栈是围绕“通路”设计的:用 ICE 框架打洞,尽力在两个端点间建立一条尽量短的 UDP 通道;如果打洞失败就降级到 TURN 服务器中转;UDP 上跑 RTP/SRTP 来传音频和视频,靠 DTLS 做密钥协商;同时用 Opus 这种专门为实时语音设计的编解码器处理音频,再用 NetEq 做抖动缓冲和丢包隐藏。

这套东西本质上关心的问题只有一个:网络路径在哪儿、包有没有丢、延迟能不能接受。但是在 WebRTC 的世界里,链路两端都是真人,他们说的话、语气里的情绪、停顿长短,通信层是完全不在意的。RTP 包里装的是“声音波形”,不是“语义”。

1.2 实时语音AI的本质:一切流量都汇向同一个模型端点

到了 Voice Agent 场景,架构前提彻底变了。用户对着麦克风说话,音频流的目的地不是另一个人的耳朵,而是一个正在跑的模型推理服务。语音要先被活动检测(VAD)切出“用户说完话了”的边界,然后送进识别模块,转成语义表示,再丢给大模型做推理,最后还要走语音合成把回复播出来。

这个链路的形态不是点对点,而是星型:所有终端的声音都流向服务端的同一个智能端点,同时服务端要把生成结果推回给终端。“终端到终端”变成了“终端到模型”,这就让过去 WebRTC 里很多核心机制变得不那么有用了,甚至会成为负担。

我自己的体验是:当时第一次接触语音 Agent 项目时,第一反应是“那直接用 WebRTC 把麦克风音频推到服务器不就行了”。实际做起来才发现,推流只是最简单的一步,真正麻烦的是事件边界。WebRTC 的音频流是连续不断的 PCM 包,服务端得自己去做 VAD、自己判断“这句话说完了没有”,而且一旦模型开始回应,还要处理打断、回声、多轮状态同步——这些和 WebRTC 媒体层完全是两套逻辑。

1.3 如果强行把 WebRTC 接到模型后端,会发生什么

这里不是说不可以,而是代价非常具体。我梳理了一下,如果坚持用 WebRTC 做 Voice Agent 的传输层,至少要面对四层额外复杂度:

  • 服务端要处理 ICE 候选收集、DTLS 握手、SRTP 加解密。这些在浏览器端是原生能力,但到了服务端,除非你直接用现成的 SFU(比如 mediasoup、Janus、LiveKit),否则就得自己折腾底层多媒体库。WebRTC 规范本质上是写给浏览器的,服务端想去“听懂”流里的内容,必须先搞定整套媒体协议栈。
  • 媒体通道和推理状态需要额外对齐。WebRTC 传的是连续的音频帧,不携带任何“用户说完了”“现在该我了”这种语义边界。服务端必须自行维护一个独立的 VAD 状态机,再把切分结果去触发模型推理。出问题时,你往往分不清是网络抖动导致的音频断裂,还是 VAD 判断失误。我实际调试时遇到过一个现象:某个用户网络波动 200ms,音频重排后产生了一个假的静音段,VAD 直接判定“用户说完了”,结果模型就在对方还在说话时抢答了。这个 bug 排查了大半天才定位到是音频缓冲和 VAD 的边界没对齐。
  • 用户配置和会话管理会变复杂。Voice Agent 是需要鉴权、计费、上下文的,这些业务逻辑都在应用层。如果用 WebRTC,你得给每个会话同时维护媒体通道和应用层连接,两个通道还要保证状态一致。会话中断、重连、切换模型,全部要双端同步。我在一个实际项目中试过这种方案,逻辑能通,但心智负担很重:凡是涉及“重新连接”的操作,都要先判断媒体层断了没有、应用层断了没有、两边状态是否一致。一旦同步出错,轻则静音,重则卡死,需要完整的重置流程才能恢复。
  • 弱网优化策略会被模型推理时序绑架。WebRTC 的 NetEq、丢包隐藏算法针对的是“人耳听着连续”这一目标。但 Voice Agent 更关心的是“模型能不能尽快拿到完整的一句话”。两者优化目标不同:前者希望用插值把丢掉的 20ms 补上,后者则更希望尽快把整句话的边界确认下来,好缩短模型开始推理的等待时间。

所以,与其说 OpenAI Realtime API 是“放弃 WebRTC”,不如说是他们在做一个以模型推理为中心的实时链路,而不是以媒体传输为中心的通信链路。这个出发点上的差异,决定了后面几乎所有架构选择。

2. OpenAI Realtime API 的架构事实:比“WebSocket 传音频”再多看一层

2.1 公开可观察到的传输选型:一条事件驱动的全双工通道

先声明一下:OpenAI 并没有把 Realtime API 的完整架构开源或写成长篇文档,很多细节是从公开演讲、SDK 源码、以及实际调试中的网络行为反推出来的。我的结论只能代表一种合理的工程推断,不是官方定性。

从运行时的角度观察,Realtime API 的传输底座用的是 WebSocket,而不是 WebRTC。客户端和服务端在一条 WebSocket 连接上双向交换消息,消息分成两类:一类是 JSON 格式的事件,比如 session.update 、 response.create 、 input_audio_buffer.speech_stopped 、 conversation.item.created ;另一类是二进制音频块,通常是 24kHz、16-bit、单声道的 PCM。

这种设计最核心的特点是:音频和语义事件在同一个通道里按顺序流动。WebSocket 天然自带消息边界和有序性,服务端发什么事件、什么时候发,客户端收到后的处理顺序是有保障的。这一点对模型驱动的对话特别关键,因为整个对话过程可以被描述成一组离散事件的有序序列,你不需要去解析 RTP 包里那些连续性音频流,只需要按事件处理就好。

另外,WebSocket 是 TCP 上的协议,天生避开了 UDP 在公网上可能出现的丢包和乱序问题。对于实时语音对话来说,偶尔的微小延迟增加,通常比音频数据丢失更可接受。而 WebRTC 的 UDP 通道追求的是低延迟传输,但 U 盘一样的不稳定性对模型推理来说并不友好。

2.2 语义事件与音频帧的关系:为什么“边界”比“音质”更重要

我在第一次接触 Realtime API 时,下意识会用 WebRTC 的思路去理解它:这不就是一个socket 传输音频嘛,和 RTP 传 PCM 没什么本质区别。但跑完一轮 demo 后我意识到,关键差异在于消息里携带的“语义信息”。

传统的 WebRTC 音频流是纯粹的声音数据,20ms 一包,服务端拿到后要自己判断这些声音里有没有人说话、说到了哪里。而 Realtime API 的事件流里,OpenAI 在服务端做了 VAD 检测,并用事件把“状态”显式告诉客户端。比如:

  • 用户开始说话时,服务端会发 input_audio_buffer.speech_started ;
  • 用户停顿超过阈值,服务端判定这句话结束了,发 input_audio_buffer.speech_stopped ;
  • 此时你再发 response.create ,模型就会开始推理。

这种机制让客户端和应用层不需要自己维护一个 VAD 状态机,直接把“那句用户输入已经完整结束”这个结论拿过来用。从工程角度讲,这相当于把“语音活动检测”这件很容易做砸的事情从应用层剥离了出去,交给专门为这项能力优化的模型侧处理。在 WebRTC 技术栈里,VAD 通常是一个附加模块,没有一个完善的事件机制告诉你“这句话的语义边界到哪儿了”,而这些语义边界正是对话式 AI 状态下流转的核心。

我自己写代码时的体感:WebRTC 方案需要花两三天把 VAD、静音检测、状态机拼起来;而 Realtime API 这边的逻辑更简洁,只需监听事件、维护一个对话状态列表,需要打断时发送 response.cancel 或 conversation.item.truncate 推给服务端即可。这不是说事件模型更“高级”,而是它跟语义推理模型的结合更加紧密。

2.3 模型端的“语义音频”设计:架构重构最关键的一步

Justin Uberti 在对外分享里多次提到一个概念,大意是:OpenAI Realtime 模型内部不一定是“语音识别成文本,再交给 LLM 推理,再转成语音”这种三段式。更合理的推测是,模型直接在音频的离散表示上做推理,文本和音频在同一个语义空间里统一表示。

这个方案在工程上的意义非常大。传统三段式有个明显天花板:中间如果依赖文本,那么语气词、停顿、情绪、同音字这种副语言信息都会在转文本过程中丢失。比如用户说“嗯……好吧”和“好吧!”在字面上可能只差一个标点,但语气完全不同。基于语义音频的模型可以直接感知这些细微变化,并生成符合当前语境的语音回复。同时,这种设计让整体链路的延迟不会因为“识别完再推理”而线性叠加,因为当用户说出最后一个词时,模型已经在用已接收的音频片段做预测了。

从行业里公开的实验数据和自己上手跑的效果来看,Realtime API 在“一句完整语料理解”上的表现确实比传统语音流水线更连贯。尤其是处理真人对话里常见的打断、犹豫、重复时,它很少出现“完全没听清你在说什么”的情况——因为模型直接理解的是声音本身,而不仅是被转成文字的内容。

3. WebRTC 在 Voice Agent 场景的位置:不是过时,而是分工不同

3.1 浏览器里采音降噪、回声消除,WebRTC 生态依然是最省事的

我不想把 WebRTC 说成一无是处。实际上,做任何 Voice Agent 应用,如果终端是浏览器,你依然躲不开 WebRTC——因为浏览器里的 getUserMedia 就是 WebRTC 体系的一部分。你通过它拿到麦克风的原始音频流,才可能做后处理、重采样并传给服务端。换句话说,WebRTC 在“音频采集和前端处理”这一层的作用依然不可替代。

比如说在浏览器端实现一个语音助手,你需要打开麦克风,做自动增益控制、回声消除、降噪。这些能力 WebRTC 规范里都覆盖了,而且已经经过十几年的打磨,稳定性很好。如果不走 getUserMedia ,你就得让用户装插件或者自己处理驱动层兼容问题,这基本是死路一条。

所以更准确的说法是:WebRTC 在 Voice Agent 链路里仍然负责“从麦克风拿到干净的 PCM 流”这一段。之后要不要用 RTP/ICE 那个传输框架把音频推到模型,则是另一个层面的选择。我会建议把“WebRTC 采集”和“WebRTC 媒体传输”分开看,不要混为一谈。前者是语音应用绕不开的基础设施,后者是你可以根据场景评估的传输通道。

3.2 哪些 Voice Agent 场景仍然适合走 WebRTC 传输

如果要做的是“人大脑对人工智能的实时语音通信”,而且产品里已经重度使用了 WebRTC 技术栈,那迁移成本会非常高,此时继续用 WebRTC 其实是合理选择。

我梳理了几个依然适合 WebRTC 路线的场景:

  • 已有音视频会议系统里嵌入 AI 助手。比如在视频会议中,AI 需要实时“听”到整场对话并给出建议。这种情况下,你得接入现有的 SFU 媒体流,从 RTP 包里拿到解码后的音频,再喂给推理模型。这时候强行绕过 WebRTC 反而会破坏现有架构,直接在媒体服务器层做旁路分析更现实。
  • 弱网环境下的低延迟语音交互。如果用户身处移动网络不佳的环境,WebRTC 的 FEC(前向纠错)、NetEq(抖动缓冲和丢包隐藏)机制对音频连续性的保障,通常优于 TCP 上重传策略的效果。模型推理本身的延迟不太依赖传输层,但对于持续的双向低延迟对话,媒体层的优化能力依然重要。
  • 多人同时参与的语音场景。当一个房间里有多个用户,AI 需要区分不同说话人的声音,这个场景用 WebRTC 的轨道概念来做会更容易。每个用户的音频是独立的媒体轨道,可以在 SFU 侧或客户端侧做分离,再结合 diarization(说话人分离)推给模型。

3.3 现有 WebRTC 架构在接入 AI 推理时的实际操作体验

我在自己做的实验项目里试过把一套已经跑通的 WebRTC 通话服务接上 AI 推理模型,也就是“用户 A 在网页上和 AI 对话,客户端通过 WebRTC 把音频推到服务端,服务端再转给模型”。整个链路跑通之后,我的感受很复杂。

能跑通,但中间环节确实多。以下是实际体验里比较典型的几个流程:

  • 浏览器端通过 getUserMedia 拿麦克风采集。
  • RTCPeerConnection 建立连接,把音频轨推给服务端的 mediasoup。
  • 服务端拿到 RTP 包,先做 SRTP 解密,再解码成 PCM。
  • 拿到 PCM 后塞给 VAD 模块做活动检测,判断用户这句话说完了,再触发模型推理。
  • 模型生成的音频再走反方向编码、加密、推回浏览器。

这套流程最终效果还可以,但排查问题的时候比较吃力。有一次用户反馈“AI 听到的内容有延迟,总是慢半拍”,排查下来发现瓶颈不在网络,而是在服务端 media 处理和解码环节上:RTP 包有 20ms 的缓冲,解码有几十毫秒的等待,VAD 给的是判断窗口,之后还要等模型返回……整套链路算下来,用户从开口到模型开始推理,中间多了不少环节。而如果用 Realtime API 这种事件流的方案,服务端的音频处理和 VAD 直接由模型侧完成,省掉了不少中间环节。

在弱网优化方面,WebRTC 的很多机制是为连续的双向通话设计的,例如 FEC、抖动缓冲、舒适噪声。但在和 AI 对话的场景里,用户说完一句话之后通常会等待 AI 回应,这时候音频链路其实是半空闲的,这些机制的作用会打折扣。而事件模型可以更灵活地在“静默期”省掉不必要的资源占用和网络流量。

4. 影响端到端体验的关键指标:延迟预算怎么拆

4.1 人类自然对话的延迟基准线

先抛一个参考值:真人对话中,一方说完话到另一方开始回应,200ms 左右是很自然的;超过 500ms,听感上就会出现明显的“顿了一下”;超过 1 秒,对话的流畅感基本就没了。这个直觉判断也可以放在 Voice Agent 上,作为设计的体验基准。

但语音 AI 的链路比人-人通话长得多。从声音进入麦克风开始,要经过本地采集、网络传输、服务端 VAD 切分、ASR 推理、LLM 推理、TTS 合成,再通过下行链路返回并播放。这意味着 AI 的响应时间很难压缩到和真人对话完全一致的水平,需要在多个环节做并行化,才能把端到端延迟拉回一个能接受的区间。

我基于自己的测试给典型 Realtime API 链路做了一个粗略的延迟拆解,当然这个数据因网络和模型负载而异,大家参考量级即可:

环节 耗时量级 备注
浏览器采集、重采样、打包 50-100ms 与设备性能相关
上行网络传输 20-80ms 看用户网络质量
服务端 VAD + 语义检测 100-300ms 通常需要看到足够多的音频才能判断“说完了”
模型首 token 推理(含 LLM 思想链) 300-800ms 受模型版本、负载影响较大
TTS 首包合成与下行传输 100-300ms 采用流式合成,先出第一段再边说边生成
播放缓冲 50-150ms 保证播放不在半途卡顿的冗余

加起来的数据通常在 800ms 到 1.6s 之间,和真人对话的 200-400ms 相比还是有不少差距。这也是为什么优化 Voice Agent 体验时,不能只看“哪个协议更好”,而要能解释清楚延迟花在了哪个环节,才有针对性做优化。

4.2 服务端 VAD 切分时机是整个链路里最微妙的选择

我一开始理所当然地认为:用户说完话,模型立刻就开始推理,这个时间应该很快。实际测试时,我发现服务端在下发 speech_stopped 事件前,往往需要一小段时间来确认用户真的停了,而不是短暂的句中停顿。如果这个确认窗口太短,容易把“嗯……然后呢”这种句中的停顿误判成一句话结束;如果太长,用户会明显感觉到 AI 反应迟钝,好像想了好一会儿才回答。

这里可以配合模型侧的流式语义理解来商量。当用户说到一半有较长的停顿时,VAD 可能已经给出“一句话结束”的信号,模型会基于已经收到的内容先开始预测。因为有语义音频模型,不需要等待完整文本转录,所以部分推理可以“预跑”,到确认结束信号后再补全。实际体验是:如果用户在句尾停顿 300ms 以上,AI 往往会在停顿结束后不到 200ms 就开始产出一段回应,比等 VAD 完全确认后再从头推理要流畅很多。

从这个角度看,WebRTC 这类纯媒体流协议本身无法回答“用户是不是说完了”的问题,它只能把音频按时间轴连续送过来。而 Voice Agent 这种场景,真正值得投入精力优化的,反而是这种服务端何时结束当前输入、何时开始新一轮推理的状态控制逻辑。这也是为什么我认为 OpenAI 选 WebSocket 是合理的——模型推理需要一个以事件驱动的语义链路来匹配音频流。

4.3 打断机制:比电话会议的“半双工”复杂得多

人机语音交互和传统电话会议还有一个体验差异:真人通电话时,双方默认遵守“一方讲、另一方听”的礼仪,需要插话时会用音量或语速信号来抢占话权。但和 AI 对话时,用户经常会“还没等 AI 说完就打断”,比如 AI 回答到一半用户突然说“不对,我是想问另一个东西”。

这种打断如果处理不好,体验会非常糟糕。WebRTC 通信是双全双工的,音频可以双向同时传输,但在模型推理链路里,你不可能让 AI 还在说话的同时又去处理用户的新输入——除非整个系统支持多任务并发:一方继续合成未说完的内容,另一方同时开始识别新输入。

Realtime 事件模型里有一个很直观的做法:当用户打断发生时,客户端或服务端发送一个 cancel/truncate 事件,把当前正在生成的语音响应截断,然后立刻把新输入的上下文加入到对话历史里,再触发下一轮生成。这套逻辑在事件驱动的架构里实现起来很顺:音频输入事件不断累积,新输入开始时间点是清晰的,旧输出只要被截断,就不会污染后续上下文。

而我用传统 WebRTC 链路做打断时碰到的最麻烦的问题是:TTS 还在播放,新输入的麦克风声音又进来了,此时服务端得判断哪些音频是“AI 自己的声音”,哪些是“用户当前的新指令”,否则就可能把 AI 刚才说的内容重新识别成用户输入,造成死循环。WebRTC 本身是有回声消除(AEC)能力,但这个能力通常只在通话两端都在浏览器端时才起作用。如果音频已经流到服务端经过模型处理再回来,服务端很难利用浏览器端采集时的 AEC 上下文,容易产生回声啸叫。事件模型则更便利:服务端用“当前是否在生成语音”这个状态,区分开可能混叠的音频来源,从逻辑上就回避了这类问题。

5. 实测一个 Voice Agent Demo:从零到一的过程

5.1 最小化跑通的实现方式

理论讲了那么多,最终还是要回到代码。我自己用官方推荐的实时方案在浏览器里跑了一个最简 demo,核心链路是这样:浏览器麦克风采集 → 重采样为 24kHz 16-bit PCM → 通过 WebSocket 把音频帧发给服务端 → 服务端转发给 Realtime API → 收到语义事件和音频回包 → 浏览器播放。

这里可以直接参考官方 realtime-js SDK,它已经把 Session 建立、音频采集、事件监听都封装好了一层,少踩不少坑。核心逻辑大致长这样:

import { RealtimeClient } from '@openai/realtime-api-beta';

const client = new RealtimeClient({ apiKey: process.env.OPENAI_API_KEY });
await client.connect();

client.sendUserMessageContent([{ type: 'input_text', text: '你好,介绍一下你自己' }]);

client.on('conversation.updated', ({ item, role }) => {
  if (item.status === 'completed' && role === 'assistant') {
    console.log(item.formatted.text || item.formatted.audio);
  }
});

如果你的产品不想依赖浏览器原生音频,也可以直接在客户端做离线采集,把音频数据封装成 base64 或二进制帧传给服务端。但要注意的是,必须要对齐采样率,24kHz 是官方 API 比较稳定的生效格式。自己写重采样逻辑的话,建议直接用 Web Audio API 的 AudioContext({ sampleRate: 24000 }) ,比手动做插值省事。

5.2 我实测到的几个关键体验数据

仅代表自己网络环境(稳定宽带、普通笔记本)下的实际感受,不是压力测试结果:

  • 从用户停止说话到 AI 开始发出第一个音频事件,我实测大约在 700ms-1.2s 之间。大部分时间在 900ms 左右,对一般工具类语音对话来说可以接受,但要说“自然得像真人”还有差距。
  • 从 AI 开始说话到浏览器播放出声音,本地网络下几乎没有可感知的延迟,基本是“事件到即播”。比较顺利时,几乎感觉不到链路延迟。
  • 在长回答的场景里,模型用的 TTS 输出是流式的,第一段音频到达时间一般比整段生成完早很多。实际播放时,AI 说完一件事情后,后面的内容还在分批传送和生成,连续感还可以。
  • 打断实测:当我在 AI 回答过程中直接补一句“等一下”,从我说出“等”这个字的 200-400ms 内,AI 会彻底停下来(播放中断),等待我下一句完整输入。这个打断响应速度比我预期中快不少,比我自己以前用 WebRTC + 服务端 VAD 方案的体验更好。

5.3 构建实时语音应用时常用的调参和状态管理经验

跑通了是一回事,在真正把 demo 变成可用系统的过程中,有几个坑是很容易踩的。简单列一下我自己的处理思路和现有方案。

第一个是会话级别的“确认对话历史”。Realtime API 默认保留对话上下文,但你需要在每次交互里维护一个“当前用户在问什么、AI 刚才回答了什么”的状态列表。如果状态没维护好,多轮里 AI 很容易把用户的新问题理解成上一个问题的延续。

第二个是输出播放的缓冲策略。如果播放端每收到一段音频就立刻播放,网络或服务端一抖动,声音就可能中断,然后突然接力,听感很碎;但如果你把音频先都缓冲完再播放,延迟又会明显上升。比较好的折中是维护一个 100-200ms 的播放抖缓(jitter buffer),在不明显增加首包延迟的情况下降低卡顿频率。

第三个是日志可观测性。这件事我强烈建议在写第一行代码时就铺好,否则后面调起来太痛苦了。

[12:00:00.123] SW send: session.update (turn_detection=server_vad)
[12:00:00.139] RV  received: input_audio_buffer.speech_started
[12:00:00.424] RV  audio frame #12 sent to server
[12:00:01.282] RV  received: input_audio_buffer.speech_stopped
[12:00:01.285] SW send: response.create
[12:00:01.614] RV  received: response.audio.delta (chunk 1)
[12:00:01.620] SW play: audio chunk
[12:00:02.930] RV  received: response.done

我习惯给每条音频帧和每个事件都打上相对时间戳,排问题时一眼就能看到链路卡在哪一步。比如有一次用户反馈说“AI 听到了我说的话但一直不回答”,我翻开日志发现是客户端收到了 speech_stopped 但没发 response.create ——后来发现是事件监听里一个分支写错了。有日志的话,这类问题定位在十分钟内,完全不靠猜。

5.4 普通 HTTP 接口方案和 Realtime API 的适用对比

在开始用正式方案前,我还试过“录完音、整段上传、等模型处理完再拿结果”的做法。这种方法适合对延迟不敏感的场景,比如语音笔记转写摘要、语音输入框等。

特点 普通输入 + 批量推理 Realtime API(事件流)
典型交互 用户按住说话,松开后上传完整音频,等待处理 用户边说话边流式传输,AI 可打断可插话
端到端延迟 受上传体积和推理排队影响,通常 2-5 秒起步 目标在 1 秒以内,多数是 700ms-1.5s
对网络要求 等整段上传完毕才处理,弱网下体验更差 流式推送,弱网下也能边传边处理,但停顿检测可能不准
服务端成本 每段音频一次推理,费用可预测 实时推理,长空闲连接也要持续计费
适用场景 语音转写、语音指令、单轮问答 语音对话、客服/陪伴助手、要求打断和情绪等多模态交互

如果只是做一个“把语音转文字再交给 AI 处理”的小工具,完全可以不用 Realtime 方案,因为延迟要求不高,成本也更可控。但如果目标产品是“和用户自然对话、允许多轮打断、及时反馈情绪”的 Voice Agent,那 Realtime 这类实时事件链路更有机会。

6. 常见坑与排查技巧实录

6.1 浏览器采到的声音和模型理解到的声音不一致

这是我遇到过的第一个“怪现象”:我用系统麦克风测试一切正常,但换个设备后,AI 经常把我说的内容识别错。最后排查发现,在新设备上浏览器默认采集的是 48kHz 的音频,部分浏览器环境的 AudioContext 没有正确转换采样率,导致发到服务端的数据变成了被“拉伸”的音频,语速变慢、音调变低。

排查方法很简单:在日志里标记发送音频帧的采样率和格式,和接收端实际收到的对比一下。如果两边信息不一致,大概率是重采样环节出了问题。

6.2 VAD 误触发:把背景音乐、咳嗽声当成用户说话

Realtime API 的事件模型依赖服务端 VAD,但如果你所处环境嘈杂,或者用户说话时旁边有其他人声,服务端的判断会受干扰。比如我在咖啡厅测试时,AI 经常在我没说话时突然发一句“你好,我在听”,原因就是背景音触发了 speech_started 。

目前的处理方案有几个:一是靠 API 参数调高触发阈值,让模型更“迟钝”一点;二是客户端做前置降噪,把安静门限提高;三是产品层给一个“手动按住说话”的交互,从根本上避免背景音误触发。没有完美的自动方案,还是要结合使用场景选择。

6.3 一直保持长连接,还是按需创建短会话?

这是一个成本和体验的平衡问题。语音 Agent 模型侧是一个有状态会话,保持长连接可以让多轮对话上下文连续,但空闲连接也在消耗服务端资源。按需创建短会话则省钱,但每次“醒来”都有建连和模型初始化的延迟,而且历史上下文可能会丢失。

我对比过两种方式的体验差异:切到短会话后,用户先说“你好”,然后要等约 1 秒才能进入可对话状态,对于应用场景要求即时响应的产品来说可能很致命。现阶段我的选择是预留少量长连接,空闲超过一定时间再主动释放,尽量让首次交互感受更接近“即说即回”。这个做法也符合我之前在 WebRTC 场景里优化“首帧延迟”的思路。

6.4 与网页现有音频发生冲突

如果你的页面同时要播放其他背景音(比如视频、音乐),麦克风采集回声的风险会显著增加。浏览器虽然提供 echoCancellation 参数,但它主要针对“扬声器播放、麦克风采集”的实时反馈场景。当 AI 播放的是远端的实时音频流,和用户本地播放器音频混在一起时,回声消除的效果会受到明显影响。

最直接的解决方案是:在 AI 对话时暂停其他音频播放,并在产品设计上明确这个交互阶段。如果非要支持“边听音乐边和 AI 聊天”,就需要在播放端加独立的声学回声消除,复杂度会大幅上升。我自己选择避开这种复杂情况,毕竟产品价值不大,投入却很可观。

6.5 常见问题速查表

问题现象 可能原因 排查思路
AI 听不懂、识别内容乱码 采样率/位深格式不匹配 检查发送帧格式是否为 24kHz、16-bit PCM
连接后长时间无反应 模型未完成初始化或等待用户输入事件 检查 logs:是否收到 session.created
反应迟钝,总在 2 秒后才回话 VAD 等待时间过长/模型推理慢 优化确认窗口,用流式并行推测
打断无效,AI 一直在说 打断目标事件没触发或状态没重置 检查 response.cancel 是否发出,查看事件序列
回声严重,AI 反复听到自己 播放和采集声学路径短路 停用本地扬声器输出、使用耳机,或开启 AEC
弱网下频繁断开或断句 TCP 流被中断后恢复慢 调整重连策略,考虑引入抖动缓冲或降低码率

这张表不一定覆盖所有情况,但我发现多数问题最终都归结到采样率、事件状态、VAD 边界这三类。如果把这三块提前做好日志埋点,排查问题时能少走很多弯路。

7. 聊点个人体会:我倾向怎么选型,以及为什么

我把 Justin Uberti 的经历捋完一遍后,最大的感触不是“WebRTC 要过时了”,而是“新场景需要新的技术栈来匹配,过去的老技术依然有它不可替代的位置”。做技术选型时不必总是基于传统熟悉度去做决策,考虑“链路主体的核心诉求”往往更关键。

我自己现在做一个 Voice Agent 项目时会先分清主体:如果核心是模型对话,我会用 Realtime API 这类事件驱动流式方案,它帮你省去了 VAD、状态机、媒体层和语义层编码等多重问题;如果核心是多人通话系统,或者需要接入已有的音视频会议架构,那 WebRTC 路线更合理,毕竟它的媒体传输、回声消除、弱网韧性十分成熟。

顺便说一个调试小技巧,如果两种方案都要试,我建议把会话日志统一记录成纯文本事件序列,对比起来效果会很好。实际在复杂语音应用开发中,日志是最有价值的资产。把每个关键事件打上毫秒时间戳、把每个音频帧标上序号,看起来琐碎,但出了诡异现象时能直接帮你还原整条链路,效率提升非常明显。

对多数开发者来说,现阶段没必要在 WebRTC 和 Realtime API 之间纠结“哪个会赢”——因为它们在场景上并不完全重叠。除非你的产品本身就是做实时通信基础设施的,否则把精力放在“让 AI 听懂人话、说对人话、接得住打断”这层业务逻辑上,收益比研究底层的媒体协议大得多。

Logo

邀请您加入社区

更多推荐