📄 rfc2733.txt
字号:
o 如果CC不为零,这里是CSRC列表(变长)
o 如果扩展位为1,这里是头部扩展(变长)
o 荷载(变长)
o 填充,如果存在的话(变长)
需要注意的是最前面的填充位是整个比特序列的最重要比特(MSB)。
如果各个媒体数据包的到的比特序列长度不相等,那么每一个都必须填充到最长的序列那
么长。填充值可以是任意的,但必须填充在整个比特序列的最后。
对所有这些比特序列进行对位异或操作,就可以得到一个监督比特序列,这个监督比特序
列就可以用来生成FEC包。称这个比特序列为FEC比特序列。
FEC比特序列中的第一个比特填入FEC包的填充位,第二个比特填入FEC包的扩展位,接
下来的四个比特填入FEC包的CC域,再下来的一个比特填入FEC包的标记位,然后的七个比
特写入FEC包头的PT恢复域,再后面的32个比特写入FEC包头的TS恢复域,再接下来的
16个比特写入FEC包头中的长度恢复域。最后剩下的比特就作为FEC包的荷载。
8 恢复过程
FEC包能够使终端系统有能力恢复出丢失的媒体数据包。丢失包的的包头中的所有数据
域,包括CSRC列表,扩展位,填充位,标记位以及荷载类型,都是可以恢复的。这一节描述
进行恢复的过程。
恢复过程中包含两个不同的操作。第一个是确定需要用哪些包(包括媒体包和FEC包)来
恢复一个丢失的包。这一步完成之后,第二步就是重建丢失的包。第二步必须按照下面的规
定来进行。第一步可以由实现者选择任意的算法来完成。不同的算法会导致在复杂度和恢复
丢包能力之间的一个不同的折中。
8.1 重建
设T为可用来恢复媒体数据包xi的一组包(包括FEC包和媒体包),重建过程如下所述:
1. 对于T中的媒体数据包,按照上一节保护操作中所规定的那样计算它们的比特序列。
2. 对于T中的FEC包,基本以同样的方式来计算比特序列,不同点仅在于用PT恢复域
的值取代荷载类型,用TS恢复域的值取代时间戳,并将CSRC列表、扩展位和填充位
都设为null。
3. 如果某个媒体数据包生成的比特序列比FEC包生成的比特序列短,就把它填充到域
FEC包生成的比特序列一样长度。填充部分必须加在比特序列的最后,可以为任意值。
4. 对这些比特序列进行按位异或操作,得到一个恢复出的比特序列。
5. 创建一个新的数据包,12个字节的标准RTP头,没有荷载。
6. 将这个新包的版本域设为2。
7. 将新包的填充位设为恢复出的比特序列的第一个比特。
8. 将新包的扩展位设为恢复出的比特序列的第二个比特。
9. 将新包的CC域设为恢复出的比特序列的接下来的4个比特。
10. 将新包的标记为设为恢复出的比特序列的接下来的一个比特。
11. 将新包的荷载类型设为恢复出的比特序列的接下来的7个比特。
12. 将新包的SN域设定为xi。
13. 将新包的TS域的值设定为恢复出的比特序列的接下来的32个比特。
14. 从恢复出的比特序列中取出接下来的16个比特,将其作为一个网络序的无符号整
数,然后从恢复出的比特序列中取出这个整数那么多的字节,添加在新包之后,这代
表新包的CSRC列表、扩展、荷载和填充。
15. 将新包的SSRC域设定为它所保护的媒体流的SSRC值。
上面的这个过程能够完全恢复出一个丢失的RTP包的包头和荷载。
8.2 何时进行恢复
前面一节讨论了当要恢复一个序号为xi的包时,所有需要的包都可用时,如何来进行恢
复。而并未涉及如何决定是否去试图恢复某个包xi,以及如何确定是否有足够的数据来恢复
这个包。这些问题将留给实现者去灵活设计,但我们将在本节给出一个可用于解决这些问题
的简单算法。
下面的这个算法是用C语言写的。代码中假定已经存在了几个函数。recover_packet()
的参数是一个包的序号和一个FEC包。用这个FEC包和以前收到的数据包,这个函数能够恢
复出指定序号的数据包。add_fec_to_pending_list()将一个给定的FEC包加入到一个存放尚
未用于恢复操作的FEC包的链表中去。wait_for_packet()等待从网络上发来的一个包,FEC
包或者是数据包。remove_from_pending_list()将一个FEC包从链表中删除。结构体packet
包含一个布尔变量fec,当这个包为FEC包时fec为真,否则为假。当它是一个FEC包时,
成员变量mask和snbase存放着FEC包头中对应值;当它是一个媒体数据包时,sn变量存放
着包的序号。全局数组A用于指示出哪些媒体数据包已经收到了,而哪些还没有。它是以包
的序号为索引的。
函数fec_recovery给出了这个算法的实现。它等待网络上发来的数据包,当它收到一个
FEC包时,就调用recover_with_fec(),即尝试用它来恢复。如果恢复操作不可能(数据不
足),就把这个FEC包存起来备用。如果收到的包是一个媒体数据包,就记录下它已经收到
了,然后检查以前的FEC包是否现在可以进行恢复了。我们对待恢复出来的包就象从网络上
收到这个包一样,并会引发进一步的恢复尝试。
一个实际的实现需要用一个循环缓冲区来代替数组A,以避免数组缓冲区溢出。并且,
下面的代码中并没有释放已经没有任何用处的FEC包。一般来说,对FEC包的释放操作可以
基于一个时间限制(play time),这个时间限制取决于发送端以多少个包为一组进行保护操
作。当一个FEC包所保护的数据包的play time已经过去的时候,这个FEC包就不再有用了。
typedef struct packet_s {
BOOLEAN fec; /* FEC or media */
int sn; /* SN of the packet, for media only */
BOOLEAN mask[24]; /* Mask, FEC only */
int snbase; /* SN Base, FEC only */
struct packet_s *next;
} packet;
BOOLEAN A[65535];
packet *pending_list;
packet *recover_with_fec(packet *fec_pkt) {
packet *data_pkt;
int pkts_present, /* number of packets from the mask that are
present */
pkts_needed, /* number of packets needed is the number of ones
in the mask minus 1 */
pkt_to_recover, /* sn of the packet we are recovering */
i;
pkts_present = 0;
/* The number of packets needed is the number of ones in the mask
minus 1. The code below increments pkts_needed by the number
of ones in the mask, so we initialize this to -1 so that the
final count is correct */
pkts_needed = -1;
/* Go through all 24 bits in the mask, and check if we have
all but one of the media packets */
for(i = 0; i < 24; i++) {
/* If the packet is here and in the mask, increment counter */
if(A[i+fec_pkt->snbase] && fec_pkt->mask[i]) pkts_present++;
/* Count the number of packets needed as well */
if(fec_pkt->mask[i]) pkts_needed++;
/* The packet to recover is the one with a bit in the
mask that's not here yet */
if(!A[i+fec_pkt->snbase] && fec_pkt->mask[i])
pkt_to_recover = i+fec_pkt->snbase;
}
/* If we can recover, do so. Otherwise, return NULL */
if(pkts_present == pkts_needed) {
data_pkt = recover_packet(pkt_to_recover, fec_pkt);
} else {
data_pkt = NULL;
}
return(data_pkt);
}
void fec_recovery() {
packet *p, /* packet received or regenerated */
*fecp, /* fec packet from pending list */
*pnew; /* new packets recovered */
while(1) {
p = wait_for_packet(); /* get packet from network */
while(p) {
/* if it's an FEC packet, try to recover with it. If we can't,
store it for later potential use. If we can recover, act as
if the recovered packet is received and try to recover some
more. Otherwise, if it's a data packet, mark it as received,
and check if we can now recover a data packet with the list
of pending FEC packets */
if(p->fec == TRUE) {
pnew = recover_with_fec(p);
if(pnew)
A[pnew->sn] = TRUE;
else
add_fec_to_pending_list(p);
/* We assign pnew to p since the while loop will continue
to recover based on p not being NULL */
p = pnew;
} else {
/* Mark this data packet as here */
A[p->sn] = TRUE;
free(p);
p = NULL;
/* Go through pending list. Try and recover a packet using
each FEC. If we are successful, add the data packet to
the list of received packets, remove the FEC packet from
the pending list, since we've used it, and then try to
recover some more */
for(fecp = pending_list; fecp != NULL; fecp = fecp->next) {
pnew = recover_with_fec(fecp);
if(pnew) {
/* The packet is now here, as we've recovered it */
A[pnew->sn] = TRUE;
/* One FEC packet can only be used once to recover,
so remove it from the pending list */
remove_fec_from_pending_list(fecp);
p = pnew;
break;
}
} /*for*/
} /*p->fec was false */
} /* while p*/
} /* while 1 */
}
9 例子
考虑这样的情况,有两个媒体数据包x和y要从SSRC2发送出去。它们的序号分别为8
和9,时间戳分别为3和5。x的荷载类型为11,y的荷载类型为18。x有十个字节的荷载,
y有11个字节的荷载。y的标记位为1。x和y的RTP包头分别如图3和图4所示。
媒体数据包 x
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0|0|0|0 0 0 0|0|0 0 0 1 0 1 1|0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
版本: 2
填充位: 0
扩展位: 0
标记位: 0
PTI: 11
SN: 8
TS: 3
SSRC: 2
图 3: 媒体包x的RTP头
媒体包 y
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0|0|0|0 0 0 0|1|0 0 1 0 0 1 0|0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -