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

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

prometheus如何使用pushgateway监控网路丢包

《prometheus如何使用pushgateway监控网路丢包》:本文主要介绍prometheus如何使用pushgateway监控网路丢包问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录监控网路丢包脚本数据图表总结监控网路丢包脚本[root@gtcq-gt-monitor-prome

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过