WebRTC GCC带宽探测原理(一)

前言

  • webrtc中带宽探测是属于拥塞控制中重要的一部分,在webrtc实现中主要由ProbeControllerProbeBitrateEstimatorBitrateProber三大模块组成。
  • ProbeController模块负责生成ProbeClusterConfig也就是探测源信号或者说判断是否需要探测。
  • ProbeBitrateEstimator模块基于twcc feedback依据ProbeClusterConfig源完成对探测带宽的估计计算。
  • BitrateProber结合Pacing模块根据ProbeClusterConfig完成探测包的发送。
  • 本文着重介绍ProbeController模块的实现原理。其综合框架为:
    000.png

探测场景介绍

  • ProbeController模块是整个探测的logic管理器,并对场景进行分类如下:
  • 初始阶段,需要进行带宽探测。
  • 应用编码设置画质码率约束更改,向拥塞控制框架allocate申请码率,会触发探测。
  • GoogCcNetworkController模块在综合基于delay_basedlossbased、以及ack码率后会设置目标估计码率到ProbeController,此时也有可能会触发带宽探测。
  • 过载恢复后会触发探测。
  • 定时周期性探测,默认25Ms
  • 以上场景,如逻辑上需要探测,则会在ProbeController模块生成ProbeClusterConfig探测源数据。

初始阶段探测

001.png
  • 初始阶段带宽探测的触发流程如上图,其代码流程如下:
void RtpTransportControllerSend::MaybeCreateControllers() {
  ...
  initial_config_.constraints.at_time =
      Timestamp::Millis(clock_->TimeInMilliseconds());
  initial_config_.stream_based_config = streams_config_;
  ...
  UpdateControllerWithTimeInterval();
  ...
}
void RtpTransportControllerSend::UpdateControllerWithTimeInterval() {
  RTC_DCHECK(controller_);
  ProcessInterval msg;
  msg.at_time = Timestamp::Millis(clock_->TimeInMilliseconds());
  PostUpdates(controller_->OnProcessInterval(msg));
}
  • 从代码来看初始流程发送在模块初始化阶段,由RtpTransportControllerSend模块发起,经由GoogCcNetworkController模块触发。
NetworkControlUpdate GoogCcNetworkController::OnProcessInterval(
    ProcessInterval msg) {
  NetworkControlUpdate update;
  if (initial_config_) {
    // 在这里触发初始探测
    update.probe_cluster_configs =
        ResetConstraints(initial_config_->constraints);
    ....
    initial_config_.reset();
  }
  return update;
}
  • 在上图中探测的最终实现在ProbeController::SetBitrates函数中。
