流媒体学习之路(WebRTC)——Pacer与GCC(5)

流媒体学习之路(WebRTC)——Pacer与GCC(5)

——
我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标:可以让大家熟悉各类Qos能力、带宽估计能力,提供每个环节关键参数调节接口并实现一个json全配置,提供全面的可视化算法观察能力。欢迎大家使用
——

文章目录

  • 流媒体学习之路(WebRTC)——Pacer与GCC(5)
  • 一、PacingController
    • 1.1 背景介绍
    • 1.2 代码
  • 二、IntervalBudget
    • 2.1 背景
    • 2.2 代码
  • 三、PacedSender
  • 四、总结


  在讲具体内容之前插一句嘴,从GCC分析(3)开始,我们将针对GCC的实现细节去分析它设计的原理,让我们理解这些类存在的意义,不再带大家去串具体的流程了。

一、PacingController

1.1 背景介绍

  Pacer(Packet Pacing)的作用是在传输数据时能平滑的发送出去,减少对网络冲击和抖动的产生,提高通信质量。在一次数据传输中,如果所有包几乎同时发送,网络就可能会遭遇到冲击,这就可能导致网络拥塞,数据包丢失等问题。为了避免这样的问题,需要通过一个定时器均匀分散发送数据包。
  特别是在音视频传输中,PACER更是非常重要的一部分。因为音视频的传输对于网络的稳定性和实时性要求非常高,任何形式的网络抖动或者丢包都会造成音视频的卡顿,延迟等问题。所以在WebRTC中使用Pacer,就是为了使音视频传输更加平滑,减少由于网络抖动造成的影响,从而达到提高实时音视频通信质量的目的。

  提到WebRTC的Pacer就需要讲述它码率控制的逻辑:
在这里插入图片描述
  从GCC输出的码率会设置给编码器以及pacer。pacer并不是完全严格设置多少就发多少,而是留有2.5倍的空间去发送。真正控制发送码率的则是输出给编码器的部分,期望控制编码器的输出码率。同时,pacer还对所有数据设置了优先级,优先级如下:

int GetPriorityForType(RtpPacketToSend::Type type) {// Lower number takes priority over higher.switch (type) {case RtpPacketToSend::Type::kAudio:// Audio is always prioritized over other packet types.return kFirstPriority + 1;case RtpPacketToSend::Type::kRetransmission:// Send retransmissions before new media.return kFirstPriority + 2;case RtpPacketToSend::Type::kVideo:case RtpPacketToSend::Type::kForwardErrorCorrection:// Video has "normal" priority, in the old speak.// Send redundancy concurrently to video. If it is delayed it might have a// lower chance of being useful.return kFirstPriority + 3;case RtpPacketToSend::Type::kPadding:// Packets that are in themselves likely useless, only sent to keep the// BWE high.return kFirstPriority + 4;}
}

  Pacer之所设计成这样,是因为我们向编码器设置码率之后想要保证丝滑清晰的画面,不可能完全控制输出码率,有时候画面复杂码率就大一些,画面简单码率就小一些。所以Pacer为了保证延迟预留了2.5倍的发送空间,也就是说真正控制码率的位置其实是编码器的输出

1.2 代码

  接下来我看看看pacer的核心代码——PacingController。这个类包含了优先级设置以及发送的逻辑,前面提到了优先级的内容下面只介绍发送逻辑:

void PacingController::ProcessPackets() {Timestamp now = CurrentTime(); // 当前时间TimeDelta elapsed_time = UpdateTimeAndGetElapsed(now); // 与上次process的间隔// 发送保活,每500ms发送一个padding包,一旦发送的数据大于拥塞窗口则不发送if (ShouldSendKeepalive(now)) {DataSize keepalive_data_sent = DataSize::Zero();// 产生padding包std::vector<std::unique_ptr<RtpPacketToSend>> keepalive_packets =packet_sender_->GeneratePadding(DataSize::bytes(1));for (auto& packet : keepalive_packets) {keepalive_data_sent +=DataSize::bytes(packet->payload_size() + packet->padding_size());packet_sender_->SendRtpPacket(std::move(packet), PacedPacketInfo());}OnPaddingSent(keepalive_data_sent);}// 处于暂停直接返回if (paused_)return;// 进入发送间隔开始计算if (elapsed_time > TimeDelta::Zero()) {DataRate target_rate = pacing_bitrate_;DataSize queue_size_data = packet_queue_.Size();// 队列中有数据才能发送if (queue_size_data > DataSize::Zero()) {// Assuming equal size packets and input/output rate, the average packet// has avg_time_left_ms left to get queue_size_bytes out of the queue, if// time constraint shall be met. Determine bitrate needed for that.// packet_queue_.UpdateQueueTime(CurrentTime());if (drain_large_queues_) {// 平均发送时间 = 最大队列时长(2s)- 平均排队时间TimeDelta avg_time_left =std::max(TimeDelta::ms(1),queue_time_limit - packet_queue_.AverageQueueTime());DataRate min_rate_needed = queue_size_data / avg_time_left;// 最发送码率大于目标码率,则目标码率等于最小需求码率if (min_rate_needed > target_rate) {target_rate = min_rate_needed;RTC_LOG(LS_VERBOSE) << "bwe:large_pacing_queue pacing_rate_kbps="<< target_rate.kbps();}}}// 设置媒体桶media_budget_.set_target_rate_kbps(target_rate.kbps());UpdateBudgetWithElapsedTime(elapsed_time);}bool first_packet_in_probe = false;bool is_probing = prober_.IsProbing();PacedPacketInfo pacing_info;absl::optional<DataSize> recommended_probe_size;// 正在探测则获取探测数据信息if (is_probing) {pacing_info = prober_.CurrentCluster();first_packet_in_probe = pacing_info.probe_cluster_bytes_sent == 0;recommended_probe_size = DataSize::bytes(prober_.RecommendedMinProbeSize());}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.// 产生padding包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;}// 获取待发送包auto* packet = GetPendingPacket(pacing_info);// 一旦产生不了数据,证明队列为空,则放入padding数据if (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;}// 发送数据std::unique_ptr<RtpPacketToSend> rtp_packet = packet->ReleasePacket();RTC_DCHECK(rtp_packet);packet_sender_->SendRtpPacket(std::move(rtp_packet), pacing_info);data_sent += packet->size();// Send succeeded, remove it from the queue.OnPacketSent(packet);if (recommended_probe_size && data_sent > *recommended_probe_size)break;}if (is_probing) {probing_send_failure_ = data_sent == DataSize::Zero();if (!probing_send_failure_) {prober_.ProbeSent(CurrentTime().ms(), data_sent.bytes());}}
}RoundRobinPacketQueue::QueuedPacket* PacingController::GetPendingPacket(const PacedPacketInfo& pacing_info) {if (packet_queue_.Empty()) {return nullptr;}// Since we need to release the lock in order to send, we first pop the// element from the priority queue but keep it in storage, so that we can// reinsert it if send fails.// 取出第一个包RoundRobinPacketQueue::QueuedPacket* packet = packet_queue_.BeginPop();bool audio_packet = packet->type() == RtpPacketToSend::Type::kAudio;bool apply_pacing = !audio_packet || pace_audio_;// 如果处于拥塞状态或者剩余数据为0则取消弹出if (apply_pacing && (Congested() || (media_budget_.bytes_remaining() == 0 &&pacing_info.probe_cluster_id ==PacedPacketInfo::kNotAProbe))) {packet_queue_.CancelPop();return nullptr;}return packet;
}

二、IntervalBudget

2.1 背景

  PacingController上述用到了IntervalBudget这个类,这个类用于做数据统计和预估。并且它作为一个抽象预估类,并不会真正的存数据,只是做了数据统计,每次排出数据后都按时间更新一次桶的容量,发送时则会把已发送的数据更新到桶数据中。
在这里插入图片描述

2.2 代码

  头文件:

