rtp序号,时间戳的会绕问题
比如一个RTP包,序列号为4890,另一个RTP包序列号为59900,可以说59900一定比4890大,是个更新的RTP包吗?输入当前数字和之前的数字,如果当前数字是更新的数字则返回。结论,当然不是这样,因为在判断序列号的连续性时要考虑回绕问题,不能直接根据数学意义上的大小进行比较。再如,序列号为 65535 的包一定比序列号为 255 的包大,是最新的包吗?该函数用于展开回绕的数字,得到更大类型
问题
在使用RTP协议时,我们需要通过序列号以及时间戳的比较,进行丢包判断。但是有个问题,
比如一个RTP包,序列号为4890,另一个RTP包序列号为59900,可以说59900一定比4890大,是个更新的RTP包吗?
序列号为 0 的包一定比序列号为 65535 的包小,是旧的包吗?
再如,序列号为 65535 的包一定比序列号为 255 的包大,是最新的包吗?
结论,当然不是这样,因为在判断序列号的连续性时要考虑回绕问题,不能直接根据数学意义上的大小进行比较。
序列号回绕解释
RTP序列号sequence number
用两字节表示,时间戳timestamp
用四字节表示。所以序列号以及时间戳就存在一个取值范围。由于是无符号数,所以序列号范围为:[0,2^16-1=65535]
,时间戳范围为:[0,2^32-1]
。当达到最大值时,将发生所谓的回绕。例如,序列号到了2^16-1 = 65535,下个包序列号就将是0。所以我们不能直接根据数学意义上的大小进行序列号以及时间戳的比较。
webertc 解决方案
在WebRTC中定义了一个大小比较算法,包含数字回绕处理,判断是否是更新的数字。下面说下算法原理:
- 假设有两个
U
类型的无符号整数:value
,prev_value
; - 定义一个常量
kBreakpoint
,为U
类型取值范围的一半;
比如,对于uint16_t
类型,kBreakpoint
为 0x8000,对于uint32_t
类型,kBreakpoint
为 0x80000000。 - 当 value == prev_value 时,认为 value 不是更新(更大)的数字。
value - prev_value = kBreakpoint
且value > prev_value
时,value
大于prev_value
;value
与prev_value
不相等,满足(U)(valude - prev_value) < kBreakpoint
时,value
大于prev_value
。
总结起来就是
-
value
与prev_value
距离小于取值范围的一半且不相等 -
或者
value
与prev_value
距离等于取值范围的一半,value
大于prev_value
,
就可以说明value
大于prev_value
。
IsNewer
modules\include\module_common_types_public.h
template <typename U>
inline bool IsNewer(U value, U prev_value) {
static_assert(!std::numeric_limits<U>::is_signed, "U must be unsigned");
// 1. 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.
// std::numeric_limits<uint16_t>::max() = 65535
// 取一半,就是中间值 32768
constexpr U kBreakpoint = (std::numeric_limits<U>::max() >> 1) + 1;
// 2. 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.
// 0-32768 false
// 32768-0 true
if (value - prev_value == kBreakpoint) {
return value > prev_value;
}
//0-255 = 65281 false
//0-65535 = 1 true
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);
}
该函数实现了数字(序列号、时间戳)的比较算法。输入当前数字和之前的数字,如果当前数字是更新的数字则返回 true
,否则返回 false
。
注意,执行该算法的数字的类型必须是无符号类型。
Unwrap 函数
template <typename U>
class Unwrapper {
public:
int64_t Unwrap(U value);
int64_t UnwrapWithoutUpdate(U value) const;
private:
absl::optional<int64_t> last_value_;
};
该函数用于展开回绕的数字,得到更大类型的真正的数字,其核心逻辑通过调用 UnwrapWithoutUpdate
函数实现。
Unwrap
函数相较于 UnwrapWithoutUpdate
函数的唯一区别是:Unwrap
函数会更新 last_value_
值,这个变量记录了上一次展开回绕后的数字的值。
在实际的流媒体场景中,包号或者时间戳所代表的的数字都是连续的,而且会发生回绕。如果有特殊的应用场景,需要得到回绕展开后的包号或者时间戳,则可以使用该函数。
更多推荐
所有评论(0)