std::vector<ProbeClusterConfig> ProbeController::SetBitrates(
    DataRate min_bitrate,
    DataRate start_bitrate,
    DataRate max_bitrate,
    Timestamp at_time) {
  // 设置初始码率,并设置估计码率为初始码率
  if (start_bitrate > DataRate::Zero()) {
    start_bitrate_ = start_bitrate;
    estimated_bitrate_ = start_bitrate;
  } else if (start_bitrate_.IsZero()) {
    start_bitrate_ = min_bitrate;
  }

  // The reason we use the variable `old_max_bitrate_pbs` is because we
  // need to set `max_bitrate_` before we call InitiateProbing.
  // 默认情况下max_bitrate不会被设置,为-1,所以最大探测带宽为5Mbps(kDefaultMaxProbingBitrate)
  DataRate old_max_bitrate = max_bitrate_;
  max_bitrate_ =
      max_bitrate.IsFinite() ? max_bitrate : kDefaultMaxProbingBitrate;
  // 默认state_为kInit,所以初始状态会进行探测
  switch (state_) {
    case State::kInit:
      if (network_available_)
        return InitiateExponentialProbing(at_time);
      break;

    case State::kWaitingForProbingResult:
      break;
    // 更新目标需求码率
    case State::kProbingComplete:
      // If the new max bitrate is higher than both the old max bitrate and the
      // estimate then initiate probing.
      if (!estimated_bitrate_.IsZero() && old_max_bitrate < max_bitrate_ &&
          estimated_bitrate_ < max_bitrate_) {
        return InitiateProbing(at_time, {max_bitrate_}, false);
      }
      break;
  }
  return std::vector<ProbeClusterConfig>();
}
std::vector<ProbeClusterConfig> ProbeController::InitiateExponentialProbing(
    Timestamp at_time) {
  ...
  // When probing at 1.8 Mbps ( 6x 300), this represents a threshold of
  // 1.2 Mbps to continue probing.
  // 默认first_exponential_probe_scale为3.0,
  std::vector<DataRate> probes = {config_.first_exponential_probe_scale *
                                  start_bitrate_};
  // 默认second_exponential_probe_scale为6.0
  // 初始阶段使用指数探测
  if (config_.second_exponential_probe_scale &&
      config_.second_exponential_probe_scale.GetOptional().value() > 0) {
    probes.push_back(config_.second_exponential_probe_scale.Value() *
                     start_bitrate_);
  }
  return InitiateProbing(at_time, probes, true);
}
  • 上述函数会生成两个探测码率,第一个为3.0start_bitrate_,第二个为6.0start_bitrate_
  • 由此可看出,webrtcstart_bitrate不宜设置太高,如果设置太高很容易出现探测带宽太高,造成丢包现象,应该根据实际业务,当前目标画质进行配置。
  • 最终调用InitiateProbing生成ProbeClusterConfig集合,这里会生成两个探测族源数据。
std::vector<ProbeClusterConfig> ProbeController::InitiateProbing(
    Timestamp now,
    std::vector<DataRate> bitrates_to_probe,
    bool probe_further) {
  // 1) 新特性,确保目标码率在瓶颈链路带宽或者当前估计码率水位线以下
  // 期望最大探测带宽为max_probe_rate
  // 最大估计链路带宽为std::min(network_estimate, estimated_bitrate_),默认由于network_estimate未实现所以这里得到的是estimated_bitrate_
  // 当前估计带宽
  // skip_if_estimate_larger_than_fraction_of_max为一个小于1的因子
  // 意思是当前估计带宽estimated_bitrate_已经大于skip_if_estimate_larger_than_fraction_of_max * max_probe_rate则完成探测
  if (config_.skip_if_estimate_larger_than_fraction_of_max > 0) {
    // 这里得到链路容量带宽上限(bottleneck link bandwidh),由于network_estimate_为空得到的是正无穷
    DataRate network_estimate = network_estimate_
                                    ? network_estimate_->link_capacity_upper
                                    : DataRate::PlusInfinity();
    // 得到最大期望探测带宽
    DataRate max_probe_rate =
        max_total_allocated_bitrate_.IsZero()
            ? max_bitrate_
            : std::min(max_total_allocated_bitrate_, max_bitrate_);
    // estimated_bitrate_ > config_.skip_if_estimate_larger_than_fraction_of_max * max_probe_rate 探测完成
    // 
    if (std::min(network_estimate, estimated_bitrate_ > ) >
        config_.skip_if_estimate_larger_than_fraction_of_max * max_probe_rate) {
      state_ = State::kProbingComplete;
      min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
      return {};
    }
  }
  // 2) 限制最大探测码率
  // max_total_allocated_bitrate_为应用编码模块向拥塞控制模块allocate的最大码率
  // 默认情况下如果应用握手阶段未设置当前session最大码率约束那么max_bitrate_为默认为5Mbps  
  // 最大探测码率限制在std::min(max_probe_bitrate, max_total_allocated_bitrate_ * 2)以内
  // 也就是说如果你的应用需求码率比较高比如几十Mbps,那么在初始化阶段一定要修改max_bitrate_
  DataRate max_probe_bitrate = max_bitrate_;
  if (max_total_allocated_bitrate_ > DataRate::Zero()) {
    // If a max allocated bitrate has been configured, allow probing up to 2x
    // that rate. This allows some overhead to account for bursty streams,
    // which otherwise would have to ramp up when the overshoot is already in
    // progress.
    // It also avoids minor quality reduction caused by probes often being
    // received at slightly less than the target probe bitrate.
    max_probe_bitrate =
        std::min(max_probe_bitrate, max_total_allocated_bitrate_ * 2);
  }
  // 3) 新特性引入拥塞控制状态对探测进行调整,limit_probe_target_rate_to_loss_bwe和not_probe_if_delay_increased默认未开启
  //    如果当前的拥塞状态为过载,码率处于下降状态(kLossLimitedBweDecreasing),则取消探测
  //    如果当前延迟增加kDelayBasedLimitedDelayIncreased或kRttBasedBackOffHighRtt,同理也取消探测
  //    如果当前正处于升码率状态,如aimd模块判定当前为轻载kLossLimitedBweIncreasing此时调整estimate_capped_bitrate(估计码率的上限)
  //    对于低延迟场景,着重体验的场景,对丢包和延迟容忍度都极低的业务场景,建议默认开启
  //    默认lossbased v2未开启,所以这里默认会是kDelayBasedLimited
  DataRate estimate_capped_bitrate = DataRate::PlusInfinity();
  if (config_.limit_probe_target_rate_to_loss_bwe) {
    switch (bandwidth_limited_cause_) {
      case BandwidthLimitedCause::kLossLimitedBweDecreasing:
        // If bandwidth estimate is decreasing because of packet loss, do not
        // send probes.
        return {};
      // 
      case BandwidthLimitedCause::kLossLimitedBweIncreasing:
        estimate_capped_bitrate =
            std::min(max_probe_bitrate,
                     estimated_bitrate_ * config_.loss_limited_probe_scale);
        break;
      case BandwidthLimitedCause::kDelayBasedLimited:
        break;
      default:
        break;
    }
  }
  if (config_.not_probe_if_delay_increased &&
      (bandwidth_limited_cause_ ==
           BandwidthLimitedCause::kDelayBasedLimitedDelayIncreased ||
       bandwidth_limited_cause_ ==
           BandwidthLimitedCause::kRttBasedBackOffHighRtt)) {
    return {};
  }
  // 4) 新特性,默认未开启,这里假设设置了一个探测间隔,并且network_estimate_网络状态估计不为空
  //    截止当前版本源码network_estimate_依然为空
  if (config_.network_state_estimate_probing_interval->IsFinite() &&
      network_estimate_ && network_estimate_->link_capacity_upper.IsFinite()) {
    if (network_estimate_->link_capacity_upper.IsZero()) {
      RTC_LOG(LS_INFO) << "Not sending probe, Network state estimate is zero";
      return {};
    }
    // config_.network_state_probe_scale默认为1.0
    // 这里是调整估计上限码率取estimate_capped_bitrate、max_probe_bitrate、和链路容量上限码率 * config_.network_state_probe_scale
    // 的最小值,这里也是一个保守操作,避免因探测不当导致码率溢出,超过最大链路容量
    estimate_capped_bitrate = std::min(
        {estimate_capped_bitrate, max_probe_bitrate,
         std::max(estimated_bitrate_, network_estimate_->link_capacity_upper *
                                          config_.network_state_probe_scale)});
  }
  // 5) 生成探测族源数据,基于用户预设的探测码率、estimate_capped_bitrate以及max_probe_bitrate
  std::vector<ProbeClusterConfig> pending_probes;
  for (DataRate bitrate : bitrates_to_probe) {
    RTC_DCHECK(!bitrate.IsZero());
    // 保守设置,取最小值,避免探测溢出超过瓶颈链接容量
    // 取estimate_capped_bitrate、max_probe_bitrate和bitrate的最小值
    bitrate = std::min(bitrate, estimate_capped_bitrate);
    if (bitrate > max_probe_bitrate) {
      bitrate = max_probe_bitrate;
      probe_further = false;
    }

    ProbeClusterConfig config;
    config.at_time = now;
    config.target_data_rate = bitrate;
    // 若用户设置了探测间隔,则使用用户设置,否则使用默认15Ms
    if (network_estimate_ &&
        config_.network_state_estimate_probing_interval->IsFinite()) {
      config.target_duration = config_.network_state_probe_duration;
    } else {
      config.target_duration = config_.min_probe_duration;
    }
    // 最小探测包个数默认5个
    config.target_probe_count = config_.min_probe_packets_sent;
    // 设置探测ID,在探测码率估计模块中会使用到
    config.id = next_probe_cluster_id_;
    next_probe_cluster_id_++;
    MaybeLogProbeClusterCreated(event_log_, config);
    // 插入到探测源数据集合当中
    pending_probes.push_back(config);
  }
  // 设置本次生成探测包的时间戳为Now,后面会用到
  time_last_probing_initiated_ = now;
  // 如果目标码率设置很大,每次探测未到达目标设置可以设置probe_further更进一步探测
  if (probe_further) {
    state_ = State::kWaitingForProbingResult;
    // Dont expect probe results to be larger than a fraction of the actual
    // probe rate.
    // further_probe_threshold = 0.7
    // 假设需要继续上探,这里设置了下一次上探的最小码率为min_bitrate_to_probe_further_
    min_bitrate_to_probe_further_ =
        std::min(estimate_capped_bitrate, (*(bitrates_to_probe.end() - 1))) *
        config_.further_probe_threshold;
  } else {
    // 否则设置状态为State::kProbingComplete,这里表示不需要等到探测成功就可以设置状态
    state_ = State::kProbingComplete;
    // 设置为正无穷大...
    min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
  }
  return pending_probes;
}
  • 该函数负责生成std::vector<ProbeClusterConfig>探测族源数据信息,包括探测ID、探测包数量,最小探测间隔等信息。
  • 在新版本的webrtc中引入了几个新特性,其中特性1),属于保守探测,判断当前的估计带宽是否已经大于最大探测码率的某个比例,如果是则表示探测完成,这样的好处有,其一是避免带宽浪费,其二是也可以降低一些不必要的丢包。
  • 特性3)引入拥塞控制状态进行调整和控制,当识别到强求延迟增加、处于过载降码率的情况下直接放弃探测。

设置目标码率触发探测

  • 设置目标码率约束的模块基本调用流程如下图:


    002.png
  • 实际上初始化探测和用户层设置目标码率的约束复用一个函数入口栈ProbeController::SetBitrates()
  • 假设在应用初始化的时候通过sdp设置了目标码率约束等信息,那么在初始阶段这些码率约束信息也会被带入。
  • 这里不同的地方在于,假设用户更新目标码率信息的时候,此时的ProbeController模块的状态机处于kProbingComplete状态,那有可能会继续触发探测。
  • 比较尴尬的是假设此时状态机的状态为kWaitingForProbingResult,也就是上次探测的因为各种原因处于等待状态,那么会被直接返回,好在这个函数的调用会更新max_bitrate_,否则会出现bug。。
std::vector<ProbeClusterConfig> ProbeController::SetBitrates(
    DataRate min_bitrate,
    DataRate start_bitrate,
    DataRate max_bitrate,
    Timestamp at_time) {
  ....
  DataRate old_max_bitrate = max_bitrate_;
  max_bitrate_ =
      max_bitrate.IsFinite() ? max_bitrate : kDefaultMaxProbingBitrate;      
  switch (state_) {
    ....
    case State::kProbingComplete:
      // If the new max bitrate is higher than both the old max bitrate and the
      // estimate then initiate probing.
      // 这里的条件是当前带宽估计码率不为0,并且当前的估计码率比用户预设的要小,则进行探测,这里并没有进行指数探测
      // 并且是不进行更多探测further为false.
      if (!estimated_bitrate_.IsZero() && old_max_bitrate < max_bitrate_ &&
          estimated_bitrate_ < max_bitrate_) {
        return InitiateProbing(at_time, {max_bitrate_}, false);
      }
      break;
  }
  return std::vector<ProbeClusterConfig>();
}
  • 最终是调用InitiateProbing()函数生成探测族源数据,经由BitrateProber结合Pacing模块根据ProbeClusterConfig完成探测包的发送。
  • 注意这里是设置的拥塞控制框架的约束。

