Nack , 即丢包重传,在RTT小的情况下,是非常有效的错误恢复手段。接收端根据接收到包的序列号判断当前是否有丢包,若接收到的最新包和上次收到的新包得序列号不连续则可以断定丢包或者乱序,当新收到的包序号大于保存的最新的包,则可以判断为丢包,当小于,则可以判断为乱序号,或者重复。当然者里面要注意序列号翻转。一般接收端会维护一个列表,用于记录收到的包的序列号,一半这个列表的长度是一个GOP,因为即使上一个关键帧的之前丢的包通过nack恢复过来了,意义也不大了。发送也不会收到nack的请求就重新发包,发送者也会缓存一个GOP或者一定时间内的包。

生成nack_list

根据最新收到的包序号计算出初步的丢包序列,第二步根据时间删掉与新收到的包序号大于10000的包,第三步,在nack的数量限制,由旧到新依次删除,直到nack的序列数量小于1000,或者找不到新的关键帧,如果找不到缓存的关键帧序号,则会直接给对端发送一个关键帧请求。最后把新计算的丢包序列号加入到nack_list,加入的时候要判断下那个包是不是有Rtx或者Fec恢复过来。

发送时机

两个时间点,新包到达,判断存在丢包时候,立刻发送重传请求。

定时重传,每隔20s,定时请求。

构造重传请求列表

在收到新包时的重传请求,只会请求当前生成的最新的丢包记录。

在定时请求的重传请求里面,重传列表请求的时间间隔需要大于一个RTT,如果还没有计算出RTT,默认的RTT 100ms。

另外还有一个可选项,就是在生成请求列表是,会跑端生成的时间间隔,默认为0,最大20ms,若时间间隔小于这个值,也不会加入重传请求列表。

NACK的格式

NACK 请求是Payload Type为PT_RTPFB=205的RTCP包FMT是1,一般与其他RTCP包打在一起,称为一个RTCP compound包。格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
 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| FMT=1 | PT=205(RTPFB) | length in 32bit - 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of this packet sender |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of media source |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PID1 | BLP1 |
| PID2 | BLP2 |
: .... :

PID是丢掉的包的序列号,BLP是这个包后面16个包的丢包的掩码。

重传格式

发送端可以在RTP History中找到原来的包,重新发一遍,但是这样会影响接收端的丢包率计算和拥塞控制。

因此,一般会按照RFC4588,增加一个RTX Header

1
2
3
4
5
6
7
8
9
10
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OSN | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| Original RTP Packet Payload |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

RTX可以有两种格式,会话复用(使用相同的ssrc)和SSRC复用(使用不同的SSRC),OSN是原始的包的序列号,因为新的RTP重传流会使用新的序列号,PT值需要通过SDP协商,其他字段和原始的RTP包一致。