深入浅出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 个配置参数,这对于模型调优来说简直就是噩梦。根据奥卡姆剃刀原理,这么复杂的模型可能是脆弱的,不必要的,有必要在更高维度上进行抽象和提炼,并进行适当简化。

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

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

相关文章

昇思25天学习打卡营第17天|计算机视觉

昇思25天学习打卡营第17天 文章目录 昇思25天学习打卡营第17天ShuffleNet图像分类ShuffleNet网络介绍模型架构Pointwise Group ConvolutionChannel ShuffleShuffleNet模块构建ShuffleNet网络 模型训练和评估训练集准备与加载模型训练模型评估模型预测 打卡记录 ShuffleNet图像分…

2023 N1CTF-n1proxy

文章目录 参考rsa握手rust_proxy源码公匙交换和签名会话钥匙后续通信生命周期和裸指针代码审计漏洞点 libc-2.27.so大致思路&#xff08;exp还有变化&#xff09;调试exp泄露libc写free_hook执行命令exp 参考 https://github.com/Nu1LCTF/n1ctf-2023/tree/main/pwn/n1proxy ht…

JVM从1%到99%【精选】-运行时数据区

目录 1.运行时数据区概括 2.什么是内存溢出 3..程序计数器 4.Java虚拟机栈 5.本地方法栈 6.堆 7.方法区 8.直接内存 1.运行时数据区概括 Java虚拟机在运行Java程序过程中管理的内存区域,称之为运行时数据区。主要分为两大类&#xff1a;线程不共享、线程共享线程不共…

TypeScript中Interface接口的深度探索与实践

定义接口 在TypeScript中&#xff0c;interface是一个强有力的概念&#xff0c;它用于定义类型签名&#xff0c;特别是对象的结构。接口可以用来描述对象应该有哪些属性、方法&#xff0c;以及这些成员的类型。它们是实现类型系统中“鸭子类型”&#xff08;duck typing&#…

【22】Android高级知识之Window(三) -WMS

一、概述 这次开始到了WindowManagerService&#xff08;WMS&#xff09;&#xff0c;你可以把它看做一个WindowManager&#xff0c;只不过呢&#xff0c;属于系统服务进程&#xff08;system_server&#xff09;中的一员&#xff0c;和应用不在同一进程&#xff0c;所以涉及了…

CSS(二)——CSS 背景

CSS 背景 CSS 背景属性用于定义HTML元素的背景。 CSS 背景属性 Property描述background简写属性&#xff0c;作用是将背景属性设置在一个声明中。background-attachment背景图像是否固定或者随着页面的其余部分滚动。background-color设置元素的背景颜色。background-image把…

《程序猿学会 Vue · 基础与实战篇》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

数据结构(二叉树-1)

文章目录 一、树 1.1 树的概念与结构 1.2 树的相关术语 1.3 树的表示 二、二叉树 2.1 二叉树的概念与结构 2.2特殊的二叉树 满二叉树 完全二叉树 2.3 二叉树的存储结构 三、实现顺序结构二叉树 3.1 堆的概念与结构 3.2 堆的实现 Heap.h Heap.c 默认初始化堆 堆的销毁 堆的插入 …

2024100读书笔记|《飞花令·夏》——鲜鲫银丝脍,香芹碧涧羹,人皆苦炎热,我爱夏日长

2024100读书笔记|《飞花令夏》——鲜鲫银丝脍&#xff0c;香芹碧涧羹&#xff0c;人皆苦炎热&#xff0c;我爱夏日长 《飞花令夏&#xff08;中国文化古典诗词品鉴&#xff09;》素心落雪 编著&#xff0c;飞花令得名于唐代诗人韩翃《寒食》中的名句“春城无处不飞花”&#xf…

matlab仿真 模拟调制(下)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第五章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; clear all ts0.001; t0:ts:10-ts; fs1/ts; dffs/length(t); msgrandi([-3 3],100,1); msg1msg*ones(1,fs/10); msg2reshape(ms…

Stable Diffusion 使用详解(1)---- 提示词及相关参数

目录 背景 提示词 内容提示词 人物及主体特征 场景 环境光照 画幅视角 注意事项及示例 标准化提示词 画质等级 风格与真实性 具体要求 背景处理 光线与色彩 负向提示词 小结 常用工具 另外几个相关参数 迭代步数 宽度与高度 提示词引导系数 图片数量 背景…

Unity | Shader基础知识(第十九集:顶点着色器的进一步理解-易错点讲解)

目录 一、前言 二、网格 三、方法UnityObjectToClipPos 四、顶点着色器和片元着色器的POSITION 五、作者的碎碎念 一、前言 之前我们简单讲解过顶点着色器&#xff0c;也简单讲解了表面着色器&#xff0c;并且一起做了一些案例&#xff0c;因为顶点着色器本身是更自由一些…

【Git多人协作开发】不同的分支下的多人协作开发模式

目录 0.前言背景 1.开发者1☞完成准备工作&协作开发 1.1查看分支情况 1.2创建本地分支feature-1 1.3三板斧 1.4push推本地分支feature-1到远程仓库 2.开发者2☞完成准备工作&协作开发 2.1创建本地分支feature-2 2.2三板斧 2.2push推送本地feature-2到远程仓库…

FineBI连接MySQL5.7

一、在FineBI系统管理中&#xff0c;点击【新建数据库连接】 选择MySQL数据库 配置数据库连接&#xff0c;如下&#xff0c;其中数据库名称就是需要连接的目标数据库

【通信协议-RTCM】MSM语句(2) - RINEXMSM7语句总结(重要!自动化开发计算卫星状态常用)

注释&#xff1a; 在工作中主要负责的是RTCM-MSM7语句相关开发工作&#xff0c;所以主要介绍的就是MSM7语句相关内容 1. 相位校准参考信号 2. MSM1、MSM2、MSM3、MSM4、MSM5、MSM6和MSM7的消息头内容 DATA FIELDDF NUMBERDATA TYPENO. OF BITSNOTES Message Number - 消息编…

DML数据操作语句和基本的DQL语句

一、MySQL对数据的增删改查 1.DML语句 1.1 增加数据(INSERT) insert into 表名 (字段名,字段名,...字段名) values/value (值,值,...值) 1.1.1 新增数据的具体实现 &#xff08;1&#xff09;全字段的插入 方式一&#xff1a; insert into student (sid,sname,birthday,ssex,…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 开源项目热度排行榜(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆Coding ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线评测,专栏文章质量平均 93 分 最新华为OD机试目录…

Linux网络-配置IP

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 本来IP配置应该放在Linux安装完成的就要配置的&#xff0c;但是由于那个时候对Linux不怎么熟悉&#xff0c;所以单独列了一个…

JVM系列(一) -浅谈虚拟机的成长史

一、摘要 众所周知&#xff0c;Java 经过多年的发展&#xff0c;已经从一门单纯的计算机编程语言&#xff0c;发展成了一套成熟的软件解决方案。从互联网到企业平台&#xff0c;Java 是目前使用最广泛的编程语言。 以下这段内容是来自 Java 的官方介绍&#xff01; 从笔记本电…

图片变更检测

20240723 By wdhuag 目录 前言&#xff1a; 参考&#xff1a; 文件监控&#xff1a; 图片占用问题&#xff1a; 源码&#xff1a; 前言&#xff1a; 由于第三方图像处理软件不能回传图片&#xff08;正常都能做&#xff0c;这里只是不想做&#xff09;&#xff0c;只能在…