WebRTC Pacer

news2025/1/15 6:58:26

目录

一. 前言

二. WebRTC Pacer

1. 数据包传入Pacer模块的队列

2. Pacer模块取出队列的包发送

(1)什么时候取出数据包发送

(2)每次发送多少数据量

(3)避免引入较大延时的处理方法


一. 前言

        实时音视频通信发送端需要一个平滑发送模块(Pacer),因为视频的关键帧比非关键帧大很多,一般一个关键帧需要打到多个 RTP 报文中,此时如果直接把所有 RTP 报文发送到网络,很容易造成网络拥塞。

        WebRTC Pacer 模块的作用就是让数据在网络上发送更加平滑,防止因为数据量的突增造成网络发生拥塞,效果如下。

        假设是只发送音频数据包,平滑发送模块作用较小,因为音频帧产生的时间间隔是固定的,而且一个音频帧编码后的数据不大,一般一个 RTP 报文可以承载。

 

二. WebRTC Pacer

1. 数据包传入Pacer模块的队列

        我们以发送视频包的函数调用栈为例说明数据包是如何被传入 Pacer 模块的。

        编码器完成帧图片编码后会回调 RtpVideoSender::OnEncodedImage 函数,然后调用 RTPSenderVideo::SendEncodedImage,再调用 RTPSenderVideo::SendVideo,之后调用 RTPSenderVideo::LogAndSendToNetwork,该函数会调用到 PacedSender::EnqueuePackets 将数据包通过 PacingController::EnqueuePacket 传入 Pacer 模块。

        不止是视频包,对于音频包,重传包,FEC 包等都要统一通过 Pacer 模块发送,不同类型报文会区分优先级。

        PacingController 中最重要的成员是 RoundRobinPacketQueue packet_queue_,packet_queue_ 是一个优先级排序的多维队列,其中重要的成员是 std::map<uint32_t, Stream> streams_,即对于相同 SSRC 的包会塞到同一个 Stream 变量中处理,Stream 结构包含了一个 PriorityPacketQueue packet_queue,它是一个按包优先级排序的队列,越优先的包越靠前,能越先发送出去。

包优先级排序规则如下:

1. 先比较包的 priority 等级,等级越小越优先

2. 如果 1 判断相等,再判断是否属于重传包,重传包比非重传包优先

3. 如果 2 判断相等,再判断包进入 Pacer 模块的顺序,例如对于视频包,越早产生的视频帧的包越早进入 Pacer 模块,同一帧的多个包,前面的包会比后面的包越早进入 Pacer 模块

        包的 priority 等级定义如下,音频包 priority 最小,优先级最高,其次是重传包,接下来是视频包和 FEC 包,最低优先级的是 Padding 包。

2. Pacer模块取出队列的包发送

        将数据包传入 Pacer 模块只需要在数据产生后塞入模块即可,但是从 Pacer 模块队列取出数据包需要考虑两个问题:什么时候取出数据包发送,每次发送的数据量大小如何确定。

(1)什么时候取出数据包发送

        PacingController::NextSendTime 和 PacingController::ProcessPackets 是 Pacer 模块控制什么时候取出,取出多少数据的核心函数。

        Pacer 有两种工作模式,一是周期模式(kPeriodic),二是动态模式(kDynamic),默认情况下使用周期模式。

        在周期模式下,每个 5ms 执行 ProcessPackets 取出报文进行发送。

