前言
基于UDP的传输协议,因为UDP本身的不可靠,需要解决丢包的问题,一般针对丢包问题有两种方案:
-
基于丢包重传的NACK和ACK 机制,NACK机制是接收端告诉发送端自己那些序列号的包没有收到,然后发送端重传该序号的包;ACK是接收端发送已确认收到的包序号给发送端,发送端根据接收端的ACK信息,检测是否需要重传包。
-
传输冗余信息的FEC机制,当传输中出现丢包,接收器根据已接收的部分包恢复完整的数据。
weberc 采用了NACK的方案,本文基于mediasoup NACK模块来分析NACK的设计要点。实际上,mediasoup NACK设计原理和webrtc中的NACK设计是一致的。
1. NACK设计
对于实时音视频来说,既要做到及时快速的重传,提高传输速率,又要做到避免过多的,过久的重传,减少带宽的使用,这也是webrtc NACK的设计要点,需要平衡音视频延时和重传带宽占用。
mediasoup 设计一个NackGenerator类进行NACK管理,它维护了一个重传队列和关键帧队列,为了避免过多的,过久的重传,重传队列设置了最大长度1000个RTP包,或者覆盖10000的包序号范围,每个包最多重传10次。
mediasoup 有两处地方会发送NACK 指令:
- 每次收到RTP数据包,会去检测是否有包丢失,如果有包丢失,第一次重传会立即进行。
- 设置了一个40毫秒的定时器,定期检测重传队列,如果当前时间距离上次重传时间超过当前计算的rtt时,会再重新进行重传,但最大重传次数是10次,超过10次会取消该序列号的重传。
如果重传一直无效,导致重传队列包数到达了最大值,mediasoup会尝试清除过久的重传包,直到最近的一个关键帧重传请求。清理后如果队列还是超出了限制,会清除整个重传队列,发送一个RTCP 的PLI 命令,直接请求发送方发一个关键帧,避免了过多的重传带宽。
2. 代码分析
mediasoup 的NACK 代码实现
// Returns true if this is a found nacked packet. False otherwise.
bool NackGenerator::ReceivePacket(RTC::RtpPacket* packet, bool isRecovered)
{
MS_TRACE();
uint16_t seq = packet->GetSequenceNumber();
bool isKeyFrame = packet->IsKeyFrame();
if (!this->started)
{
this->started = true;
this->lastSeq = seq;
if (isKeyFrame)
this->keyFrameList.insert(seq);
return false;
}
// Obviously never nacked, so ignore.
if (seq == this->lastSeq)
return false;
// May be an out of order packet, or already handled retransmitted packet,
// or a retransmitted packet.
if (SeqManager<uint16_t>::IsSeqLowerThan(seq, this->lastSeq))
{
auto it = this->nackList.find(seq);
// It was a nacked packet.
if (it != this->nackList.end())
{
MS_DEBUG_DEV(
"NACKed packet received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", recovered:%s]",
packet->GetSsrc(),
packet->GetSequenceNumber(),
isRecovered ? "true" : "false"

本文详细介绍了mediasoup中的NACK机制设计原理及其实现细节,包括NACK生成、重传队列管理和重传时机等内容,并探讨了潜在的问题及其解决方案。
最低0.47元/天 解锁文章
1474

被折叠的 条评论
为什么被折叠?



