在使用RTP协议时,如果需要网络对抗,保障QoS(Quality of Service,服务质量),我们需要通过序列号以及时间戳的比较,进行丢包判断。但是有个问题,比如一个RTP包,序列号为number1:5000,另一个RTP包序列号为number2:60000,可以说60000一定比5000大,是个更新的RTP包吗?
当然不是了,首先我们先重温下RTP数据包的结构。
在RFC3550中RTP固定报头结构按如下定义:
1 2 3 4 5 6 7 8 9 10 11 12 |
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P|X| CC |M| PT | sequence number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | synchronization source (SSRC) identifier | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | contributing source (CSRC) identifiers | | .... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
可以看到,RTP序列号sequence number
用两字节表示,时间戳timestamp
用四字节表示。所以序列号以及时间戳就存在一个取值范围。由于是无符号数,所以序列号范围为:[0,2^16-1]
,时间戳范围为:[0,2^32-1]
。当达到最大值时,将发生所谓的回绕。例如,序列号到了2^16-1,下个包序列号就将是0。所以我们不能直接根据数学意义上的大小进行序列号以及时间戳的比较。
在WebRTC中定义了一个大小比较算法,包含数字回绕处理,判断是否是更新的数字。下面说下算法原理:
1)假设有两个U
类型的无符号整数:value
, prev_value
;
2)定义一个常量kBreakpoint
,为U
类型取值范围的一半;
3)value > prev_value
,满足value - prev_value = kBreakpoint
时,value
大于prev_value
;
4)value
与prev_value
不相等,满足(U)(valude - prev_value) < kBreakpoint
时,value
大于prev_value
。
总结起来就是value
与prev_value
距离小于取值范围的一半且不相等或者value
与prev_value
距离等于取值范围的一半,value
大于prev_value
,就可以说明value
大于prev_value
。
在modules\include\module_common_types_public.h,相应代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
template <typename U> inline bool IsNewer(U value, U prev_value) { static_assert(!std::numeric_limits<U>::is_signed, "U must be unsigned"); // kBreakpoint is the half-way mark for the type U. For instance, for a // uint16_t it will be 0x8000, and for a uint32_t, it will be 0x8000000. constexpr U kBreakpoint = (std::numeric_limits<U>::max() >> 1) + 1; // Distinguish between elements that are exactly kBreakpoint apart. // If t1>t2 and |t1-t2| = kBreakpoint: IsNewer(t1,t2)=true, // IsNewer(t2,t1)=false // rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false. if (value - prev_value == kBreakpoint) { return value > prev_value; } return value != prev_value && static_cast<U>(value - prev_value) < kBreakpoint; } // NB: Doesn't fulfill strict weak ordering requirements. // Mustn't be used as std::map Compare function. inline bool IsNewerSequenceNumber(uint16_t sequence_number, uint16_t prev_sequence_number) { return IsNewer(sequence_number, prev_sequence_number); } // NB: Doesn't fulfill strict weak ordering requirements. // Mustn't be used as std::map Compare function. inline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) { return IsNewer(timestamp, prev_timestamp); } |
顺便也说下如上函数的一个应用。在WebRTC丢包重传判断中(jitter_buffer模块),需要保存上一次收到的最新RTP序列号latest_received_sequence_number_
,以便用于丢包判断(请参考:WebRTC研究:丢包判断),这个RTP包必须是新的数据,不能是重传的旧数据。所以每来一个RTP包时,可以将该RTP包序列号传入如上函数,与之前收到的最新RTP序列号进行对比,从而判断当前RTP包是不是最新的。相应代码如下:
1 2 3 4 5 6 7 8 9 |
inline uint16_t LatestSequenceNumber(uint16_t sequence_number1, uint16_t sequence_number2) { return IsNewerSequenceNumber(sequence_number1, sequence_number2) ? sequence_number1 : sequence_number2; } latest_received_sequence_number_ = LatestSequenceNumber(latest_received_sequence_number_, packet.seq_num); |
参考
[1].rfc1982.https://tools.ietf.org/html/rfc1982
文章评论
请问kBreakpoint常量值为U类型取值范围的一半,为什么webrtc要做一个这样的定法呢? 看RFC1982看不太懂,其中是否规定了使用序列号的递增的间隔只能在[1, U类型取值范围的一半]这个区间, 如果递增间隔大于[ U类型取值范围的一半]那么回环很容易发生,无法判断前后了,出于这个原因webrtc把kBreakpoint定义成U类型取值范围的一半的一般?
@sshsu 递增间隔取一半以内大小基本可以满足要求。你觉得在RTC传输中,什么场景会出现前后两个包距离大于一半值范围。以音频包为例,一秒大概50 packet,值范围一半大概覆盖655s时间跨度,前后两个包跨度这么长时间,肯定出问题了,而且libsrtp中的加密算法也有序列号校验,也会认为是异常,加密失败。算法也是要结合实际场景来分析的。
想请教一下这个式子:(U)(valude - prev_value) < kBreakpoint 中的(U)是什么意思?
@yy 传统C语言的强制类型转换 + C++模板技术