各路stream allocate 带宽

  • OnMaxTotalAllocatedBitrate函数在默认实现中只要当处于alr应用受限阶段才会触发。
  • 这个函数一般在用户切换画质,对编码器流级别的码率进行更新的时候会用到。
  • 在初始化阶段假设app层手动设置了编码器的BitrateAllocation,在初始化探测后也会进行探测。
std::vector<ProbeClusterConfig> ProbeController::OnMaxTotalAllocatedBitrate(
    DataRate max_total_allocated_bitrate,
    Timestamp at_time) {
  // 这里表示触发了应用受限
  const bool in_alr = alr_start_time_.has_value();
  const bool allow_allocation_probe = in_alr;
  // 探测条件为:
  // 1) 当前状态为State::kProbingComplete
  // 2) 前后两次max_total_allocated_bitrate不相等
  // 3) 当前估计带宽小于最大码率约束
  // 4) 当前估计带宽小于编码模块申请的最大码率
  // 5) 当前处于应用受限状态
  if (config_.probe_on_max_allocated_bitrate_change &&
      state_ == State::kProbingComplete &&
      max_total_allocated_bitrate != max_total_allocated_bitrate_ &&
      estimated_bitrate_ < max_bitrate_ &&
      estimated_bitrate_ < max_total_allocated_bitrate &&
      allow_allocation_probe) {
    max_total_allocated_bitrate_ = max_total_allocated_bitrate;

    if (!config_.first_allocation_probe_scale)
      return std::vector<ProbeClusterConfig>();
    // 第一次探测为max_total_allocated_bitrate的一倍
    DataRate first_probe_rate = max_total_allocated_bitrate *
                                config_.first_allocation_probe_scale.Value();
    // 默认为正无穷
    DataRate probe_cap = config_.allocation_probe_max.Get();
    first_probe_rate = std::min(first_probe_rate, probe_cap);
    std::vector<DataRate> probes = {first_probe_rate};
    // 第二次探测为max_total_allocated_bitrate的两倍
    if (config_.second_allocation_probe_scale) {
      DataRate second_probe_rate =
          max_total_allocated_bitrate *
          config_.second_allocation_probe_scale.Value();
      second_probe_rate = std::min(second_probe_rate, probe_cap);
      if (second_probe_rate > first_probe_rate)
        probes.push_back(second_probe_rate);
    }
    return InitiateProbing(at_time, probes,
                           config_.allocation_allow_further_probing.Get());
  }
  max_total_allocated_bitrate_ = max_total_allocated_bitrate;
  return std::vector<ProbeClusterConfig>();
}
  • 本文总结了该探测可能触发的两个经典场景如下逻辑流程图:


    003.png
  • 场景1为当初始化的时候我们手动对编码器进行了一些配置,比如限制编码器最大码率等。
  • 场景2其实和google webrtc原生的设计有点出入,我们在正常的业务场景当中,实际上我们是不希望造成带宽浪费的,同时可能客户端也会涉及到各种不同的画质档位如标清、高清、超清、蓝光这些,而每一个档位我们希望拥塞控制引擎的最大上限也不会是网络实际带宽那么大,我们更希望的是这个带宽上限更加接近我们的画质档位,同时每个不同画质档位会想拥塞框架引擎申请一个合理的码率上限。
  • 实际应用中OnMaxTotalAllocatedBitrate()函数的实现逻辑可能需要进行稍加修改,因为默认实现是一定要处于alr状态下才进行上探,有时候我们直接从标清切换到蓝光,业务上压根不会这么考虑。
  • 所以如果对这一块逻辑思路不是很清楚,我们在实际应用中很有可能就会出现切换档位,码率确上不去的问题。

网络中断或者恢复触发探测

std::vector<ProbeClusterConfig> ProbeController::OnNetworkAvailability(
    NetworkAvailability msg) {
  network_available_ = msg.network_available;

  if (!network_available_ && state_ == State::kWaitingForProbingResult) {
    state_ = State::kProbingComplete;
    min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
  }

  if (network_available_ && state_ == State::kInit && !start_bitrate_.IsZero())
    return InitiateExponentialProbing(msg.at_time);
  return std::vector<ProbeClusterConfig>();
}
  • 网络有效、并且状态为初始状态的情况下才会触发探测。

设置估计码率

void GoogCcNetworkController::MaybeTriggerOnNetworkChanged(
    NetworkControlUpdate* update,
    Timestamp at_time) {
  ...
  DataRate loss_based_target_rate = bandwidth_estimation_->target_rate();
  .....
    auto probes = probe_controller_->SetEstimatedBitrate(
        loss_based_target_rate,
        GetBandwidthLimitedCause(
            bandwidth_estimation_->loss_based_state(),
            bandwidth_estimation_->IsRttAboveLimit(),
            delay_based_bwe_->last_state(),
            probe_controller_->DontProbeIfDelayIncreased()),
        at_time);
    update->probe_cluster_configs.insert(update->probe_cluster_configs.end(),
                                         probes.begin(), probes.end());
    ...
  }
}
  • GoogCcNetworkController模块每次拥塞事件,估计完码率后调用,会将估计码率告知给ProbeController模块。
  • 在当前较新的版本中会将当前的拥塞状态传递给ProbeController模块
std::vector<ProbeClusterConfig> ProbeController::SetEstimatedBitrate(
    DataRate bitrate,
    BandwidthLimitedCause bandwidth_limited_cause,
    Timestamp at_time) {
  //记录当前拥塞状态
  // kLossLimitedBweIncreasing:丢包率在某个阈值以下,码率递增状态(需要基于LossbasedV2)
  // kLossLimitedBweDecreasing:丢包在某个阈值以上,码率递减状态(需要基于LossbasedV2)
  // kDelayBasedLimitedDelayIncreased:基于delay_based,kBwOverusing或kBwUnderusing
  // kRttBasedBackOffHighRtt:基于RttBasedBackoff模块,当前RTT大于某个阈值默认3秒
  bandwidth_limited_cause_ = bandwidth_limited_cause;
  // 如果当前的估计码率突然低于上次估计码率的0.66倍,kBitrateDropThreshold为0.66
  // 则设置码率瞬降的时间戳为at_time,并记录码率瞬降前的码率为上一次的估计码率
  if (bitrate < kBitrateDropThreshold * estimated_bitrate_) {
    time_of_last_large_drop_ = at_time;
    bitrate_before_last_large_drop_ = estimated_bitrate_;
  }
  // 更新估计码率
  estimated_bitrate_ = bitrate;
  // 如果当前状态为kWaitingForProbingResult才有机会触发探测
  if (state_ == State::kWaitingForProbingResult) {
    // Continue probing if probing results indicate channel has greater
    // capacity.
    DataRate network_state_estimate_probe_further_limit =
        config_.network_state_estimate_probing_interval->IsFinite() &&
                network_estimate_
            ? network_estimate_->link_capacity_upper *
                  config_.further_probe_threshold
            : DataRate::PlusInfinity();
    RTC_LOG(LS_INFO) << "Measured bitrate: " << bitrate
                     << " Minimum to probe further: "
                     << min_bitrate_to_probe_further_ << " upper limit: "
                     << network_state_estimate_probe_further_limit;
    // 当前的估计码率大于min_bitrate_to_probe_further_(最后一次估计码率的0.7倍)
    if (bitrate > min_bitrate_to_probe_further_ &&
        bitrate <= network_state_estimate_probe_further_limit) {
      return InitiateProbing(
          at_time, {config_.further_exponential_probe_scale * bitrate}, true);
    }
  }
  return {};
}
  • 首先是更新estimated_bitrate_当前的估计码率
  • 其次是若需要继续探测,并且当前的估计码率比min_bitrate_to_probe_further_0.7倍的上次估计码率。

