1. 这个选型问题,先说说我的结论
做了一段时间的大模型语音交互,接到最多的问题就是:客户端想把语音喂给大模型,到底是走 WebSocket 还是 WebRTC?
先给结论:绝大多数场景,WebSocket 就够了;只有对实时性和弱网抗性要求极其苛刻、或者要双向音视频通话的场景,才需要 WebRTC。
这两年大模型语音接入的火爆,主要是两类应用:一类是语音助手(比如给智能客服加语音入口),另一类是对接实时语音大模型(比如能直接听懂语气、情绪的多模态对话)。两类应用的技术链路差别很大,选型逻辑也完全不同。我分别踩过坑,把整个过程梳理出来,希望能帮你少走弯路。
WebSocket 和 WebRTC 不是同一个维度的东西。WebSocket 是应用层协议,解决的是"客户端和服务端之间建立一个双向、持久、低延迟的通道";WebRTC 是一整套实时音视频通信框架,解决的是"如何在不可控的网络上把音视频数据以尽可能低的延迟、尽可能高的质量传过去"。所以严格说,这不是二选一,而是"用 WebSocket 拼装一条语音链路"还是"直接上 WebRTC 这套重型武器"的问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么大模型语音需要"实时通道"
2.1 大模型对话的端到端延迟预算
先看一个真实的语音对话链路。用户说一句话,整个过程要经过:
音频采集 -> 编码 -> 网络传输 -> 语音识别(ASR)-> 大模型推理(LLM)-> 文本生成 -> 语音合成(TTS)-> 编码 -> 传输回客户端 -> 播放
每个环节都有延迟。实测下来,通用 ASR 识别一句话大约 300-800ms(流式模式首字更快),大模型推理首 token 大约 300-1000ms,TTS 首次出声约 200-500ms。还不算网络 RTT。整个链路的端到端延迟通常会到 1.5-3 秒。
这个延迟预算意味着,网络传输本身不是瓶颈。WebSocket 在正常网络下的传输延迟也就几十毫秒,完全在可接受范围内。真正要优化的反而是 ASR、LLM 推理和 TTS 这三段。所以大多数场景根本不需要 WebRTC 的极致低延迟。
2.2 全双工不等于同传
很多人被"全双工"这个词迷惑了。WebSocket 确实是全双工协议,服务端和客户端可以同时发消息。但在大模型语音场景里,多数产品的交互模式是"你说一句,它回一句",本质上还是半双工的人机对话模式。
真正常见的瓶颈反而是:客户端采集音频 -> 推到服务端 -> 服务端在做 ASR 和 LLM 推理的这几秒里,客户端不能打断、不能叠加发送。这跟协议无关,是产品逻辑和状态机的问题。
2.3 大模型 API 的接入方式决定了选型
这是关键。目前市面上能接的大模型 API,绝大多数提供的流式接口就是 HTTP SSE 或者 WebSocket。你服务端拿到的是一段文本流、或者一个音频流,转发给客户端最自然的方式就是 WebSocket。
换句话说,服务端怎么接大模型,决定了客户端该怎么连服务端。如果大模型给的是 WebSocket 流式接口,你客户端搞 WebRTC 就得多加一层适配,得不偿失。
3. WebSocket + 大模型语音:我的主力方案
3.1 一条完整的 WebSocket 语音链路怎么搭
我实际落地过的一套方案是这样的:
- 浏览器端用
AudioWorklet采集音频,opus编码(或者直接取 PCM16 压缩编码),通过 WebSocket 二进制帧推给服务端。 - 服务端收到音频帧,转发给 ASR 服务做流式识别。
- ASR 给出中间结果和最终结果,文本送入大模型。
- 大模型流式返回 token,经过 TTS 合成音频,服务端把音频帧通过同一个 WebSocket 推回客户端。
- 客户端播放音频。
这套链路的好处是:一条连接走到底,服务端逻辑简单,调试方便。浏览器端只要处理数据收发和播放,不需要管 NAT 穿透、ICE 协商这些糟心事。
3.2 音频帧怎么发才不卡
很多新手犯的错误是:把整个音频文件一次性发过去。这没法流式。正确做法是按固定帧长切片,比如每 20ms 或者 40ms 一个 block,带上 seq 序号和时间戳。服务端按序处理。
给一个简单的发送逻辑示例(浏览器端 AudioWorklet 配合 WebSocket):
javascript复制const ws = new WebSocket('wss://your-server/audio');
const audioContext = new AudioContext({ sampleRate: 16000 });
const source = await navigator.mediaDevices.getUserMedia({ audio: true });
const workletNode = new AudioWorkletNode(audioContext, 'audio-capture');
workletNode.port.onmessage = (event) => {
// Float32Array 转 Int16 PCM 再发送
const pcm = float32ToInt16(event.data);
ws.send(pcm.buffer);
};
const micSource = audioContext.createMediaStreamSource(stream);
micSource.connect(workletNode);
服务端收到 ArrayBuffer,先解析出 PCM 数据,再转给 ASR。这个方案我在本地和公网都测过,60 秒以内的对话,音频断帧率不到 1%。
3.3 断线重连和心跳
WebSocket 在大模型这种长连接场景里,一个容易忽略的问题是中间设备(比如代理、网络切换)会悄悄断开 TCP 连接,而两端都感知不到。解决方案是心跳机制:客户端每 15 秒发一个 ping,服务端收到回 pong,连续 2 次超时判定为掉线。然后客户端自动重连,同时带上音频会话 ID 让服务端恢复上下文。
断线恢复要格外小心。语音对话的上下文不能丢,重连后服务端要能通过 session_id 把之前 ASR 的中间过程和 LLM 的上下文找回来,否则用户说了一半就断,重连后大模型根本不记得之前聊了什么。
4. WebRTC + 大模型语音:实力很强,但代价不小
4.1 WebRTC 到底提供了什么
WebRTC 不是一句"低延迟"就能概括的。它内置了一整套音视频工程能力:Opus 编码、回声消除(AEC)、降噪(NR)、自动增益(AGC)、抖动缓冲(Jitter Buffer)、丢包重传(NACK)、前向纠错(FEC)、带宽估计和自适应码率。
这就意味着,如果你要做的是"用户对着手机或者网页说话,要求背景噪音环境下也能识别清楚",WebRTC 开箱即用地帮你处理掉噪音环境下的高质量音频采集。而 WebSocket 方案里,这些全得你自己做或者靠 ASR 服务端硬撑。
4.2 WebRTC 接入大模型的两条路线
路线一:整条链路走 WebRTC,大模型服务端也接入 MediaStream 轨道。
服务端跑一个 SFU(比如 mediasoup、Janus、LiveKit)收客户端的音频,再过一个推理节点,将大模型的音频输出推回来。
这个方案的好处是端到端延迟低、实时性强,降噪处理全部内置;坏处是架构复杂度直接翻倍——你得维护媒体服务器、ICE/STUN/TURN、证书信令服务,还得处理大模型推理服务的音频适配。
路线二:WebRTC 只负责采集和播放,后端中转给普通大模型 API。也就是 WebRTC 作为"高质量音频采集 + 低延迟播放"组件,真正的对话逻辑还是靠 HTTP 或者 WebSocket 调用大模型。
这个方案相对折中:前端体验好,后端不用大改,但实现上还是得处理 WebRTC 的协商、重连等逻辑,工作量不见得小。
4.3 弱网能力是 WebRTC 的护城河
我实测过同一个网络环境(Wi-Fi 信号差、丢包 10% 上下),WebSocket 发送音频的 MOS 质量分数明显下降,语音识别准确率掉了差不多 8-10 个百分点;而 WebRTC 因为有 NACK 和 FEC,再加上 Opus 自带的丢包隐藏,同样的网络条件下还能维持不错的识别率。
如果你的用户群体里有大量移动网络弱网环境(地铁、高铁、电梯、地下室),WebRTC 的抗弱网能力是实打实的优势。WebSocket 在这种环境里,尤其是音频流发送,对网络抖动非常敏感,因为 TCP 的拥塞控制会把延迟瞬间拉高。
5. 核心指标对比:延迟 / 成本 / 复杂度
| 维度 | WebSocket 方案 | WebRTC 方案 |
|---|---|---|
| 音频采集 | 浏览器 API + 自己处理降噪 | 内置 AEC/NR/AGC,自带 Opus |
| 网络传输 | TCP,稳定优先 | UDP + SRTP,延迟优先 |
| 弱网抗性 | 差,丢包会导致延迟陡增 | 强,NACK/FEC 兜底 |
| 服务端难度 | AI 编排简单,一个 WS 服务即可 | 需要媒体服务器,架构复杂 |
| 移动端支持 | 所有平台都支持 | iOS/Android 支持,但需要额外库 |
| 大模型集成 | 天然适合文本/音频流式接口 | 要额外适配或转码 |
| 整个项目成本 | 低 | 高,至少多 40% 工作量 |
这张表不是否定 WebRTC,而是告诉你:选 WebRTC 一定是有明确理由的,而不是因为"听起来更高级"。
6. 实操:WebSocket 接入大模型语音的完整代码链路
6.1 服务端怎么设计连接、会话、转发
我把服务端拆成三个角色:
ConnectionManager管理 WebSocket 连接,负责心跳、断线检测。AudioSession管理会话状态,包含 ASR 流、大模型调用、TTS 流的串联。LLMAdapter封装大模型 API 调用,支持流式返回。
伪代码大致是这样:
python复制class ConnectionManager:
async def handle_websocket(self, ws, path):
async for msg in ws:
if isinstance(msg, bytes):
await self.session.push_audio(msg) # 音频帧
elif isinstance(msg, str):
if msg == 'ping':
await ws.send('pong')
# 其他控制消息
关键点:handle_websocket 和 push_audio 之间不要有耗时操作,音频帧到了就立即投递给 ASR 客户端。如果在 ASR 回调里同步调大模型,音频肯定处理不过来。应该让 ASR 的结果异步进入一个队列,由一个独立的 worker 拉取并调用大模型。
6.2 前端的完整状态机
前端不能只管发音频,得维护对话状态:
javascript复制const state = {
IDLE: 'IDLE',
LISTENING: 'LISTENING', // 客户端录音中,发送音频
SPEAKING: 'SPEAKING', // 大模型回复中,播放音频
WAITING: 'WAITING' // 音频已发完,等大模型回复
};
对话开始时进入 LISTENING,音频帧一直发;用户说完话,前端静音检测(VAD)判断停顿超过 600ms 就切换到 WAITING,发送一个 "end_of_speech" 控制消息;服务端收到后完成 ASR 最终识别,调大模型;大模型返回结果后,如果 TTS 音频流开启,客户端播放时就进入 SPEAKING,同时允许用户按打断键终止播放、重新进入 LISTENING。
6.3 音频格式对齐:最容易踩的坑
浏览器采集的音频默认是 48kHz 的 Float32 数据,而很多 ASR 服务要求 16kHz 的 Int16 PCM。很多人就在这一步翻车。必须在 AudioWorklet 里做重采样和类型转换:
javascript复制function float32ToInt16(float32Array) {
const int16Array = new Int16Array(float32Array.length);
for (let i = 0; i < float32Array.length; i++) {
const s = Math.max(-1, Math.min(1, float32Array[i]));
int16Array[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
}
return int16Array;
}
重采样可以用 AudioContext({ sampleRate: 16000 }) 直接设置,浏览器会自动做重采样。这个技巧实测很稳,省了很多麻烦。
7. 实操:WebRTC 场景的两种落地姿势
7.1 纯采集型:WebRTC 只负责收音
这是我第二个项目采用的方式。场景是需要抗噪的语音助手,但是对话逻辑依旧走 WebSocket。整个架构是这样的:
客户端先建立 WebRTC RTCPeerConnection,只发送音频轨道(不做播放),同时通过信令服务器交换 SDP。服务端收到音频轨道后,用 MediaStream 导出 PCM 数据,再转给 ASR。
这种方式的好处是,前端不用自己做降噪和 VAD,getUserMedia + WebRTC 底层的 AGC 和降噪效果比我手写的好太多。缺点是需要维护信令服务,而且服务端必须跑媒体处理(比如用 GStreamer 或者编解码器把 Opus 转 PCM),复杂度确实上来了。
7.2 全链路型:双向音视频和低延迟对话都要
如果你的产品是数字人、虚拟形象这种"既要听到用户说话,又要用高质量音频和形象回话"的场景,那就全链路走 WebRTC 吧。选择开源的 mediasoup 做 SFU,服务端把 WebRTC 的音频接入推理服务。推理服务需要支持连续音频流输入,否则你还要在服务端做一个音频拼接器。
实测这种全链路方案,首字延迟可以压到 800ms 以内(在网络好的内网环境下),比 WebSocket + 多段 API 拼接方案快不少。代价就是排查问题的时候,你需要同时看 WebRTC 协商日志、SFU 状态、推理服务 CPU 和内存,调试成本翻了几倍。
7.3 何时必须用 TURN Server
WebRTC 有一个永远绕不开的话题:NAT 穿透。P2P 打不通的时候,必须走 TURN 中转。而 TURN 服务器开销很大,纯中转每个月流量成本可能是媒体服务器本身的几倍。
我建议在项目早期就做好评估:如果你的用户 80% 在办公网或家庭宽带,NAT 穿透率能到 85% 以上;如果是大型企业网或者运营商级 NAT,穿透率可能只有 50%,那 TURN 的成本会非常高。WebSocket 方案没有这个问题,因为它本来就走服务器中转。
8. 大模型 API 对流式的支持程度,决定技术路线
现在的大模型接口,流式能力差别很大。有的只支持 HTTP SSE,有的支持 WebSocket,有的甚至只支持普通 HTTP 一次性提交。
这个叫做 stream 参数的东西,千万不要忽略。我在一个项目里踩过坑:大模型 API 不支持流式返回,我却在客户端用 WebSocket 加了"打字机"效果,结果发现服务端要等整个回复生成完才能推送,毫无实时性。后来换了支持流式的模型接口,体验才正常。
选型时先搞清楚大模型 API 的流式协议,再决定你的传输层。如果大模型侧走 SSE,你完全可以让服务端用 SSE 对接大模型,WebSocket 只负责和客户端通信,这样两边都不委屈。
9. 我实际测过的两组数据
放两组实测数据供参考。测试环境:同一台云服务器部署,客户端分别是 Chrome 和微信内置浏览器,网络分别用有线宽带和 4G 弱网(模拟)。
第一组:WebSocket + ASR + 通用大模型(非实时语音大模型)
- 正常宽带:端到端延迟约 1.8-2.5 秒,偶发 0.5 秒级抖动。
- 4G 弱网(丢包 5%):延迟飙升到 4-6 秒,偶尔重连。
- 体验评价:可用,但弱网下明显发闷。
第二组:WebRTC + 实时语音大模型(支持音频流式输入)
- 正常宽带:端到端延迟约 0.9-1.2 秒。
- 4G 弱网(丢包 5%):延迟约 1.5-2 秒,无明显断音。
- 体验评价:接近真人对话节奏,弱网下依然自然。
差距很明显,但也要算笔账:第二组的开发量是第一组的 2.5 倍左右,而且实时语音大模型的接口认证、配额和计费模式跟普通文本模型还不一样,成本也要高不少。
10. 选型决策清单:照这个判断就行
我整理了一张决策清单,直接对号入座:
- 你只是做语音问答助手,用户不说话时不需要保持连接,选 WebSocket。
- 你需要实时打断、多轮对话且延迟必须低于 1.5 秒,可以考虑 WebRTC。
- 你的用户大量处于弱网环境(地铁、高铁、野外),WebRTC 的弱网优化更值得投入。
- 你的产品是数字人、语音视频双通道交互,直接选 WebRTC。
- 你的团队后端经验多但音视频经验少,先 WebSocket 做 MVP,等数据证明需要再迁移。
- 大模型 API 只支持 HTTP SSE,那就 WebSocket 做前端通道,别硬上 WebRTC。
- 你的公司有严格的内网环境限制 UDP 流量,那 WebRTC 可能根本跑不通,老老实实 WebSocket。
这个清单不是教条。我走了不少弯路,最后发现核心就一句话:延迟敏感度和弱网占比,是选型的唯一决定性因素。 如果你的产品两个指标都没到瓶颈,就不要让 WebRTC 的复杂度来拖慢你的迭代速度。
11. 最后分享一个经验:不要怕后面换方案
我第一个语音项目就是 WebSocket 起步的,当时没有想太多,一个月上线了 MVP。后来用户反馈中确实有弱网卡顿投诉,比率大约 12% 左右,我们用埋点统计了网络类型和延迟分布,才下决心在 2.0 版本迁移到 WebRTC。迁移的核心价值在于:WebSocket 阶段的会话管理、VAD 分段、状态机设计,在 WebRTC 迁移时原样保留下来了,只重写了传输层。
所以不要一上来就追求"完美的架构"。先把 WebSocket 链路跑通,把产品逻辑打磨好,用数据说话。当你真的需要 WebRTC 的时候,你会有充分的数据支撑,而且迁移路径也是清晰的。反过来,如果你一开始就选了 WebRTC,后面发现复杂度拖垮了团队,回退到 WebSocket 反而要砍掉很多东西。
更适合自己的技术方案,永远不是纸面上的最优解,而是团队能力、产品阶段和用户场景叠加后的那个解。
