丢包检测与重传作为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"状态的包的到达时间信息。 [crayon-69c6a40501666264933952/] 检测原理 前面我们简单回顾了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以及到达时间。 [crayon-69c6a4050166f715636910/] 发送端 发送端处理代码位于RtpVideoSender::OnPacketFeedbackVector中。主要处理代码如下: [crayon-69c6a40501673338288839/] 通过SSRC找到对应的RTPSender,传入RTP序列号完成重传。 可能引起的问题 由于WebRTC中存在这种机制,那么我们自己编写接收端代码时需要注意一些事(一般是服务端),否则会引起问题。 举个例子,客户端上行推Simulcast流。大流B表示,小流S表示,后面跟着传输序列号。某次传输情况如下: [crayon-69c6a40501676929307488/] 假如我们服务端是单独处理大小流,构造大小流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本身在不断演进,我们服务端开发的得时刻关注,做好兼容处理。