RTP协议中有序列号字段 sequence number ,这个字段主要用于丢包、乱序的处理。RTP 包的头部序列号字段长度为 16 bits,取值范围为 [0, 65535]。
实际项目中需要解决的问题:对于两个 RTP 包,如何比较哪一个包才是最新的包?
比如,序列号为 0 的包一定比序列号为 65535 的包小,是旧的包吗?再如,序列号为 65535 的包一定比序列号为 255 的包大,是最新的包吗?这就涉及到回绕的问题。
序列号回绕
对于无符号类型的数据来说,当到达临界值时继续递增,就会出现回绕。序列号的回绕方式有两种,分别是向前回绕和向后回绕。
前向回绕(forward wrap)
所谓前向回绕,就是顺时针回绕,只有在序列号递增的情况下才会发生。有如下特点:
- 包号呈向前递增趋势
- 当前的包号却比上一个包号小
- 从上一个包号到当前的包号,需要跨越0这个数
- 包号之间的距离小于包号类型能表示的数字个数的一半
- 逻辑上认定当前包号才是更新更大的包
后向回绕(backward wrap)
向后回绕是逆时针的回绕,有如下特点:
- 包号呈向后递减趋势
- 当前的包号很大,而前一个包号很小
- 从上一个包号向后跨越包号 0 到当前的包号
- 包号之间的距离大于包号类型能表示的数字个数的一半
- 逻辑上认定当前包号是更小的包号,即旧的包
如何判断两个u16数大小
实现代码
#define UINT16_MAX 65535
#define U16_K_BREAKPOINT ((UINT16_MAX >> 1) + 1)
bool isNewerSeqNum(UINT16 value, UINT16 prevValue) {
// 32768 ... 是U16最大值的一半
uint16_t breakPoint = U16_K_BREAKPOINT;
// 如果两值差正好对半,直接与前值判断大小即可
if (value - prevValue == breakPoint) {
return value > prevValue;
}
// 如果重叠,不会算是新包
if (value == prevValue) {
return false;
}
bool ret = (UINT16)(value - prevValue) < breakPoint;
return ret;
}
测试场景
测试需覆盖:包号相等、包号无回绕、包号向前回绕、包号向后回绕、包号距离为临界值(32768)这五种场景。
1)包号相等
//
// |____________________|____________________|
// ^ 0 32768
// 65530
//
void wrap_test() {
uint16_t seqNum = 65530;
uint16_t prevSeqNum = 65530;
bool ret = isNewerSeqNum(seqNum, prevSeqNum);
printf("seqNum:%d prevSeqNum:%d return:%d\n", seqNum, prevSeqNum, ret);
}
2)包号无回绕
//
// |____________________|____________________|
// 0 ^ ^ 32768
// 3278 6553
// |----> |---->
//
void wrap_test() {
uint16_t seqNum = 6553;
uint16_t prevSeqNum = 3278;
bool ret = isNewerSeqNum(seqNum, prevSeqNum);
printf("seqNum:%d prevSeqNum:%d return:%d\n", seqNum, prevSeqNum, ret);
}
3)包号向前回绕
//
// |____________________|____________________|
// ^ 0 ^ 32768
// 65530 32761
// ---->| |---->
//
void wrap_test() {
uint16_t seqNum = 32761;
uint16_t prevSeqNum = 65530;
bool ret = isNewerSeqNum(seqNum, prevSeqNum);
printf("seqNum:%d prevSeqNum:%d return:%d\n", seqNum, prevSeqNum, ret);
}
4)包号向后回绕
//
// |____________________|____________________|
// ^ 0 ^ 32768
// 65530 32761
// |<---- <----|
//
void wrap_test() {
uint16_t seqNum = 65530;
uint16_t prevSeqNum = 32761;
bool ret = isNewerSeqNum(seqNum, prevSeqNum);
printf("seqNum:%d prevSeqNum:%d return:%d\n", seqNum, prevSeqNum, ret);
}
5)包号距离为临界值
//
// |____________________|____________________|
// 0 32768
// ^ ^
// ---->| |---->
//
void wrap_test() {
uint16_t seqNum = 0;
uint16_t prevSeqNum = 32768;
bool ret = isNewerSeqNum(seqNum, prevSeqNum);
printf("seqNum:%d prevSeqNum:%d return:%d\n", seqNum, prevSeqNum, ret);
}