当前位置: 首页 > news >正文

深入浅出WebRTC—GCC

GoogCcNetworkController 是 GCC 的控制中心,它由 RtpTransportControllerSend 通过定时器和 TransportFeedback 来驱动。GoogCcNetworkController 不断更新内部各个组件的状态,并协调组件之间相互配合,向外输出目标码率等重要参数,实现拥塞控制功能。

1. 静态结构

GoogCcNetworkController 继承自 NetworkContollerInterface,其内部调动了一大堆类来实现拥塞控制,包括丢包估计器、延时估计器、带宽探测控制器以及一些码率相关的估计器。

1)RtpTransportControllerSend

RtpTransportControllerSend 是 GoogCcNetworkController 的驱动器,主要包括定时器和 TranportFeedback 两种驱动;同时,也是 GoogCcNetworkController 融入 WebRTC 整体架构的接口,GoogCcNetworkController 的重要输出包括目标码率、带宽探测、拥塞状态等,都是通过 RtpTransportControllerSend 带向其他模块。

2)ProbeBitrateEstimator

带宽探测由 GoogCcNetworkController 触发,但执行不归 GoogCcNetworkController 管,不过,带宽探测的结果最终要回到 GoogCcNetworkController 这里来,ProbeBitrateEstimator 就是负责带宽探测结果评估的,评估后的探测带宽直接交给 GoogCcNetworkController 。

3)AlrDetector

GoogCcNetworkController 带宽评估器大部分时候是在链路容量的极限边缘试探,这是它们的预设工况。但当发送码率明显小于估计带宽(分配带宽),这时候工况发生变化,WebRTC 需要能检测到这种变化,并及时通知相关模块进行调整,否则估计值可能会产生很大偏差,AlrDetector 就是干这事的。

4)CongestionWindowPushbackController

从名字可以看出来与拥塞状态相关。经典的拥塞控制算法一般都会用到拥塞控制窗口,比如 TCP 的拥塞控制。WebRTC 的拥塞控制虽然走的不是 TCP 那条路子,但还是尊重基于拥塞窗口判断拥塞状态的有效性,具体体现是,不管你前面耍了多少花活,捣鼓了各种估计器,最终的估计带宽还是得基于拥塞状态进行调整。CongestionWindowPushbackController 就是就是这个调整器。

5)SendBandwidthEstimation

SendBandwidthEstimation 内部包含了基于丢包的带宽估计器:LossBasedBweV2, 其输出的是综合延时估计和丢包估计的带宽估计值,就是最早大家熟知的延时估计和丢包估计两者取其小的逻辑,当然,最新代码实现远比这个表述复杂。

6)ProbeController

GoogCcNetworkController 不直接控制带宽探测,带宽探测的发起、参数和冲突处理等都是由 ProbeController 负责,GoogCcNetworkController 会更新相关状态和通知相关事件的发生,ProbeController 会基于状态和事件来判断是否需要启动带宽探测任务。当然,也有主动请求带宽探测的情况,比如链路经历一次排空后需要立即进行一次带宽探测。

7)AckowledgedBitrateEstimator

深入阅读 GCC 源码就会有一种感觉,ACK 码率满天飞。这是因为,ACK 码率太重要了,它是 GCC 能观测到的唯二带宽,另一个是探测码率(带宽)。作为链路容量最重要的观测值,ACK 码率是各大带宽估计器的基础输入。为了彰显尊贵身份,针对 ACK 码率的估计祭出了贝叶斯估计器,也算是相得益彰。

8)DelayBasedBWE

其实,GCC 真正的扛把子是延迟带宽估计器,看下它后面的小弟就知道了。事实上,GCC 最依仗就是延迟估计带宽,基于丢包的估计带宽可以认为是一种调整。下面的码率关系图也可以看到,延迟估计带宽会作为丢包估计器的一个输入,但是延迟估计器不会参考丢包估计带宽。相比最开始的版本,DelayBasedBWE 也有很大改变,比如卡尔曼滤波没了、引入了基于最小二乘的趋势线判断等,具体细节就不展开了。

2. 整体架构

2.1. 在整体架构中的位置

下图描述的是视频处理逻辑架构,拥塞控制器 Controller 基于丢包估计器和延时估计器的输出计算得到目标码率 target_bitrate,目标码率会传递到带宽分配器 Allocator,Allocator 根据一定算法为编码器和 FEC 分配所需码率。Contoller 会向 Pacer 设置平滑发送码率和链路当前拥塞状态,控制平滑发送模块的行为。Controller 自己不会直接发起带宽探测,而是通过状态和事件驱动 Prober 适时发起带宽探测。

2.2. 各种码流之间的关系

GoogCcNetworkController 对外输出的码流主要有两个:target_bitrate 和 stable_target_bitrate,target_bitrate 由 LossBasedBWE 输出,它是综合丢包估计和延迟估计的估计值,stable_target_bitrate 由 LinkCapacityTracker 输出,是链路容量的一个观测值,因为观测值是真实测量得到的,所以是 stable 的。CongestionWindowPushbackController 基于网络拥塞状态对 target_bitrate 做了进一步调整,输出 pushback_target_bitrate。

ACK 码率是所有估计的基础,对不同估计器的其作用机理不同:

1)对于 LinkCapacityTracker,ACK 码率是估计值的上限,且只会提高估计值,而不会降低估计值。估计值的下限由延迟估计码率决定,延迟估计码率只会降低估计值,而不会提高估计值。

之所以这么设计,是因为 ACK 码率不会超过链路真实容量,如果当前 ACK 码率高于之前链路容量的估计值,完全有理由使用此 ACK 码率作为当前链路容量的估计值(会进行平滑处理);但如果 ACK 码率低于当前链路容量估计值,不能此 ACK 码率来更新链路容量估计值,因为,较低的 ACK 码率可能是由一个低于链路容量发送码率导致。

延迟估计带宽是链路容量的一个估计值,如果发现延迟估计带宽比上一次的估计值低(处于下降趋势中),有理由认为延迟估计带宽是在链路容量的极限附近试探,如果此次延迟估计带宽低于当前链路容量的估计值,那么可以可以用延迟估计带宽更新链路容量估计值。

2)对于 DelayBasedBWE,ACK 码率主要用来控制码率下降或上升的幅度。当链路处于 underusing 或 normal 状态时,会用探测码率直接更新估计值。

3)对于 LossBasedBWE,ACK 码率主要用来生成候选者和辅助调整最佳候选者的带宽,进而影响最终的估计值。

2.3. 重要调用关系

下图展示了以 GoogCcNetworkController 为核心拥塞控制相关的重要类以及它们之间的调用关系。调用方法后的 F 表示是由 TransportFeedback 驱动的调用,调用方法后的 T 表示是由定时器驱动的调用,调用方法后的 U 表示是由 MaybeTriggerOnNetworkChanged 驱动的调用。

2.3.1. PacingController

1)SetCongested

PacingController 在拥塞状态下会停止发送媒体报文(需要继续发送keep-alive报文)。如果未确认的报文的大小超出拥塞窗口的大小,就认为链路处于拥塞状态,未确认报文越多则拥塞越严重。

2)SetPacingRates

GoogCcNetworkController 获得新的带宽估计值,需要设置到 PacingController,用来控制 PacingController 平滑发送速率。包含两个码率:PacingRate 和 PaddingRate,在讲解平滑发送时再详细说明。

3)CreateProbeClusters

如果要发起带宽探测,ProbeController 最终会调用 PacingController 的接口创建带宽探测任务(其实是间接调用,由 RtpTransportControllerSend 完成委托任务)。

2.3.2. BitrateAllocator

GoogCcNetworkController 获得新的带宽估计值,需要通知 BitrateAllocator 重新进行带宽分配。包含两个码率:target_bitrate 和 stable_bitrate,在讲解码率分配时再详细说明。

2.3.3. AcknowledgeBitrateEstimator

1)IncomingPacketFeedbackVector

AcknowledgeBitrateEstimator 基于 TransportFeedback 计算 ACK 码率。其内部使用数据窗口进行采样,每个采样窗口计算一个码率,然后使用贝叶斯算法进行平滑。

2)SetAlrEndedTime

在进行贝叶斯平滑计算时,AcknowledgeBitrateEstimator 会给处于 ALR 状态的样本赋以更低的权重(更大的不确定性),因为 ALR 状态采集的样本不能反映真实的链路带宽。

2.3.4. CongestionWindowPushbackController

CongestionWindowPushbackController 基于 DataWindow 和 OutstandingData 来判断链路拥塞情况,然后根据链路拥塞情况来调整目标码率。

1)SetDataWindow

DataWindow 是基于估计带宽和 RTT 计算的理想拥塞窗口大小。因为 WebRTC 应用在实时音视频场景,其拥塞窗口大小不能设置的太激进,否则可能导致网络管道拥堵,延迟增加。

2)UpdateOutstandingData

OutstandingData 是观测到的未确认数据的大小。

3)UpdatePacingQueue

之所以要获取 PacingQueue 大小,是因为 CongestionWindowPushbackController 支持设置实验参数来决定是否将平滑发送模块队列中缓存的数据也记为 OutstandingData。

2.3.5. ProbeBitrateEstimator

ProbeBitrateEstimator 基于 TransportFeedback 来统计探测带宽。

2.3.6. DelayBasedBWE

DelayBasedBWE 依赖 TransportFeedback 计算报文到达延迟差。

2.3.7. ProbeController

1)Process

需要循环检查 ALR 状态以启动 ALR 带宽探测任务。

2)SetAlrStartTimeMs

设置 ALR 状态开始时间。

3)SetAlrEndedTimeMs

设置 ALR 状态结束时间。

4)RequestProbe

延迟带宽估计器在收到 TransportFeedback 去更新网络使用状态时,如果发现网络使用状态从 underusing 变为 normal,说明网络经历了一次排空,需要调用 RequestProbe 立即启动一次带宽探测。

5)SetEstimatedBitrate

带宽探测完成后,需要将探测结果告知 ProbeController,一来新的带宽可以用来生成后续目标探测带宽,另外,对于设置了 probe_further 参数的带宽探测任务,可能需要创建进一步的带宽探测任务。

2.3.8. AlrDetector

AlrDetector 基于估计带宽去消耗 budget,因此,估计带宽变化需要及时通知 AlrDetector,否则会导致 ALR 状态会判断错误。

2.3.9. SendSideBandwidthEstimation

1)UpdateEstimate

这是一个定时调用。在 LossBasedBWE 还未准备好之前,SendSideBandwidthEstimation 需要通过另一套算法来持续更新估计带宽。LossBasedBWE 好以后,则是获取 LossBasedBWE 估计结果。同时,还需要时刻监视 RTT,一旦 RTT 异常需要采取行动降低带宽。

2)UpdatePropagationRtt

Propagation RTT 是 RttBasedBackoff 所需,用来监控 RTT 异常。

3)SetAcknowledgedRate

ACK 码率会通过 OnRateUpdate 传入 LinkCapacityTracker,通过 SetAcknowledgedBitrate 传入 LossBasedBWE。LinkCapacityTracker 基于 ACK 码率来跟踪链路容量。LossBasedBWE 中 ACK 码率被作为估计值的下限,还有其他很多用途,具体参考相关章节。

4)UpdateDelayBasedEstimate

延迟带宽估计值会设置到 LinkCapacityTracker 作为估计值的下限。

5)UpdateLossbasedEstimate

调用 UpdateBandwidthEstimate 接口将 TransportFeedback 和 延迟估计带宽传入到 LossBasedBWE,LossBasedBWE 输出的估计带宽会选择丢包估计和延迟估计的较小者。

3. 源码分析

3.1. OnTransportPacketsFeedback

收到 TransportFeedback 会通过层层调用到 GoogCcNetworkController,调用流程如下图所示:

TransportFeedback 被用来计算延迟梯度和丢包率,进而更新延迟带宽估计器和丢包带宽估计器。

NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback(TransportPacketsFeedback report) {// 判断if (report.packet_feedbacks.empty()) {return NetworkControlUpdate();}// 当前已启用congestion_window_pushback_controller_,更新infightif (congestion_window_pushback_controller_) {congestion_window_pushback_controller_->UpdateOutstandingData(report.data_in_flight.bytes());}TimeDelta max_feedback_rtt = TimeDelta::MinusInfinity();TimeDelta min_propagation_rtt = TimeDelta::PlusInfinity();Timestamp max_recv_time = Timestamp::MinusInfinity();// max_recv_time是最及时反馈的数据项std::vector<PacketResult> feedbacks = report.ReceivedWithSendInfo();for (const auto& feedback : feedbacks)max_recv_time = std::max(max_recv_time, feedback.receive_time);for (const auto& feedback : feedbacks) {// 反馈RTT = feedback_ts - send_timeTimeDelta feedback_rtt =report.feedback_time - feedback.sent_packet.send_time;// 相对最后一个报文的偏差,这个偏差表示收到数据后,等待了这么长时间才反馈TimeDelta min_pending_time = max_recv_time - feedback.receive_time;// 真实链路RTT需要减去等待处理时间TimeDelta propagation_rtt = feedback_rtt - min_pending_time;// 获取最大反馈RTTmax_feedback_rtt = std::max(max_feedback_rtt, feedback_rtt);// 获取最小链路RTTmin_propagation_rtt = std::min(min_propagation_rtt, propagation_rtt);}if (max_feedback_rtt.IsFinite()) {// 维护feedback_max_rtts_队列feedback_max_rtts_.push_back(max_feedback_rtt.ms());const size_t kMaxFeedbackRttWindow = 32;if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow)feedback_max_rtts_.pop_front();// 更新链路RTT,用来跟踪RTT over limitbandwidth_estimation_->UpdatePropagationRtt(report.feedback_time,min_propagation_rtt);}// 获取ALR状态absl::optional<int64_t> alr_start_time =alr_detector_->GetApplicationLimitedRegionStartTime();// 退出ALR状态if (previously_in_alr_ && !alr_start_time.has_value()) {int64_t now_ms = report.feedback_time.ms();acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time);probe_controller_->SetAlrEndedTimeMs(now_ms);}// 更新ALR状态previously_in_alr_ = alr_start_time.has_value();// Ack码率估计acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(report.SortedByReceiveTime());// 获取ACK码率估计结果auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate();// 丢包带宽估计需要ack码率bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate,report.feedback_time);// 识别带宽探测数据包反馈,计算探测码率for (const auto& feedback : report.SortedByReceiveTime()) {if (feedback.sent_packet.pacing_info.probe_cluster_id !=PacedPacketInfo::kNotAProbe) {probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback);}}// 获取探测到的带宽absl::optional<DataRate> probe_bitrate =probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate();// 限制探测带宽低于ack码率,这样有助于排空网络管道// kProbeDropThroughputFraction = 0.85if (limit_probes_lower_than_throughput_estimate_ &&probe_bitrate && acknowledged_bitrate) {DataRate limit =std::min(delay_based_bwe_->last_estimate(),*acknowledged_bitrate * kProbeDropThroughputFraction);probe_bitrate = std::max(*probe_bitrate, limit);}NetworkControlUpdate update;bool recovered_from_overuse = false;// 先更新延迟带宽估计器DelayBasedBwe::Result result;result = delay_based_bwe_->IncomingPacketFeedbackVector(report,acknowledged_bitrate,probe_bitrate,estimate_,alr_start_time.has_value());if (result.updated) {if (result.probe) {// 延迟码率是使用探测码率进行更新的,则将探测码率作为目标码率???bandwidth_estimation_->SetSendBitrate(result.target_bitrate,report.feedback_time);}// 调用了SetSendBitrate,还需调用UpdateDelayBasedEstimate进行更新bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time,result.target_bitrate);}// 再更新丢包带宽估计器,因为丢包带宽估计器依赖于延迟带宽估计器的结果bandwidth_estimation_->UpdateLossBasedEstimator(report,result.delay_detector_state,probe_bitrate,alr_start_time.has_value());if (result.updated) {// 丢包带宽估计器得到的才是最终带宽评估结果,需要将评估结果更新到// ProbeController,以防需要进行带宽探测MaybeTriggerOnNetworkChanged(&update, report.feedback_time);}recovered_from_overuse = result.recovered_from_overuse;// 从OverUse恢复,立马触发一次带宽探测if (recovered_from_overuse) {probe_controller_->SetAlrStartTimeMs(alr_start_time);auto probes = probe_controller_->RequestProbe(report.feedback_time);update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),probes.begin(), probes.end());}if (rate_control_settings_.UseCongestionWindow() &&max_feedback_rtt.IsFinite()) {// 根据估计码率和RTT计算并更新current_data_window_UpdateCongestionWindowSize();}if (congestion_window_pushback_controller_ && current_data_window_) {// 使用了congestion_window_pushback_controller_,则更新计算的拥塞窗口大小congestion_window_pushback_controller_->SetDataWindow(*current_data_window_);} else {// 没有使用congestion_window_pushback_controller_,需要将当前计算得到的// 拥塞窗口大小通知给RtpTransportControllerSend,然后根据实际inflight判断// 拥塞状态并设置到Pacer,进而控制Pacer行为。update.congestion_window = current_data_window_;}return update;
}

3.2. OnProcessInterval

GoogCcNetworkController 自己并没有定时器,而是通过 RtpTransportControllerSend 来驱动的。

在 OnProcessInterval 方法中,SendSideBandwidthEstimation 和 ProbeController 需要定时器进行驱动。

NetworkControlUpdate GoogCcNetworkController::OnProcessInterval(ProcessInterval msg) {NetworkControlUpdate update;// 初始化if (initial_config_) {// 重置参数,并设置ProbeController,获取带宽探测Cluster配置update.probe_cluster_configs =ResetConstraints(initial_config_->constraints);// 更新pacing参数update.pacer_config = GetPacingRates(msg.at_time);// 根据配置决定是否启动ALR带宽探测,ALR探测是周期探测if (initial_config_->stream_based_config.requests_alr_probing) {probe_controller_->EnablePeriodicAlrProbing(*initial_config_->stream_based_config.requests_alr_probing);}// 设置最大分配码率,如果需要探测,则添加探测Cluster配置absl::optional<DataRate> total_bitrate =initial_config_->stream_based_config.max_total_allocated_bitrate;if (total_bitrate) {auto probes = probe_controller_->OnMaxTotalAllocatedBitrate(*total_bitrate, msg.at_time);update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),probes.begin(), probes.end());}initial_config_.reset();}// pacer_queue 大小可能被配置为 outstanding dataif (congestion_window_pushback_controller_ && msg.pacer_queue) {congestion_window_pushback_controller_->UpdatePacingQueue(msg.pacer_queue->bytes());}// RTT backoff 监控及启动阶段估计值更新bandwidth_estimation_->UpdateEstimate(msg.at_time);// 获取 ALR 状态absl::optional<int64_t> start_time_ms =alr_detector_->GetApplicationLimitedRegionStartTime();// ProbeController 可能启动了 ALR 带宽探测probe_controller_->SetAlrStartTimeMs(start_time_ms);// 主要用来驱动 ALR 带宽探测auto probes = probe_controller_->Process(msg.at_time);// 可能有带宽探测任务update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),probes.begin(), probes.end());// 根据 RTT 和目标码率更新拥塞窗口大小if (rate_control_settings_.UseCongestionWindow() && !feedback_max_rtts_.empty()) {UpdateCongestionWindowSize();}// 更新拥塞窗口大小if (congestion_window_pushback_controller_ && current_data_window_) {congestion_window_pushback_controller_->SetDataWindow(*current_data_window_);} else {update.congestion_window = current_data_window_;}// 检查状态变化MaybeTriggerOnNetworkChanged(&update, msg.at_time);return update;
}

3.4. MaybeTriggerOnNetworkChanged

网络状态变化,需要及时通知相关利益方,以便执行相应处理动作,包括触发带宽探测、码率重分配、设置平滑发送码率等。

void GoogCcNetworkController::MaybeTriggerOnNetworkChanged(NetworkControlUpdate* update,Timestamp at_time) {// 丢包率uint8_t fraction_loss = bandwidth_estimation_->fraction_loss();// RTTTimeDelta round_trip_time = bandwidth_estimation_->round_trip_time();// 丢包估计目标码率(综合丢包估计和延迟估计)DataRate loss_based_target_rate = bandwidth_estimation_->target_rate();// 丢包估计状态LossBasedState loss_based_state = bandwidth_estimation_->loss_based_state();// 使用丢包估计目标码率初始化 pushback 目标码率DataRate pushback_target_rate = loss_based_target_rate;double cwnd_reduce_ratio = 0.0;if (congestion_window_pushback_controller_) {// 传入丢包估计目标码率,获取根据拥塞状态调整后的 pushback 码率int64_t pushback_rate =congestion_window_pushback_controller_->UpdateTargetBitrate(loss_based_target_rate.bps());// 最小码率约束pushback_rate = std::max<int64_t>(bandwidth_estimation_->GetMinBitrate(),pushback_rate);// 转换为 DataRatepushback_target_rate = DataRate::BitsPerSec(pushback_rate);// 根据拥塞窗口计算后的下调带宽比率if (rate_control_settings_.UseCongestionWindowDropFrameOnly()) {cwnd_reduce_ratio = static_cast<double>(loss_based_target_rate.bps() -pushback_target_rate.bps()) /loss_based_target_rate.bps();}}// 获取稳定目标码率,并用 pushback 目标码率进行了约束DataRate stable_target_rate = bandwidth_estimation_->GetEstimatedLinkCapacity();stable_target_rate = std::min(stable_target_rate, pushback_target_rate);if ((loss_based_target_rate != last_loss_based_target_rate_) || // 带宽估计值变化(loss_based_state != last_loss_base_state_) ||              // 带宽评估状态变化(fraction_loss != last_estimated_fraction_loss_) ||         // 丢包率变化(round_trip_time != last_estimated_round_trip_time_) ||     // RTT变化(pushback_target_rate != last_pushback_target_rate_) ||     // pushback 码率变化(stable_target_rate != last_stable_target_rate_)) {         // 稳定目标码率变化// 更新last_loss_based_target_rate_ = loss_based_target_rate;last_pushback_target_rate_ = pushback_target_rate;last_estimated_fraction_loss_ = fraction_loss;last_estimated_round_trip_time_ = round_trip_time;last_stable_target_rate_ = stable_target_rate;last_loss_base_state_ = loss_based_state;// 设置ALR探测器估计码率alr_detector_->SetEstimatedBitrate(loss_based_target_rate.bps());TimeDelta bwe_period = delay_based_bwe_->GetExpectedBwePeriod();TargetTransferRate target_rate_msg;target_rate_msg.at_time = at_time;if (rate_control_settings_.UseCongestionWindowDropFrameOnly()) {// 如果携带cwnd_reduce_ratio,则目标码率为评估的目标码率target_rate_msg.target_rate = loss_based_target_rate;target_rate_msg.cwnd_reduce_ratio = cwnd_reduce_ratio;} else {// 如果不携带cwnd_reduce_ratio,则目标码率为经拥塞窗口调整后的码率target_rate_msg.target_rate = pushback_target_rate;}target_rate_msg.stable_target_rate = stable_target_rate;target_rate_msg.network_estimate.at_time = at_time;target_rate_msg.network_estimate.round_trip_time = round_trip_time;target_rate_msg.network_estimate.loss_rate_ratio = fraction_loss / 255.0f;target_rate_msg.network_estimate.bwe_period = bwe_period;update->target_rate = target_rate_msg;// 更新带宽探测控制器的估计码率auto probes = probe_controller_->SetEstimatedBitrate(loss_based_target_rate,GetBandwidthLimitedCause(bandwidth_estimation_->loss_based_state(),bandwidth_estimation_->IsRttAboveLimit(),delay_based_bwe_->last_state()),at_time);// 添加带宽探测 Cluste r配置update->probe_cluster_configs.insert(update->probe_cluster_configs.end(),probes.begin(), probes.end());// 更新 Pacing 参数update->pacer_config = GetPacingRates(at_time);}
}

4. 总结

简单总结下 WebRTC 拥塞控制思路。拥塞控制的核心是获取链路的带宽,对于实时音视频通信来说,还要考虑延时指标。因为只有获得了链路的真实带宽,才能确保发送的码率不会超过链路容量,从而避免产生拥塞。那有没有一种办法,在不发送码流的情况下,提前知道链路的真实带宽呢?答案是没有。这个问题貌似变成了一个先有鸡还是先有蛋的问题。理论上是这样的,但实践中,可以采用一个带有负反馈回路的控制算法来打破这个循环魔咒,这就是 WebRTC 拥塞控制的核心思想。

我们回过头来总结 GCC 实现的底层逻辑:设置一个起始码率,按照起始码率开始发送报文,通过 RTCP 收集各种反馈,将反馈导入估计器(延时带宽估计器、丢包带宽估计器以及其他一堆估计器),产生估计值,调整发送码率,按照新的目标码率发送报文,继续收集反馈产生新的估计值,形成循环反馈回路。

这样就完了吗?并没有!如何产生更可靠估计值成了控制算法的关键问题。为了更准确、更可靠的估计链路容量,WebRTC 采用了延时带宽估计器和丢包带宽估计器两个估计器,同时,还引入 RTT、ACK 码率、探测码率、链路容量等一系列估计值来对估计结果进行调整和修正,有一大坨代码都是在解决如果获得更好估计这个问题。如果要深刻理解 WebRTC 是如何解决这个问题的,就必须深入算法和模块细节,比如延迟估计中是如何实现网络状态检测的,丢包估计中是如何实现最优参数搜索的等。理解了必要的细节后,再跳脱出来将这些模块和算法联系起来,将一个个逻辑链条组成前面提到的基于反馈回路的控制算法逻辑。

必须得承认,GCC 算法整体是非常优秀的,主要表现在以下几个方面:

1)良好的顶层架构

模块职责划分清晰,模块接口和模块之间的关系设计合理,使得 GCC 能够很好的融入 WebRTC 整体架构。

2)坚实的理论基础

基于延迟梯度的拥塞控制算法,在学术界有很多研究成果;基于链路固有丢包率属性和二项分布丢包模型,设计精巧的目标函数,通过收集观测样本并搜索最优参数组合,算法整体有很好的数学理论支撑。

3)大量使用成熟算法

基本上每个估计器都有经典算法的加持,比如指数加权移动平均算法、贝叶斯估计算法、最小二乘线性拟合算法等。

不过,GCC 经过多年的演化,也有几个问题值得探讨:

1)GCC 的实现越来越复杂,这种复杂性目前主要体现在子模块内部,但也有向上蔓延的趋势,子模块之间的耦合越来越强。GCC 当前的实现相比他们第一次发表的论文,做了很多改进和优化,甚至一些关键逻辑有了很大变化,导致的现象是分支和判断越来越多,复杂度不断加码,有一种不断往之前算法框架上打补丁的既视感。

2)GCC 里面涉及到一堆估计器,这些估计器的融合,看起来缺少理论基础。比如延迟估计和丢包估计,两者取其小一定是合理的吗?ACK 码率、探测码率、延迟估计码率、丢包估计码率、pushback 码率、链路容量等,这些概念相互交织,相关限制和调整逻辑看似合理,但缺少足够的说服力。

3)引入大量模型参数,导致模型理解困难,实现复杂。比如 LossBasedBweV2::Config,足足有 39 个配置参数,这对于模型调优来说简直就是噩梦。根据奥卡姆剃刀原理,这么复杂的模型可能是脆弱的,不必要的,有必要在更高维度上进行抽象和提炼,并进行适当简化。

相关文章:

深入浅出WebRTC—GCC

GoogCcNetworkController 是 GCC 的控制中心&#xff0c;它由 RtpTransportControllerSend 通过定时器和 TransportFeedback 来驱动。GoogCcNetworkController 不断更新内部各个组件的状态&#xff0c;并协调组件之间相互配合&#xff0c;向外输出目标码率等重要参数&#xff0…...

leetcode日记(49)旋转链表

其实不难&#xff0c;就是根据kk%len判断需要旋转的位置&#xff0c;再将后半段接在前半段前面就行。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : …...

InteliJ IDEA最新2024版下载安装与快速配置激活使用教程+jdk下载配置

第一步&#xff1a;下载ideaIC-2024.1.4 方法1&#xff1a;在线链接 IntelliJ IDEA – the Leading Java and Kotlin IDE (jetbrains.com) 选择社区版进行下载 方法2&#xff1a;百度网盘 链接&#xff1a;https://pan.baidu.com/s/1ydS6krUX6eE_AdW4uGV_6w?pwdsbfm 提取…...

【23】Android高级知识之Window(四) - ThreadedRenderer

一、概述 在上一篇文章中已经讲了setView整个流程中&#xff0c;最开始的addToDisplay和WMS跨进程通信的整个过程做了什么。继文章Android基础知识之Window(二)&#xff0c;这算是另外一个分支了&#xff0c;接着讲分析在performTraversals的三个操作中&#xff0c;最后触发pe…...

Java-根据前缀-日期-数字-生成流水号(不重复)

&#x1f388;边走、边悟&#x1f388;迟早会好 小伙伴们在日常开发时可能会遇到的业务-生成流水号&#xff0c;在企业中可以说是比较常见的需求&#xff0c; 可以采用"前缀日期数字"的方式&#xff08;ps:此方式是需要用到缓存的&#xff09;前缀&#xff1a;为了…...

跟李沐学AI:卷积层

从全连接层到卷积 多层感知机十分适合处理表格数据&#xff0c;其中行对应样本&#xff0c;列对应特征。但对于图片等数据&#xff0c;全连接层会导致参数过多。卷积神经网络&#xff08;convolutional neural networks&#xff0c;CNN&#xff09;是机器学习利用自然图像中一…...

使用RedisTemplate操作executePipelined

前言 RedisTemplate 是 Spring 提供的用于操作 Redis 的模板类&#xff0c;它封装了 Redis 的连接、连接池等管理&#xff0c;并提供了一系列的操作方法来简化 Redis 的使用。其中&#xff0c;executePipelined 方法是 RedisTemplate 中的一个高级特性&#xff0c;用于支持 Re…...

react-native从入门到实战系列教程一环境安装篇

充分阅读官网的环境配置指南&#xff0c;严格按照他的指导作业&#xff0c;不然你一直只能在web或沙箱环境下玩玩 极快的网络和科学上网&#xff0c;必备其中的一个较好的心理忍受能力&#xff0c;因为上面一点就可以让你放弃坚持不懈&#xff0c;努力尝试 成功效果 三大件 …...

【Gin】精准应用:Gin框架中工厂模式的现代软件开发策略与实施技巧(下)

【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(下) 大家好 我是寸铁&#x1f44a; 【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(下)✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 本次文章分为上下两部分&…...

国科大作业考试资料-人工智能原理与算法-2024新编-第十二次作业整理

袋子里面有3个有偏差的硬币a、b和c,抛掷硬币正面朝上的概率分别是20%、60%和80%。从袋子里随机取出一个硬币(3个硬币被取出的概率是相等的),并把取出的硬币抛掷3次,得到抛掷结果依次是X1 , X2和 X3。 a. 画出对应的贝叶斯网络并定义必要的CPT表。 b. 如果抛掷结果是2次正…...

《0基础》学习Python——第二十一讲__网络爬虫/<4>爬取豆瓣电影电影信息

爬取网页数据&#xff08;获取网页信息全过程&#xff09; 1、爬取豆瓣电影的电影名称、导演、主演、年份、国家、评价 2、首先我们先爬取页面然后再获取信息 1、爬取网页源码 import requests from lxml import etree if __name__ __main__:#UA伪装head{User-Agent:Mozilla/…...

【C++初阶】string类

【C初阶】string类 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;C&#x1f96d; &#x1f33c;文章目录&#x1f33c; 1. 为什么学习string类&#xff1f; 1.1 C语言中的字符串 1.2 实际中 2. 标准库中的string类 2.1 string类 2.…...

RAS--APEI 报错解析流程(2)

RAS--APEI 报错解析流程(1) 除了APEI 中除了GHES会记录错误&#xff0c;在Post过程中的错误通常是通过BERT Table汇报 1.BERT Boot Error Record Table is used to report unhandled errors that occurred in a previous boot&#xff0c;it is reported as a ‘one-time polle…...

微软蓝屏事件对企业数字化转型有什么影响?

引言&#xff1a;从北京时间2024年7月19日&#xff08;周五&#xff09;下午2点多开始&#xff0c;全球大量Windows用户出现电脑崩溃、蓝屏死机、无法重启等情况。事发后&#xff0c;网络安全公司CrowdStrike称&#xff0c;收到大量关于Windows电脑出现蓝屏报告&#xff0c;公司…...

【Gin】精准应用:Gin框架中工厂模式的现代软件开发策略与实施技巧(上)

【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(上) 大家好 我是寸铁&#x1f44a; 【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(上)✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 本次文章分为上下两部分&…...

浅谈Devops

1.什么是Devops DevopsDev&#xff08;Development&#xff09;Ops&#xff08;Operation&#xff09; DevOps&#xff08;Development和Operations的混合词&#xff09;是一种重视“软件开发人员&#xff08;Dev&#xff09;”和“IT运维技术人员&#xff08;Ops&#xff09;”…...

大文件分片上传(前端TS实现)

大文件分片上传 内容 一般情况下&#xff0c;前端上传文件就是new FormData,然后把文件 append 进去&#xff0c;然后post发送给后端就完事了&#xff0c;但是文件越大&#xff0c;上传的文件也就越长&#xff0c;如果在上传过程中&#xff0c;突然网络故障&#xff0c;又或者…...

unity2D游戏开发02添加组件移动玩家

添加组件 给PlayGame和EnemyObject添加组件BoxCollider 2D碰撞器&#xff0c;不用修改参数 给PlayGame添加组件Rigibody 2D 设置数据 添加EnemyObject&#xff0c;属性如下 Edit->project setting->Physics 2D 将 y的值改为0 给playerObject添加标签 新建层 将PlayerObj…...

设计模式 之 —— 单例模式

目录 什么是单例模式&#xff1f; 定义 单例模式的主要特点 单例模式的几种设计模式 1.懒汉式&#xff1a;线程不安全 2.懒汉式&#xff1a;线程安全 3.饿汉式 4.双重校验锁 单例模式的优缺点 优点&#xff1a; 缺点&#xff1a; 适用场景&#xff1a; 什么是单例模…...

深入浅出WebRTC—ULPFEC

FEC 通过在发送端添加额外的冗余信息&#xff0c;使接收端即使在部分数据包丢失的情况下也能恢复原始数据&#xff0c;从而减轻网络丢包的影响。在 WebRTC 中&#xff0c;FEC 主要有两种实现方式&#xff1a;ULPFEC 和 FlexFEC&#xff0c;FlexFEC 是 ULPFEC 的扩展和升级&…...

Python从0到100(四十三):数据库与Django ORM 精讲

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...

Redis-主从模式

目录 前言 一.主从节点介绍 二.配置redis主从结构 二.主从复制 四.拓扑结构 五.数据同步 全量复制&#xff08;Full Sync Replication&#xff09; 局部复制&#xff08;Partial Replication&#xff09; Redis的学习专栏&#xff1a;http://t.csdnimg.cn/a8cvV 前言 …...

加速决策过程:企业级爬虫平台的实时数据分析

摘要 在当今数据驱动的商业环境中&#xff0c;企业如何才能在海量信息中迅速做出精准决策&#xff1f;本文将探讨企业级爬虫平台如何通过实时数据分析加速决策过程&#xff0c;实现数据到决策的无缝衔接。我们聚焦于技术如何赋能企业&#xff0c;提升数据处理效率&#xff0c;…...

字典树(前缀树)数组实现(只能查26个单词)

这段代码实现了一个基于 Trie 树的字典树&#xff08;Trie&#xff09;数据结构&#xff0c;用于存储和检索字符串。其中包含以下几个方法. insert(String word): 向 Trie 树中插入一个单词。首先将单词转换为字符数组&#xff0c;然后遍历字符数组&#xff0c;逐个字符在 Trie…...

CTF-pwn-虚拟化-vmmware 前置

文章目录 参考vmware逃逸简介虚拟机和主机通信机制(guest to host)共享内存&#xff08;弃用&#xff09;backdoor机制Message_Send和Message_RecvGuestRPC实例RpcOutSendOneRawWork实例 vmware-rpctool info-get guestinfo.ip各个步骤对应的backdoor操作Open RPC channelSend …...

thinkphp8结合layui2.9 图片上传验证

<?php declare (strict_types 1);namespace app\index\validate;use think\Validate;class Upload extends Validate {/*** 定义验证规则* 格式&#xff1a;字段名 > [规则1,规则2...]** var array*/protected $rule [image > fileExt:jpg,png|fileSize:204800|fi…...

农村污水处理难题:探索低成本高效解决方案

农村污水处理难题&#xff1a;探索低成本高效解决方案 农村污水处理作为国家生态文明建设的重要一环&#xff0c;面临着诸多挑战&#xff0c;尤其是技术落后、管理分散、资源匮乏等问题。物联网技术的引入&#xff0c;为解决这些痛点提供了创新途径&#xff0c;实现了对污水处…...

lightningcss介绍及使用

lightningcss介绍及使用 一款使用 rust 编写的 css 解析器&#xff0c;转换器、及压缩器。 特性 特别快&#xff1a;可以在毫秒级别解析、压缩大量的 css 文件&#xff0c;而且比其他工具的打包结果更小给值添加类型&#xff1a;许多其他css解析器会将值解析成一个无类型的 …...

HTTP服务的应用

1、编辑json请求参数&#xff1b; 2、把json发送到服务url&#xff0c;接收服务的返回参数&#xff1b; 3、解析返回参数。 procedure TfrmCustomQuery.btnFullUpdateClick(Sender: TObject); varfrm: TfrmInputQueryConditionEX;b_OK: Boolean;sBeginDate, sEndDate, sJSON…...

uni-app:踩坑路---scroll-view内使用fixed定位,无效的问题

前言&#xff1a; emmm&#xff0c;说起来这个问题整得还挺好笑的&#xff0c;本人在公司内&#xff0c;奋笔疾书写代码&#xff0c;愉快的提交测试的时候&#xff0c;测试跟我说&#xff0c;在苹果手机上你这个样式有bug&#xff0c;我倒是要看看&#xff0c;是什么bug。 安卓…...

MySQL4.索引及视图

1.建库 create database mydb15_indexstu; use mydb15_indexstu;2.建表 2.1 student表学&#xff08;sno&#xff09;号为主键&#xff0c;姓名&#xff08;sname&#xff09;不能重名&#xff0c;性别&#xff08;ssex&#xff09;仅能输入男或女&#xff0c;默认所在系别&a…...

MongoDB - 聚合阶段 $match、$sort、$limit

文章目录 1. $match 聚合阶段1. 构造测试数据2. $match 示例3. $match 示例 2. $sort 聚合阶段1. 排序一致性问题2. $sort 示例 3. $limit 聚合阶段 1. $match 聚合阶段 $match 接受一个指定查询条件的文档。 $match 阶段语法&#xff1a; { $match: { <query> } }$ma…...

ModuleNotFoundError: No module named ‘scrapy.utils.reqser‘

在scrapy中使用scrapy-rabbitmq-scheduler会出现报错 ModuleNotFoundError: No module named scrapy.utils.reqser原因是新的版本的scrapy已经摒弃了该方法,但是scrapy-rabbitmq-scheduler 没有及时的更新,所以此时有两种解决方法 方法一.将scrapy回退至旧版本,找到对应的旧版…...

vue3+ts+vite+electron+electron-packager打包成exe文件

目录 1、创建vite项目 2、添加需求文件 3、根据package.json文件安装依赖 4、打包 5、electron命令运行 6、electron-packager打包成exe文件 Build cross-platform desktop apps with JavaScript, HTML, and CSS | Electron 1、创建vite项目 npm create vitelatest 2、添…...

使用脚本搭建MySQL数据库基础环境

数据库的基本概念 数据&#xff08;Data&#xff09; 描述事物的符号记录 包括数字&#xff0c;文字&#xff0c;图形。图像&#xff0c;声音&#xff0c;档案记录等。 以记录形式按统一格式进行存储 表 将不同的记录组织在一起 用来储存具体数据 数据库 表的集合&#xff0c;是…...

Parameter index out of range (2 > number of parameters, which is 1【已解决】

文章目录 1、SysLogMapper.xml添加注释导致的2、解决方法3、总结 1、SysLogMapper.xml添加注释导致的 <!--定义一个查询方法&#xff0c;用于获取日志列表--><!--方法ID为getLogList&#xff0c;返回类型com.main.server.api.model.SysLogModel,参数类型为com.main.se…...

rk3588s 定制版 USB adb , USB2.0与USB3.0 区别,adb 由typeC 转换到USB3.0(第二部分)

硬件资源&#xff1a; rk3588s 核心板定制的地板 软件资源&#xff1a; 网盘上的 android12 源码 1 硬件上 客户只想使用 type c 接口中的 usb2.0 OTG 。在硬件上&#xff0c;甚至连 CC芯片都没有连接。 关于一些前置的知识。 1 USB2.0 与 USB3.0 的区别。 usb3.0 兼容2.0 …...

Cookie与Session 实现登录操作

Cookie Cookie 是网络编程中使用最广泛的一项技术&#xff0c;主要用于辨识用户身份。 客户端&#xff08;浏览器&#xff09;与网站服务端通讯的过程如下图所示&#xff1a; 从图中看&#xff0c;服务端既要返回 Cookie 给客户端&#xff0c;也要读取客户端提交的 Cookie。所…...

通过IEC104转MQTT网关轻松接入阿里云平台

随着智能电网和物联网技术的飞速发展&#xff0c;电力系统中的传统IEC 104协议设备正面临向现代化、智能化转型的迫切需求。阿里云作为全球领先的云计算服务提供商&#xff0c;其强大的物联网平台为IEC 104设备的接入与数据处理提供了强大的支持。本文将深入探讨钡铼网关在MQTT…...

lua 游戏架构 之 游戏 AI (五)ai_autofight_find_way

这段Lua脚本定义了一个名为 ai_autofight_find_way 的类&#xff0c;继承自 ai_base 类。 lua 游戏架构 之 游戏 AI &#xff08;一&#xff09;ai_base-CSDN博客文章浏览阅读238次。定义了一套接口和属性&#xff0c;可以基于这个基础类派生出具有特定行为的AI组件。例如&…...

vue3+openLayers点击标记事件

<template><!--地图--><div class"distributeMap" id"distributeMap"></div> </template> <script lang"ts" setup> import { onMounted, reactive } from "vue"; import { Feature, Map, View }…...

深入分析 Android ContentProvider (三)

文章目录 深入分析 Android ContentProvider (三)ContentProvider 的高级使用和性能优化1. 高级使用场景1.1. 数据分页加载示例&#xff1a;分页加载 1.2. 使用 Loader 实现异步加载示例&#xff1a;使用 CursorLoader 加载数据 1.3. ContentProvider 与权限管理示例&#xff1…...

养宠浮毛异味双困扰?性价比高的宠物空气净化器推荐

家里养了两只银渐层&#xff0c;谁懂啊&#xff01;一下班打开家门就看到家里飘满了猫浮毛雪&#xff0c;空气中还传来隐隐约约的异味。每天不是在吸毛的路上&#xff0c;就是在洗猫砂盆的路上&#xff0c;而且空气中的浮毛还很难清理干净&#xff0c;这是最让人头疼的问题。 …...

maven项目容器化运行之3-优雅的利用Jenkins和maven使用docker插件调用远程docker构建服务并在1Panel中运行

一.背景 在《maven项目容器化运行之1》中&#xff0c;我们开启了1Panel环境中docker构建服务给到了局域网。在《maven项目容器化运行之2》中&#xff0c;我们基本实现了maven工程创建、远程调用docker构建镜像、在1Panel选择镜像运行容器三大步骤。 但是&#xff0c;存在一个问…...

docker 打包orbbec

docker pull humble容器 sudo docker run -it osrf/ros:humble-desktop docker 启动容器 sudo docker run -u root --device/dev/bus/usb:/dev/bus/usb -it -v /home/wl:/share --name wl4 osrf/ros:humble-desktop /bin/bash新开一个终端 查看本地存在的容器&#xff1a;…...

无涯·问知财报解读,辅助更加明智的决策

财报解读就像是给公司做一次全面的体检&#xff0c;是理解公司内部运作机制和市场表现的一把钥匙&#xff0c;能够有效帮助投资者、分析师、管理层以及所有市场参与者判断一家公司的健康程度和发展潜力。 星环科技无涯问知的财经库内置了企业年报及财经类信息&#xff0c;并对…...

【Apache Doris】数据副本问题排查指南

【Apache Doris】数据副本问题排查指南 一、问题现象二、问题定位三、问题处理 本文主要分享Doris中数据副本异常的问题现象、问题定位以及如何处理此类问题。 一、问题现象 问题日志 查询报错 Failed to initialize storage reader, tablet{tablet_id}.xxx.xxx问题说明 查…...

【HarmonyOS】关于鸿蒙消息推送的心得体会(二)

【HarmonyOS】关于鸿蒙消息推送的心得体会&#xff08;二&#xff09; 前言 推送功能的开发与传统功能开发还是有很大区别。首先最大的区别点就在于需要多部门之间的协同&#xff0c;作为鸿蒙客户端开发&#xff0c;你需要和产品&#xff0c;运营&#xff0c;以及后台开发一起…...

零基础入门:创建一个简单的Python爬虫管理系统

摘要&#xff1a; 本文将手把手教你&#xff0c;从零开始构建一个简易的Python爬虫管理系统&#xff0c;无需编程基础&#xff0c;轻松掌握数据抓取技巧。通过实战演练&#xff0c;你将学会设置项目、编写基本爬虫代码、管理爬取任务与数据&#xff0c;为个人研究或企业需求奠…...

【Node.js基础04】node.js模块化

一&#xff1a;什么是模块化 在Node.js中&#xff0c;每个文件都可视为一个独立的模块。模块化提高了代码的复用性&#xff0c;按需加载&#xff0c;具有独立的作用域 二&#xff1a;如何实现多个文件间导入和导出 1 CommonJS标准&#xff08;默认&#xff09;-导入和导出 …...