(2)每次发送多少数据量

        WebRTC 预估带宽后会将预估带宽值传给 Pacer 模块,知道了预估带宽值以及当前时间与上次发送时间的差值就可以知道本次可以发送的数据量大小。例如预估带宽为 300kbps,周期是 5ms,那么可以发送的数据量大小为 300kbps * 5ms / 8 == 187.5 byte。

        WebRTC 引入了 IntervalBudget 类用于处理每个周期可以发送多少数据量的问题,set_target_rate_kbps 用于传入预估带宽,IncreseBudget 用于增加可以发送的预留空间,例如每经过 delta_time_ms 时间,就可以增加 target_rate_kbps * delta_time_ms / 8 (bytes) 大小的数据发送,UseBudget 用于每次真正发送数据包后减少对应可以发送的预留空间,当剩余可以发送的字节小于 0,表示本周期不可以再继续发送包了。

        确定了每次可以发送的数据量大小,我们再看 PacingController::ProcessPackets 是如何发送数据包的,主要代码如下,首先是为 media_budget 设置对应的目标码率以及更新可以发送的预留空间大小。

        之后通过 GetPendingPacket 从 RoundRobinPacketQueue packet_queue_ 取出报文,如果成功取到数据包,则通过 packet_sender_->SendRtpPacket(std::move(rtp_packet), pacing_info) 发送,并在 PacingController::OnPacketSent 更新已经消耗的预留空间大小,如果没有取到数据包(rtp_packet == nullptr),说明本周期已经不能再发送数据包了(可能是已经达到了本周期可以发送的最大数据量,当然也可能是队列没数据或者拥塞了)。

  DataSize data_sent = DataSize::Zero();

  // The paused state is checked in the loop since it leaves the critical
  // section allowing the paused state to be changed from other code.
  while (!paused_) {
    if (small_first_probe_packet_ && first_packet_in_probe) {
      // If first packet in probe, insert a small padding packet so we have a
      // more reliable start window for the rate estimation.
      auto padding = packet_sender_->GeneratePadding(DataSize::Bytes(1));
      // If no RTP modules sending media are registered, we may not get a
      // padding packet back.
      if (!padding.empty()) {
        // Insert with high priority so larger media packets don't preempt it.
        EnqueuePacketInternal(std::move(padding[0]), kFirstPriority);
        // We should never get more than one padding packets with a requested
        // size of 1 byte.
        RTC_DCHECK_EQ(padding.size(), 1u);
      }
      first_packet_in_probe = false;
    }

    if (mode_ == ProcessMode::kDynamic &&
        previous_process_time < target_send_time) {
      // Reduce buffer levels with amount corresponding to time between last
      // process and target send time for the next packet.
      // If the process call is late, that may be the time between the optimal
      // send times for two packets we should already have sent.
      UpdateBudgetWithElapsedTime(target_send_time - previous_process_time);
      previous_process_time = target_send_time;
    }

    // Fetch the next packet, so long as queue is not empty or budget is not
    // exhausted.
    std::unique_ptr<RtpPacketToSend> rtp_packet =
        GetPendingPacket(pacing_info, target_send_time, now);

    if (rtp_packet == nullptr) {
      // No packet available to send, check if we should send padding.
      DataSize padding_to_add = PaddingToAdd(recommended_probe_size, data_sent);
      if (padding_to_add > DataSize::Zero()) {
        std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets =
            packet_sender_->GeneratePadding(padding_to_add);
        if (padding_packets.empty()) {
          // No padding packets were generated, quite send loop.
          break;
        }
        for (auto& packet : padding_packets) {
          EnqueuePacket(std::move(packet));
        }
        // Continue loop to send the padding that was just added.
        continue;
      }

      // Can't fetch new packet and no padding to send, exit send loop.
      break;
    }

    RTC_DCHECK(rtp_packet);
    RTC_DCHECK(rtp_packet->packet_type().has_value());
    const RtpPacketMediaType packet_type = *rtp_packet->packet_type();
    DataSize packet_size = DataSize::Bytes(rtp_packet->payload_size() +
                                           rtp_packet->padding_size());

    if (include_overhead_) {
      packet_size += DataSize::Bytes(rtp_packet->headers_size()) +
                     transport_overhead_per_packet_;
    }
    packet_sender_->SendRtpPacket(std::move(rtp_packet), pacing_info);

    data_sent += packet_size;

    // Send done, update send/process time to the target send time.
    OnPacketSent(packet_type, packet_size, target_send_time);
    if (recommended_probe_size && data_sent > *recommended_probe_size)
      break;

    if (mode_ == ProcessMode::kDynamic) {
      // Update target send time in case that are more packets that we are late
      // in processing.
      Timestamp next_send_time = NextSendTime();
      if (next_send_time.IsMinusInfinity()) {
        target_send_time = now;
      } else {
        target_send_time = std::min(now, next_send_time);
      }
    }
  }

  last_process_time_ = std::max(last_process_time_, previous_process_time);