过载恢复进行探测

NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback(
    TransportPacketsFeedback report) {
  ....
  recovered_from_overuse = result.recovered_from_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());
  }
  ....
  return update;
}
  • 当基于DelayBased模块检测到过载恢复的时候,会触发带宽探测,为了更快的提升码率。
std::vector<ProbeClusterConfig> ProbeController::RequestProbe(
    Timestamp at_time) {
  // Called once we have returned to normal state after a large drop in
  // estimated bandwidth. The current response is to initiate a single probe
  // session (if not already probing) at the previous bitrate.
  //
  // If the probe session fails, the assumption is that this drop was a
  // real one from a competing flow or a network change.
  bool in_alr = alr_start_time_.has_value();
  // 这里是3秒间隔阈值,距离应用受限结束的时间为3秒以内
  bool alr_ended_recently =
      (alr_end_time_.has_value() &&
       at_time - alr_end_time_.value() < kAlrEndedTimeout);
  // 通过"WebRTC-BweRapidRecoveryExperiment/Enabled"开启in_rapid_recovery_experiment_,默认为false
  // 1) 当前处于应用首先状态
  // 2) 当前处于距离应用受限结束的时间为3秒以内
  // 3) 开启快速回复的试验
  // 4) 当前的状态机的状态处于kProbingComplete,也就是探测完成
  if (in_alr || alr_ended_recently || in_rapid_recovery_experiment_) {
    if (state_ == State::kProbingComplete) {
      // 上一中记录了码率瞬降前的估计码率,kProbeFractionAfterDrop=0.85
      // 这里得到一个建议的探测码率,为码率瞬降前的码率的0.85倍
      DataRate suggested_probe =
          kProbeFractionAfterDrop * bitrate_before_last_large_drop_;
      // kProbeUncertainty = 0.05
      // 这里是得到一个最小期望探测码率,为0.95倍的建议探测码率
      DataRate min_expected_probe_result =
          (1 - kProbeUncertainty) * suggested_probe;
      // 距离上次码率瞬降的时间间隔
      TimeDelta time_since_drop = at_time - time_of_last_large_drop_;
      // 距离上次探测降低的时间间隔,Ret()的时候为设置last_bwe_drop_probing_time_为Now
      // 所以假设过载前没有出现drop_probing,那这里得到的time_since_probe会是一个无穷,意思和上次因为bwe降低导致的上探之间的间隔
      TimeDelta time_since_probe = at_time - last_bwe_drop_probing_time_;
      // kMinTimeBetweenAlrProbes = TimeDelta::Seconds(5)
      // kBitrateDropTimeout = TimeDelta::Seconds(5)
      // 5) 最小期望探测码率比当前的估计码率大
      // 6) 上次码率因过载等导致巨降的时间到当前的间隔小于5秒
      // 7) 和上次因为过载等码率导致降低恢复后上探的时间间隔要大于5秒,这里也说明了当过载恢复的时候,主动上探的间隔至少5秒
      if (min_expected_probe_result > estimated_bitrate_ &&
          time_since_drop < kBitrateDropTimeout &&
          time_since_probe > kMinTimeBetweenAlrProbes) {
        RTC_LOG(LS_INFO) << "Detected big bandwidth drop, start probing.";
        // Track how often we probe in response to bandwidth drop in ALR.
        ....
        // 设置该变量,这就说明了,这个变量一般都是在过载恢复的时候,主动上探的时候对其赋值
        last_bwe_drop_probing_time_ = at_time;
        return InitiateProbing(at_time, {suggested_probe}, false);
      }
    }
  }
  return std::vector<ProbeClusterConfig>();
}

