congestion

您所在的位置:网站首页 截止阀有调节作用吗 congestion

congestion

2023-12-18 00:35| 来源: 网络整理| 查看: 265

目录

 

1.概述

2.congestion_controller模块

2.1 congestion_controller模块组成

2.2 远端带宽探测

2.3 loss_based_bandwidth_estimation基于丢包的带宽评估

2.4 delay_based_bwe基于延迟的带宽评估

2.5 receive_side_congestion_controller接收端拥塞控制

3.remote_bitrate_estimator模块

4.pacing模块

1.概述

congestion_controller江湖又称gcc (google congestion controller),可以称得上webrtc中的一个核心模块,顾名思义就是用来进行拥塞控制的,这玩意可是出了名的,都当做草稿提交到IETF去了https://tools.ietf.org/pdf/draft-ietf-rmcat-gcc-02.pdf。为什么要进行拥塞控制?网络环境变差时如果不控制数据包的发送,数据包还是拼命的往网络里发,那么网络吞吐量只会越来越差,最终严重影响网络通信。怎么进行拥塞控制呢?通过一定的算法对网络状态做出评估,然后根据带宽评估结果调整数据发送量,从而避免网络拥塞。congestion_controller模块的拥塞控制算法可分为两部分,一个是基于丢包的带宽评估,另一个是基于延迟的带宽评估,通过比较这两个算法评估结果最终得出远端带宽的评估值。

remote_bitrate_estimator模块的主要作用是根据当前的网络状态和远端带宽,对远端带宽评估做出调整,带宽调整采用了aimd算法(additive increases multiplicative decreases),aimd算法可以简单的理解为上调码率时采用加法上调(比较慢),下调码率时采用乘法(比较快)。aimd是一个来源于TCP拥塞控制的概念,webrtc在实现aimd算法时基于沿用了这个概念。

pacing模块, pacing单词意思为 步测、步调,该模块用于平滑网络数据包的发送,我个人更喜欢把它叫做定速器(pacer)。通过pacing模块,可以控制网络数据包以一个平滑的速度发送到网络,不至于所有的数据包瞬间发送而导致出现网络瞬时拥塞。

2.congestion_controller模块 2.1 congestion_controller模块组成

congestion_controller模块包含了多个类,其中核心类的类图如下:

下面对这类的作用作简要描述:

GoogCcNetworkController: GoogCcNetworkController类是整个模块congestion_controller模块的中心类,其它模块也是通过GoogCcNetworkController类的接口与congestion_controller模块进行交互。

TrendlineEstimator:使用线性回归之最小二乘法对RTP包的延迟趋势进行计算,通过延迟趋势可以评估出网络当前状态。

DelayBasedBwe:基于延迟的带宽评估,可通过音频、视频RTP包进行评估计算,默认通过视频RTP包进行。

LossBasedBandwitdhEstimation:基于丢包的带宽评估。

SendSideBandwidthEstimation:发送端带宽评估,结合了LossBasedBandwidthEstimation和DelayBasedBwe的评估结果,还有接收端通过RTCP REMB/TMMBR消息报告的带宽评估值,通过这几个值的比较得到current_target_,current_target_就是接收端的带宽评估值。这个值最终会作用到pacing模块还有编码模块。90版本(版本号通过src目录下的README.chromium查看)的webrtc的带宽评估都是通过发送端来实现的了,听说以前版本的delay_based的算法是由接收端实现,得到带宽评估值后再通过RTCP REMB消息反馈给发送端。

AlrDector:用来检测应用程序最近500ms发送的码率是否达到估算的远端码率。通过远端码率估算得到一个目标码率,但实际上发送码率可能没有达到目标码率,这可能是因为编码能力、程序处理能力的限制,这表明此时带宽利用不足。发送码率低于目标码率的某个比例Alr会触发,高于某个比例会停止。主要用于计算什么时间点编码器产生的数据量过小。

CongestionWindowPushbackController:用来计算视频编码码率调整比率。

ProbeBitrateEstimator:根据探测包对远端带宽进行评估

ProbeController:探测控制模块,congestion_controller模块在必要的时候会对远端带宽进行一个快速探测,得到一个探测结果并以此为远端带宽进行数据包发送,例如会话刚刚建立的时候、视频编码发生变化的时候。

AcknowledgedBitrateEstimator:使用了BitrateEstimator类。

BitrateEstimator:使用了贝叶斯估计算法,根据rtp feedback消息估算出一个远端码率。

congestion_controller模块主要运行在rtp_send_controller线程,估算出的远端带宽最终会作用到视频编码和pacing模块,相关函数调用栈如下:

源码call/rtp_transport_controller.send.cc的UpdateControllerWithTimeInterval函数,会定时更新bitrate,调用栈如下:

call/rtp_transport_controller_send.cc UpdateControllerWithTimeInterval 

-> call/call.cc OnTargetTransferRate

-> call/bitrate_allocator.cc OnNetworkEstimateChanged

-> video/video_send_stream_impl.cc OnBitrateUpdated

->video/video_stream_encoder.cc OnBitrateUpdated

2.2 远端带宽探测

在会话刚建立时需要确定远端的一个初始带宽,congestion_controller通过ProbeController和ProbeBitrateEstimator类来进行远端带宽探测。带宽探测的基本思路是以cluster为单位按照一定的速度来发送RTP包,然后在收到RTP包的反馈消息时计算发送速度和接收端的接收速度,取这两个速度的最小值为远端带宽速度。一次探测为一个cluster, 在同一个cluster内RTP包的cluster_id都相同。ProbeController类用来控制探测行为,例如设定开始探测比特率、分配cluster_id等。用于探测带宽的RTP包其实就是音视频的RTP包,如果没有发送过音视频RTP包那么探测行为不会发生。音视频编码出生的RTP数量有限,在探测带宽时为满足以一定的速度发送数据的要求,很可能会对已经发送过的RTP包进行填充发送。和所有的RTP、RTCP包的发送一样,探测包的发送也是通过pacing模块来进行的,所有发送的RTP包都会被保存到congestion_controller模块,函数调用栈如下:

PacingController::ProcessPackets ->

PacedSender::SendRtpPacket ->

PacketRouter::SendPacket ->

ModuleRtpRtcpImpl::TrySendPacket ->

RtpSenderEgress::SendPacket ->

RtpSenderEgress::AddPacketToTransportFeedback ->

RtpTransportControllerSend::OnAddPacket ->

转到 rtp_send_controller线程

modules/congestion_controller的

rtp/TransportFeedbackAdapter::AddPacket

在收到feedback消息时,如果此RTP包是用来探测带宽的,那么就会调用到ProbeBitrateEstimator::HandleProbeAndEstimateBitrate函数进行处理。HandleProbeAndEstimateBitrate函数处理一个RTP包的reedback,当一个cluster里面的RTP都收到feedback时,计算这个cluster包里面的发送速度和接收速度,send_rate = send_size / send_interval, recv_rate = recv_size / recv_interval,取send_rate和recv_rate中的最小值做为探测带宽大小。会话刚建立时会探测两次,以及编码器配置改变时会探测两次,一个cluster为一次探测,会话刚建立时进行两次探测的bps分别为900000、1800000,一般连续的两次探测,第二次的bps为第一次的两倍。

2.3 loss_based_bandwidth_estimation基于丢包的带宽评估

loss_based_bandwidth_estimation.cc默认不开启,但是可以通过调用 webrtc::field_trial::InitFieldTrialsFromString函数来启用,传入字符串 "WebRTC-Bwe-LossBasedControl/Enabled/"。基于丢包的拥塞控制算法的基本思路就是丢包率越大拥塞就越严重,这种情况就要降低带宽评估值,丢包率越小带宽拥塞情况越小,这种情况就要提升带宽评估值。实际的算法实现中,会设置一个初始的带宽评估值 loss_based_bitrate_,当有丢包情况发生时,会计算出一个时间增量内的平均丢包率 average_loss_max_,然后使用average_loss_max_ 与增长阀值loss_increase_threshold和下降阀值loss_decrease_threshold比较

average_loss_max_  < loss_increase_threshold,提升码率,loss_increase_threshold是通过loss_bandwidth_balance_increase(500)与当前码率进行指数计算得到的一个结果。

average_loss_max_ >  loss_decrease_threshold,降低码率,loss_decrease_threshold是通过loss_bandwidth_balance_decrease(4000)与当前码率进行指数计算得到的一个结果。

其它情况则保持当前码率不变。算法中用到的指数运算和自然指数e,猜测其作用应该是让结算结果更平滑、更准确,但其中的数学理论依据是搞不懂的了。

2.4 delay_based_bwe基于延迟的带宽评估

默认是基于视频RTP包来估算,当然也可以设置启用音频RTP包估算。基于延迟的拥塞控制基本思路是数据包经过网络设备到达接收端的延迟在网络正常的情况是均等的,如果这个延迟变大,说明数据包在网络设备中的排队时间变长网络拥塞加重,延迟变小说明排队时间变短拥塞减轻,通过对这个延迟时间的变化趋势可以对当前网络状态作出评估,网络带宽状态可以分为三种状态normal、overuseing、underuseing。如果是normal状态则保持当前带宽,如果是overuseing则需要降低带宽,如果是underuseing则提高带宽。需要注意一下的就是DelayBasedBwe并不是对单个RTP包进行延迟计算,而是以组为单位进行计算,接收时间不超过5ms的RTP包为一组。DelayBaseBwe使用了TrendlineEstimator这个类来进行带宽评估,而TrendlineEstimator则采用了线性回归之最小二乘法来对RTP包的延迟趋势进行计算。这个算法的名称看起来很高大上的,但简单点来说就是通过输入RTP包的到达时间和延迟时间,然后建立一个关于RTP包的到达时间和延迟时间的线性关系模型,这个线性模型的斜率便是延迟时间的变化趋势。算法的核心代码如下:

absl::optional LinearFitSlope( const std::deque& packets) { RTC_DCHECK(packets.size() >= 2); // Compute the "center of mass". double sum_x = 0; double sum_y = 0; for (const auto& packet : packets) { sum_x += packet.arrival_time_ms; sum_y += packet.smoothed_delay_ms; } double x_avg = sum_x / packets.size(); double y_avg = sum_y / packets.size(); // Compute the slope k = \sum (x_i-x_avg)(y_i-y_avg) / \sum (x_i-x_avg)^2 double numerator = 0; double denominator = 0; for (const auto& packet : packets) { double x = packet.arrival_time_ms; double y = packet.smoothed_delay_ms; numerator += (x - x_avg) * (y - y_avg); denominator += (x - x_avg) * (x - x_avg); } if (denominator == 0) return absl::nullopt; return numerator / denominator; }

代码还是比较容易理解的,这个函数返回线程方程的斜率slope。

slope < 0 表明延迟变小

slope == 0 表明延迟没有发生变化

0 < slope < 1 表明延迟在变大。

2.5 receive_side_congestion_controller接收端拥塞控制

位于congestion_controller目录下面,主要使用的remote_bitrate_estimator模块里面的remote_bitrate_estimator_single_stream.cc类,而remote_bitrate_estimator_single_stream.cc又使用了aimd_rate_control.cc类,这个类使用了aimd算法。

虽然接收端也对带宽做出了评估,但调试发现接收端并没有把这个评估值通过RTCP发送给发送送端。

 

3.remote_bitrate_estimator模块

overuse_detector.cc、overuse_estimator.cc、remote_bitrate_estimator_abs_send_time.cc这几个类调试发现并没有使用到,我猜测是为了兼容旧版webrtc而保留的代码(旧版webrtc的基于延迟的拥塞控制算法是在接收端实现)。remote_bitrate_estimator模块的作用就是根据当前的网络状态和带宽,采用aimd算法对带宽做出评估,当前网络状态可分为overuseing、undeuseing、hold。remote_bitrate_estimator模块会被congestion_controller模块里面的delay_based_bwe和receive_side_congestion_controller所使用。

aimd算法的核心就是加性增加,乘性减少。处于kRcIncrease状态时,如果还没有对链路进行过带宽评估,那么就进行乘法增加以便更快速的接近链路的带宽容量,否则就进行加性增加。采用aimd算法进行码率调整的一个特点就是,网络变差时视频码率能够一下子就下降下来,而网络恢复时需要比较长的时候才能恢复到正常的水平。内网使用限速软件进行视频通话测试,没有限速时上行带宽为300KB/S,限制速度为50KB/S后大概5秒视频码率就下来了并继续正常播放,而取消限速后需要大概25秒才能恢复到300KB/S的速度,同时视频码率也恢复为原来的水平。

DataRate AimdRateControl::MultiplicativeRateIncrease( Timestamp at_time, Timestamp last_time, DataRate current_bitrate) const { double alpha = 1.08; if (last_time.IsFinite()) { auto time_since_last_update = at_time - last_time; alpha = pow(alpha, std::min(time_since_last_update.seconds(), 1.0)); } DataRate multiplicative_increase = std::max(current_bitrate * (alpha - 1.0), DataRate::BitsPerSec(1000)); return multiplicative_increase; } DataRate AimdRateControl::AdditiveRateIncrease(Timestamp at_time, Timestamp last_time) const { double time_period_seconds = (at_time - last_time).seconds(); double data_rate_increase_bps = GetNearMaxIncreaseRateBpsPerSecond() * time_period_seconds; return DataRate::BitsPerSec(data_rate_increase_bps); }

如果处于kRcDecrease状态,则current_bitrate_ = estimated_throughput * 0.85.

4.pacing模块

pacing模块用来平滑网络数据包的发送。所有的音视频RTP/RTCP包都是经过pacing模块,再由pacing模块发送到网络中的(实际发送到网络由其它模块实现)。pacing平滑发送的实现原理就是按时间片(5ms)来发送一定量的数据,这样每个时间片内发送的数据就是几乎相等的,那么整体来看数据就是平滑发送的,不会出现某个时间段内发送数量暴涨,而其它时间片内发送数据量就偏低。例如远端的评估带宽为 1000000 bps, 那么每5ms可以发送的数据量就是 s = 1000000 / 1000 * 5 / 8 = 625Byte,也就是说每5ms内可以发送625个字节,发完之后就要等下一个时间片到来才能再发送。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3