WebRTC服务质量(08)- 重传机制(05) RTX机制

WebRTC服务质量(01)- Qos概述
WebRTC服务质量(02)- RTP协议
WebRTC服务质量(03)- RTCP协议
WebRTC服务质量(04)- 重传机制(01) RTX NACK概述
WebRTC服务质量(05)- 重传机制(02) NACK判断丢包
WebRTC服务质量(06)- 重传机制(03) NACK找到真正的丢包
WebRTC服务质量(07)- 重传机制(04) 接收NACK消息
WebRTC服务质量(08)- 重传机制(05) RTX机制
WebRTC服务质量(09)- Pacer机制(01) 流程概述
WebRTC服务质量(10)- Pacer机制(02) RoundRobinPacketQueue
WebRTC服务质量(11)- Pacer机制(03) IntervalBudget
WebRTC服务质量(12)- Pacer机制(04) 向Pacer中插入数据

一、前言:

RTX协议(Retransmission,即重传协议)是 WebRTC 中用于处理丢包恢复的一部分。由于网络通信中的丢包不可避免,WebRTC RTP协议栈支持多种丢包恢复机制,其中之一便是通过RTX协议实现的重传机制

RTX协议会根据 RTP 丢包检测机制(具体由 RTCP 的 NACK 包触发),将数据包重传给接收端。RTX 的核心思想是发送端将丢失的数据包重新封装并发送,而接收端根据识别重新封装的数据包进行补偿处理,从而还原完整的流媒体内容。

二、RTX协议的工作原理:

RTX 的工作紧密依赖于 RTP 和 RTCP 协议。以下是 RTX 的通信流程:

  1. 发送端发送数据包: 发送端按照 RTP 协议发送音视频流,每个 RTP 数据包都有唯一的序列号(sequence number),用于接收端检测是否丢包。
  2. 接收端检测丢包: 接收端通过接收 RTP 包的序列号,以及 RTP/RTCP 的统计信息,检测数据是否丢失。一旦丢包,接收端会发送 RTCP NACK(Negative Acknowledgment)反馈包给发送端,并指定丢失数据包的序列号。
  3. 发送端重新发送丢失的数据包: 发送端根据接收到的 RTCP NACK 请求,使用 RTX 流重传对应的丢失数据包。RTX 流不同于主流(primary stream),通常会有独立的 SSRC(Synchronization Source Identifier)用于区分两者。
  4. 接收端合并重传包: 接收端接收到 RTX 包后,根据 RTX 的特殊封装格式提取出原始 Payload(数据载荷)并通过序列号将其合并到主流中。

RTX 的核心点在于:

  • 独立的 SSRC:RTX 使用独立的 SSRC,和主流区分开,这样可以识别重传流。
  • 修改过的 RTP 包头:RTX 包在 RTP 层修改了一些元信息,用于兼容重传流和主流的处理。
  • RTX包有自己的payload type
  • RTX包是按照自己的Sequence Number进行排序的。

三、如何找到RTX包:

  • 找到Offer/Answer这种SDP信息。
  • 从SDP中找到RTX的SSRC。
  • 然后抓包,根据SSRC过滤出RTX包。

四、RTX协议格式:

在这里插入图片描述

  1. RTP Header:和前面介绍协议时候的RTP Header是一样的;
  2. OSN:重传的是哪个原始数据的序列包,注意我们RTX属于RTP协议,这儿的Original指的是RTP包;
  3. 可以看出RTX就是普通的RTP协议加了2字节的OSN,注意不是RTCP;

五、抓取RTX包:

5.1、获取本机的RTX流:

在这里插入图片描述

看看RTX的seq:

在这里插入图片描述

seq独立可以更好的统计出丢包。

5.2、看看原始流哪些包丢失了:

在这里插入图片描述

BLP是0x0001。知道BLP是什么吗?

1)NACK PID和NACK BLP:

1)概念:

在 NACK 中,NACK PID 和 NACK BLP 是两个关键的字段,用于标识和管理需要重传的数据包。

  • NACK PID:NACK PID(Packet ID)是 NACK 报文中的一个字段,用于指示需要重传的丢失数据包的序列号。当接收端检测到数据包丢失时,会发送 NACK 报文,其中 NACK PID 指示了具体丢失的数据包的序列号。
  • NACK BLP:NACK BLP(Bitmask of Lost Packets)是 NACK 报文中的另一个字段,用于指示一连串连续丢失的数据包。NACK BLP 是一个比特掩码,每个比特位对应一个数据包序列号,用于表示一段连续的丢失数据包范围。

2)举个例子:

假设在一个 WebRTC 实时通信会话中,发送端发送了一系列 RTP 数据包给接收端,序列号分别为 10、11、12、13、14、15。接收端在接收过程中发现数据包 11 和 13 丢失了。接收端会发送 NACK 报文给发送端,请求重传这两个丢失的数据包。

在这个例子中,NACK 报文中的 NACK PID 和 NACK BLP 可能会被设置如下:

  • NACK PID:11, 13
    • NACK PID 指示需要重传的具体丢失数据包的序列号,即数据包 11 和数据包 13。
  • NACK BLP:010010
    • 在这个二进制掩码中,每个比特位对应一个数据包序列号。接收端标记了丢失的数据包范围,从 10 到 15 中的第 2 和第 4 个数据包丢失。
    • 这表示数据包 11 和数据包 13 丢失,而其他数据包正常接收。

3)本文丢包情况:

NACK 报文中 NACK PID 的值为 6806,而 NACK BLP 的值为 0x0001,可以解释如下:

  • NACK PID:6806
    • NACK PID 表示需要重传的具体丢失数据包的序号,即数据包序号为 6806 的数据包需要进行重传。
  • NACK BLP:0x0001
    • NACK BLP 是一个十六进制数,转换为二进制为 0000 0000 0000 0001。
    • 在这个二进制掩码中,每个比特位对应一个数据包序号。由于只有一个比特位为 1,表示只有一个数据包丢失。
    • 在这种情况下,第一个(最低位)的 1 表示序号为 6806 的数据包丢失,其它数据包接收正常。

5.3、使用RTX进行重传:

如果你在抓包软件中找不到RTX包,记得使用时间戳来找。

在这里插入图片描述

因此发送的RTX一定在这个点之后;

在这里插入图片描述

  1. 可以看出872691小于收到NACK的时间点876269,肯定不是。
  2. 881999大于收到NACK的时间点,可能是。
  3. 通过前面协议我们知道RTX的前2个字节是OSN,表示原始数据的序列号,因此,我们将0x1a96转换为十进制看是多少;

在这里插入图片描述

看到没有,重传的额就是6806这个数据包。

六、发送RTX的过程:

我们得先看下NACK接收流程:

在这里插入图片描述

会来到这里:

void RTPSender::OnReceivedNack(
    const std::vector<uint16_t>& nack_sequence_numbers,
    int64_t avg_rtt) {
  packet_history_->SetRtt(5 + avg_rtt);
  // 遍历nack中每个需要重传的seq,进行重传
  for (uint16_t seq_no : nack_sequence_numbers) {
    // 发送RTX
    const int32_t bytes_sent = ReSendPacket(seq_no);
    if (bytes_sent < 0) {
      // Failed to send one Sequence number. Give up the rest in this nack.
      RTC_LOG(LS_WARNING) << "Failed resending RTP packet " << seq_no
                          << ", Discard rest of packets.";
      break;
    }
  }
}

看看具体怎么重传的:

int32_t RTPSender::ReSendPacket(uint16_t packet_id) {
  absl::optional<RtpPacketHistory::PacketState> stored_packet =
      packet_history_->GetPacketState(packet_id);
  if (!stored_packet || stored_packet->pending_transmission) {
    return 0;
  }

  const int32_t packet_size = static_cast<int32_t>(stored_packet->packet_size);
  const bool rtx = (RtxStatus() & kRtxRetransmitted) > 0;
  std::unique_ptr<RtpPacketToSend> packet =
      packet_history_->GetPacketAndMarkAsPending(
          packet_id, [&](const RtpPacketToSend& stored_packet) {
            std::unique_ptr<RtpPacketToSend> retransmit_packet;
            if (retransmission_rate_limiter_ &&
                !retransmission_rate_limiter_->TryUseRate(packet_size)) {
              return retransmit_packet;
            }
            if (rtx) {
              retransmit_packet = BuildRtxPacket(stored_packet);
            } else {
              retransmit_packet =
                  std::make_unique<RtpPacketToSend>(stored_packet);
            }
            if (retransmit_packet) {
              retransmit_packet->set_retransmitted_sequence_number(
                  stored_packet.SequenceNumber());
            }
            return retransmit_packet;
          });
  if (!packet) {
    return -1;
  }

  packet->set_packet_type(RtpPacketMediaType::kRetransmission);
  packet->set_fec_protect_packet(false);

  std::vector<std::unique_ptr<RtpPacketToSend>> packets;
  packets.emplace_back(std::move(packet));

  paced_sender_->EnqueuePackets(std::move(packets));

  return packet_size;
}

我们根据代码逻辑发现,并不一定要使用RTX:

  1. 如果启用了 RTX(Retransmission),则构造并发送独立的 RTX 包。
  2. 如果没有启用 RTX,则直接从包历史记录中取出丢失的原始 RTP 包并重传。

代码比较负责,关键部分展开说明下:

1)构造重传包:

  • 匿名函数:

    std::unique_ptr<RtpPacketToSend> packet =
        packet_history_->GetPacketAndMarkAsPending(
            packet_id, [&](const RtpPacketToSend& stored_packet) {
                std::unique_ptr<RtpPacketToSend> retransmit_packet;
    
                // 检查重传速率限制
                if (retransmission_rate_limiter_ &&
                    !retransmission_rate_limiter_->TryUseRate(packet_size)) {
                    return retransmit_packet;
                }
    
                if (rtx) {
                    // 如果支持 RTX,构造一个 RTX 包
                    retransmit_packet = BuildRtxPacket(stored_packet);
                } else {
                    // 否则直接使用原始 RTP 包
                    retransmit_packet =
                        std::make_unique<RtpPacketToSend>(stored_packet);
                }
    
                if (retransmit_packet) {
                    retransmit_packet->set_retransmitted_sequence_number(
                        stored_packet.SequenceNumber());
                }
    
                return retransmit_packet;
            });
    
    

    这段代码的核心是获取丢失的历史包并将其打包为重传包:

    1. 调用 packet_history_->GetPacketAndMarkAsPending
      • 从历史记录中取出丢失的 RTP 包,并标记该包已进入待发送状态(防止重复重传)。
    2. 使用匿名函数对包进行加工处理
      • 重传速率限制:
        • 如果当前重传速率超出了允许的带宽,则直接丢弃该重传包,不做进一步处理。
      • RTX 构造:
        • 如果启用了 RTX,通过调用 BuildRtxPacket 方法,将原始包封装为 RTX 包。
        • 关键点:RTX 包的特殊结构包含原始包的 OSN(Original Sequence Number),用于表示其原始 RTP 包的序列号。
      • 普通重传包:
        • 如果没有启用 RTX,则直接复制原始 RTP 包来重发。
      • 设置序列号:
        • 调用 set_retransmitted_sequence_number,设置重传包所对应的原始序列号。

2)重传包的最终处理:

if (!packet) {
    return -1;
}

packet->set_packet_type(RtpPacketMediaType::kRetransmission);
packet->set_fec_protect_packet(false);
std::vector<std::unique_ptr<RtpPacketToSend>> packets;
packets.emplace_back(std::move(packet));

// 将重传包加入到 paced sender 的发送队列
paced_sender_->EnqueuePackets(std::move(packets));

  1. 检查是否成功获取重传包:
    • 如果没有生成有效的重传包(可能被带宽限制丢弃),返回 -1
  2. 设置包类型:
    • 通过 set_packet_type 设置为 kRetransmission,标明这是一个重传包。
    • set_fec_protect_packet(false):通知不对重传包应用 FEC(前向纠错)保护,因为 RTX 本身是另一个冗余机制。
  3. 插入发送队列:
    • 将重传包添加到 paced_sender_(节奏发送器)模块中,以有节奏地发送包,避免瞬时大量重传耗尽带宽。