(3)避免引入较大延时的处理方法

        引入 Pacer 模块相当于缓冲区等待发送,缓冲区的引入必然会增加延时,理想情况下预估带宽传入 Pacer 模块以及编码器模块后,编码器码率会快速收敛到预估带宽值,但是如果是短时间内依然有较大的数据量也会导致 Pacer 模块队列的包等待时间越来越大,增加了端到端延时。

        WebRTC PacingController 有一个 queue_time_limit 变量(默认值为 2000ms),PacingController 会结合队列包的平均等待时间 AverageQueueTime,查看目标码率是否小于剩余包需要在 queue_time_limit-AverageQueueTime 时间内发完的码率,如果是则调高目标码率值,避免包引入较大延时。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

@MapperScan 和 @Mapper 源码走读

一.从开发中遇到的问题开始 问题描述 : 在一个springbootmybatis的项目中,在dao也就是Mapper接口上配置了Mapper注解&#xff0c;其他同事在启动类还配置了MapperScan注解&#xff08;包扫描没有配全面&#xff09;&#xff0c;进行批量指定所生成的Mapper接口动态代理接口类&…

TFT-LCD移植LVGL详细过程记录

TFT-LCD移植LVGL LVGL(轻量级和通用图形库)是一个免费和开源的图形库&#xff0c;它提供了创建嵌入式GUI所需的一切&#xff0c;具有易于使用的图形元素&#xff0c;美丽的视觉效果和低内存占用。 LVGL更多介绍&#xff1a;https://zhuanlan.zhihu.com/p/406294618 本次实验…

第六章 图论 16 AcWing 1558. 加油站

第六章 图论 16 AcWing 1558. 加油站 原题链接 AcWing 1558. 加油站 算法标签 图论 最短路 枚举 思路 枚举加油站位置&#xff0c;对于每个加油站位置进行dijkstra&#xff0c;选择符合要求1的最小距离最大值&#xff08;要求2&#xff09;与的距离和最小值&#xff08;要…

(九)Java算法:快速排序(详细图解)

目录一、前言1.1、概念1.2、算法过程二、maven依赖三、流程解析3.1、全部数据分区3.2、左边数据分区3.3、右边数据分区四、编码实现结语一、前言 1.1、概念 快速排序&#xff1a;用数组的第一个数作为基准数据&#xff0c;然后将所有比它小的数都放到它左边&#xff0c;所有比…

使用 Spring Boot 设置 Hibernate Envers

Hibernate Envers是一个实现持久实体的审核和版本控制的模块。审计和版本控制是构建生产级Spring 启动微服务的关键组件。Hibernate Envers与Spring Boot无缝集成以实现相同的目标。 在这篇文章中&#xff0c;我们将在我们的Spring Boot Starter应用程序中集成Hibernate Envers…

生信工作流框架搭建 | 02-nextflow 实战

目录生信工作流框架搭建 | 02-nextflow前情提要开始使用依赖安装核心概念一个fastqc的示例&#xff0c;加深理解快速搭建你的程序你需要仔细阅读的&#xff1a;可以快速浏览&#xff08;但需要知道大概有什么&#xff0c;以便后来查览&#xff09;&#xff1a;报错&#xff01;…

IPD-需求管理流程

一、产品需求管理模型 在确定客户需求时,要考虑影响用户购买标准的八类基本需求($APPEALS),并基于客户视角进行详细分解,形成有针对性的产品。 1.1、需求管理业务流程 二、需求收集流程 2.1、需求收集的来源 路标规划:通过市场管理流程分析,落实到路标规划中的需求…

基于Paddle的手写数字识别模型

百度飞桨(paddlepaddle)是百度的开源深度学习平台&#xff0c;今天就利用paddle来编写入门级的手写数字模型&#xff0e; 一&#xff0c;准备数据 下载数据集&#xff0c;这里我们使用的是MNIST数据集 # 下载原始的 MNIST 数据集并进行解压 wget https://paddle-imagenet-mode…

12.数组的初始化和引用

数组的初始化 定义数组的时候&#xff0c;顺便给数组的元素赋予初值&#xff0c;即开辟空间的同时并且给数组元素赋值 一维数组的初始化 a. 全部初始化 int a[5] {2,4,7,8,5}; 代表的意思&#xff1a;a[0] 2 , a[1] 4 , a[2] 7 , a[3] 8, a[4] 5; b. 部分初始化 int …

Clever Internet Suite for Delphi, C++Builder