class IntervalBudget {public:explicit IntervalBudget(int initial_target_rate_kbps);IntervalBudget(int initial_target_rate_kbps, bool can_build_up_underuse);void set_target_rate_kbps(int target_rate_kbps);// TODO(tschumim): Unify IncreaseBudget and UseBudget to one function.void IncreaseBudget(int64_t delta_time_ms);void UseBudget(size_t bytes);size_t bytes_remaining() const;double budget_ratio() const;int target_rate_kbps() const;private:int target_rate_kbps_;int64_t max_bytes_in_budget_;int64_t bytes_remaining_;bool can_build_up_underuse_;
};

  CPP文件:

constexpr int64_t kWindowMs = 500;
}IntervalBudget::IntervalBudget(int initial_target_rate_kbps): IntervalBudget(initial_target_rate_kbps, false) {}IntervalBudget::IntervalBudget(int initial_target_rate_kbps,bool can_build_up_underuse): bytes_remaining_(0), can_build_up_underuse_(can_build_up_underuse) {set_target_rate_kbps(initial_target_rate_kbps);
}void IntervalBudget::set_target_rate_kbps(int target_rate_kbps) {target_rate_kbps_ = target_rate_kbps;// 默认按500ms计算最大桶码率max_bytes_in_budget_ = (kWindowMs * target_rate_kbps_) / 8;// 计算剩余码率bytes_remaining_ = std::min(std::max(-max_bytes_in_budget_, bytes_remaining_),max_bytes_in_budget_);
}void IntervalBudget::IncreaseBudget(int64_t delta_time_ms) {// 按时换算桶的码率int64_t bytes = target_rate_kbps_ * delta_time_ms / 8;if (bytes_remaining_ < 0 || can_build_up_underuse_) {// We overused last interval, compensate this interval.// 把当前的码率加上bytes_remaining_ = std::min(bytes_remaining_ + bytes, max_bytes_in_budget_);} else {// If we underused last interval we can't use it this interval.// 一旦剩余码率为负则重新使用新计算的码率bytes_remaining_ = std::min(bytes, max_bytes_in_budget_);}
}void IntervalBudget::UseBudget(size_t bytes) {// 把使用的数据进行统计bytes_remaining_ = std::max(bytes_remaining_ - static_cast<int>(bytes),-max_bytes_in_budget_);
}size_t IntervalBudget::bytes_remaining() const {return rtc::saturated_cast<size_t>(std::max<int64_t>(0, bytes_remaining_));
}double IntervalBudget::budget_ratio() const {if (max_bytes_in_budget_ == 0)return 0.0;return static_cast<double>(bytes_remaining_) / max_bytes_in_budget_;
}int IntervalBudget::target_rate_kbps() const {return target_rate_kbps_;
}

三、PacedSender

  上述的PacingController把具体的发送数据进行具体的计算,WebRTC把发送的逻辑和控制逻辑抽离了出来,其实PacingSender在构造时创建了PacingController并传入了this指针。因此对于PacingController来说PacingSender作为控制器在内部进行了回调。

  其他的函数我们不做具体的描述,只介绍定时函数:

int64_t PacedSender::TimeUntilNextProcess() {rtc::CritScope cs(&critsect_);// When paused we wake up every 500 ms to send a padding packet to ensure// we won't get stuck in the paused state due to no feedback being received.// 从controller中获取间隔TimeDelta elapsed_time = pacing_controller_.TimeElapsedSinceLastProcess();if (pacing_controller_.IsPaused()) {// 最大间隔为500msreturn std::max(PacingController::kPausedProcessInterval - elapsed_time,TimeDelta::Zero()).ms();}auto next_probe = pacing_controller_.TimeUntilNextProbe();if (next_probe) {return next_probe->ms();}const TimeDelta min_packet_limit = TimeDelta::ms(5);return std::max(min_packet_limit - elapsed_time, TimeDelta::Zero()).ms();
}

四、总结

  本文介绍了Pacer相关的内容,但我们的目的是通过Pacer去理解GCC的逻辑,在经过多个版本的迭代,Pacer与GCC的配合已经非常娴熟,同时耦合也是非常严重的:

  1. 每次Pacer的溢出发送,都需要GCC兜底(GCC的灵敏可以有效地检测到网络的排队,任何一个溢出的数据都能快速的下调码率,在遇到瓶颈带宽的时候出现了明显的锯齿状发送曲线);
    在这里插入图片描述

  2. 码率不足与拥塞探测的矛盾(编码器的输出往往会收到一定的限制不可能无线地上涨,在当今环境下很难探测到带宽瓶颈。Pacer的做法是提供Padding的数据作为补充探测,但大部分厂商为了避免流量过度消耗,就把探测的逻辑关闭了。在这方面来看,Pacer真是没有完全听GCC的话);

  也正是因为这样,WebRTC的Pacer是GCC的Pacer其他的拥塞算法来了,估计都水土不服,参考BBR被移除可知。

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

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

