备注:基于 webrtc 89 版本,本文只针对 ULP fec,不涉及 Flex fec !!!
一,概述
Mask 存在于 Ulp Level header 中,根据 FEC Header 标志位 L 取值不同而改变,当 L 等于 0 时,mask 长度为 16bits,当 L 设置为 1 时,长度为 48bits ,它描述了被保护媒体包的位置分布,如果想要理解 fec 封包与解包流程,必须要熟悉 Mask 的规则,本文就个人理解进行概述。
二,Mask 规则
下文摘自RFC5109第 7.4 章节,描述了 mask 字段的设置必须遵循的规则
a. A media packet SHALL be protected only once at each protection
level higher than level 0. A media packet MAY be protected more
than once at level 0 by different packets, providing the
protection lengths of level 0 of these packets are equal.
b. For a media packet to be protected at level p, it MUST also be
protected at level p-1 in any FEC packets. Please note that the
protection level p for a media packet can be in an FEC packet that
is different from the one that contains protection level p-1 for
the same media packet.
c. If a ULP FEC packet contains protection at level p, it MUST also
contain protection at level p-1. Note that the combination of
payload packets that are protected in level p may be different
from those of level p-1.
The rationale for rule (a) is that multiple protection increases the
complexity of the recovery implementation. At higher levels, the
multiple protection offers diminishing benefit, so its application is
restricted to level 0 for simpler implementation. The rationale for
rule (b) is that the protection offset (for each associated packet)
is not explicitly signaled in the protocol. With this restriction,
the offset can be easily deducted from protection lengths of the
levels. The rationale of rule (c) is that the level of protection is
not explicitly indicated. This rule is set to implicitly specify the
levels.
复制代码
翻译如下:
a: 媒体包在高于 Level 0 只能被保护一次,但是在 Level 0 可以被多个 fec 包保护,这些数据包的 0 级别保护长度必须是相等的。(多重保护增加恢复实现的复杂性。在更高的层次上多重保护的效益递减,因此为了更简单的实现,限制到 0 级)
b:媒体包在 p 级别被保护,它也必须是 p-1 级被保护,注意:保护 p,p-1 媒体包的 fec 包可能不是同一个。(保证了媒体包被保护的连续性,即不存在媒体包某段没有被保护的可能性)
c:如果一个 ULP FEC 包含 p 级保护,它也必须包含 p-1 级别的保护。注意:p 级保护和 p-1 级保护的媒体包可能不是同一个。(保证了 fec 包保护的连续性,即不存在某个级别的 fec 包不保护媒体包的可能性)
三,Mask 表查询
kPacketMaskRandomTbl 是 webrtc 针对 ulp fec 定制的一张表,它包含了媒体包个数小于 12 的所有保护策略,说人话就是当媒体包个数小于 12,且使用的是 ulp fec 时,我们仅需查表就可以得到我们想要 fec mask 的信息,kPacketMaskRandomTbl 定义如下:
#define kMaskRandom1_1 \
0x80, 0x00
#define kMaskRandom2_1 \
0xc0, 0x00
#define kMaskRandom2_2 \
0xc0, 0x00, \
0x80, 0x00
......
#define kPacketMaskRandom1 1, \
kMaskRandom1_1
#define kPacketMaskRandom2 2, \
kMaskRandom2_1, \
kMaskRandom2_2
#define kPacketMaskRandom3 3, \
kMaskRandom3_1, \
kMaskRandom3_2, \
kMaskRandom3_3
......
const uint8_t kPacketMaskRandomTbl[] = {
12,
kPacketMaskRandom1, // 2 byte entries.
kPacketMaskRandom2,
kPacketMaskRandom3,
kPacketMaskRandom4,
kPacketMaskRandom5,
kPacketMaskRandom6,
kPacketMaskRandom7,
kPacketMaskRandom8,
kPacketMaskRandom9,
kPacketMaskRandom10,
kPacketMaskRandom11,
kPacketMaskRandom12,
};
复制代码
当看到上面代码的第一眼,给我的感觉是“我靠,什么玩意!”,估计好多人跟我一样吧,但是当我第一个元素'12'(这个元素并没有什么用途,仅仅是为了做检查,后面还会提到它)拿掉,然后放到下面的表格中,突然我悟了。
简单解释,现设 K 为媒体包的数量,表格中对应格对应的内容如下:
K-1 | kMaskRandom{K-1}_1.... kMaskRandom{K-1}_{K-1}
K | kMaskRandom{K}_1 .... kMaskRandom{K}_{K}
K+1 | kMaskRandom{K+1}_1.... kMaskRandom{K+1}_{K+1}
我们结合代码解释下如何根据媒体包和 fec 包的数量查找 mask 表,现设 M,N 分别为媒体包和 fec 包个数,M N 都等于 4,我们的目标掩码表是 kMaskRandom4_4,
// 功能:根据输入的媒体包和fec包数量查找fec mask表,
// media_packet_index:M 减一
// fec_index:N 减一
rtc::ArrayView<const uint8_t> LookUpInFecTable(const uint8_t* table,
int media_packet_index,
int fec_index) {
// table[0]就是kPacketMaskRandomTbl 第一个元素12 ,在这里做了检查
RTC_DCHECK_LT(media_packet_index, table[0]);
// 跳过kPacketMaskRandomTbl[0]
const uint8_t* entry = &table[1];
uint8_t entry_size_increment = 2; // 0-16 are 2 byte wide, then changes to 6.
// 此时media_packet_index为3,当由4个媒体包生产fec 包时 所生成的mask 保护策略必然在第4行,
// 这两个for 循环就是为了跳过前面的3行,也就是绿色部分。
for (int i = 0; i < media_packet_index; ++i) {
if (i == 16)
entry_size_increment = 6;
uint8_t count = entry[0];
++entry; // skip over the count.
for (int j = 0; j < count; ++j) {
entry += entry_size_increment * (j + 1); // skip over the data.
}
}
if (media_packet_index == 16)
entry_size_increment = 6;
RTC_DCHECK_LT(fec_index, entry[0]);
++entry; // Skip over the size.
// 此时fec_index为3,而我们需要找到kMaskRandom4_4的位置, 我们直接跳过去kMaskRandom4_1,
//kMaskRandom4_2,kMaskRandom4_3,也就是灰色部分,
for (int i = 0; i < fec_index; ++i)
entry += entry_size_increment * (i + 1); // skip over the data.
// 最终entry 指向了kMaskRandom4_4 的初始位置,
// entry_size_increment * (fec_index + 1)就是kMaskRandom4_4的大小
size_t size = entry_size_increment * (fec_index + 1);
return {&entry[0], size};
}
复制代码
四,Mask 使用
这个 mask 表其实就是一个'0-1 矩阵’,row 代表 fec 包保护了那些媒体包,而 column 则表示媒体包被那些 fec 包保护,举个 4 个媒体包由 4 个 fec 包保护例子:
0xc0 : 1100 0000 第一个 fec 包保护媒体包 snbase + 1, snbase + 2
0xa0: 1010 0000 第二个 fec 包保护媒体包 snbase + 1, snbase +3
0x30: 0011 0000 第三个 fec 包保护媒体包 snbase + 3,snbase +4
0x50: 0101 0000 第四个 fec 包媒体包保护 snbase + 2 ,snbase +4
Sn base :参考 FEC Header
评论