为Internet应用程序添加即时SSL/TLS安全性&#xff0c;并实现许多有用的Internet相关功能。 聪明的互联网套件允许您添加下载、上传和提交互联网资源;发送和接收MIME消息;HTTP、FTP、SMTP、POP3、IMAP和NNTP客户端/服务器解决方案;带有数字证书的SSL/TLS通道支持您的VCL应用程序…

电脑分辨率怎么调?电脑分辨率怎么调合适

​无论是笔记本电脑的用户&#xff0c;还是说台式电脑的用户&#xff0c;在使用电脑的时候&#xff0c;如果电脑分辨率调整的不对&#xff0c;很容易造成显示与观感方面的模糊。电脑分辨率怎么调&#xff1f;电脑分辨率怎么调最佳&#xff1f;本篇文章&#xff0c;小编就来教教…

ASEMI肖特基二极管1N5822参数,1N5822特征,1N5822应用

编辑-Z ASEMI肖特基二极管1N5822参数&#xff1a; 型号&#xff1a;1N5822 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;40V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;28V 最大直流阻断电压&#xff08;VDC&#xff09;&#xff1a…

三、简单了解kafka设计原理

系列文章目录 文章目录系列文章目录一、Kafka核心总控制器Controller二、kafka高性能简单理解一、Kafka核心总控制器Controller 在Kafka集群中会有一个或者多个broker&#xff0c;其中有一个broker会被选举为控制器&#xff08;Kafka Controller&#xff09;&#xff0c;它负责…

[ZJCTF 2019]Login--动态调试--详细版

前言 主要是因为太菜了&#xff0c;看了别人的exp&#xff0c;还是懵懵懂懂的&#xff0c;都是静态分析&#xff0c;不明白为会在改密码的时候会导致最后的getshell。今天给它动态分析整一个&#xff0c;看看到底哪里出错了。 基本原理 网上有很多介绍的&#xff0c;在这里说…

Linux学习——01 gcc编译器

一、程序构建过程 高级语言的代码无法被计算机执行&#xff0c;需要将高级语言代码编译成汇编语言&#xff0c;然后再将汇编语言翻译成机器指令&#xff0c;最后通过链接生成最后的可执行文件&#xff0c;此时该文件才可以被计算机执行。总共有四步&#xff1a; 1.1 预编译&a…

[02] BLEMotion-Kit 基于QMI8658传感器使用加速度计进行倾斜检测

文章目录1. 先修知识2. 原理&#xff08;单轴为例&#xff09;2.1 单轴倾斜2.2 双轴倾斜2.3 三轴倾斜1. 先修知识 2. 原理&#xff08;单轴为例&#xff09; 首先我们要知道的是&#xff1a;当目标轴(本例中为X轴)与地球表面平行时,传感器处于 0g 场。顺时针或逆时针旋转90 将…

springboot+java大学生西部计划志愿者岗位补助管理系统

本课题要求实现一套大学生西部计划管理系&#xff0c;系统主要包括系统个人中心、志愿者管理、岗位信息管理、补助信息管理、交流论坛、系统管理等功能模块。 为完善志愿者、岗位信息&#xff0c;应当建立健全志愿者的补助和管理机制&#xff0c;建立有效的激励机制&#xff0c…

Android Studio无法连接设备,一直显示Loading Devices...

不知道什么时候做了啥&#xff0c;从某个时间点之后&#xff0c;电脑就特别容易断开adb&#xff0c;有时候重启电脑都不管用。 一直显示"Loading Devices..."&#xff0c;拔插设备&#xff0c;重启Android Studio都没用&#xff0c;甚至重启电脑有时候也不行。 反正…

全部售罄!1,000 多个Sports Land NFT 在 24 小时内被抢空!

现在还来得及&#xff0c;抓紧时间&#xff01;&#x1f440; 在不到24小时的时间里&#xff0c;来自《Sports Land&#xff1a;足球爱好者》作品集&#xff08;2022 年 11 月 16 日发布&#xff09;的1000 多个可穿戴 NFT 已被售出&#xff01; 祝贺 Hermit Crab Game Studio …

bootstrap学习(一)

&#xff08;1&#xff09;bootstrap第一个程序 &#xff08;2&#xff09;bootstrap排版 &#xff08;1&#xff09;bootstrap第一个程序 创建boot文件夹方置bootstrap所需要的文件目录&#xff0c;拷贝过来 创建base目录&#xff0c;创建html页面&#xff1a; 引入css&#…