WebRTC动态码率-AlrDetector原理

前言:

  • AlrDetector即(应用受限区域探测器,Application limited region detector)模块,该模块利用某段时间值,以及这段时间发送的字节数判断当前输出网络流量是否受限。这些限制主要跟应用程序本身输出网络流量的能力有关,例如编码器性能,不能编码出设置的目标码率
  • WebRTC GCC(Google Congestion Control) 模块中首先根据rtp包发送评估出一个实际码率(target_bitrate_bps),此时理论上程序会按照该码率进行数据发送,但是在实际的运行过程中,由于网络环境的复杂性,或者硬件的性能等一系列的原因如,编码速度跟不上,假设编码出的数据码率比(target_bitrate_bps)要低,使得数据发送码率低于目标码率,带宽利用率不足,发送码率占最大预算码率值比例低于某个值的话Alr(Application limited region)就会被触发了,恢复到某个值以上,Alr会停止。

工作流程

1)AlrDetector相关数据结构及其初始化

AlrDetector-001.png
struct AlrDetectorConfig {
  // Sent traffic ratio as a function of network capacity used to determine
  // application-limited region. ALR region start when bandwidth usage drops
  // below kAlrStartUsageRatio and ends when it raises above
  // kAlrEndUsageRatio. NOTE: This is intentionally conservative at the moment
  // until BW adjustments of application limited region is fine tuned.
  double bandwidth_usage_ratio = 0.65;
  double start_budget_level_ratio = 0.80;
  double stop_budget_level_ratio = 0.50;
  std::unique_ptr Parser();
};
  • 配置结构,根据其注释可得当bandwidth降低到kAlrStartUsageRatio的时候和bandwidth高于kAlrEndUsageRatio的时候会出发ALR。
AlrDetector::AlrDetector(AlrDetectorConfig config, RtcEventLog* event_log)
    : conf_(config), alr_budget_(0, true), event_log_(event_log) {}

AlrDetector::AlrDetector(const WebRtcKeyValueConfig* key_value_config)
    : AlrDetector(GetConfigFromTrials(key_value_config), nullptr) {}

AlrDetector::AlrDetector(const WebRtcKeyValueConfig* key_value_config,
                         RtcEventLog* event_log)
    : AlrDetector(GetConfigFromTrials(key_value_config), event_log) {}
  • 默认初始化设置 IntervalBudget的can_build_up_underuse_成员变量为true。
  • IntervalBudget根据输入的评估码率,计算当前还能发送多少数据。

2)AlrDetector主动函数调用栈及其原理

AlrDetector-002.png
  • GoogCcNetworkController模块中经过相应的算法处理,判断当前的网络状态,在MaybeTriggerOnNetworkChanged函数中会评估出一个新码率。
  • 将新码率通过AlrDetector::SetEstimatedBitrate()函数作用到AlrDetector模块,同时计算目标码率。
  • 然后调用IntervalBudget::set_target_rate_kbps()将目标码率作用到IntervalBudget
  • 最终IntervalBudget会根据目标码率更新预算。
  • 其代码如下:
void AlrDetector::SetEstimatedBitrate(int bitrate_bps) {
  RTC_DCHECK(bitrate_bps);
  int target_rate_kbps =
      static_cast(bitrate_bps) * conf_.bandwidth_usage_ratio / 1000;
  alr_budget_.set_target_rate_kbps(target_rate_kbps);
}
  • 现假设bitrate_bps为300kbps,bandwidth_usage_ratio = 0.8,bandwidth_usage_ratio:0.8 start_budget_level_ratio:0.4 stop_budget_level_ratio:-0.6
  • 经过alr处理评估目标码率为300000 * 0.8 / 1000 = 240kbps
void IntervalBudget::set_target_rate_kbps(int target_rate_kbps) {
  target_rate_kbps_ = target_rate_kbps;
  max_bytes_in_budget_ = (kWindowMs * target_rate_kbps_) / 8;
  bytes_remaining_ = std::min(std::max(-max_bytes_in_budget_, bytes_remaining_),
                              max_bytes_in_budget_);
}
  • 在IntervalBudget中根据目标码率配合时间窗口进行发送预算。
  • 按照上面的假设经计算得出max_bytes_in_budget_按照500ms最大时间窗口进行最大预算为15000字节。
  • 计算剩余预算,bytes_remaining_首次默认情况下为0,那么经过赋值后本次剩余的预算为0(初始值)。
namespace {
constexpr int64_t kWindowMs = 500;
}

3)AlrDetector函数回调栈及其原理

AlrDetector-003.png
  • 静paced模块发送rtp包时,发送成功后会进行发送成功回调,经call模块最后作用到GoogCcNetworkController模块
  • 最后在AlrDetector模块中根据本次发送的数据大小进行发送预算代码如下:
void AlrDetector::OnBytesSent(size_t bytes_sent, int64_t send_time_ms) {
  if (!last_send_time_ms_.has_value()) {
    last_send_time_ms_ = send_time_ms;
    // Since the duration for sending the bytes is unknwon, return without
    // updating alr state.
    return;
  }
  int64_t delta_time_ms = send_time_ms - *last_send_time_ms_;
  last_send_time_ms_ = send_time_ms;

  alr_budget_.UseBudget(bytes_sent);
  alr_budget_.IncreaseBudget(delta_time_ms);
  bool state_changed = false;
  if (alr_budget_.budget_ratio() > conf_.start_budget_level_ratio &&
      !alr_started_time_ms_) {
    alr_started_time_ms_.emplace(rtc::TimeMillis());
    state_changed = true;
  } else if (alr_budget_.budget_ratio() < conf_.stop_budget_level_ratio &&
             alr_started_time_ms_) {
    state_changed = true;
    alr_started_time_ms_.reset();
  }
  if (event_log_ && state_changed) {
    event_log_->Log(
        std::make_unique(alr_started_time_ms_.has_value()));
  }
}
  • 首先得到时延(当前次发送时间-上一次发送的时间),并更新上次发送时间供下一次使用。
  • 现在假设每隔10ms发送一次数据,并且以带宽利用率的90%进行数据发送,将码率和时间间隔转换成实际发送的字节数如下:
  • kEstimatedBitrateBps * usage_percentage_ * kTimeStepMs / (8 * 100 * 1000) ,其中usage_percentage_ 为90,kTimeStepMs 为10ms,kEstimatedBitrateBps为300000bps 得到每隔10ms将发送337字节的数据。
  • 那么首次发送bytes_sent = 337,delta_time_ms = 10,后续以该速度进行发送。
  • 调用alr_budget_.UseBudget(bytes_sent)对剩余要发送的数据量进行更新。
  • 调用alr_budget_.IncreaseBudget(bytes_sent)来增加预算。
void IntervalBudget::UseBudget(size_t bytes) {
  bytes_remaining_ = std::max(bytes_remaining_ - static_cast(bytes),
                              -max_bytes_in_budget_);
}
  • 实际本次发送了多少字节,用bytes_remaining_和bytes相减然后和最大预算的反值取最大
void IntervalBudget::IncreaseBudget(int64_t delta_time_ms) {
  int64_t bytes = target_rate_kbps_ * delta_time_ms / 8;
  if (bytes_remaining_ < 0 || can_build_up_underuse_) {
    // We overused last interval, compensate this interval.
    bytes_remaining_ = std::min(bytes_remaining_ + bytes, max_bytes_in_budget_);
  } else {
    // If we underused last interval we can't use it this interval.
    bytes_remaining_ = std::min(bytes, max_bytes_in_budget_);
  }
}
  • 按照目标码率(240kbps),上面假设这个目标码率进行预算增加

  • 最后调用alr_budget_.budget_ratio()和start_budget_level_ratio以及stop_budget_level_ratio进行比较

double IntervalBudget::budget_ratio() const {
  if (max_bytes_in_budget_ == 0)
    return 0.0;
  return static_cast(bytes_remaining_) / max_bytes_in_budget_;
}
  • budget_ratio()表示剩余要发送的数据占最大预算发送数据max_bytes_in_budget_的比例。
  • 回到AlrDetector::OnBytesSent()函数如果IntervalBudget::budget_ratio()大于start_budget_level_ratio,即剩余数据还有很多,发送码率低了,带宽没充分利用,Alr将被触发,若IntervalBudget::budget_ratio()小于stop_budget_level_ratio时,alr_started_time_ms_将被重置,Alr停止。
  • alr如果被触发则更新alr_started_time_ms_为rtc::TimeMillis(),被设置成当前时间。
  • 最终GoogCcNetworkController模块会通过调用alr模块的GetApplicationLimitedRegionStartTime()函数来得到受限区域的时间,进而进行新的码率估计。
  • 该函数在GoogCcNetworkController模块的三个地方被调用:
NetworkControlUpdate GoogCcNetworkController::OnProcessInterval(
    ProcessInterval msg) {
    ...
  bandwidth_estimation_->UpdateEstimate(msg.at_time);
  absl::optional start_time_ms =
      alr_detector_->GetApplicationLimitedRegionStartTime();
  probe_controller_->SetAlrStartTimeMs(start_time_ms);    
}
NetworkControlUpdate GoogCcNetworkController::OnSentPacket(
    SentPacket sent_packet) {
  alr_detector_->OnBytesSent(sent_packet.size.bytes(),
                             sent_packet.send_time.ms());
  acknowledged_bitrate_estimator_->SetAlr(
      alr_detector_->GetApplicationLimitedRegionStartTime().has_value());
  .....
}

NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback(
    TransportPacketsFeedback report) {
    
  absl::optional alr_start_time =
      alr_detector_->GetApplicationLimitedRegionStartTime();
}
  • 以上分别做了什么?后续进行分析

4)总结:

  • AlrDetector模块的核心作用是根据每次数据的发送的字节数,以500ms为时间窗口,以及实际的发送码率,对当前网络带宽是否被有效利用或是否已经发送过载,进行检测。其最终的产物是一个时间区域的起点(受限区域起点)。
  • 同理GoogCcNetworkController模块在其工作流程中会通过调用AlrDetector模块的GetApplicationLimitedRegionStartTime()来获取受限时间起点时间是否触发,如果触发则会进行新的码率估计。

你可能感兴趣的:(WebRTC动态码率-AlrDetector原理)