3)小结:

  1. RTX 重传的实现流程
    • RTX 重传包通过函数 BuildRtxPacket 生成。
    • RTX 中包含一个特殊的字段 OSN,用于恢复原始包的序列号。
    • 通过独立的 SSRC 和 Payload Type 区分 RTX 包与普通主流包。
  2. 带宽管理和速率控制
    • retransmission_rate_limiter_ 限制了重传包的发送速率,防止网络因重传过载。
  3. 灵活处理重传策略
    • 支持两种模式:RTX 和普通 RTP 重传。
    • RTX 是更成熟的丢包恢复机制,但需要双方支持;否则退回到简单的 RTP 重传方式。

七、总结:

RTPSender::ReSendPacket 函数是 WebRTC 中 RTP 协议层实现丢包恢复的重要部分。它利用 RtpPacketHistory 记录和 RTX 协议实现高效的重传机制,同时考虑到带宽管理、速率限制和两种重传策略(RTX 和普通 RTP)的兼容性。在具体代码实现中,它通过灵活的封装和严格的速率控制,保证了无损恢复的同时避免了网络拥塞情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/942850.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kibana8.17.0在mac上的安装

1、Kibana是什么 Kibana是与elasticsearch配套使用的数据分析与可视化工具&#xff0c;通过Kibana可以轻松与es中存储的数据进行高效的交互&#xff0c;包括数据写入、检索、删除等操作&#xff0c;并可以通过编写部分代码将数据做成各种报表&#xff0c;从而进行非常直观的统…

【Linux】:线程安全 + 死锁问题

&#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 1. 线程安全和重入问题&…

AI应用-本地模型实现AI生成PPT(简易版)

文章目录 前言技术栈效果展示 一、实现思路二、实现步骤1.本地安装marp-cli2.后端实现3.前端实现 三、代码地址及说明 前言 在许多项目中&#xff0c;生成 PPT 是常见的需求&#xff0c;尤其在教育和报告展示中。传统的生成 PPT 的方法需要手动创建&#xff0c;而使用生成模型…

Linux 服务管理 service systemd systemctl

Linux 服务管理 service systemd systemctl 1 介绍1.1 概述1.2 发展历程1.3 特点1.4 linux 服务管理 发展历程Ubuntu 14.04及更早版本&#xff1a;使用upstart作为默认的init系统旧版本新版本 Ubuntu 15.04到16.04版本&#xff1a;默认使用systemd作为init系统&#xff0c;但仍…

vue 基础学习

一、ref 和reactive 区别 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><div id"app"><h1>{{Web.title}}</h1><h1&…

光谱相机在农业中的具体应用案例

作物生长监测与产量预测 美国爱荷华州玉米种植园&#xff1a;农场主使用无人机搭载高光谱相机&#xff0c;定期对玉米田进行拍摄。通过分析光谱数据&#xff0c;获取玉米的叶面积指数、叶绿素含量等生长参数。在玉米生长关键期&#xff0c;依据这些参数及时调整施肥和灌溉方案…

LSTM-SVM时序预测 | Matlab基于LSTM-SVM基于长短期记忆神经网络-支持向量机时间序列预测

LSTM-SVM时序预测 | Matlab基于LSTM-SVM基于长短期记忆神经网络-支持向量机时间序列预测 目录 LSTM-SVM时序预测 | Matlab基于LSTM-SVM基于长短期记忆神经网络-支持向量机时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.LSTM-SVM时序预测 | Matlab基于LSTM…

05.HTTPS的实现原理-HTTPS的握手流程(TLS1.2)

05.HTTPS的实现原理-HTTPS的握手流程&#xff08;TLS1.2&#xff09; 简介1. TLS握手过程概述2. TLS握手过程细化3. 主密钥&#xff08;对称密钥&#xff09;生成过程4. 密码规范变更 简介 主要讲述了混合加密流程完成后&#xff0c;客户端和服务器如何共同获得相同的对称密钥…

PostgreSQL编译安装教程

