深入浅出WebRTC—GCC

news2024/12/25 9:32:28

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_,更新infight
  if (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_time
    TimeDelta 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;
    // 获取最大反馈RTT
    max_feedback_rtt = std::max(max_feedback_rtt, feedback_rtt);
    // 获取最小链路RTT
    min_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 limit
    bandwidth_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.85
  if (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 data
  if (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();
  // RTT
  TimeDelta 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);

    // 转换为 DataRate
    pushback_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.coloradmin.cn/o/1937044.html

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

相关文章

汽车及零部件研发项目管理系统:一汽东机工选择奥博思 PowerProject 提升研发项目管理效率

在汽车行业中&#xff0c;汽车零部件的研发和生产是一个关键的环节。随着汽车市场的不断扩大和消费者需求的不断增加&#xff0c;汽车零部件项目管理的重要性日益凸显。通过有效的项目管理方法及利用先进的数字项目管理系统&#xff0c;可以大幅提高项目的成功率和顺利度&#…

WebRTC QOS方法十三.1(TimestampExtrapolator接收时间预估)

一、背景介绍 虽然我们可通过时间戳的差值和采样率计算出发送端视频帧的发送节奏&#xff0c;但是由于网络延迟、抖动、丢包&#xff0c;仅知道视频发送端的发送节奏是明显不够的。我们还需要评估出视频接收端的视频帧的接收节奏&#xff0c;然后进行适当平滑&#xff0c;保证…

关于 Qt输入法在arm特定的某些weston下出现调用崩溃 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140423667 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

C#知识|账号管理系统-修改账号按钮功能的实现

哈喽,你好啊,我是雷工! 前边学习了通过选择条件查询账号的功能: 《提交查询按钮事件的编写》 本节继续学习练习C#,今天练习修改账号的功能实现。 以下为学习笔记。 01 实现功能 ①:从查询到的账号中,选择某一账号,然后点击【修改账号】按钮,将选中的信息获取显示到…

攻防世界 re新手模式

Reversing-x64Elf-100 64位ida打开 看if语句&#xff0c;根据i的不同&#xff0c;选择不同的数组&#xff0c;后面的2*i/3选择数组中的某一个元素&#xff0c;我们输入的是a1 直接逆向得到就行 二维字符数组写法&#xff1a;前一个是代表有几个字符串&#xff0c;后一个是每…

《蔚蓝档案》模拟器联动皮肤H5+KOC

《蔚蓝档案》模拟器联动皮肤H5KOC 《蔚蓝档案》自上线以来老师们与MuMu模拟器的共同历程&#xff0c;重温难忘瞬间&#xff0c;回忆游戏历程。蔚蓝档案一周年模拟器联动主题皮肤福利&#xff0c;于7月18日-8月16日&#xff0c;在MuMu模拟器搜索【蔚蓝档案联动】进入活动页面&a…

离散数学,半群性质的证明,群,群的性质,子群

目录 1.半群性质的证明 半群的性质 定理5-3.2证明 定理5-3.3证明 半群的性质 定理5-3.4证明 例子 2.群 群是每个元素都可逆的独异点 例子 有限群&#xff0c;阶数&#xff0c;无限群&#xff0c;平凡群 3.群的性质 群中不可能有零元 群中任一元素逆元…

Paypal个人支付申请及沙箱测试配置

目录 一. 申请paypal账号二. Sanbox 测试配置申请买家Account申请卖家AccountSandbox的Client ID及密钥申请Live的Client ID及密钥申请IPN回调设置 一. 申请paypal账号 浏览器输入https://www.paypal.com, 单击注册按钮 2. 我这里申请个人账户&#xff0c;如果你需要企业账户&…

如何提升EVs应用潜力?EVs与工程化材料的结合!

细胞外囊泡 (EVs)作为细胞间通讯的重要载体&#xff0c;在组织工程和再生医学中具有巨大的应用潜力。然而&#xff0c;EVs在体内的半衰期很短&#xff0c;难以有效地到达靶组织并发挥其生物学功能。因此&#xff0c;如何控制EVs的释放和保留成为实现其临床应用的关键。近年来&a…

智慧旅游平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;景点分类管理&#xff0c;旅游景点管理&#xff0c;景区活动管理&#xff0c;留言板管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;旅游景点&…

(七)原生js案例之评分功能

业务开发中&#xff0c;评分组件基本都是用element,antd这些框架的已经给我们封装好了现成的。现在手写一个原生的评分组件 效果 代码实现 必要的css .rating {width: 600px;margin: 60px auto;text-align: center;}.rating img {width: 30px;height: 30px;cursor: pointer…

uniapp上传功能用uni-file-picker实现

文章目录 html代码功能实现css样式代码 html代码 <uni-file-pickerselect"onFileSelected"cancel"onFilePickerCancel"limit"1"class"weightPage-upload-but"file-mediatype"image"></uni-file-picker><imag…

Java记事本工具Notepad++

常见的高级记事本 Editplus、Notepad、Sublime Notepad软件的安装和使用 安装&#xff1a;傻瓜式安装 1、选择中文-->【OK】 2、点击【下一步】 3、协议点击【我接受】 4、选择安装路径-->【下一步】 5、点击【下一步】 6、最后点击【安装】 7、将运行取消-->点击…

机械学习—零基础学习日志(高数06——函数特性)

零基础为了学人工智能&#xff0c;真的开始复习高数 函数的性质&#xff0c;开始新的学习&#xff01; 有界性&#xff1a; 解法放这里&#xff1a; 证明有界&#xff0c;其实内部的包含知识点很多。第一&#xff0c;如果有界&#xff0c;你需要证明函数在一定区间内&#xff…

【体外诊断】ARM/X86+FPGA嵌入式计算机在免疫分析设备中的应用

体外诊断 信迈提供基于Intel平台、AMD平台、NXP平台的核心板、2.5寸主板、Mini-ITX主板、4寸主板、PICO-ITX主板&#xff0c;以及嵌入式准系统等计算机硬件。产品支持GAHDMI等独立双显&#xff0c;提供丰富串口、USB、GPIO、PCIe扩展接口等I/O接口&#xff0c;扩展性强&#xf…

pytorch学习(九)激活函数

1.pytorch常用激活函数如下&#xff1a; #ReLU激活函数 #Leaky ReLU激活函数 #Sigmoid激活函数 #Tanh激活函数 #Softmax激活函数 #Softplus2.代码 import torch.nn as nn import torch import numpy from torch.utils.tensorboard import SummaryWriterwriter SummaryWriter…

buu做题(5)

目录 [GXYCTF2019]禁止套娃 方法一: 方法二: [NCTF2019]Fake XML cookbook [GXYCTF2019]禁止套娃 页面里啥也没有 使用dirsearch 扫一下目录 发现有 git 使用工具githack拉取源码 <?php include "flag.php"; echo "flag在哪里呢&#xff1f;<br&g…

空间计算开发:Volu的集成开发工具包

在空间计算技术迅速发展的今天,VR和AR项目的开发需求日益增长。Volu,一个面向空间计算赛道的开发者工具,正致力于简化这一过程。本文将深入探讨Volu如何通过其集成环境,为开发者提供一站式的解决方案。 一、定位:空间计算的得力助手 Volu定位为一个专为空间开发设计的集…

亚马逊自养号测评系统:电商卖家的销量加速器

搭建一套属于自己的测评系统&#xff0c;以实现批量优质账号的养成和自主掌控真实买家行为&#xff0c;对于电商卖家来说&#xff0c;无疑是一个极具吸引力和竞争力的选择。以下是对您提出的实现价值点的详细解析&#xff1a; 实现价值详细解析 1.全面掌控与灵活应对&#xf…

Chromium CI/CD 之Jenkins实用指南2024-添加Windows节点(八)

1. 引言 在现代软件开发流程中&#xff0c;持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;已成为确保代码质量和加速发布周期的关键实践。Jenkins作为一款广泛应用的开源自动化服务器&#xff0c;通过其强大的插件生态系统和灵活的配置选项&#xf…