一、基本原理
WebRTC是基于udp协议来进行传输音视频数据的,所以基于udp的特性,RTC采用了2种方式来优化丢包问题
- fec,前向纠错,在每个数据包中添加一些关于前一个信息的信息,以防丢失。在接收端,需要重新构建它们,如果fec为5%,那么在丢包小于5%的情况下,都可以通过fec进行恢复,组成完整的帧,但是需要发送额外的包,会占用更大的带宽,具体实现不做讨论。
- NACK机制,和TCP的ACK机制正好相反,NACK是用来确认丢包的发送协议,当接收方检测到有丢包时,它会发送NACK类型的RTCP包给发送方,发送方会重发这些数据。
NACK 模块是 WebRTC 对抗弱网的核心 QoS 技术之一,有两种发送模式,一种是基于时间序列的发送模式,一种是基于包序列号的发送模式。
二、具体实现
当接收方检测到有丢包时,它会发送NACK类型的RTCP包给发送方。针对现实中网络的复杂程度,我们思考以下几个问题:
- 如何判断丢包?也就是如何选择发送NACK的时机?因为UDP是无连接状态的,不能保证数据的连续性,比如我们先收到了序号1的包, 第二次收到了序号3的包,那么此时是否可以认为序号2的包已经丢失,需要发送NACK报告丢包情况,但很有可能下一时刻2号包就到了。
- 是否需要一直发送NACK包?假如我们发送NACK后,可能因为网络原因等等,一直没有收到接受方发送的重发包,那么还要一直继续发送NACK吗,会不会导致服务链路拥塞
- 如果丢包数量过多,超过了一定的数量,是否需要放弃之前的丢包数据,不再进行发送NACK?
RTC内部也考虑到了这些问题,目前有一些实施的策略来保证,记住几个关键的数字如下。
const int kMaxNackRetries = 10;
const int kProcessIntervalMs = 20;
const int kDefaultRttMs = 100;
const int kMaxNackPackets = 1000;
const int kMaxPacketAge = 10000;
- NACK 模块对同一包号的最大请求次数是10次,超过这个最大次数限制,会把该包号移出 nack_list,放弃对该包的重传请求。
- NACK 模块每隔 20 毫秒批量处理 nack_list,获取一批请求包号存储到 nack_batch,生成 nack 包并发送。不过,nack_list 的处理周期并不是固定的 20ms ,而是基于 20ms 动态变化
- NACK 模块默认 rtt 时间,如果距离上次 nack 发送时间不到一个 rtt 时间,那么不会发送 nack