周期性探测

  • 周期性探测只在alr应用受限状态下会触发。
  • 比如在某些静态页面,如果一直处于静态页面,此时编码出来的数据量极少,如果长时间处于这种状态,就会导致Ack码率计算偏低,从而错误的估计出较低的目标码率,所以需要有alr应用受限处理,来避免这种误判
  • 当应用受限的时候会适当的补充一下padding包来进行发送,从而确保带宽估计的准确性。
NetworkControlUpdate GoogCcNetworkController::OnProcessInterval(
    ProcessInterval msg) {
  NetworkControlUpdate update;
  ...
  absl::optional<int64_t> start_time_ms =
      alr_detector_->GetApplicationLimitedRegionStartTime();
  probe_controller_->SetAlrStartTimeMs(start_time_ms);

  auto probes = probe_controller_->Process(msg.at_time);
  update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
                                      probes.begin(), probes.end());

  ....
  return update;
}
bool ProbeController::TimeForAlrProbe(Timestamp at_time) const {
  if (enable_periodic_alr_probing_ && alr_start_time_) {
    Timestamp next_probe_time =
        std::max(*alr_start_time_, time_last_probing_initiated_) +
        config_.alr_probing_interval;
    return at_time >= next_probe_time;
  }
  return false;
}
  • TimeForAlrProbe用于计算因alr探测的时间间隔阈值,只有当前时间和上次探测或者alr开始的时间间隔超过某个阈值的时候才让开始探测。
  • config_.alr_probing_interval的默认值为5秒。
  • 这说明只有当前时间和上次探测或者alr开始的时间间隔超过5秒的时候才让开始探测。
std::vector<ProbeClusterConfig> ProbeController::Process(Timestamp at_time) {
  // InitiateProbing正常生成探测族源数据的时候会对time_last_probing_initiated_进行赋值
  // 如果当前时间 - 上一次生成探测源数据的间隔大于1秒,kMaxWaitingTimeForProbingResult为一秒,则判定为超时
  // 此时需要设置状态机的状态为State::kProbingComplete,以便后续逻辑能正常处理
  if (at_time - time_last_probing_initiated_ >
      kMaxWaitingTimeForProbingResult) {
    if (state_ == State::kWaitingForProbingResult) {
      RTC_LOG(LS_INFO) << "kWaitingForProbingResult: timeout";
      state_ = State::kProbingComplete;
      min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
    }
  }
  // 如果当前的估计码率为0或者当前状态不为kProbingComplete则忽略这次定时处理
  if (estimated_bitrate_.IsZero() || state_ != State::kProbingComplete) {
    return {};
  }
  // 距离上次探测或alr的间隔5秒以上
  if (TimeForAlrProbe(at_time) || TimeForNetworkStateProbe(at_time)) {
    // alr的上探系数为2.0,在上次估计码率的基础上进行2.0倍探测
    return InitiateProbing(
        at_time, {estimated_bitrate_ * config_.alr_probe_scale}, true);
  }
  return std::vector<ProbeClusterConfig>();
}
  • 周期性探测能使得码率估计更加稳定,但是也有弊病,实际上带宽探测有点浪费带宽,同时假设配置不合理,也会造成一定的额外丢包。
  • 所以在实际的应用中,可能需要大家正确的优化该模块,尽量和实际的业务场景进行匹配,尽量降低带宽浪费和避免额外丢包。

总结

  • 本文通过深入学习ProbeController模块,深入学习webrtc中探测模块的逻辑控制。
  • 同时通过本文的学习,也能认识到在google原生的实现过程中,主要还是基于低码率场景的,对于高码率场景有些策略或者是参数配置,还是有点不大友好,需要进行调整。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容