WebRTC NACK 机制,Mediasoup对NACK的处理

由于webrtc所用的rtp协议底层是基于udp传输,所以并不能保证数据的可靠性。在发生丢包时,为了保证音视频的质量需要进行重传,而nack机制就是用来处理重传逻辑的,需要注意一点由于udp本身是无序的,所以在进行丢包重传时需要注意一下该包是否乱序,然后再决定是否要重传。

NACK 协议介绍

NACK属于反馈包的一种,具体格式如下:

6.1.   Common Packet Format for Feedback Messages

   All FB messages MUST use a common packet format that is depicted in
   Figure 3:
   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   |       PT      |          length               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                  SSRC of packet sender                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                  SSRC of media source                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   :            Feedback Control Information (FCI)                 :
   :                                                               :

        Figure 3: Common Packet Format for Feedback Messages

其中:

  • PT = 205
  • FMT = 1
  • SSRC of packet sender :发送者ssrc,这个ssrc没有具体的用处,可以填一个默认值
  • SSRC of media source :media ssrc,这个值非常重要,服务器就是根据该值判断用户类型的。
    反馈包是订阅者发送给发布者,所以该值可以填发布者ssrc,但是有些服务器如licode,它会基于订阅者ssrc找到对应的发布者然后处理该反馈包,所以该值也可以填订阅者ssrc。我们需要明白一点就好,这个包是发给发布者的,服务器需要基于media source ssrc找到对应的发布者。
  • FCI:具体的反馈信息

对于nack ,FCI内容如下,就是用下面的格式填充上文提到的FCI字段


   The Feedback Control Information (FCI) field has the following Syntax
   (Figure 4):

    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
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |            PID                |             BLP               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           Figure 4: Syntax for the Generic NACK message

字段内容:

  • PID : 该NACK报文反馈的第一个丢失包的序列号,NACK 一次可以反馈16个包的丢失情况, 这16个包通过BLP字段表示
  • BLP: bitmask of following lost packets (BLP): 16 bits  。BLP位掩码,表示继第一个PID表示的序号之后16个包的丢失情况,当丢失之后,对应的二进制位被置为1。例如:PID=100, 从101 到116这16个包,如果103,105丢失则BLP内容为:0010100000000000, 103和105对应的位被置为1,其它的位都为0。假如连续丢包超过16个,就需要一个新的FCI了

mediasoup对nack的处理

详情见:NackGenerator.cpp

关键结构:

struct NackInfo
{
uint16_t seq{ 0u };
uint16_t sendAtSeq{ 0u };
uint64_t sentAtMs{ 0u };
uint8_t retries{ 0u };
};

NackInfo:每个需要通过nack通知对方进行重传的包,会先封装为一个NackInfo结构体,记录该包的序列号、发送时间、重传次数

这些重传包会保存到nackList中

std::map<uint16_t, NackInfo, RTC::SeqManager<uint16_t>::SeqLowerThan> nackList;
mediasoup会启动一个定时器,每隔一个rtt时间,从nackList中获取需要进行重传的包通知对方,所有需要重传的包信息都封装为上文提到的BLP中。

关键帧列表

std::set<uint16_t, RTC::SeqManager<uint16_t>::SeqLowerThan> keyFrameList;

会记录组成关键帧的所有包,当nackList中缓存的包数过多或者缓存时间过长时,需要清除一些旧数据。每次清除时需要判断被清除的包是否是关键帧包,如果不是就清除,判断方式就是对比nackList和keyFrameList中序号是否相等,小于关键帧包序号的可以删除。

具体的源码如下:

 // 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();
    //收到第一个包,判断是否开始,如果没有开始,started置为true,表示开始
  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))
  {
    //首先判断nackList中是否有该包,如果有的话说明该包是nack之后的重传包,把它从
    //nackList中删除
   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");

    this->nackList.erase(it);

    return true;
   }

   // Out of order packet or already handled NACKed packet.
   if (!isRecovered)
   {
    MS_WARN_DEV(
      "ignoring older packet not present in the NACK list [ssrc:%" PRIu32 ", seq:%" PRIu16 "]",
      packet->GetSsrc(),
      packet->GetSequenceNumber());
   }

   return false;
  }

  // If we are here it means that we may have lost some packets so seq is
  // newer than the latest seq seen.
    如果到这里说明上个包到当前包之间有丢包,需要进行nack处理
    首先判断该包是否是关键帧包
  if (isKeyFrame)
   this->keyFrameList.insert(seq);
    判断是否超过了关键帧最大缓存列表,超过的话,就需要清除旧的关键帧
  // Remove old keyframes.
  {
   auto it = this->keyFrameList.lower_bound(seq - MaxPacketAge);

   if (it != this->keyFrameList.begin())
    this->keyFrameList.erase(this->keyFrameList.begin(), it);
  }
    重传的恢复包
  if (isRecovered)
  {
   this->recoveredList.insert(seq);

   // Remove old ones so we don't accumulate recovered packets.
   auto it = this->recoveredList.lower_bound(seq - MaxPacketAge);

   if (it != this->recoveredList.begin())
    this->recoveredList.erase(this->recoveredList.begin(), it);

   // Do not let a packet pass if it's newer than last seen seq and came via
   // RTX.
   return false;
  }
    上个包lastSeq到当前seq之间出现丢包,把这些丢弃的包放到nackList中
  AddPacketsToNackList(this->lastSeq + 1, seq);

  this->lastSeq = seq;
    判断需要进行nack的包是否超过了最大重试次数,如果超过了就不再进行重传。(默认尝试10次)
  // Check if there are any nacks that are waiting for this seq number.
  std::vector<uint16_t> nackBatch = GetNackBatch(NackFilter::SEQ);
    //封装nack,取16个包到BLP中
  if (!nackBatch.empty())
   this->listener->OnNackGeneratorNackRequired(nackBatch);

  // This is important. Otherwise the running timer (filter:TIME) would be
  // interrupted and NACKs would never been sent more than once for each seq.
    //启动定时器,一个rtt发送一次
  if (!this->timer->IsActive())
   MayRunTimer();
  return false;
 }

详细内容大家可以看mediasoup中的NackGenerator.cpp文件。

作者:音视频之路

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论