当我们调试WebRTC web客户端时,经常会打开chrome://webrtc-internals/
这个页面,在这里我们可以看到音视频流的各种统计。今天我们就来看下其中的一个统计参数:抖动(jitter)。
什么是抖动
在网络传输中,每个包从发送端到接收端的时延都是不相同的,而jitter就是用来衡量这种不同。一般在发送端,数据包发送时间间隔是相同的,也就是均匀发送数据,但是传输过程中由于各种情况,例如拥塞,丢包,网络错误等,接收端收到的数据包间隔就会不一样,可能一会大,一会小,导致时延发生变化,这个时延的变化程度就是所谓的抖动,接收端如果不做任何处理,就会影响用户体验。例如对于视频来说,会导致帧率变化,视频播放就不平滑,有卡顿感。如下图片是抖动的一个形象描述。
WebRTC内部通过NetEQ与JitterBuffer进行音视频的抖动处理,消除抖动造成的影响,这些会在我的后续文章介绍。
抖动计算
根据RFC3550,假设RTP包i,RTP报头记录的时间戳为Si(单位为采样率),到达接收端的时间为Ri(单位同样为采样率),对于包i,j,我们定义一个变量\(D\),描述两个包时间的差异,由接收时间差减去发送时间差:
\[D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)\tag{1}\]
抖动\(J\)按如下公式定义:
\[J(i) = J(i-1) + (|D(i-1,i)| - J(i-1))/16\tag{2}\]
上述公式中i-1指的是前一个收到的包,不是按RTP序列号计数的。增益参数1/16是为了消除噪声影响,使抖动收敛在较合理范围内,避免突发数据的影响。
由公式可知,如果接受端收到的包间隔跟发送端发送间隔一样,那么抖动为0。
代码导读
WebRTC统计参数中的抖动计算代码位于StreamStatisticianImpl::UpdateJitter
中,看下如何计算。
1)计算接收时间差,也就是\(Ri - R_{i-1}\):
1 2 3 4 5 |
// 计算接收时间差 int64_t receive_diff_ms = receive_time_ms - last_receive_time_ms_; // 接收时间差:ms时间单位转换为采样率描述的时间单位 uint32_t receive_diff_rtp = static_cast<uint32_t>( (receive_diff_ms * packet.payload_type_frequency()) / 1000); |
2)计算\(|D(i-1,i)|\),其中发送时间为RTP报头记录的时间戳:
1 2 3 4 5 |
// D:接收时间差 - 发送时间差 int32_t time_diff_samples = receive_diff_rtp - (packet.Timestamp() - last_received_timestamp_); time_diff_samples = std::abs(time_diff_samples); |
3)由公式(2),使用jitter_diff
表示\(|D(i-1,i)|-J(i-1)\):
\[jitter\_diff = time\_diff\_samples - J(i-1)\tag{3}\]
\[J(i) = J(i-1) + jitter\_diff/16\tag{4}\]
所以相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 |
// lib_jingle sometimes deliver crazy jumps in TS for the same stream. // If this happens, don't update jitter value. Use 5 secs video frequency // as the threshold. // 由于一般视频的时钟采样率为90kHz,5S用采样率单位表示刚好是450000 if (time_diff_samples < 450000) { // 根据公式(3)计算jitter_diff:q4后缀表示左移4位,也就是乘以16 // 两边同乘以16,也就是<<4,这样做是为了避免计算使用float表示 int32_t jitter_diff_q4 = (time_diff_samples << 4) - jitter_q4_; // 根据公式(4)更新jitter_q4_:(jitter_diff_q4 + 8)是为了四舍五入 jitter_q4_ += ((jitter_diff_q4 + 8) >> 4); } |
至此我们得到了jitter_diff_q4
,单位为采样率,如果需要转换为毫秒时间单位,需要如下计算:
1)由于jitter_diff_q4
放大了16倍,首先需要还原回去:
1 |
jitter = jitter_q4_ >> 4; |
2)转换为毫秒单位:
1 2 |
// 一般视频的时钟采样频率clockrate_hz为:90kHz jitter_ms = jitter / (clockrate_hz / 1000); |
这样我们就得到了以毫秒表示的抖动统计参数,chrome://webrtc-internals/
中抖动的单位是秒。
参考
[1] Jitter.https://en.wikipedia.org/wiki/Jitter.
[2] RFC3550.https://tools.ietf.org/html/rfc3550.
文章评论
谢谢Jeff,讲解的很清晰,受教了。
另外文中有一处笔误:“到达接收端的时间为Si(根据上下文应该是Ri)(单位同样为采样率)“。
@MarkCao 已纠正
补充一点自己的理解:RTP包的时间戳timestamp虽然是用64位表示,但是webrtc中只用了32位表示,即整数和小数部分个16位,即4个字节。这也就是计算jitter_diff时使用Q4格式来避免浮点计算的原因。
公式写错了吧,应该是J(i)=J(i−1)+( 16*|D(i−1,i)| −J(i−1) ) / 16
// 两边同乘以16,也就是<<4,这样做是为了避免计算使用float表示
int32_t jitter_diff_q4 = (time_diff_samples << 4) - jitter_q4_; 这个段代码,为什么 jitter_q4没有<<4呢
@hello123# 理解了 不用回复了 谢谢
@hello123# 能不能讲一下呢,我也没理解
@hello123# 突然懂了