相关文章

数据库-MySQL 启动方式

以管理员身份运行命令行 或者Shell net start //查看所有服务 net start MYSQL80 //启动服务 net stop MYSQL80 //停止服务完整安装MySQL社区版本的 会有这个 启动服务 停止服务 重启服务

C# 进阶语法,Linq入门到详解

什么是Linq LINQ (Language Integrated Query) 即语言集成查询–用来作查询一些操作类库主要负责对象的查询。 1、LINQ to Objects 主要负责对象的查询 2、LINQ to XML 主要负责XML的查询。 3、LINQ to ADO.NET 主要负责数据库的查询。 linq核心就是对数据源的操作 学linq另外…

15、Kubernetes核心技术 - 探针

目录 一、概述 二、探针类型 2.1、就绪探针&#xff08;Readiness Probe&#xff09; 2.2、存活探针&#xff08;Liveness Probe&#xff09; 三、探针探测方法 3.1、exec 3.2、httpGet 3.3、tcpSocket 四、探针配置项 五、探针使用 5.1、就绪探针&#xff08;Readin…

Java框架相关高频面试题

一&#xff0c;Spring 1&#xff0c;Spring框架中单例bean是线程安全的吗&#xff1f; 2&#xff0c;什么是AOP&#xff1f;你项目有用过吗&#xff1f; 3&#xff0c;Spring事务的失效场景有哪些&#xff1f; 发生自身调用&#xff08;类中使用this调用本类的方法&#xff0…

这货能大大增强ChatGpt的战斗力

今天我给你介绍一个能大大增强ChatGpt的战斗力的工具&#xff1a; gapier。 注册gapier ChatGpt推出了GPTs的功能&#xff0c;在创建GPTs的时候有个Actions的选项&#xff0c;是给我们调用第三方接口用的&#xff0c;以前一直不知道这么用。 直到我发现了一个网站&#xff1a…

11.盛水最多的容器(双指针,C解法)

题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;…

FX3U-1PG使用

作为扩展模块的安装 伺服驱动器的参数设置 1.设置为0&#xff0c;为位置模式&#xff0c;发送脉冲控制&#xff1b; 2. 设置旋转方向&#xff0c;以及脉冲方式&#xff0c;通常设置为01&#xff0c;因为FX3U-1PG只支持正方向脉冲负方向脉冲方式&#xff1b; 当然想改变电机运…

docker如何配置阿里云镜像加速?

登录阿里云后&#xff0c;我们点击右上角的控制台&#xff0c;控制台中搜索镜像加速服务&#xff0c;然后点击帮助文档的官方镜像加速&#xff1a; 点击容器镜像服务控制台&#xff1a; 在镜像工具里面的镜像加速器中就可以看到&#xff1a; 分别执行即可&#xff1a; 之后我们…

Docker与虚拟机的比对

在Windows操作系统上的对比&#xff1a; 但是官方还是建议我们尽量不要将Docker直接安装到Windows操作系统上。

k8s---声明式资源管理(yml文件)

在k8s当中支持两种声明资源的方式&#xff1a; 1、 yaml格式&#xff1a;主要用于和管理资源对象 2、 json格式&#xff1a;主要用于在API接口之间进行消息传递 声明式管理方法(yaml)文件 1、 适合对资源的修改操作 2、 声明式管理依赖于yaml文件&#xff0c;所有的内容都在y…

5大自动化测试的Python框架,看完就能涨薪5k 【实用干货】

目前&#xff0c;它在Tiobe指数中排名第三个&#xff0c;仅次于Java和C。随着该编程语言的广泛使用&#xff0c;基于Python的自动化测试框架也应运而生&#xff0c;且不断发展与丰富。 因此&#xff0c;开发与测试人员在为手头的项目选择测试框架时&#xff0c;需要考虑许多方…

