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。

延迟测量的核心逻辑

说了这么多,我画了一张图来总结延迟测量的整体思路。你看完应该能对「怎么测」「测什么」有个全局认识。

WebRTC 延迟测量方法总览 发送端 接收端 网络传输 方法一:RTT 往返测量 发送端发 ping → 接收端回 pong 计算 (t₂ - t₁) / 2 估算单向延迟 局限:假设网络对称,实际有偏差 适用:快速评估、开发调试 方法二:NTP 时间戳同步 RTCP SR 包携带 NTP 时间戳 接收端用本地 NTP 减去发送端 NTP 优势:直接得到单向延迟 适用:精度要求高的场景 方法三:播放延迟测量 发送端显示计时器 接收端拍摄屏幕对比差值 优势:最接近真实用户体验 适用:上线前验收测试 方法四:getStats() API 浏览器内置统计接口 可获取 jitter、RTT、丢包率等 优势:无需额外打点,实时监控 局限:浏览器兼容性差异 建议:开发期用 RTT + getStats,验收期用播放延迟测量

看到这张图你应该明白了——没有一种方法能解决所有问题。我个人的做法是:开发阶段用 getStats() 实时监控,配合 RTT 做快速验证。上线前再用播放延迟测量做一次全链路验收。三种方法互补,才能把延迟问题看清楚。

我的经验:测量延迟时,一定要在真实网络环境下测。我在办公室内网测出来的延迟只有 20ms,结果上线后用户反馈延迟 300ms。后来才发现,办公室网络太理想了,完全没反映出公网的抖动和丢包。所以,测试环境越接近真实用户场景,测量结果越有参考价值。