剑痴乎

剑痴乎
代码为剑,如痴如醉
  1. 首页
  2. WebRTC
  3. 正文

WebRTC研究:基于Transport Feedback的早期丢包检测

2022年2月17日 1920点热度 16人点赞 0条评论

丢包检测与重传作为WebRTC中一个抗弱网机制,对提高QOS起了很大作用。WebRTC中目前主要靠NACK机制实现丢包检测与重传,接收端判断丢包情况并记录,然后通过NACK RTCP报文(RFC 4585)反馈丢包信息给发送端,发送端根据NACK报文信息进行包的重传。

NACK机制针对的是某个SSRC的单独流,对于某个SSRC流,对于一个包N,接收端要检测它是否丢了,需要前面收到的该流的包N-1,以及后面收到该流的包N+1的信息,通过检测N-1以及N+1间是否有空隙判断N是否丢了。如果因为一些特殊原因,包N+1到晚了些,那么我们就无法判断N是否丢失了,假如N真丢了,会造成包N的恢复时间变长。如果该流所在传输通道,还有其它SSRC流,那么可否借助其它流信息更早检测丢包?

目前WebRTC默认都是采用基于发送端的带宽估计(Send-side BWE),通过报头扩展携带的传输序列号Transport sequence number以及Transport Feedback RTCP报文携带的信息进行相关处理。对于单个传输通道,包含的所有流,携带的传输序列号采用统一的计数。Transport Feedback RTCP中基于传输序列号记录着包的详细信息,例如是否收到,到达时间等。既然Transport Feedback RTCP记录着接收端收到的所有流的包信息,那么就可以利用这个进行丢包检测。这样对于某个SSRC流,要检测丢包,就可以借助其他SSRC流的传输序列号了。对于单个传输通道多条流场景,例如单PC或者Simulcast,相比NACK机制,能够更早地检测到丢包情况。在WebRTC这个机制叫做早期丢包检测(Early loss detection)。

与NACK机制差异

我们通过某个例子解释下早期丢包检测机制为什么会快些。假设某个传输通道下有视频流A与B,它们的RTP包记为Ra(n,m),Rb(n,m),n表示RTP sequence number,m表示Transport sequence number,这样同一个传输通道(PeerConnection)下,视频流按如下形式传输:
Ra(1,1),Ra(2,2),Rb(1,3),Rb(2,4),Ra(3,5),Ra(4,6),Rb(3,7),Rb(4,8)
假设只有Ra(2,2)丢了,在NACK机制中,只能处理单个SSRC的流,通过RTP序列号判断丢包。需要收到Ra(3,5)或者更靠后的Ra包,才能判断Ra(2,2)丢失。而在早期丢包检测机制中,可以直接通过Rb(1,3)的传输序列号判断Ra(2,2)丢失,所以能更早地检测到。

这里我们总结下跟NACK机制的差异:
1)NACK机制处理的是某个SSRC流,通过报头携带的RTP序列号判断
2)Early loss detection机制处理的是某个传输通道所有流,通过报头扩展携带的传输序列号判断。

接下来我们详细介绍下Early loss detection机制。

Transport Feedback RTCP

首先我们简单复习下Transport Feedback RTCP报文格式。Transport Feedback RTCP中记录着一系列包的到达状态以及时间。

记录的第一个RTP包传输序列号Transport sequence number为base sequence number,总共记录着packet status count个RTP包信息,这些RTP包Transport sequence number以base sequence number为基准,并依次递增。

通过packet chunk记录各个RTP包的到达状态,主要有:packet received,packet not received这两种状态。

以reference time为基准,通过recv delta记录各个"packet received"状态的包的到达时间信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        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=15 |    PT=205     |           length              |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                     SSRC of packet sender                     |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                      SSRC of media source                     |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |      base sequence number     |      packet status count      |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                 reference time                | fb pkt. count |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |          packet chunk         |         packet chunk          |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       .                                                               .
       .                                                               .
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |         packet chunk          |  recv delta   |  recv delta   |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       .                                                               .
       .                                                               .
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |           recv delta          |  recv delta   | zero padding  |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

检测原理

前面我们简单回顾了Transport Feedback RTCP的格式,那么通过这个RTCP检测丢包就很简单了。"packet not received"状态的包就是我们所认为的丢包。只要我们解析Transport Feedback RTCP,获取这些"packet not received"状态的包,然后通过这些RTP包的Transport sequence number,从发送端记录的RTP包发送历史中获取对应RTP包的SSRC以及RTP sequence number,告知相应发送模块,即可实现重传。

接收端

接收端主要处理代码位于RemoteEstimatorProxy::MaybeBuildFeedbackPacket中,packet_arrival_times_记录着收到的RTP包的Transport Sequence Number以及到达时间。

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  for (int64_t seq = start_seq; seq < end_seq; ++seq) {
    int64_t arrival_time_ms = packet_arrival_times_.get(seq);
    if (arrival_time_ms == 0) {
      continue;
    }
 
    if (feedback_packet == nullptr) {
      feedback_packet =
          std::make_unique<rtcp::TransportFeedback>(include_timestamps);
      feedback_packet->SetMediaSsrc(media_ssrc_);
      feedback_packet->SetBase(
          static_cast<uint16_t>(begin_sequence_number_inclusive & 0xFFFF),
          arrival_time_ms * 1000);
      feedback_packet->SetFeedbackSequenceNumber(feedback_packet_count_++);
    }
    // 添加收到的包的传输序列号以及到达时间
    if (!feedback_packet->AddReceivedPacket(static_cast<uint16_t>(seq & 0xFFFF),
                                            arrival_time_ms * 1000)) {
      break;
    }
  }

