拥塞控制技术的笔记三: TWCC 在 libwebrtc 中的实现

Posted on Sun 13 February 2022 in Journal

Abstract 拥塞控制技术的笔记三: TWCC 在 libwebrtc 中的实现
Authors Walter Fan
 Category     learning note  
Status WIP
Updated 2022-2-10

构建 libwebrtc

  1. 安装 Chromium 软件库工具.

参见 * WebRTC 开发依赖软件 webrtc-prerequisite-sw * `安装 WebRTC 开发工具 webrtc-depot-tools

  1. 下载 WebRTC 源码
   $ mkdir webrtc-checkout
   $ cd webrtc-checkout
   $ fetch --nohooks webrtc
   $ gclient sync --force
  1. 更新源码到你自己的分支
   $ git checkout main
   $ git pull origin main
   $ gclient sync
   $ git checkout my-branch
   $ git merge main
  1. 构建

先要安装 ninja 构建工具 ninja-tool 这一构建工具, 通过它来生成构建脚本

在 Linux 系统上,比较简单的方法是运行 ./build/install-build-deps.sh

   $ cd src
   $ python build/util/lastchange.py build/util/LASTCHANGE
   # generate project files using the defaults (Debug build)
   $ gn gen out/Default
   # clean all build artifacts in a directory but leave the current GN configuration untouched
   $ gn clean out/Default
   $ ninja -C out/Default

在 windows 系统上,建议安装 visual studio 和 windows 10 SDK

注意:

1)一定要在系统设置中选择 Windows SDK , 再选择修改,安装 debugging tool) 2)为了使用本地安装的 visual studio, 需要先设置一下环境变量 set DEPOT_TOOLS_WIN_TOOLCHAIN=0

   gn gen --ide=vs out\Default

然后用 visual studio 打开 out\Default\all.sln

主要模块

拥塞控制的相关代码主要存在于 webrtc 代码的以下两个模块中

  • modules/remote_bitrate_estimator 基于 REMB 的旧版本,基本已经废弃了,其中几个类在新版本中也有使用
  • congestion_controller 基于 Transport Wide CC 的新版本,新旧版本中接收端的估算逻辑移到了发送端,其中的卡尔曼滤波算法也改成了线性回归算法

还有相关联的一些模块,例如:

  • modules/pacing: 控制发送速率
  • modules/rtp_rtcp: 支持 RTP 协议
  • modules/video_coding: 控制编码速率

主要接口

NetworkControllerInterface 是拥塞控制的主要,其实现类是 GoogCcNetworkController

method parameter description
OnNetworkAvailability NetworkAvailability 当网络连接有效或无效时
OnNetworkRouteChange NetworkRouteChange 当网络地址更改时
OnProcessInterval ProcessInterval 定时回调,以检查网络
OnRemoteBitrateReport RemoteBitrateReport 当收到 REMB RTCP 消息时回调
OnRoundTripTimeUpdate RoundTripTimeUpdate 当 RTT 更改时回调(可通过 RTCP RR)
OnSentPacket SentPacket 当发出一个 RTP 包时
OnReceivedPacket ReceivedPacket 当收到一个 RTP 包时
OnStreamsConfig StreamsConfig 当有媒体流相关的配置更新时
OnTargetRateConstraints TargetRateConstraints 当目标速率约束更改时
OnTransportLossReport 当收到 TransportLossReport 时
OnTransportPacketsFeedback TransportPacketsFeedback 当收到 TransportPacketsFeedback 时
OnNetworkStateEstimate NetworkStateEstimate 当网络状态估计更新时,还在开发中

主要流程

带宽探测 Bandwidth Probe

相关类有:

  • class ProbeController 控制在什么时候如何进行带宽探测
  • class ProbeBitrateEstimator 根据探测包的反馈结果进行带宽估算 主要方法有:
  • HandleProbeAndEstimateBitrate
  • FetchAndResetLastEstimatedBitrate

主要流程如下,详细分析另外找个时间再说

s=>start: start
e=>end: end
OnNetworkAvailability1=>operation: GoogCcNetworkController::OnNetworkAvailability
OnNetworkAvailability2=>operation: ProbeController::OnNetworkAvailability
is_available=>condition: is network available
InitiateExponentialProbing=>operation: ProbeController::InitiateExponentialProbing
InitiateProbing=>operation: ProbeController::InitiateProbing: 900k, 1.8m

s->OnNetworkAvailability1->OnNetworkAvailability2->is_available
is_available(yes)->InitiateExponentialProbing->InitiateProbing->e
is_available(no)->e

带宽估算 Bandwidth estimate

  • GoogCcNetworkController 是核心类, ## 主要方法是 OnTransportPacketsFeedback 处理传输通道的 RTP 包的状态反馈报告

可以通过 webrtc library 自带的 peerconnection example 和一些单元测试来了解其流程.

在 WebRTC 的 test/scenario 模块中有一个称为 Scenario 的类, 它是一个拥有测试场景所有内容的类。 它创建并保存网络节点、呼叫客户端和媒体流。 它还提供了在运行时改变行为的方法。

由于它始终保持所创建组件的所有权,因此它通常返回的指针是不具有拥有仅的,只能由 Scenario 自己来保持其对象的生命,直到它被销毁。

对于接受配置结构体的方法,一般会提供一个修改函数接口。 这样测试者可以简单地覆盖部分默认的配置。

在发送端的流程大致有

1) RTCP message 接收后由 TransportFeedbackAdapter 解析

absl::optional<TransportPacketsFeedback> TransportFeedbackAdapter::ProcessTransportFeedback(
    const rtcp::TransportFeedback& feedback,
    Timestamp feedback_receive_time) {
  if (feedback.GetPacketStatusCount() == 0) {
    RTC_LOG(LS_INFO) << "Empty transport feedback packet received.";
    return absl::nullopt;
  }

  TransportPacketsFeedback msg;
  msg.feedback_time = feedback_receive_time;

  msg.prior_in_flight = in_flight_.GetOutstandingData(network_route_);
  msg.packet_feedbacks =
      ProcessTransportFeedbackInner(feedback, feedback_receive_time);
  if (msg.packet_feedbacks.empty())
    return absl::nullopt;

  auto it = history_.find(last_ack_seq_num_);
  if (it != history_.end()) {
    msg.first_unacked_send_time = it->second.sent.send_time;
  }
  msg.data_in_flight = in_flight_.GetOutstandingData(network_route_);

  return msg;
}

2) 将 RTCP 消息转化为 TransportPacketsFeedback

struct TransportPacketsFeedback {
  TransportPacketsFeedback();
  TransportPacketsFeedback(const TransportPacketsFeedback& other);
  ~TransportPacketsFeedback();

  Timestamp feedback_time = Timestamp::PlusInfinity();
  Timestamp first_unacked_send_time = Timestamp::PlusInfinity();
  DataSize data_in_flight = DataSize::Zero();
  DataSize prior_in_flight = DataSize::Zero();
  std::vector<PacketResult> packet_feedbacks;

  // Arrival times for messages without send time information.
  std::vector<Timestamp> sendless_arrival_times;

  std::vector<PacketResult> ReceivedWithSendInfo() const;
  std::vector<PacketResult> LostWithSendInfo() const;
  std::vector<PacketResult> PacketsWithFeedback() const;
  std::vector<PacketResult> SortedByReceiveTime() const;
};

可以跑这个单元测试来加深理解

# ./out/Default/modules_unittests --gtest_filter=TransportFeedbackAdapterTest.AdaptsFeedbackAndPopulatesSendTimes


class TransportFeedbackAdapterTest : public ::testing::Test {
 public:
  TransportFeedbackAdapterTest() : clock_(0) {}

  virtual ~TransportFeedbackAdapterTest() {}

  virtual void SetUp() {
    adapter_.reset(new TransportFeedbackAdapter(&clock_));
  }

  virtual void TearDown() { adapter_.reset(); }

 protected:
  void OnReceivedEstimatedBitrate(uint32_t bitrate) {}

  void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks,
                                    int64_t rtt,
                                    int64_t now_ms) {}

  void OnSentPacket(const PacketFeedback& packet_feedback) {
    adapter_->AddPacket(kSsrc, packet_feedback.sequence_number,
                        packet_feedback.payload_size,
                        packet_feedback.pacing_info);
    adapter_->OnSentPacket(packet_feedback.sequence_number,
                           packet_feedback.send_time_ms);
  }

  static constexpr uint32_t kSsrc = 8492;

  SimulatedClock clock_;
  std::unique_ptr<TransportFeedbackAdapter> adapter_;
};

3) Feedback 消息交由拥塞控制器来进行拥塞检测和带宽评估

基于延迟的带宽评估主要入口是下面这个方法 OnTransportPacketsFeedback(TransportPacketsFeedback report) 这个方法相当复杂,先把代码贴出来,详细流程需要再单独剖析

这个方法用到了若干个子类

  • ProbleController, ProbeBitrateEstimatro 用来做初始化时,以及网络吞吐急剧变化的探测和带宽估计
  • AcknowledgedBitrateEstimator 和 BitrateEstimator 用来计算网络上的吞吐量,用到了滑动窗口和卡尔曼滤波
  • SendSideBandwidthEstimation 基于丢包进行带宽的评估, 它同时也参考了 Propagation RTT, 最后还要看下面 DelayBasedBwe 做的基于延迟的估算结果
  • AlrDetector 处理了 Application Limited Region 的检测,并根据配置看要不要启用 Proble
  • DelayBasedBwe 基于延迟的码率估算,通过指数移动平均 EWMV 和 Trendline filter 估算出带宽
  • CongestionWindowPushbackController : 根据当前窗口内的数据用量,对目标码率做进一步的调整
NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback(
    TransportPacketsFeedback report) {
  if (report.packet_feedbacks.empty()) {
    // TODO(bugs.webrtc.org/10125): Design a better mechanism to safe-guard
    // against building very large network queues.
    return NetworkControlUpdate();
  }

  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();

  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) {
    TimeDelta feedback_rtt =
        report.feedback_time - feedback.sent_packet.send_time;
    TimeDelta min_pending_time = feedback.receive_time - max_recv_time;
    TimeDelta propagation_rtt = feedback_rtt - min_pending_time;
    max_feedback_rtt = std::max(max_feedback_rtt, feedback_rtt);
    min_propagation_rtt = std::min(min_propagation_rtt, propagation_rtt);
  }

  if (max_feedback_rtt.IsFinite()) {
    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();
    // TODO(srte): Use time since last unacknowledged packet.
    bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time,
                                                min_propagation_rtt);
  }
  if (packet_feedback_only_) {
    if (!feedback_max_rtts_.empty()) {
      int64_t sum_rtt_ms = std::accumulate(feedback_max_rtts_.begin(),
                                           feedback_max_rtts_.end(), 0);
      int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size();
      if (delay_based_bwe_)
        delay_based_bwe_->OnRttUpdate(TimeDelta::Millis(mean_rtt_ms));
    }

    TimeDelta feedback_min_rtt = TimeDelta::PlusInfinity();
    for (const auto& packet_feedback : feedbacks) {
      TimeDelta pending_time = packet_feedback.receive_time - max_recv_time;
      TimeDelta rtt = report.feedback_time -
                      packet_feedback.sent_packet.send_time - pending_time;
      // Value used for predicting NACK round trip time in FEC controller.
      feedback_min_rtt = std::min(rtt, feedback_min_rtt);
    }
    if (feedback_min_rtt.IsFinite()) {
      bandwidth_estimation_->UpdateRtt(feedback_min_rtt, report.feedback_time);
    }

    expected_packets_since_last_loss_update_ +=
        report.PacketsWithFeedback().size();
    for (const auto& packet_feedback : report.PacketsWithFeedback()) {
      if (!packet_feedback.IsReceived())
        lost_packets_since_last_loss_update_ += 1;
    }
    if (report.feedback_time > next_loss_update_) {
      next_loss_update_ = report.feedback_time + kLossUpdateInterval;
      bandwidth_estimation_->UpdatePacketsLost(
          lost_packets_since_last_loss_update_,
          expected_packets_since_last_loss_update_, report.feedback_time);
      expected_packets_since_last_loss_update_ = 0;
      lost_packets_since_last_loss_update_ = 0;
    }
  }
  absl::optional<int64_t> alr_start_time =
      alr_detector_->GetApplicationLimitedRegionStartTime();

  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);
  }
  previously_in_alr_ = alr_start_time.has_value();
  acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(
      report.SortedByReceiveTime());
  auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate();
  bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate,
                                             report.feedback_time);
  bandwidth_estimation_->IncomingPacketFeedbackVector(report);
  for (const auto& feedback : report.SortedByReceiveTime()) {
    if (feedback.sent_packet.pacing_info.probe_cluster_id !=
        PacedPacketInfo::kNotAProbe) {
      probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback);
    }
  }

  if (network_estimator_) {
    network_estimator_->OnTransportPacketsFeedback(report);
    auto prev_estimate = estimate_;
    estimate_ = network_estimator_->GetCurrentEstimate();
    // TODO(srte): Make OnTransportPacketsFeedback signal whether the state
    // changed to avoid the need for this check.
    if (estimate_ && (!prev_estimate || estimate_->last_feed_time !=
                                            prev_estimate->last_feed_time)) {
      event_log_->Log(std::make_unique<RtcEventRemoteEstimate>(
          estimate_->link_capacity_lower, estimate_->link_capacity_upper));
    }
  }
  absl::optional<DataRate> probe_bitrate =
      probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate();
  if (ignore_probes_lower_than_network_estimate_ && probe_bitrate &&
      estimate_ && *probe_bitrate < delay_based_bwe_->last_estimate() &&
      *probe_bitrate < estimate_->link_capacity_lower) {
    probe_bitrate.reset();
  }
  if (limit_probes_lower_than_throughput_estimate_ && probe_bitrate &&
      acknowledged_bitrate) {
    // Limit the backoff to something slightly below the acknowledged
    // bitrate. ("Slightly below" because we want to drain the queues
    // if we are actually overusing.)
    // The acknowledged bitrate shouldn't normally be higher than the delay
    // based estimate, but it could happen e.g. due to packet bursts or
    // encoder overshoot. We use std::min to ensure that a probe result
    // below the current BWE never causes an increase.
    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;
  bool backoff_in_alr = 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);
    }
    // Since SetSendBitrate now resets the delay-based estimate, we have to
    // call UpdateDelayBasedEstimate after SetSendBitrate.
    bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time,
                                                    result.target_bitrate);
    // Update the estimate in the ProbeController, in case we want to probe.
    MaybeTriggerOnNetworkChanged(&update, report.feedback_time);
  }
  recovered_from_overuse = result.recovered_from_overuse;
  backoff_in_alr = result.backoff_in_alr;

  if (recovered_from_overuse) {
    probe_controller_->SetAlrStartTimeMs(alr_start_time);
    auto probes = probe_controller_->RequestProbe(report.feedback_time.ms());
    update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
                                        probes.begin(), probes.end());
  } else if (backoff_in_alr) {
    // If we just backed off during ALR, request a new probe.
    auto probes = probe_controller_->RequestProbe(report.feedback_time.ms());
    update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
                                        probes.begin(), probes.end());
  }

  // No valid RTT could be because send-side BWE isn't used, in which case
  // we don't try to limit the outstanding packets.
  if (rate_control_settings_.UseCongestionWindow() &&
      max_feedback_rtt.IsFinite()) {
    UpdateCongestionWindowSize();
  }
  if (congestion_window_pushback_controller_ && current_data_window_) {
    congestion_window_pushback_controller_->SetDataWindow(
        *current_data_window_);
  } else {
    update.congestion_window = current_data_window_;
  }

  return update;
}

拥塞控制相关的单元测试

$ ./modules_unittests --gtest_filter="GoogCc*" --gtest_output="xml:goog-cc-ut-report.xml"

$ gtest2html.py --input=./goog-cc-ut-report.xml --output=goog-cc-ut-report.md

gtest2html.py 是我写的一个 python 脚本,将测试结果由 xml 转化成下面这样的 markdown 格式

Test suite: GoogCcNetworkControllerTest

  • tests=6, failures=0, errors=0, disabled=0, time=0.076
# suite case time result
1 GoogCcNetworkControllerTest InitializeTargetRateOnFirstProcessInterval 0.016 pass
2 GoogCcNetworkControllerTest ReactsToChangedNetworkConditions 0 pass
3 GoogCcNetworkControllerTest OnNetworkRouteChanged 0 pass
4 GoogCcNetworkControllerTest ProbeOnRouteChange 0 pass
5 GoogCcNetworkControllerTest UpdatesDelayBasedEstimate 0.054 pass
6 GoogCcNetworkControllerTest PaceAtMaxOfLowerLinkCapacityAndBwe 0 pass

Test suite: GoogCcScenario

  • tests=20, failures=0, errors=0, disabled=0, time=22.666
# suite case time result
7 GoogCcScenario CongestionWindowPushbackOnNetworkDelay 0.509 pass
8 GoogCcScenario CongestionWindowPushbackDropFrameOnNetworkDelay 0.432 pass
9 GoogCcScenario PaddingRateLimitedByCongestionWindowInTrial 0.366 pass
10 GoogCcScenario LimitsToFloorIfRttIsHighInTrial 0.255 pass
11 GoogCcScenario UpdatesTargetRateBasedOnLinkCapacity 2.233 pass
12 GoogCcScenario StableEstimateDoesNotVaryInSteadyState 1.559 pass
13 GoogCcScenario LossBasedControlUpdatesTargetRateBasedOnLinkCapacity 2.23 pass
14 GoogCcScenario LossBasedControlDoesModestBackoffToHighLoss 3.106 pass
15 GoogCcScenario LossBasedRecoversFasterAfterCrossInducedLoss 5.93 pass
16 GoogCcScenario LossBasedEstimatorCapsRateAtModerateLoss 1.211 pass
17 GoogCcScenario MaintainsLowRateInSafeResetTrial 0.016 pass
18 GoogCcScenario CutsHighRateInSafeResetTrial 0.015 pass
19 GoogCcScenario DetectsHighRateInSafeResetTrial 0.079 pass
20 GoogCcScenario TargetRateReducedOnPacingBufferBuildupInTrial 0.169 pass
21 GoogCcScenario NoBandwidthTogglingInLossControlTrial 0.082 pass
22 GoogCcScenario NoRttBackoffCollapseWhenVideoStops 0.071 pass
23 GoogCcScenario NoCrashOnVeryLateFeedback 2.252 pass
24 GoogCcScenario IsFairToTCP 0.289 pass
25 GoogCcScenario FastRampupOnRembCapLifted 1.077 pass
26 GoogCcScenario SlowRampupOnRembCapLiftedWithFieldTrial 0.775 pass

测试用例比较多,先看看下面这个比较简单的用例

./out/Default/modules_unittests --gtest_filter=GoogCcNetworkControllerTest.UpdatesDelayBasedEstimate --gtest_output="xml:goog_cc_ut.xml" > goog_cc_ut.log 2>&1

代码如下,其实挺简单

  1. 调用 PacketTransmissionAndFeedbackBlock, 也就是发送包并接收反馈消息,反馈的延迟为 0, 时长为 6000 ms 1.1 创建并发送RTP 包,调用 controller->OnSentPacket 1.2 构造反馈包,并喂给拥塞控制器,调用 update = controller->OnTransportPacketsFeedback(feedback); 1.3 再过 50ms 调用 controller->OnProcessInterval(), 取出 target_bitrate, 也就是估算出的带宽

  2. 调用 PacketTransmissionAndFeedbackBlock, 也就是发送包并接收反馈消息,反馈的延迟为 50ms, 时长为 6000 ms

  3. 检查第二次估算的带宽 bitrate , 它一定是小于第一次估算的延迟

而 OnTransportPacketsFeedback() 和 OnProcessInterval() 就是我们需要重点关注的方法

// Bandwidth estimation is updated when feedbacks are received.
// Feedbacks which show an increasing delay cause the estimation to be reduced.
TEST(GoogCcNetworkControllerTest, UpdatesDelayBasedEstimate) {
  NetworkControllerTestFixture fixture;
  std::unique_ptr<NetworkControllerInterface> controller =
      fixture.CreateController();
  const int64_t kRunTimeMs = 6000;
  Timestamp current_time = Timestamp::Millis(123);

  // The test must run and insert packets/feedback long enough that the
  // BWE computes a valid estimate. This is first done in an environment which
  // simulates no bandwidth limitation, and therefore not built-up delay.
  absl::optional<DataRate> target_bitrate_before_delay =
      PacketTransmissionAndFeedbackBlock(controller.get(), kRunTimeMs, 0,
                                         current_time);
  ASSERT_TRUE(target_bitrate_before_delay.has_value());

  // Repeat, but this time with a building delay, and make sure that the
  // estimation is adjusted downwards.
  absl::optional<DataRate> target_bitrate_after_delay =
      PacketTransmissionAndFeedbackBlock(controller.get(), kRunTimeMs, 50,
                                         current_time);
  EXPECT_LT(*target_bitrate_after_delay, *target_bitrate_before_delay);
}

// Simulate sending packets and receiving transport feedback during
// `runtime_ms`.
absl::optional<DataRate> PacketTransmissionAndFeedbackBlock(
    NetworkControllerInterface* controller,
    int64_t runtime_ms,
    int64_t delay,
    Timestamp& current_time) {
  NetworkControlUpdate update;
  absl::optional<DataRate> target_bitrate;
  int64_t delay_buildup = 0;
  int64_t start_time_ms = current_time.ms();
  while (current_time.ms() - start_time_ms < runtime_ms) {
    constexpr size_t kPayloadSize = 1000;
    PacketResult packet =
        CreatePacketResult(current_time + TimeDelta::Millis(delay_buildup),
                           current_time, kPayloadSize, PacedPacketInfo());
    delay_buildup += delay;
    update = controller->OnSentPacket(packet.sent_packet);
    if (update.target_rate) {
      target_bitrate = update.target_rate->target_rate;
    }
    TransportPacketsFeedback feedback;
    feedback.feedback_time = packet.receive_time;
    feedback.packet_feedbacks.push_back(packet);
    update = controller->OnTransportPacketsFeedback(feedback);
    if (update.target_rate) {
      target_bitrate = update.target_rate->target_rate;
    }
    current_time += TimeDelta::Millis(50);
    update = controller->OnProcessInterval({.at_time = current_time});
    if (update.target_rate) {
      target_bitrate = update.target_rate->target_rate;
    }
  }
  return target_bitrate;
}

运行结果如下

[ RUN      ] GoogCcNetworkControllerTest.UpdatesDelayBasedEstimate
(alr_experiment.cc:79): Using ALR experiment settings: pacing factor: 1, max pacer queue length: 2875, ALR bandwidth usage percent: 80, ALR start budget level percent: 40, ALR end budget level percent: -60, ALR experiment group ID: 3
(trendline_estimator.cc:185): Using Trendline filter for delay change estimation with settings sort:false,cap:false,beginning_packets:7,end_packets:7,cap_uncertainty:0,window_size:20 and no network state predictor
(trendline_estimator.cc:185): Using Trendline filter for delay change estimation with settings sort:false,cap:false,beginning_packets:7,end_packets:7,cap_uncertainty:0,window_size:20 and no network state predictor
(aimd_rate_control.cc:112): Using aimd rate control with back off factor 0.85
(delay_based_bwe.cc:88): Initialized DelayBasedBwe with separate audio overuse detectionenabled:false,packet_threshold:10,time_threshold:1 s and alr limited backoff disabled
(trendline_estimator.cc:185): Using Trendline filter for delay change estimation with settings sort:false,cap:false,beginning_packets:7,end_packets:7,cap_uncertainty:0,window_size:20 and no network state predictor
(trendline_estimator.cc:185): Using Trendline filter for delay change estimation with settings sort:false,cap:false,beginning_packets:7,end_packets:7,cap_uncertainty:0,window_size:20 and no network state predictor
(delay_based_bwe.cc:301): BWE Setting start bitrate to: 60 kbps
PLOT    1       fraction_loss_%:0@-     0.123000        0.000000
PLOT    1       rtt_ms:0@-      0.123000        0.000000
PLOT    1       Target_bitrate_kbps:0@- 0.123000        5.000000
PLOT    1       fraction_loss_%:0@-     0.173000        0.000000
PLOT    1       rtt_ms:0@-      0.173000        0.000000
PLOT    1       Target_bitrate_kbps:0@- 0.173000        60.000000

...

PLOT    1       rtt_ms:0@-      12.073000       0.000000
PLOT    1       Target_bitrate_kbps:0@- 12.073000       41.000000
PLOT    1       acknowledged_bitrate:0@-        18.023000       93079.859375
PLOT    1       accumulated_delay_ms:0@-        18.023000       5900.000000
PLOT    1       smoothed_delay_ms:0@-   18.023000       5450.001794
PLOT    1       trendline_slope:0@-     18.023000       0.499994
PLOT    1       T:0@-   18.023000       119.998624
PLOT    1       threshold:0@-   18.023000       119.998445
PLOT    1       fraction_loss_%:0@-     12.123000       0.000000
PLOT    1       rtt_ms:0@-      12.123000       0.000000
PLOT    1       Target_bitrate_kbps:0@- 12.123000       41.000000
[       OK ] GoogCcNetworkControllerTest.UpdatesDelayBasedEstimate (11 ms)
[----------] 1 test from GoogCcNetworkControllerTest (11 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (12 ms total)
[  PASSED  ] 1 test.

以上的 PLOT 是由 宏 BWE_TEST_LOGGING_PLOT 调用

#define BWE_TEST_LOGGING_PLOT(figure, name, time, value)                     \
  do {                                                                       \
    __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name,           \
                                       static_cast<int64_t>(time), true);    \
    webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value); \
  } while (0)

void Logging::Plot(int figure, const std::string& name, double value) {
  Plot(figure, name, value, 0, "-");
}

void Logging::Plot(int figure,
                   const std::string& name,
                   double value,
                   uint32_t ssrc,
                   const std::string& alg_name) {
  MutexLock lock(&mutex_);
  ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
  RTC_DCHECK(it != thread_map_.end());
  const State& state = it->second.stack.top();
  if (state.enabled) {
    printf("PLOT\t%d\t%s:%" PRIu32 "@%s\t%f\t%f\n", figure, name.c_str(), ssrc,
           alg_name.c_str(), state.timestamp_ms * 0.001, value);
  }
}

第 1 列:PLOT 关键字 第 2 列:figure number 第 3 列:figure name + ssrc + alg name 第 4 列:timestamp 第 5 列:value

这样我们就可以根据这种格式绘制各种各样的图表了