mediasoup丢包重传机制的实现

2023-11-23 04:21

本文主要是介绍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。

这篇关于mediasoup丢包重传机制的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/415325

相关文章

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、