mediasoup丢包重传机制的实现

目录

一. 前言

二. 丢包重传的工作机制

三. NACK报文

四. RTX报文

五. mediasoup server处理丢包重传的源码剖析

1. 判断包的前后关系

2. 判断丢包产生NACK报文的逻辑

3. 接收处理NACK报文并重传RTX的逻辑


一. 前言

        在音视频通话中,我们常常使用 RTP 协议包荷载音视频数据,底层经常使用 UDP 作为其传输层协议,而 UDP 并不保证数据可靠性,因此当 RTP 产生丢包时,接收端需要告知源端哪些序号的包认为丢失了,请重传一遍过来。

二. 丢包重传的工作机制

         如上所示,A 与 B 进行媒体协商时会协商媒体的 payload type 以及如果使用重传时使用哪个  payload type 的包作为该 payload 的重传包,上图中 payload type=96 表示 VP8 的 RTP 包,rtcp-fb:96 表示使用 nack,payload type 97 作为 payload type 96 的重传包。

        A 发送 RTP 报文给 B,B 接收过程中判断到包有丢失时会生成 NACK 报文给 A,告诉 A 哪些序号的包丢失了需要重传,A 收到之后 NACK 报文后从发送历史队列中取出丢失序号的包,打成 RTX 包重传给 B。

三. NACK报文

        在这篇文章我们介绍了 RTCP 协议,它经常跟 RTP 协议配合使用以改善传输质量,RTCP 协议的 RTPFB 报文是用于传输层质量反馈的。

        RTPFB 称为传输层反馈报文,当 PT=205,FMT=1 时表示该协议包为丢包反馈报文,又称 NACK 报文。SSRC 表示源端发送者的 SSRC,RTPFB 协议包 FMT 不同时 FCI 包含的具体字段也不同, 对于 NACK 反馈包 FCI 包含两个字段(PID,BLP),具体参考 RFC4585

PID(Packet Identifier):发生丢失的 RTP 包序号

BLP(Bitmap of Lost Packets):从 PID 开始接下来 16 个 RTP 数据包的丢失情况,一个 NACK 报文可以携带多个连续 RTP 包丢失的信息

例如当 PID = 100,BLP = 0b0000100010001000 时表示序号为 104,108,112 的 RTP 包丢失了。

四. RTX报文

RTX 包可以用来重传丢失的包,协议格式如上,关于 RTX 包的 RTP Header 字段,有如下需要说明的点。

1. RTX 包有自己的 SSRC,与原始 RTP 包使用的 SSRC 不同

2. RTX 包有自己的 payload type,与原始 RTP 包的 payload type 不同

3. RTX 包的 sequence number 是按自己的顺序排序的,并不是丢失包的 sequence number

OSN(Origin Sequence Number):表示对应哪个丢失序号的 RTP 包

Origin RTP Packet Payload:表示原先丢失序号的包的负载

协议更多细节可以参考 RFC4588

五. mediasoup server处理丢包重传的源码剖析

1. 判断包的前后关系

        在 RTP 协议中序列号是使用无符号 16 位表示的,它可以表示 [0, 65535],当序列号使用到最大值时又要回溯到 0 重新开始增长,而回溯后的序列号为 0 的包是在位于序列号为 65535 之后的,因此不能简单使用 <,> 进行比较,mediasoup 中 SeqManager 模板类处理该逻辑。

template<typename T>
bool SeqManager<T>::SeqLowerThan::operator()(const T lhs, const T rhs) const
{
    return ((rhs > lhs) && (rhs - lhs <= MaxValue / 2)) ||
		   ((lhs > rhs) && (lhs - rhs > MaxValue / 2));
}

template<typename T>
bool SeqManager<T>::SeqHigherThan::operator()(const T lhs, const T rhs) const
{
	return ((lhs > rhs) && (lhs - rhs <= MaxValue / 2)) ||
	       ((rhs > lhs) && (rhs - lhs > MaxValue / 2));
}

template<typename T>
const typename SeqManager<T>::SeqLowerThan SeqManager<T>::isSeqLowerThan{};

template<typename T>
const typename SeqManager<T>::SeqHigherThan SeqManager<T>::isSeqHigherThan{};

template<typename T>
bool SeqManager<T>::IsSeqLowerThan(const T lhs, const T rhs)
{
	return isSeqLowerThan(lhs, rhs);
}

template<typename T>
bool SeqManager<T>::IsSeqHigherThan(const T lhs, const T rhs)
{
	return isSeqHigherThan(lhs, rhs);
}

        对于 SeqManager<T>::IsSeqLowerThan(const T lhs, const T rhs),如果 rhs 大于 lhs 并且差值小于 T 类型所能表示的最大值的一半,或者 lhs 大于 rhs 但是差值大于 T 类型所能表示的最大值的一半,就认为 lhs 是小于 rhs 的。

        可以这样理解,lhs 小于 rhs 时,rhs-lhs 的差值需要较小才能认为 lhs 在 rhs 之前,不然当 lhs=0,rhs=65535 时显然序号已经产生了回溯,没有差值较小的约束就会误认为 lhs 序号的包是在 rhs 序号的包之前的,同理如果 lhs 大于 rhs,lhs-rhs 的差值需要较大才能认为 lhs 在 rhs 之前。

2. 判断丢包产生NACK报文的逻辑

        由于网络存在丢包,抖动等问题,mediasoup server 在接收 RTP 报文时并非理想地按序号递增一的方式接收,当上一次接收到的序号为 lastSeq,而本次接收到的序号 seq > lastSeq + 1 时,mediasoup server 就认为 [lastSeq+1, seq) 区间的 RTP 数据包丢失了,主动产生 NACK 报文发送回源端请求重传,如果经过发送重传请求后 [lastSeq+1, seq) 区间的 RTP 报文仍然一直没有到达服务器,mediasoup server 最多请求 10 次,而且请求间隔不会小于当前 RTT,源码分析如下。

        每次收到 RTP 报文后传入 NackGenerator 模块判断是否需要生成 NACK 报文,如果当前 RTP 报文的 seq 小于上一次接收到的 RTP 报文序号,就在 nackList 查找并删除该 seq,以免之前设置了该 seq 的重传请求后仍然一直在请求重传。

         接下来运行到 AddPacketsToNackList 将 [lastSeq+1, seq) 区间的序号加入到 nackList 中,如果新添加序号后 nackList 大小超过设定阈值 MaxNackPackets(默认配置为 1000),则清理序号较前的 RTP NACK 通知。

         接下来调用 GetNackBatch 获取需要 NACK 的序号集合 nackBatch,并调用 OnNackGeneratorNackRequired(nackBatch) 产生 RTCP-RTPFB-NACK 报文发送给源端。

        由于我们发送的 NACK 报文可能会丢失,源端不一定能知道某些序号的包需要重传,又或者即使源端收到了 NACK 请求,由于网络原因重传的 RTP 包又丢失了,因此需要定时请求 NACK。

        NackGenerator 维护了一个定时器,用于检查在 nackList 集合里的序号上一次 NACK 时间与当前是否已经超过一个 RTT 时间,如果是则放入 nackBatch 集合,等待再次发送 NACK。

        当接收端收到在 nackList 中的该乱序包或者重传恢复包后会将该 seq 从 nackList 中删除,因此如果该包恢复不会一直请求重传至最大次数。

3. 接收处理NACK报文并重传RTX的逻辑

        服务器在发送 RTP 给用户端时也存在丢包乱序等问题,如果开启了 NACK,用户在判断丢包时则会发送 NACK 报文给服务器,服务器接收到该报文后主动重传用户端需要的 RTP 包,源码分析如下。

        首先判断 RTCP 报文类型为 RTPFB NACK 报文后,调用 consumer->ReceiveNack 处理,它会调用 RtpStreamSend::ReceiveNack 处理。

        FillRetransmissionContainer 取出 NACK 报文中要求重传的 RTP 序号,首先判断发送缓存是否有该序号的包,如果服务端已经没有缓存该序号的包则不做处理,接下来如果是有缓存的情况下,判断要求重传序号的 RTP 报文与服务器收到的发送端的最后一个报文的时间差是否超过 MaxRetransmissionDelay(代码里该值为 2s),如果是则不做处理,即可以理解为不能要求重传源端已经产生超过 2s 的数据。

接下来再判断如果要求重传的 RTP 包在该 rtt 周期内已经重传过,也不做处理。

其余情况则将 RTP 包打包成 RTX 重传包然后存放到 RetransmissionContrainer 数组中,等待遍历重传 RTP 包。

RTX 重传包设置逻辑如下,主要是将 RTP 包的 payloadType 换成重传的 payloadType,ssrc 换成重传的 ssrc,序号换成重传维护的序号,然后将 RTP 包负载数据后移两字节,并在负载前两字节位置设置 OSN。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

椛茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值