发送端

发送端处理代码位于RtpVideoSender::OnPacketFeedbackVector中。主要处理代码如下:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  // Map from SSRC to vector of RTP sequence numbers that are indicated as
  // lost by feedback, without being trailed by any received packets.
  std::map<uint32_t, std::vector<uint16_t>> early_loss_detected_per_ssrc;
 
  for (const StreamPacketInfo& packet : packet_feedback_vector) {
    // Only include new media packets, not retransmissions/padding/fec.
    if (!packet.received && packet.ssrc && !packet.is_retransmission) {
      early_loss_detected_per_ssrc[*packet.ssrc].push_back(
          packet.rtp_sequence_number);
    } else {
      // Packet received, so any loss prior to this is already detectable.
      early_loss_detected_per_ssrc.erase(*packet.ssrc);
    }
  }
 
  for (const auto& kv : early_loss_detected_per_ssrc) {
    const uint32_t ssrc = kv.first;
    auto it = ssrc_to_rtp_module_.find(ssrc);
    RTC_CHECK(it != ssrc_to_rtp_module_.end());
    RTPSender* rtp_sender = it->second->RtpSender();
    for (uint16_t sequence_number : kv.second) {
      rtp_sender->ReSendPacket(sequence_number);
    }
  }

通过SSRC找到对应的RTPSender,传入RTP序列号完成重传。

可能引起的问题

由于WebRTC中存在这种机制,那么我们自己编写接收端代码时需要注意一些事(一般是服务端),否则会引起问题。

举个例子,客户端上行推Simulcast流。大流B表示,小流S表示,后面跟着传输序列号。某次传输情况如下:

1
B1 B2 B3 B4 S5 S6 B7 B8 B9 B10 S11 S12...

假如我们服务端是单独处理大小流,构造大小流Transport Feecback RTCP报文也是单独处理,那么就会有个问题。

对大流的处理模块,收到的流为B1 B2 B3 B4 B7 B8 B9 B10,构造的Transport Feecback报文中记录的收到的传输序列号为:1 2 3 4 7 8 9 10,其中5,6由于属于小流导致记录为没收到。所以在发送端处理该Transport Feedback RTCP报文时,会将传输序列号5,6所属的RTP包重传,直到收到小流反馈的Transport Feecback报文。这样就会导致每收到反馈的Transport Feedback RTCP,就会重传RTP包,虽然并没有丢包。如果通过浏览器推流,那么在chrome://webrtc-internals/中会看到:

其中retransmittedPacketsSent一直在增加。

不过对于接收端而言,这些重传的包会被SRTP处理过滤掉,在libSRTP库中会报srtp_err_status_replay_fail错误,这是因为libSRTP的一个安全措施。libSRTP中有个窗口,记录着收到的包,如果后面收到的包在这个窗口内,会被认为是重复的包,从而报srtp_err_status_replay_fail错误,然后被丢弃。

所以编写服务端代码时,同一个传输通道的流,无论单PC,还是Simulcast场景,Transport Feecback RTCP构造得统一处理,这样就可以利用到早期丢包检测机制优势,同时避免增加不必要的带宽消耗。

总结

本文主要介绍了NACK机制与早期丢包检测机制差异,回顾了Transport Feedback RTCP报文格式,并结合代码简单介绍了早期丢包检测机制,最后举例讲了下这个机制可能引起的问题。WebRTC本身在不断演进,我们服务端开发的得时刻关注,做好兼容处理。

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
标签: WebRTC
最后更新:2022年3月27日

Jeff

管理员——代码为剑,如痴如醉

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理。

版权声明

为支持原创,创作更好的文章,未经许可,禁止任何形式的转载与抄袭,如需转载请邮件私信!本人保留所有法定权利。违者必究!

文章目录
  • 与NACK机制差异
  • Transport Feedback RTCP
  • 检测原理
  • 接收端
  • 发送端
  • 可能引起的问题
  • 总结
最近评论
ztt 发布于 3 周前(04月05日) 你好,想看里面的视频和图片为什么没有显示呢?需要下flash吗还是什么。
huowa222 发布于 1 个月前(03月26日) 同问
邱国禄 发布于 2 个月前(02月17日) Receive Delta以0.25ms为单位,reference time以64ms为单位,kDe...
啊非 发布于 4 个月前(12月30日) 大神,请教一个问题: constexpr int kBaseScaleFactor = Tran...
啊非 发布于 4 个月前(12月30日) reference time:3字节,表示参考时间,以64ms为单位,但是 代码里面是 Trans...
相关文章
  • WebRTC资讯:H265支持进展
  • WebRTC研究:Audio level
  • Mac平台WebRTC编译
  • WebRTC研究:RTP时间戳的产生
  • WebRTC研究:统计参数之丢包率

COPYRIGHT © 2024 jianchihu.net. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang