webrtc弱网-LossBasedBweV2类源码分析与算法原理

1. 核心功能

LossBasedBweV2是WebRTC Google Congestion Control (GoogCC) 算法套件中的第二代基于丢包的带宽估计器。它的核心功能是:

  • 带宽估计: 根据网络数据包的丢失情况,估算当前网络路径可用的带宽上限。其核心假设是:当发送速率超过网络容量时,将出现拥塞,表现为数据包丢失。通过分析丢包模式,它可以反向推断出这个容量瓶颈。

  • 状态输出 : 不仅输出一个带宽估计值(DataRate),还输出一个状态(LossBasedState)。这个状态用于指导上层控制器(如GoogCCNetworkController)的行为,例如:

    • kIncreasing/kIncreaseUsingPadding: 建议可以尝试增加发送速率,后者暗示可以使用填充数据来探测。

    • kDecreasing: 估计值正在因丢包而下降,应减少发送速率。

    • kDelayBasedEstimate: 当前未处于丢包受限状态,应优先使用基于延迟的估计结果。

  • 多源信息融合: 它并非孤立工作,而是融合了多种信息源:

    • 丢包信息 : 主要输入,来自RTCP Receiver Reports或传输反馈。

    • 延迟基于估计值 : 来自DelayBasedBwe的另一个估计值,用作上限或候选值。

    • 确认速率: 当前网络的吞吐量测量值,用作参考和下限。

    • 配置参数: 通过字段试验(Field Trials)提供大量可调参数,用于控制算法行为。

2. 核心算法原理

LossBasedBweV2的核心算法基于最大似然估计(Maximum Likelihood Estimation, MLE),其基本原理可以概括为:

  1. 建立丢包模型: 算法假设一个信道模型,其中包丢失概率由两部分组成:

    • 固有丢失(inherent_loss: 代表与拥塞无关的随机丢包(如无线链路错误)。

    • 拥塞丢失 : 当发送速率(sending_rate)超过信道的"丢包受限带宽"(loss_limited_bandwidth)时,按一定比例((sending_rate - loss_limited_bandwidth) / sending_rate)产生额外丢包。

      总的丢包概率函数为:
      loss_probability = inherent_loss + (1 - inherent_loss) * max(0, (sending_rate - loss_limited_bandwidth) / sending_rate)

  2. 定义似然函数 : 给定一组观测到的包接收/丢失情况,计算在这组信道参数(inherent_loss, loss_limited_bandwidth)下,观察到这组结果的似然度(Likelihood)。似然度是模型参数的函数。

  3. 最大化似然函数 : 算法的目标是找到一组参数(inherent_loss, loss_limited_bandwidth),使得观察到的数据的似然度最大 。这组参数就是最可能产生当前观测数据的信道状态,其中的loss_limited_bandwidth就是最终的带宽估计值。

  4. 牛顿法优化 : 为了高效地找到使似然函数最大化的参数,算法对inherent_loss参数使用了牛顿法(Newton's Method) 进行迭代优化。GetDerivatives()函数计算似然函数的一阶和二阶导数,NewtonsMethodUpdate()根据导数更新inherent_loss的值。

  5. 高带宽偏好 : 为了防止算法在存在随机丢包(高inherent_loss)时过于保守,似然函数中引入了一个高带宽偏置项(high_bandwidth_bias 。该项会随着候选带宽的增加而增加,从而让算法更倾向于选择高带宽的估计,即使它需要假设一个更高的固有丢包率来解释观测到的丢包。GetHighBandwidthBias()AdjustBiasFactor()负责计算这个偏置。

3. 关键数据结构

  • LossBasedBweV2::Config

    • 作用 : 包含所有可配置的实验参数,通过字段试验字符串(如WebRTC-Bwe-LossBasedBweV2)解析而来。这些参数控制着算法的几乎所有方面,包括观察窗口大小、候选因子、牛顿法迭代次数、各种阈值和开关等。其有效性由IsConfigValid()函数验证。

    • 重要性: 使得算法行为高度可调,便于A/B测试和算法迭代。

  • LossBasedBweV2::ChannelParameters

    • 作用: 表示信道模型的一个候选参数集。

      • loss_limited_bandwidth: 待评估的带宽估计值。

      • inherent_loss: 对应的固有丢包率估计。

    • 重要性: MLE优化过程的核心操作对象。

  • LossBasedBweV2::Observation

    • 作用 : 存储一个时间窗口内的丢包观测结果。由多次UpdateBandwidthEstimate调用中积累的PacketResult汇总而成,直到持续时间满足observation_duration_lower_bound

      • num_packets, num_lost_packets, num_received_packets

      • size, lost_size (字节数,use_byte_loss_rate为true时使用)

      • sending_rate: 该观测窗口内的平均发送速率(经过平滑处理)。

      • id: 观测的唯一ID,用于时间加权。

    • 重要性: 是似然计算的基础数据单元。

  • LossBasedBweV2::Result

    • 作用: 算法的输出结果。

      • bandwidth_estimate: 最终计算出的基于丢包的带宽估计值。

      • state: 估计器的状态(Increasing/Decreasing/DelayBasedEstimate),用于指导拥塞控制行为。

  • HoldInfoPaddingInfo

    • 作用 : 用于实现状态保持(Hold) 和**填充(Padding)**逻辑。

      • HoldInfo: 在估计减少后,暂时限制估计值的增长幅度和持续时间,避免过快回升再次引发丢包。

      • PaddingInfo: 当状态为kIncreaseUsingPadding时,记录用于判断是否持续进行填充探测的速率和时间戳。

    • 重要性: 这些是增强算法稳定性和主动探测能力的机制。

4. 核心方法详解

  • UpdateBandwidthEstimate

    • 入口方法 。接收新的包反馈信息(packet_results)、当前的延迟估计值(delay_based_estimate)和是否在应用限制区域(in_alr)的标志。

    • 流程:

      1. 更新观测值 : 调用PushBackObservation将新的包结果汇总到当前部分观测中,如果持续时间足够,则创建一个新的Observation并加入环形缓冲区。

      2. 生成候选 : 调用GetCandidates生成一组待评估的ChannelParameters候选。候选来自:

        • 当前估计乘以配置的candidate_factors(如1.02, 1.0, 0.95)。

        • 确认速率(acknowledged_bitrate_)。

        • 延迟估计值(delay_based_estimate_)。

        • 即时上限(GetInstantUpperBound(),在ALR状态下)。

      3. 评估候选 : 对每个候选,使用牛顿法优化其inherent_lossNewtonsMethodUpdate),然后计算其似然函数值加上高带宽偏置后的目标函数值GetObjective)。

      4. 选择最佳候选 : 选择目标函数值最大的候选作为best_candidate

      5. 应用约束和规则

        • 平均丢失检查 : 如果平均上报丢包率大于最佳候选的固有丢包率,可能禁止增加(not_increase_if_inherent_loss_less_than_average_loss)。

        • 延迟增加窗口 : 在刚减少带宽后的一段时间(delayed_increase_window)内,限制增长上限(bandwidth_limit_in_current_window)。

        • 确认速率约束 : 在丢包受限状态下,如果估计要增加,则其上限受acknowledged_bitrate_乘以一个因子约束。

      6. 最终边界处理 : 将最佳候选的带宽与即时下限(GetInstantLowerBound,主要来自确认速率)、即时上限(GetInstantUpperBound,基于平均丢包率计算)和延迟估计值进行比对,得到最终的bounded_bandwidth_estimate

      7. 状态机更新 : 根据最终估计值的变化、与延迟估计的关系以及Hold/Padding的状态,决定新的LossBasedState

      8. 更新内部状态 : 设置current_best_estimate_loss_based_result_last_hold_info_last_padding_info_等。

  • GetCandidates

    • 根据配置和当前状态,生成一系列带宽候选值,然后为每个带宽值创建一个ChannelParameters结构体,并为其设置一个可行的初始inherent_lossGetFeasibleInherentLoss)。
  • GetDerivativesNewtonsMethodUpdate

    • MLE优化的核心。GetDerivatives计算当前参数下似然函数对inherent_loss的一阶和二阶导数。NewtonsMethodUpdate根据牛顿迭代公式 inherent_loss -= step_size * (first_derivative / second_derivative) 更新参数,并将结果钳位到可行范围内 [inherent_loss_lower_bound, GetInherentLossUpperBound]
  • GetObjective

    • 计算候选参数的目标函数值,即对数似然值 + 高带宽偏置。算法目标是最大化该函数。
  • GetInstantUpperBoundCalculateInstantUpperBound

    • 基于近期平均丢包率计算一个带宽上限。其模型为:instant_upper_bound = bandwidth_balance / (average_loss - loss_offset)。如果平均丢包率很低,这个上限会很高(max_bitrate_),丢包率越高,这个上限越低。
  • GetInstantLowerBoundCalculateInstantLowerBound

    • 计算带宽下限,通常取max(min_bitrate_, acknowledged_bitrate_ * factor)。确保估计值不会低于当前网络的实际吞吐量太多。

5. 设计亮点

  1. 基于模型的MLE方法 : 相比于简单的丢包阈值法(如SendSideBandwidthEstimation中使用的),V2版本采用了更严谨的概率模型和优化方法,理论上能更准确地反映网络状态。

  2. 灵活可配置性: 通过大量的字段试验参数暴露了算法的核心旋钮,使得算法可以快速迭代和调优,无需修改代码。

  3. 多候选评估: 不是只做一次优化,而是生成多个候选点并评估,避免陷入局部最优,并能融合其他估计源(如延迟估计、确认速率)。

  4. 状态机与控制逻辑 : 输出的State为上层控制器提供了丰富的语义信息,使得拥塞控制策略可以更精细,例如区分"可用填充数据探测"的增加和普通增加。

  5. 时间加权 : 对观测窗口内的历史数据使用指数衰减加权(temporal_weights_),更重视近期的网络状况。

  6. Hold机制: 在带宽下降后引入一个"冷静期",防止估计过快反弹,提高稳定性。

  7. 区分包丢失率和字节丢失率 : 通过use_byte_loss_rate开关,可以选择是基于包计数还是基于字节计数来计算丢包率,以适应不同的场景。

6. 典型工作流程

  1. 初始化 : 创建LossBasedBweV2对象,通过字段试验解析并验证配置(CreateConfig, IsConfigValid)。初始化观察窗口、权重向量等。初始状态为未就绪。

  2. 设置初始值 : 通常由上层控制器调用SetBandwidthEstimate或通过UpdateBandwidthEstimate传入的第一个delay_based_estimate来设置初始带宽估计。

  3. 持续更新

    • 上层控制器定期(如每次收到传输反馈时)调用UpdateBandwidthEstimate

    • 该方法汇总包反馈,形成观测点。

    • 生成多个候选带宽值。

    • 对每个候选,优化其固有丢包率,并计算目标函数。

    • 选择最佳候选。

    • 应用各种约束和规则(Hold、ACK速率约束等)得到最终估计。

    • 根据最终估计值的变化和与其他估计值的关系,更新状态机。

    • 返回Result(带宽+状态)。

  4. 控制决策 : 上层控制器(如GoogCCNetworkController)根据返回的Result中的statebandwidth_estimate,结合其他模块(如延迟基于BWE、探测器)的输出,最终决定目标码率、是否发起探测等控制命令。

7. LossBasedBweV2 核心算法流程图

7.1.总体流程图

7.2.处理包结果并创建观测

7.3.生成候选带宽值

7.4.评估候选并选择最佳

7.5.应用后处理规则

7.6.确定最终估计与状态

7.7.牛顿法优化过程

8.核心算法精解

复制代码
void LossBasedBweV2::UpdateBandwidthEstimate(
    rtc::ArrayView<const PacketResult> packet_results,
    DataRate delay_based_estimate,
    bool in_alr) {
  // 存储延迟基于的带宽估计值,用于后续比较和约束
  delay_based_estimate_ = delay_based_estimate;
  
  // 检查估计器是否已启用
  if (!IsEnabled()) {
    RTC_LOG(LS_WARNING) << "The estimator must be enabled before it can be used.";
    return;
  }

  // 检查是否有包结果数据
  if (packet_results.empty()) {
    RTC_LOG(LS_VERBOSE) << "The estimate cannot be updated without any loss statistics.";
    return;
  }

  // 处理包结果并创建观测数据
  if (!PushBackObservation(packet_results)) {
    return;
  }

  // 如果当前最佳估计尚未初始化,使用延迟基于的估计进行初始化
  if (!IsValid(current_best_estimate_.loss_limited_bandwidth)) {
    if (!IsValid(delay_based_estimate)) {
      RTC_LOG(LS_WARNING) << "The delay based estimate must be finite: " << ToString(delay_based_estimate);
      return;
    }
    current_best_estimate_.loss_limited_bandwidth = delay_based_estimate;
    loss_based_result_ = {.bandwidth_estimate = delay_based_estimate,
                          .state = LossBasedState::kDelayBasedEstimate};
  }

  // 初始化最佳候选为当前最佳估计
  ChannelParameters best_candidate = current_best_estimate_;
  double objective_max = std::numeric_limits<double>::lowest();
  
  // 生成并评估所有候选带宽值
  for (ChannelParameters candidate : GetCandidates(in_alr)) {
    // 使用牛顿法优化候选的固有丢包率参数
    NewtonsMethodUpdate(candidate);

    // 计算候选的目标函数值
    const double candidate_objective = GetObjective(candidate);
    
    // 选择目标函数值最大的候选
    if (candidate_objective > objective_max) {
      objective_max = candidate_objective;
      best_candidate = candidate;
    }
  }
  
  // 如果估计值减少了,记录减少时间
  if (best_candidate.loss_limited_bandwidth <
      current_best_estimate_.loss_limited_bandwidth) {
    last_time_estimate_reduced_ = last_send_time_most_recent_observation_;
  }

  // 如果平均丢包率大于当前固有丢包率,且配置允许,禁止增加估计值
  if (GetAverageReportedLossRatio() > best_candidate.inherent_loss &&
      config_->not_increase_if_inherent_loss_less_than_average_loss &&
      current_best_estimate_.loss_limited_bandwidth <
          best_candidate.loss_limited_bandwidth) {
    best_candidate.loss_limited_bandwidth =
        current_best_estimate_.loss_limited_bandwidth;
  }

  // 如果处于丢包受限状态,应用额外的约束
  if (IsInLossLimitedState()) {
    // 在延迟增加窗口内限制估计值的增长
    if (recovering_after_loss_timestamp_.IsFinite() &&
        recovering_after_loss_timestamp_ + config_->delayed_increase_window >
            last_send_time_most_recent_observation_ &&
        best_candidate.loss_limited_bandwidth >
            bandwidth_limit_in_current_window_) {
      best_candidate.loss_limited_bandwidth =
          bandwidth_limit_in_current_window_;
    }

    // 检查估计是否在丢包受限状态下增加
    bool increasing_when_loss_limited = IsEstimateIncreasingWhenLossLimited(
        /*old_estimate=*/current_best_estimate_.loss_limited_bandwidth,
        /*new_estimate=*/best_candidate.loss_limited_bandwidth);
    
    // 如果估计在增加且确认速率有效,使用确认速率约束估计值
    if (increasing_when_loss_limited && IsValid(acknowledged_bitrate_)) {
      double rampup_factor = config_->bandwidth_rampup_upper_bound_factor;
      
      // 如果在保持状态下,使用不同的增长因子
      if (IsValid(last_hold_info_.rate) &&
          acknowledged_bitrate_ <
              config_->bandwidth_rampup_hold_threshold * last_hold_info_.rate) {
        rampup_factor = config_->bandwidth_rampup_upper_bound_factor_in_hold;
      }

      // 应用确认速率约束
      best_candidate.loss_limited_bandwidth =
          std::max(current_best_estimate_.loss_limited_bandwidth,
                   std::min(best_candidate.loss_limited_bandwidth,
                            rampup_factor * (*acknowledged_bitrate_)));
      
      // 如果状态为减少但估计值未变,稍微增加以确保状态切换
      if (loss_based_result_.state == LossBasedState::kDecreasing &&
          best_candidate.loss_limited_bandwidth ==
              current_best_estimate_.loss_limited_bandwidth) {
        best_candidate.loss_limited_bandwidth =
            current_best_estimate_.loss_limited_bandwidth +
            DataRate::BitsPerSec(1);
      }
    }
  }

  // 应用最终边界约束
  DataRate bounded_bandwidth_estimate = DataRate::PlusInfinity();
  if (IsValid(delay_based_estimate_)) {
    bounded_bandwidth_estimate =
        std::max(GetInstantLowerBound(),
                 std::min({best_candidate.loss_limited_bandwidth,
                           GetInstantUpperBound(), delay_based_estimate_}));
  } else {
    bounded_bandwidth_estimate = std::max(
        GetInstantLowerBound(), std::min(best_candidate.loss_limited_bandwidth,
                                         GetInstantUpperBound()));
  }
  
  // 如果配置要求约束最佳候选,并且约束后的估计小于最佳候选,重置估计
  if (config_->bound_best_candidate &&
      bounded_bandwidth_estimate < best_candidate.loss_limited_bandwidth) {
    RTC_LOG(LS_INFO) << "Resetting loss based BWE to " << bounded_bandwidth_estimate.kbps()
                     << "due to loss. Avg loss rate: " << GetAverageReportedLossRatio();
    current_best_estimate_.loss_limited_bandwidth = bounded_bandwidth_estimate;
    current_best_estimate_.inherent_loss = 0;
  } else {
    current_best_estimate_ = best_candidate;
    
    // 确保估计不低于确认速率的下限
    if (config_->lower_bound_by_acked_rate_factor > 0.0) {
      current_best_estimate_.loss_limited_bandwidth =
          std::max(current_best_estimate_.loss_limited_bandwidth,
                  GetInstantLowerBound());
    }
  }

  // 处理保持状态下的估计约束
  if (loss_based_result_.state == LossBasedState::kDecreasing &&
      last_hold_info_.timestamp > last_send_time_most_recent_observation_ &&
      bounded_bandwidth_estimate < delay_based_estimate_) {
    // 确保确认速率是保持速率的下限
    if (config_->lower_bound_by_acked_rate_factor > 0.0) {
      last_hold_info_.rate =
          std::max(GetInstantLowerBound(), last_hold_info_.rate);
    }
    
    // 带宽估计不允许超过保持速率
    loss_based_result_.bandwidth_estimate =
        std::min(last_hold_info_.rate, bounded_bandwidth_estimate);
    return;
  }

  // 确定最终状态
  if (IsEstimateIncreasingWhenLossLimited(
          /*old_estimate=*/loss_based_result_.bandwidth_estimate,
          /*new_estimate=*/bounded_bandwidth_estimate) &&
      CanKeepIncreasingState(bounded_bandwidth_estimate) &&
      bounded_bandwidth_estimate < delay_based_estimate_ &&
      bounded_bandwidth_estimate < max_bitrate_) {
    // 设置增加状态,可能使用填充数据进行探测
    if (config_->padding_duration > TimeDelta::Zero() &&
        bounded_bandwidth_estimate > last_padding_info_.padding_rate) {
      last_padding_info_.padding_rate = bounded_bandwidth_estimate;
      last_padding_info_.padding_timestamp =
          last_send_time_most_recent_observation_;
    }
    loss_based_result_.state = config_->padding_duration > TimeDelta::Zero()
                                   ? LossBasedState::kIncreaseUsingPadding
                                   : LossBasedState::kIncreasing;
  } else if (bounded_bandwidth_estimate < delay_based_estimate_ &&
             bounded_bandwidth_estimate < max_bitrate_) {
    // 设置减少状态
    if (loss_based_result_.state != LossBasedState::kDecreasing &&
        config_->hold_duration_factor > 0) {
      RTC_LOG(LS_INFO) << this << " " << "Switch to HOLD. Bounded BWE: "
                       << bounded_bandwidth_estimate.kbps()
                       << ", duration: " << last_hold_info_.duration.ms();
      last_hold_info_ = {
          .timestamp = last_send_time_most_recent_observation_ +
                       last_hold_info_.duration,
          .duration =
              std::min(kMaxHoldDuration, last_hold_info_.duration *
                                             config_->hold_duration_factor),
          .rate = bounded_bandwidth_estimate};
    }
    last_padding_info_ = PaddingInfo();
    loss_based_result_.state = LossBasedState::kDecreasing;
  } else {
    // 重置保持和填充信息,使用延迟基于的估计
    last_hold_info_ = {.timestamp = Timestamp::MinusInfinity(),
                       .duration = kInitHoldDuration,
                       .rate = DataRate::PlusInfinity()};
    last_padding_info_ = PaddingInfo();
    loss_based_result_.state = LossBasedState::kDelayBasedEstimate;
  }
  
  // 设置最终带宽估计值
  loss_based_result_.bandwidth_estimate = bounded_bandwidth_estimate;

  // 更新延迟增加窗口和带宽限制
  if (IsInLossLimitedState() &&
      (recovering_after_loss_timestamp_.IsInfinite() ||
       recovering_after_loss_timestamp_ + config_->delayed_increase_window <
           last_send_time_most_recent_observation_)) {
    bandwidth_limit_in_current_window_ =
        std::max(kCongestionControllerMinBitrate,
                 current_best_estimate_.loss_limited_bandwidth *
                     config_->max_increase_factor);
    recovering_after_loss_timestamp_ = last_send_time_most_recent_observation_;
  }
}

LossBasedBweV2是WebRCC中一个设计复杂、高度可配置的第二代基于丢包的带宽估计器。它采用最大似然估计原理,结合了信道模型、优化算法、多源信息融合和状态机逻辑,旨在更准确、更稳定地从丢包事件中推断网络带宽,并为拥塞控制决策提供丰富的依据。

相关推荐
NMZH105 小时前
排序算法(全--C语言)
算法·排序算法
正在走向自律5 小时前
WebRTC开启实时通信新时代
webrtc·mediastream·webcodecs·信令·网页即时通信·rtcpeerconne·ice机制
對玛祷至昏5 小时前
算法学习路径
学习·算法·排序算法
Yingye Zhu(HPXXZYY)9 小时前
ICPC 2023 Nanjing R L 题 Elevator
算法
程序员Xu12 小时前
【LeetCode热题100道笔记】二叉树的右视图
笔记·算法·leetcode
笑脸惹桃花13 小时前
50系显卡训练深度学习YOLO等算法报错的解决方法
深度学习·算法·yolo·torch·cuda
阿维的博客日记13 小时前
LeetCode 48 - 旋转图像算法详解(全网最优雅的Java算法
算法·leetcode
GEO_YScsn14 小时前
Rust 的生命周期与借用检查:安全性深度保障的基石
网络·算法
程序员Xu14 小时前
【LeetCode热题100道笔记】二叉搜索树中第 K 小的元素
笔记·算法·leetcode