转载:WebRTC研究:RTP中的序列号以及时间戳比较
在使用RTP协议时,如果需要网络对抗,保障QoS(Quality of Service,服务质量),我们需要通过序列号以及时间戳的比较,以进行丢包、乱序等处理。但是有个问题,比如一个RTP包,序列号为number1:5000,另一个RTP包序列号为number2:60000,可以说60000一定比5000大,是个更新的RTP包吗?
当然不是了,首先我们先重温下RTP数据包的结构。
在 RFC3550 中 RTP 头部结构按如下定义:
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
。
在src\modules\include\module_common_types_public.h,相应代码如下:
template
inline bool IsNewer(U value, U prev_value) {
static_assert(!std::numeric_limits::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::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(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_
,以便用于丢包判断,这个RTP包必须是新的数据,不能是重传的旧数据。所以每来一个RTP包时,可以将该RTP包序列号传入如上函数,与之前收到的最新RTP序列号进行对比,从而判断当前RTP包是不是最新的。相应代码如下:
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] Serial Number Arithmetic