1. WebRTC 延迟概述
聊 WebRTC 优化,第一个绕不开的就是延迟。我见过不少团队,功能都跑通了,用户却抱怨「画面卡卡的」「声音对不上」。说白了,问题都出在延迟上。
延迟这东西,你感受不到的时候觉得它不存在。一旦感受到,体验就崩了。今天我们就把它拆开看看——延迟到底是什么、它怎么影响实时通信、我们又该怎么测量它。
延迟的定义
延迟,简单讲就是数据从发送端到接收端所花的时间。在 WebRTC 里,这个「数据」可以是视频帧、音频包,也可以是数据通道里的消息。
但注意,延迟不是单一数值。它是一整条链路的时间总和。我习惯把它分成几个阶段来看:
- 采集延迟:摄像头/麦克风把模拟信号转成数字数据的时间
- 编码延迟:压缩原始音视频数据的时间
- 网络传输延迟:数据包在网络上跑的时间
- 网络抖动缓冲延迟:接收端为了平滑播放而缓存数据的时间
- 解码延迟:解压缩数据的时间
- 渲染/播放延迟:把数据送到屏幕或扬声器的时间
关键认知:端到端延迟 = 采集 + 编码 + 网络传输 + 抖动缓冲 + 解码 + 渲染。任何一个环节出问题,整体延迟都会飙升。
举个例子。我在做一款在线教育产品时,发现老师端说话后,学生端要等将近 2 秒才能听到。一开始以为是网络问题,后来逐段排查才发现——是采集端的音频缓冲区设得太大了。嗯,这就是典型的「采集延迟」拖了后腿。
延迟对实时通信的影响
延迟不是「有或没有」的问题,而是「多少算可以接受」的问题。不同场景对延迟的容忍度完全不同。
| 场景 | 可接受延迟 | 延迟过高的表现 |
|---|---|---|
| 视频通话 | < 200ms | 对话重叠、抢话、尴尬停顿 |
| 直播互动 | < 500ms | 弹幕与画面不同步、主播回应延迟 |
| 在线游戏 | < 100ms | 操作滞后、画面跳变 |
| 远程协作 | < 300ms | 白板不同步、鼠标位置偏移 |
你想想看,两个人视频聊天,如果延迟超过 300ms,对话就开始变得不自然了。一个人说完话,另一个人要等一会儿才回应。这种「错位感」会让双方都觉得别扭。
我曾经参与过一个远程医疗项目,医生需要远程指导护士操作设备。延迟超过 500ms 时,医生的指令和护士的动作完全对不上。这种场景下,延迟不只是体验问题,而是安全问题。
避坑提醒:我曾经以为只要网络带宽够,延迟就不会高。后来发现完全不是这么回事。高带宽只能保证吞吐量,但延迟受路由跳数、队列缓冲、处理速度等多因素影响。带宽和延迟是两个独立维度。
延迟的测量方法
测量延迟,听起来简单,做起来坑不少。我刚开始做 WebRTC 时,直接用 Date.now() 打时间戳来算延迟。结果发现数据忽高忽低,完全没法用。后来才明白——发送端和接收端的时钟不同步,时间戳对比没有意义。
目前业界常用的测量方法有几种:
1. RTT(往返时间)测量
这是最基础的方法。发送端发一个包,接收端收到后立刻回一个确认包。发送端计算从发出到收到确认的时间差,再除以 2 得到单向延迟的估算值。
// 伪代码示例:RTT 测量
const startTime = performance.now();
dataChannel.send('ping');
dataChannel.onmessage = () => {
const rtt = performance.now() - startTime;
const estimatedOneWay = rtt / 2;
console.log(`RTT: ${rtt}ms, 估算单向延迟: ${estimatedOneWay}ms`);
};
但这个方法有个问题——它假设网络是对称的。实际上,上行和下行延迟往往不一样。所以 RTT/2 只是个估算值。
2. NTP 时间戳同步
WebRTC 的 RTCP 包中携带了 NTP 时间戳。发送端和接收端如果都同步到同一个 NTP 服务器,就可以直接计算单向延迟。
具体做法是:发送端在 RTCP 发送者报告(SR)中写入当前 NTP 时间。接收端收到后,用本地 NTP 时间减去发送端 NTP 时间,就得到单向延迟。
实用技巧:我建议在开发阶段同时使用 RTT 和 NTP 两种方法测量。对比两者的差异,可以帮助你判断网络是否对称。如果差异超过 50ms,说明上行和下行路径可能不同,需要进一步排查。
3. 播放延迟测量
这是用户真正感受到的延迟。测量方法是在发送端显示一个计时器,接收端用摄像头拍摄屏幕,然后对比两个计时器的差值。
听起来很原始对吧?但这是最接近真实体验的测量方式。我在做直播延迟优化时,就靠这个方法发现了问题——网络延迟只有 50ms,但播放延迟却高达 800ms。最后定位到是接收端的抖动缓冲区设得太大了。
4. WebRTC 内置统计 API
Chrome 和 Firefox 提供了 getStats() API,可以直接获取各种延迟指标。
// 获取 WebRTC 统计信息
const pc = new RTCPeerConnection();
// ... 建立连接后
setInterval(async () => {
const stats = await pc.getStats();
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'video') {
console.log(`视频接收延迟: ${report.jitter}ms`);
console.log(`丢包率: ${report.packetsLost / report.packetsReceived * 100}%`);
}
if (report.type === 'remote-inbound-rtp') {
console.log(`往返时间: ${report.roundTripTime}ms`);
}
});
}, 1000);
这个 API 返回的数据很丰富,但要注意——不同浏览器的实现有细微差异。我踩过这个坑:在 Chrome 上能拿到的字段,在 Safari 上可能返回 undefined。
延迟测量的核心逻辑
说了这么多,我画了一张图来总结延迟测量的整体思路。你看完应该能对「怎么测」「测什么」有个全局认识。
看到这张图你应该明白了——没有一种方法能解决所有问题。我个人的做法是:开发阶段用 getStats() 实时监控,配合 RTT 做快速验证。上线前再用播放延迟测量做一次全链路验收。三种方法互补,才能把延迟问题看清楚。
我的经验:测量延迟时,一定要在真实网络环境下测。我在办公室内网测出来的延迟只有 20ms,结果上线后用户反馈延迟 300ms。后来才发现,办公室网络太理想了,完全没反映出公网的抖动和丢包。所以,测试环境越接近真实用户场景,测量结果越有参考价值。