《小学生》知网期刊投稿方式、投稿邮箱

《小学生》是国家新闻出版总署批准的正规期刊&#xff0c;杂志立足教育&#xff0c;服务全国&#xff0c;致力于为广大基础教育工作者搭建一个展示基础教育理论研究成果&#xff0c;交流经验、合作共进的学术平台。是广大专家、学者、教师、学子发表论文、交流信息的重要平台。…

【EI会议征稿通知】第三届艺术设计与数字化技术国际学术会议( ADDT 2024)

第三届艺术设计与数字化技术国际学术会议( ADDT 2024&#xff09; 2024 3rd International Conference on Art Design and Digital Technology 所谓艺术设计&#xff0c;就是将艺术的审美感应用到与日常生活密切相关的设计中&#xff0c;使其不仅具有审美功能&#xff0c;而且…

电风扇目标检测数据集VOC格式1100张

电风扇的全方位介绍 一、功能特性 电风扇作为一种晋及化的家用电器&#xff0c;其主要功能是利用电机驱动扇叶旋转&#xff0c;从而产生风力&#xff0c;用干调节室内空气流通&#xff0c;达至降温、通风和改善室内环境的目的。此外&#xff0c;现代电风扇还具备定时、遥控、…

阶段十-分布式-Redis02

第一章 Redis 事务 1.1 节 数据库事务复习 数据库事务的四大特性 A&#xff1a;Atomic &#xff0c;原子性&#xff0c;将所以SQL作为原子工作单元执行&#xff0c;要么全部执行&#xff0c;要么全部不执行&#xff1b;C&#xff1a;Consistent&#xff0c;一致性&#xff0…

day04 两两交换链表中的节点 删除链表的倒数第N个节点 链表相交 环形链表Ⅱ

题目1&#xff1a;24 两两交换链表中的节点 题目链接&#xff1a;24 两两交换链表中的节点 题意 两两交换链表中相邻的节点&#xff0c;返回交换后链表的头节点 虚拟头节点 注意终止条件&#xff0c;考虑节点的奇偶数&#xff0c;根据奇偶数确定终止条件 注意定义中间变量…

新闻稿发布:媒体重要还是价格重要

在当今信息爆炸的数字时代&#xff0c;企业推广与品牌塑造不可或缺的一环就是新闻稿发布。新闻稿是一种通过媒体渠道传递企业信息、宣传品牌、事件或产品新闻的文本形式。发布新闻稿的过程旨在将企业的声音传递给更广泛的受众&#xff0c;借助媒体平台实现品牌故事的广泛传播。…

探索Allure Report:提升自动化测试效率的秘密武器

亲爱的小伙伴们&#xff0c;由于微信公众号改版&#xff0c;打乱了发布时间&#xff0c;为了保证大家可以及时收到文章的推送&#xff0c;可以点击上方蓝字关注测试工程师成长之路&#xff0c;并设为星标就可以第一时间收到推送哦&#xff01; 一.使用 Allure2 运行方式-Python…

阿里云服务器云盘ESSD Entry、SSD、高效云盘性能测评

阿里云服务器系统盘或数据盘支持多种云盘类型&#xff0c;如高效云盘、ESSD Entry云盘、SSD云盘、ESSD云盘、ESSD PL-X云盘及ESSD AutoPL云盘等&#xff0c;阿里云百科aliyunbaike.com详细介绍不同云盘说明及单盘容量、最大/最小IOPS、最大/最小吞吐量、单路随机写平均时延等性…

什么猫粮比较好?哪些牌子的主食冻干健康又实惠?

很多养猫的小伙伴们都磨刀霍霍准备给猫咪屯猫些猫冻干吧&#xff0c;特别是家里有挑食猫咪的家庭。有养猫的铲屎官们应该都知道&#xff0c;猫咪是对蛋白质的需求量很高&#xff0c;而且对植物蛋白的吸收效率比较低&#xff0c;所以蛋白质最好都是来自动物的优质蛋白。猫咪挑食…