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丢包重传机制的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/weixin_38102771/article/details/122030961
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/415325

相关文章

SpringRetry重试机制之@Retryable注解与重试策略详解

《SpringRetry重试机制之@Retryable注解与重试策略详解》本文将详细介绍SpringRetry的重试机制,特别是@Retryable注解的使用及各种重试策略的配置,帮助开发者构建更加健... 目录引言一、SpringRetry基础知识二、启用SpringRetry三、@Retryable注解

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

OpenCV图像形态学的实现

《OpenCV图像形态学的实现》本文主要介绍了OpenCV图像形态学的实现,包括腐蚀、膨胀、开运算、闭运算、梯度运算、顶帽运算和黑帽运算,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起... 目录一、图像形态学简介二、腐蚀(Erosion)1. 原理2. OpenCV 实现三、膨胀China编程(

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

SpringBatch数据写入实现

《SpringBatch数据写入实现》SpringBatch通过ItemWriter接口及其丰富的实现,提供了强大的数据写入能力,本文主要介绍了SpringBatch数据写入实现,具有一定的参考价值,... 目录python引言一、ItemWriter核心概念二、数据库写入实现三、文件写入实现四、多目标写入

Android Studio 配置国内镜像源的实现步骤

《AndroidStudio配置国内镜像源的实现步骤》本文主要介绍了AndroidStudio配置国内镜像源的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、修改 hosts,解决 SDK 下载失败的问题二、修改 gradle 地址,解决 gradle

SpringKafka错误处理(重试机制与死信队列)

《SpringKafka错误处理(重试机制与死信队列)》SpringKafka提供了全面的错误处理机制,通过灵活的重试策略和死信队列处理,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、Spring Kafka错误处理基础二、配置重试机制三、死信队列实现四、特定异常的处理策略五