背景
最近对于时延做了一些调研和优化,总结和梳理一下在WebRTC P2P连接中,影响P2P时延的因素。
本篇文章中,主要针对的是包含视频的WebRTC连接中编解码因素进行探讨,需要注意这里的理解时延不单指网络时延RTT, 而是整个连接过程中,画面的采集端采集一帧画面到这一帧画面在另外一侧画面被扫描的这个时延我们认为是一个完整的P2P时延。
准备工作
我们先做一个小demo来具象化理解一下时延。我们写了两个简单的页面,一个是接收端、一个发送端,代表WebRTC P2P的两侧。放在同一台电脑,接收端和发送端放置在两个窗口
上图是我们的做的一个简单的示例截图,左边是我们的发送端,右边是我们的接收端。我们在连接建立之后,通过截图方法就可以得到不同端在同一个时间点的照片,我们通过canvas在画面上面渲染当前的时间。我们可以通过对比他们的时间差可以作为我们的两端的时延。
我们经过多轮测试,可以测试时延在10~30ms之间,因为我的显示器是100HZ刷新率,屏幕的刷新时延会在10ms扫描一帧,所以我们看到的时延差基本都是整数。
可以看到同一个窗口的时延统计,60fps的视频流在内网的视频流在两个窗口之间传输,他们的网络间时延因为是内网传输,所以我们看到是1ms的低延迟值,考虑到单侧传输,所以取0.5ms作为传输值。可以看到这里的时延也不是特别稳定,处于20~30ms之间。
理解了时延之后,我们继续分析这个过程中什么变量会影响时延。
控制变量法
调整编码方式
const { codecs } = RTCRtpReceiver.getCapabilities('video');
const selectCodes = codecs.reduce((selectCodes, item) => {
const { mimeType } = item;
if (mimeType === 'video/H264') {
selectCodes.unshift(item);
} else {
selectCodes.push(item);
}
return selectCodes;
}, []);
const targetCodesIndex = selectCodes.findIndex(item => item.sdpFmtpLine === 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f')
selectCodes.splice(0, 0, selectCodes.splice(targetCodesIndex, 1)[0])
if (typeof peerConnection.getTransceivers === 'function') {
const transceiver = peerConnection.getTransceivers().find(item => {
return item?.receiver.track && item.receiver.track.kind === 'video';
});
if (typeof transceiver?.setCodecPreferences === 'function') transceiver.setCodecPreferences(selectCodes);
}
通过上面的方式,我们可以将我们的Chrome优先使用的VP8的编解码方式更换为H264编解码方式,调整完之后通过调试面板发现已经生效
接着对比了时延,发现了时延的差距在30~60ms波动,大部分都是40或者50ms。
这里的H264的时延明显大于VP8,我们打印了一下时延,发现了一下是因为jitterBuffer的缓冲时延比较高,并且和帧率FPS呈现负相关性,就是帧率越低,我们的jitterBufferDelay越高,帧率越高,我们的jitterBufferDelay就越低,当画面是60fps采集的时候,我们的jitterBufferDelay的平均jitterBuffer时延是13~14ms。
顺便做了一下测试,当采集画面的频率为100fps的时候,我们的画面的端到端时延在H264的编解码器下就基本稳定在30ms了(偶尔会是40ms)。说明帧率对我们的端到端时延有直接影响。并且这时候的jitterBuffer时延是7~8ms
在同样的100fps帧率下,VP8的编码的端到端时延在20ms(偶尔会是30ms),jitterBuffer的时延在10~12ms左右,编码的时延在1.3ms左右,解码的时延在1.08左右