下载安装 1.在家目录创建一个文件夹放下载安装包 mkdir softwarecd software 2.下载文件压缩包 wget https://ftp.postgresql.org/pub/source/v16.0/postgresql-16.0.tar.gz 3.解压 tar -xzvf postgresql-16.0.tar.gz 4.编译 在software/postgresql-16.0下 cd software…

009-spring-bean的实例化流程

1 spring容器初始化时&#xff0c;将xml配置的bean 信息封装在 beandefinition对象 2 所有的beandefinition存储在 beandefinitionMap的map集合中 3 spring对map进行遍历&#xff0c;使用反射创建bean实例对象 4 创建好的bean存在名为singletonObjects的map集合中 5 调用ge…

定位方式:css

使用相对路径 div ul #div下的所有ul&#xff0c;空格表示相对路径&#xff08;这个实际中用的多一些&#xff09; 绝对路径-一般不用绝对路径 html>head>div&#xff0c;“>”表示根路径 使用class名称定位 使用.表示 使用id定位 使用#表示 使用属性定位 [属性名…

基于深度学习(HyperLPR3框架)的中文车牌识别系统-搭建开发环境

本篇内容为搭建开发环境。包括&#xff1a;python开发环境&#xff0c;Qt/C开发环境&#xff0c;以及用到的各个库的安装和配置。 一、Python开发环境搭建与配置 1、下载并安装Anaconda 我没有用最新的版本&#xff0c;安装的是 Anaconda3-2021.05-Windows-x86_64.exe&#…

kamailio.cfg 图形化

kamailio.cfg 文件随着业务增加&#xff0c;会越来越大&#xff0c;越来越复杂&#xff0c;很难看出整体结构来。 有没有什么工具看出整体轮廓呢?有。主要依靠下面的python文件&#xff1a; python脚本 调用命令如下&#xff1a; python3 route_graph.py kamailio.cfg 执…

【Linux】进程控制,手搓简洁版shell

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;Linux 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 1、进程创建2、进程终止3、进程等待4、进程程序替换5、手写简洁版shell 1、进程创建 fork函数&#xff1a;从已经存在的进程中创…

rabbitmq相关使用

使用rabbitmq实现异步解耦 使用步骤&#xff1a; 1、pom依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency> 2、yml配置文件 spring:rabbitmq:host: 12…

单机和微服务的区别,微服务有什么问题?数据一致性问题怎么解决?幂等问题怎么解决?

单机和微服务的区别&#xff0c;微服务有什么问题&#xff1f;数据一致性问题怎么解决&#xff1f;幂等问题怎么解决&#xff1f; 单机架构和微服务架构在设计理念、部署和扩展性上有显著区别。 单机架构 vs 微服务架构 单机架构 定义&#xff1a;所有组件&#xff08;前端…

VUE3+django接口自动化部署平台部署说明文档(使用说明,需要私信)

网址连接&#xff1a;http://118.25.110.213:5200/#/login 账号/密码&#xff1a;renxiaoyong 1、VUE3部署本地。 1.1本地安装部署node.js 1.2安装vue脚手架 npm install -g vue/cli # 或者 yarn global add vue/cli1.3创建本地项目 vue create my-vue-project1.4安装依赖和插…

overleaf中出现TeX capacity exceeded PDF object stream buffer=5000000的原因和解决方案

在插入pdf 配图后&#xff0c;编译出错提示信息如图&#xff0c;很可能的一个原因是pdf文件大小太大了&#xff0c;最好压缩一下&#xff0c;压缩到1MB以内。

Golang的并发编程问题解决

Golang的并发编程问题解决 第一部分&#xff1a;Golang并发编程基础 并发与并行 在计算机领域&#xff0c;经常会听到并发和并行这两个词语。简单来说&#xff0c;并发是指在单处理器上通过时间片轮转实现多任务同时执行&#xff0c;而并行则是指多个任务在多个处理器上同时执行…

【玩转MacBook】Maven安装

下载Maven 官网&#xff1a; https://maven.apache.org/download.cgi 下载 Zip 类型的压缩包&#xff1a; 配置环境变量 以管理员身份编辑配置文件。注意&#xff0c;由于 MacBook 上使用了 zsh 命令行&#xff0c;所以需要编辑~/.zshrc文件而不是~/.bash_profile文件&am…