Add a bandwidth estimator based on loss statistics and maximum likelihood modelling.

Bug: webrtc:12707
Change-Id: Ia221d0b7aee6edb5ae7b0f3b0ad08ac610b3340e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/224300
Commit-Queue: Christoffer Rodbro <crodbro@webrtc.org>
Reviewed-by: Christoffer Rodbro <crodbro@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34626}
diff --git a/modules/congestion_controller/goog_cc/BUILD.gn b/modules/congestion_controller/goog_cc/BUILD.gn
index ea20da8..9aafedb 100644
--- a/modules/congestion_controller/goog_cc/BUILD.gn
+++ b/modules/congestion_controller/goog_cc/BUILD.gn
@@ -27,9 +27,9 @@
     ":alr_detector",
     ":delay_based_bwe",
     ":estimators",
-    ":loss_based_controller",
     ":probe_controller",
     ":pushback_controller",
+    ":send_side_bwe",
     "../..:module_api",
     "../../../api:network_state_predictor_api",
     "../../../api/rtc_event_log",
@@ -146,15 +146,55 @@
   ]
 }
 
-rtc_library("loss_based_controller") {
+rtc_library("loss_based_bwe_v2") {
+  sources = [
+    "loss_based_bwe_v2.cc",
+    "loss_based_bwe_v2.h",
+  ]
+  deps = [
+    "../../../api:array_view",
+    "../../../api/transport:network_control",
+    "../../../api/transport:webrtc_key_value_config",
+    "../../../api/units:data_rate",
+    "../../../api/units:data_size",
+    "../../../api/units:time_delta",
+    "../../../api/units:timestamp",
+    "../../../rtc_base:logging",
+    "../../../rtc_base/experiments:field_trial_parser",
+  ]
+  absl_deps = [
+    "//third_party/abseil-cpp/absl/algorithm:container",
+    "//third_party/abseil-cpp/absl/types:optional",
+  ]
+}
+
+rtc_library("loss_based_bwe_v1") {
   configs += [ ":bwe_test_logging" ]
   sources = [
     "loss_based_bandwidth_estimation.cc",
     "loss_based_bandwidth_estimation.h",
+  ]
+  deps = [
+    "../../../api/transport:network_control",
+    "../../../api/transport:webrtc_key_value_config",
+    "../../../api/units:data_rate",
+    "../../../api/units:time_delta",
+    "../../../api/units:timestamp",
+    "../../../rtc_base:checks",
+    "../../../rtc_base/experiments:field_trial_parser",
+  ]
+  absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
+}
+
+rtc_library("send_side_bwe") {
+  configs += [ ":bwe_test_logging" ]
+  sources = [
     "send_side_bandwidth_estimation.cc",
     "send_side_bandwidth_estimation.h",
   ]
   deps = [
+    ":loss_based_bwe_v1",
+    ":loss_based_bwe_v2",
     "../../../api/rtc_event_log",
     "../../../api/transport:network_control",
     "../../../api/transport:webrtc_key_value_config",
@@ -269,6 +309,7 @@
         "delay_based_bwe_unittest_helper.cc",
         "delay_based_bwe_unittest_helper.h",
         "goog_cc_network_control_unittest.cc",
+        "loss_based_bwe_v2_test.cc",
         "probe_bitrate_estimator_unittest.cc",
         "probe_controller_unittest.cc",
         "robust_throughput_estimator_unittest.cc",
@@ -280,9 +321,10 @@
         ":delay_based_bwe",
         ":estimators",
         ":goog_cc",
-        ":loss_based_controller",
+        ":loss_based_bwe_v2",
         ":probe_controller",
         ":pushback_controller",
+        ":send_side_bwe",
         "../../../api/rtc_event_log",
         "../../../api/test/network_emulation",
         "../../../api/test/network_emulation:create_cross_traffic",
@@ -291,12 +333,15 @@
         "../../../api/transport:network_control",
         "../../../api/transport:webrtc_key_value_config",
         "../../../api/units:data_rate",
+        "../../../api/units:data_size",
+        "../../../api/units:time_delta",
         "../../../api/units:timestamp",
         "../../../logging:mocks",
         "../../../logging:rtc_event_bwe",
         "../../../rtc_base:checks",
         "../../../rtc_base:rtc_base_approved",
         "../../../rtc_base:rtc_base_tests_utils",
+        "../../../rtc_base:stringutils",
         "../../../rtc_base/experiments:alr_experiment",
         "../../../system_wrappers",
         "../../../test:explicit_key_value_config",
diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc
new file mode 100644
index 0000000..ae52b2d
--- /dev/null
+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc
@@ -0,0 +1,652 @@
+/*
+ *  Copyright 2021 The WebRTC project authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/congestion_controller/goog_cc/loss_based_bwe_v2.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <cstdlib>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "api/transport/network_types.h"
+#include "api/transport/webrtc_key_value_config.h"
+#include "api/units/data_rate.h"
+#include "api/units/data_size.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "rtc_base/experiments/field_trial_list.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+bool IsValid(DataRate datarate) {
+  return datarate.IsFinite();
+}
+
+bool IsValid(Timestamp timestamp) {
+  return timestamp.IsFinite();
+}
+
+struct PacketResultsSummary {
+  int num_packets = 0;
+  int num_lost_packets = 0;
+  DataSize total_size = DataSize::Zero();
+  Timestamp first_send_time = Timestamp::PlusInfinity();
+  Timestamp last_send_time = Timestamp::MinusInfinity();
+};
+
+// Returns a `PacketResultsSummary` where `first_send_time` is `PlusInfinity,
+// and `last_send_time` is `MinusInfinity`, if `packet_results` is empty.
+PacketResultsSummary GetPacketResultsSummary(
+    rtc::ArrayView<const PacketResult> packet_results) {
+  PacketResultsSummary packet_results_summary;
+
+  packet_results_summary.num_packets = packet_results.size();
+  for (const PacketResult& packet : packet_results) {
+    if (!packet.IsReceived()) {
+      packet_results_summary.num_lost_packets++;
+    }
+    packet_results_summary.total_size += packet.sent_packet.size;
+    packet_results_summary.first_send_time = std::min(
+        packet_results_summary.first_send_time, packet.sent_packet.send_time);
+    packet_results_summary.last_send_time = std::max(
+        packet_results_summary.last_send_time, packet.sent_packet.send_time);
+  }
+
+  return packet_results_summary;
+}
+
+double GetLossProbability(double inherent_loss,
+                          DataRate loss_limited_bandwidth,
+                          DataRate sending_rate) {
+  if (inherent_loss < 0.0 || inherent_loss > 1.0) {
+    RTC_LOG(LS_WARNING) << "The inherent loss must be in [0,1]: "
+                        << inherent_loss;
+    inherent_loss = std::min(std::max(inherent_loss, 0.0), 1.0);
+  }
+  if (!sending_rate.IsFinite()) {
+    RTC_LOG(LS_WARNING) << "The sending rate must be finite: "
+                        << ToString(sending_rate);
+  }
+  if (!loss_limited_bandwidth.IsFinite()) {
+    RTC_LOG(LS_WARNING) << "The loss limited bandwidth must be finite: "
+                        << ToString(loss_limited_bandwidth);
+  }
+
+  // We approximate the loss model
+  //     loss_probability = inherent_loss + (1 - inherent_loss) *
+  //     max(0, sending_rate - bandwidth) / sending_rate
+  // by
+  //     loss_probability = inherent_loss +
+  //     max(0, sending_rate - bandwidth) / sending_rate
+  // as it allows for simpler calculations and makes little difference in
+  // practice.
+  double loss_probability = inherent_loss;
+  if (IsValid(sending_rate) && IsValid(loss_limited_bandwidth) &&
+      (sending_rate > loss_limited_bandwidth)) {
+    loss_probability += (sending_rate - loss_limited_bandwidth) / sending_rate;
+  }
+  return std::min(std::max(loss_probability, 1.0e-6), 1.0 - 1.0e-6);
+}
+
+}  // namespace
+
+LossBasedBweV2::LossBasedBweV2(const WebRtcKeyValueConfig* key_value_config)
+    : config_(CreateConfig(key_value_config)) {
+  if (!config_.has_value()) {
+    RTC_LOG(LS_VERBOSE) << "The configuration does not specify that the "
+                           "estimator should be enabled, disabling it.";
+    return;
+  }
+  if (!IsConfigValid()) {
+    RTC_LOG(LS_WARNING)
+        << "The configuration is not valid, disabling the estimator.";
+    config_.reset();
+    return;
+  }
+
+  current_estimate_.inherent_loss = config_->initial_inherent_loss_estimate;
+  observations_.resize(config_->observation_window_size);
+  temporal_weights_.resize(config_->observation_window_size);
+  tcp_fairness_temporal_weights_.resize(config_->observation_window_size);
+  CalculateTemporalWeights();
+}
+
+bool LossBasedBweV2::IsEnabled() const {
+  return config_.has_value();
+}
+
+bool LossBasedBweV2::IsReady() const {
+  return IsEnabled() && IsValid(current_estimate_.loss_limited_bandwidth) &&
+         num_observations_ > 0;
+}
+
+DataRate LossBasedBweV2::GetBandwidthEstimate() const {
+  if (!IsReady()) {
+    if (!IsEnabled()) {
+      RTC_LOG(LS_WARNING)
+          << "The estimator must be enabled before it can be used.";
+    } else {
+      if (!IsValid(current_estimate_.loss_limited_bandwidth)) {
+        RTC_LOG(LS_WARNING)
+            << "The estimator must be initialized before it can be used.";
+      }
+      if (num_observations_ <= 0) {
+        RTC_LOG(LS_WARNING) << "The estimator must receive enough loss "
+                               "statistics before it can be used.";
+      }
+    }
+    return DataRate::PlusInfinity();
+  }
+
+  return std::min(current_estimate_.loss_limited_bandwidth,
+                  GetTcpFairnessBandwidthUpperBound());
+}
+
+void LossBasedBweV2::SetAcknowledgedBitrate(DataRate acknowledged_bitrate) {
+  if (IsValid(acknowledged_bitrate)) {
+    acknowledged_bitrate_ = acknowledged_bitrate;
+  } else {
+    RTC_LOG(LS_WARNING) << "The acknowledged bitrate must be finite: "
+                        << ToString(acknowledged_bitrate);
+  }
+}
+
+void LossBasedBweV2::SetBandwidthEstimate(DataRate bandwidth_estimate) {
+  if (IsValid(bandwidth_estimate)) {
+    current_estimate_.loss_limited_bandwidth = bandwidth_estimate;
+  } else {
+    RTC_LOG(LS_WARNING) << "The bandwidth estimate must be finite: "
+                        << ToString(bandwidth_estimate);
+  }
+}
+
+void LossBasedBweV2::UpdateBandwidthEstimate(
+    rtc::ArrayView<const PacketResult> packet_results) {
+  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_estimate_.loss_limited_bandwidth)) {
+    RTC_LOG(LS_VERBOSE)
+        << "The estimator must be initialized before it can be used.";
+    return;
+  }
+
+  ChannelParameters best_candidate = current_estimate_;
+  double objective_max = std::numeric_limits<double>::lowest();
+  for (ChannelParameters candidate : GetCandidates()) {
+    NewtonsMethodUpdate(candidate);
+
+    const double candidate_objective = GetObjective(candidate);
+    if (candidate_objective > objective_max) {
+      objective_max = candidate_objective;
+      best_candidate = candidate;
+    }
+  }
+
+  current_estimate_ = best_candidate;
+}
+
+// Returns a `LossBasedBweV2::Config` iff the `key_value_config` specifies a
+// configuration for the `LossBasedBweV2` which is explicitly enabled.
+absl::optional<LossBasedBweV2::Config> LossBasedBweV2::CreateConfig(
+    const WebRtcKeyValueConfig* key_value_config) {
+  FieldTrialParameter<bool> enabled("Enabled", false);
+  FieldTrialParameter<double> bandwidth_rampup_upper_bound_factor(
+      "BwRampupUpperBoundFactor", 1.1);
+  FieldTrialList<double> candidate_factors("CandidateFactors",
+                                           {1.05, 1.0, 0.95});
+  FieldTrialParameter<double> higher_bandwidth_bias_factor("HigherBwBiasFactor",
+                                                           0.00001);
+  FieldTrialParameter<double> inherent_loss_lower_bound(
+      "InherentLossLowerBound", 1.0e-3);
+  FieldTrialParameter<DataRate> inherent_loss_upper_bound_bandwidth_balance(
+      "InherentLossUpperBoundBwBalance", DataRate::KilobitsPerSec(15.0));
+  FieldTrialParameter<double> inherent_loss_upper_bound_offset(
+      "InherentLossUpperBoundOffset", 0.05);
+  FieldTrialParameter<double> initial_inherent_loss_estimate(
+      "InitialInherentLossEstimate", 0.01);
+  FieldTrialParameter<int> newton_iterations("NewtonIterations", 1);
+  FieldTrialParameter<double> newton_step_size("NewtonStepSize", 0.5);
+  FieldTrialParameter<TimeDelta> observation_duration_lower_bound(
+      "ObservationDurationLowerBound", TimeDelta::Seconds(1));
+  FieldTrialParameter<int> observation_window_size("ObservationWindowSize", 20);
+  FieldTrialParameter<double> sending_rate_smoothing_factor(
+      "SendingRateSmoothingFactor", 0.0);
+  FieldTrialParameter<double> tcp_fairness_temporal_weight_factor(
+      "TcpFairnessTemporalWeightFactor", 0.99);
+  FieldTrialParameter<DataRate> tcp_fairness_upper_bound_bandwidth_balance(
+      "TcpFairnessUpperBoundBwBalance", DataRate::KilobitsPerSec(15.0));
+  FieldTrialParameter<double> tcp_fairness_upper_bound_loss_offset(
+      "TcpFairnessUpperBoundLossOffset", 0.05);
+  FieldTrialParameter<double> temporal_weight_factor("TemporalWeightFactor",
+                                                     0.99);
+
+  if (key_value_config) {
+    ParseFieldTrial(
+        {&enabled, &bandwidth_rampup_upper_bound_factor, &candidate_factors,
+         &higher_bandwidth_bias_factor, &inherent_loss_lower_bound,
+         &inherent_loss_upper_bound_bandwidth_balance,
+         &inherent_loss_upper_bound_offset, &initial_inherent_loss_estimate,
+         &newton_iterations, &newton_step_size,
+         &observation_duration_lower_bound, &observation_window_size,
+         &sending_rate_smoothing_factor, &tcp_fairness_temporal_weight_factor,
+         &tcp_fairness_upper_bound_bandwidth_balance,
+         &tcp_fairness_upper_bound_loss_offset, &temporal_weight_factor},
+        key_value_config->Lookup("WebRTC-Bwe-LossBasedBweV2"));
+  }
+
+  absl::optional<Config> config;
+  if (!enabled.Get()) {
+    return config;
+  }
+  config.emplace();
+  config->bandwidth_rampup_upper_bound_factor =
+      bandwidth_rampup_upper_bound_factor.Get();
+  config->candidate_factors = candidate_factors.Get();
+  config->higher_bandwidth_bias_factor = higher_bandwidth_bias_factor.Get();
+  config->inherent_loss_lower_bound = inherent_loss_lower_bound.Get();
+  config->inherent_loss_upper_bound_bandwidth_balance =
+      inherent_loss_upper_bound_bandwidth_balance.Get();
+  config->inherent_loss_upper_bound_offset =
+      inherent_loss_upper_bound_offset.Get();
+  config->initial_inherent_loss_estimate = initial_inherent_loss_estimate.Get();
+  config->newton_iterations = newton_iterations.Get();
+  config->newton_step_size = newton_step_size.Get();
+  config->observation_duration_lower_bound =
+      observation_duration_lower_bound.Get();
+  config->observation_window_size = observation_window_size.Get();
+  config->sending_rate_smoothing_factor = sending_rate_smoothing_factor.Get();
+  config->tcp_fairness_temporal_weight_factor =
+      tcp_fairness_temporal_weight_factor.Get();
+  config->tcp_fairness_upper_bound_bandwidth_balance =
+      tcp_fairness_upper_bound_bandwidth_balance.Get();
+  config->tcp_fairness_upper_bound_loss_offset =
+      tcp_fairness_upper_bound_loss_offset.Get();
+  config->temporal_weight_factor = temporal_weight_factor.Get();
+  return config;
+}
+
+bool LossBasedBweV2::IsConfigValid() const {
+  if (!config_.has_value()) {
+    return false;
+  }
+
+  bool valid = true;
+
+  if (config_->bandwidth_rampup_upper_bound_factor <= 1.0) {
+    RTC_LOG(LS_WARNING)
+        << "The bandwidth rampup upper bound factor must be greater than 1: "
+        << config_->bandwidth_rampup_upper_bound_factor;
+    valid = false;
+  }
+  if (config_->higher_bandwidth_bias_factor < 0.0) {
+    RTC_LOG(LS_WARNING)
+        << "The higher bandwidth bias factor must be non-negative: "
+        << config_->higher_bandwidth_bias_factor;
+    valid = false;
+  }
+  if (config_->inherent_loss_lower_bound < 0.0 ||
+      config_->inherent_loss_lower_bound >= 1.0) {
+    RTC_LOG(LS_WARNING) << "The inherent loss lower bound must be in [0, 1): "
+                        << config_->inherent_loss_lower_bound;
+    valid = false;
+  }
+  if (config_->inherent_loss_upper_bound_bandwidth_balance <=
+      DataRate::Zero()) {
+    RTC_LOG(LS_WARNING)
+        << "The inherent loss upper bound bandwidth balance "
+           "must be positive: "
+        << ToString(config_->inherent_loss_upper_bound_bandwidth_balance);
+    valid = false;
+  }
+  if (config_->inherent_loss_upper_bound_offset <
+          config_->inherent_loss_lower_bound ||
+      config_->inherent_loss_upper_bound_offset >= 1.0) {
+    RTC_LOG(LS_WARNING) << "The inherent loss upper bound must be greater "
+                           "than or equal to the inherent "
+                           "loss lower bound, which is "
+                        << config_->inherent_loss_lower_bound
+                        << ", and less than 1: "
+                        << config_->inherent_loss_upper_bound_offset;
+    valid = false;
+  }
+  if (config_->initial_inherent_loss_estimate < 0.0 ||
+      config_->initial_inherent_loss_estimate >= 1.0) {
+    RTC_LOG(LS_WARNING)
+        << "The initial inherent loss estimate must be in [0, 1): "
+        << config_->initial_inherent_loss_estimate;
+    valid = false;
+  }
+  if (config_->newton_iterations <= 0) {
+    RTC_LOG(LS_WARNING) << "The number of Newton iterations must be positive: "
+                        << config_->newton_iterations;
+    valid = false;
+  }
+  if (config_->newton_step_size <= 0.0) {
+    RTC_LOG(LS_WARNING) << "The Newton step size must be positive: "
+                        << config_->newton_step_size;
+    valid = false;
+  }
+  if (config_->observation_duration_lower_bound <= TimeDelta::Zero()) {
+    RTC_LOG(LS_WARNING)
+        << "The observation duration lower bound must be positive: "
+        << ToString(config_->observation_duration_lower_bound);
+    valid = false;
+  }
+  if (config_->observation_window_size < 2) {
+    RTC_LOG(LS_WARNING) << "The observation window size must be at least 2: "
+                        << config_->observation_window_size;
+    valid = false;
+  }
+  if (config_->sending_rate_smoothing_factor < 0.0 ||
+      config_->sending_rate_smoothing_factor >= 1.0) {
+    RTC_LOG(LS_WARNING)
+        << "The sending rate smoothing factor must be in [0, 1): "
+        << config_->sending_rate_smoothing_factor;
+    valid = false;
+  }
+  if (config_->tcp_fairness_temporal_weight_factor <= 0.0 ||
+      config_->tcp_fairness_temporal_weight_factor > 1.0) {
+    RTC_LOG(LS_WARNING)
+        << "The TCP fairness temporal weight factor must be in (0, 1]"
+        << config_->tcp_fairness_temporal_weight_factor;
+    valid = false;
+  }
+  if (config_->tcp_fairness_upper_bound_bandwidth_balance <= DataRate::Zero()) {
+    RTC_LOG(LS_WARNING)
+        << "The TCP fairness upper bound bandwidth balance must be positive: "
+        << ToString(config_->tcp_fairness_upper_bound_bandwidth_balance);
+    valid = false;
+  }
+  if (config_->tcp_fairness_upper_bound_loss_offset < 0.0 ||
+      config_->tcp_fairness_upper_bound_loss_offset >= 1.0) {
+    RTC_LOG(LS_WARNING)
+        << "The TCP fairness upper bound loss offset must be in [0, 1): "
+        << config_->tcp_fairness_upper_bound_loss_offset;
+    valid = false;
+  }
+  if (config_->temporal_weight_factor <= 0.0 ||
+      config_->temporal_weight_factor > 1.0) {
+    RTC_LOG(LS_WARNING) << "The temporal weight factor must be in (0, 1]: "
+                        << config_->temporal_weight_factor;
+    valid = false;
+  }
+
+  return valid;
+}
+
+double LossBasedBweV2::GetAverageReportedLossRatio() const {
+  if (num_observations_ <= 0) {
+    return 0.0;
+  }
+
+  int num_packets = 0;
+  int num_lost_packets = 0;
+  for (const Observation& observation : observations_) {
+    if (!observation.IsInitialized()) {
+      continue;
+    }
+
+    double tcp_fairness_temporal_weight =
+        tcp_fairness_temporal_weights_[(num_observations_ - 1) -
+                                       observation.id];
+    num_packets += tcp_fairness_temporal_weight * observation.num_packets;
+    num_lost_packets +=
+        tcp_fairness_temporal_weight * observation.num_lost_packets;
+  }
+
+  return static_cast<double>(num_lost_packets) / num_packets;
+}
+
+std::vector<LossBasedBweV2::ChannelParameters> LossBasedBweV2::GetCandidates()
+    const {
+  std::vector<DataRate> bandwidths;
+  for (double candidate_factor : config_->candidate_factors) {
+    bandwidths.emplace_back(candidate_factor *
+                            current_estimate_.loss_limited_bandwidth);
+  }
+
+  if (acknowledged_bitrate_.has_value()) {
+    bandwidths.emplace_back(*acknowledged_bitrate_);
+  }
+
+  // TODO(crodbro): Consider adding the delay based estimate as a candidate.
+
+  const DataRate candidate_bandwidth_upper_bound =
+      acknowledged_bitrate_.has_value()
+          ? config_->bandwidth_rampup_upper_bound_factor *
+                (*acknowledged_bitrate_)
+          : DataRate::PlusInfinity();
+
+  std::vector<ChannelParameters> candidates;
+  candidates.resize(bandwidths.size());
+  for (size_t i = 0; i < bandwidths.size(); ++i) {
+    ChannelParameters candidate = current_estimate_;
+    candidate.loss_limited_bandwidth = std::min(
+        bandwidths[i], std::max(current_estimate_.loss_limited_bandwidth,
+                                candidate_bandwidth_upper_bound));
+    candidate.inherent_loss = GetFeasibleInherentLoss(candidate);
+    candidates[i] = candidate;
+  }
+  return candidates;
+}
+
+LossBasedBweV2::Derivatives LossBasedBweV2::GetDerivatives(
+    const ChannelParameters& channel_parameters) const {
+  Derivatives derivatives;
+
+  for (const Observation& observation : observations_) {
+    if (!observation.IsInitialized()) {
+      continue;
+    }
+
+    double loss_probability = GetLossProbability(
+        channel_parameters.inherent_loss,
+        channel_parameters.loss_limited_bandwidth, observation.sending_rate);
+
+    double temporal_weight =
+        temporal_weights_[(num_observations_ - 1) - observation.id];
+
+    derivatives.first +=
+        temporal_weight *
+        ((observation.num_lost_packets / loss_probability) -
+         (observation.num_received_packets / (1.0 - loss_probability)));
+    derivatives.second -=
+        temporal_weight *
+        ((observation.num_lost_packets / std::pow(loss_probability, 2)) +
+         (observation.num_received_packets /
+          std::pow(1.0 - loss_probability, 2)));
+  }
+
+  if (derivatives.second >= 0.0) {
+    RTC_LOG(LS_ERROR) << "The second derivative is mathematically guaranteed "
+                         "to be negative but is "
+                      << derivatives.second << ".";
+    derivatives.second = -1.0e-6;
+  }
+
+  return derivatives;
+}
+
+double LossBasedBweV2::GetFeasibleInherentLoss(
+    const ChannelParameters& channel_parameters) const {
+  return std::min(
+      std::max(channel_parameters.inherent_loss,
+               config_->inherent_loss_lower_bound),
+      GetInherentLossUpperBound(channel_parameters.loss_limited_bandwidth));
+}
+
+double LossBasedBweV2::GetInherentLossUpperBound(DataRate bandwidth) const {
+  if (bandwidth.IsZero()) {
+    return 1.0;
+  }
+
+  double inherent_loss_upper_bound =
+      config_->inherent_loss_upper_bound_offset +
+      config_->inherent_loss_upper_bound_bandwidth_balance / bandwidth;
+  return std::min(inherent_loss_upper_bound, 1.0);
+}
+
+double LossBasedBweV2::GetObjective(
+    const ChannelParameters& channel_parameters) const {
+  double objective = 0.0;
+
+  for (const Observation& observation : observations_) {
+    if (!observation.IsInitialized()) {
+      continue;
+    }
+
+    double loss_probability = GetLossProbability(
+        channel_parameters.inherent_loss,
+        channel_parameters.loss_limited_bandwidth, observation.sending_rate);
+
+    double temporal_weight =
+        temporal_weights_[(num_observations_ - 1) - observation.id];
+
+    objective +=
+        temporal_weight *
+        ((observation.num_lost_packets * std::log(loss_probability)) +
+         (observation.num_received_packets * std::log(1.0 - loss_probability)));
+    objective +=
+        temporal_weight * (config_->higher_bandwidth_bias_factor *
+                           channel_parameters.loss_limited_bandwidth.kbps() *
+                           observation.num_packets);
+  }
+
+  return objective;
+}
+
+DataRate LossBasedBweV2::GetSendingRate(
+    DataRate instantaneous_sending_rate) const {
+  if (num_observations_ <= 0) {
+    return instantaneous_sending_rate;
+  }
+
+  const int most_recent_observation_idx =
+      (num_observations_ - 1) % config_->observation_window_size;
+  const Observation& most_recent_observation =
+      observations_[most_recent_observation_idx];
+  DataRate sending_rate_previous_observation =
+      most_recent_observation.sending_rate;
+
+  return config_->sending_rate_smoothing_factor *
+             sending_rate_previous_observation +
+         (1.0 - config_->sending_rate_smoothing_factor) *
+             instantaneous_sending_rate;
+}
+
+DataRate LossBasedBweV2::GetTcpFairnessBandwidthUpperBound() const {
+  if (num_observations_ <= 0) {
+    return DataRate::PlusInfinity();
+  }
+
+  const double average_reported_loss_ratio = GetAverageReportedLossRatio();
+  if (average_reported_loss_ratio <=
+      config_->tcp_fairness_upper_bound_loss_offset) {
+    return DataRate::PlusInfinity();
+  }
+  return config_->tcp_fairness_upper_bound_bandwidth_balance /
+         (average_reported_loss_ratio -
+          config_->tcp_fairness_upper_bound_loss_offset);
+}
+
+void LossBasedBweV2::CalculateTemporalWeights() {
+  for (int i = 0; i < config_->observation_window_size; ++i) {
+    temporal_weights_[i] = std::pow(config_->temporal_weight_factor, i);
+    tcp_fairness_temporal_weights_[i] =
+        std::pow(config_->tcp_fairness_temporal_weight_factor, i);
+  }
+}
+
+void LossBasedBweV2::NewtonsMethodUpdate(
+    ChannelParameters& channel_parameters) const {
+  if (num_observations_ <= 0) {
+    return;
+  }
+
+  for (int i = 0; i < config_->newton_iterations; ++i) {
+    const Derivatives derivatives = GetDerivatives(channel_parameters);
+    channel_parameters.inherent_loss -=
+        config_->newton_step_size * derivatives.first / derivatives.second;
+    channel_parameters.inherent_loss =
+        GetFeasibleInherentLoss(channel_parameters);
+  }
+}
+
+bool LossBasedBweV2::PushBackObservation(
+    rtc::ArrayView<const PacketResult> packet_results) {
+  if (packet_results.empty()) {
+    return false;
+  }
+
+  PacketResultsSummary packet_results_summary =
+      GetPacketResultsSummary(packet_results);
+
+  partial_observation_.num_packets += packet_results_summary.num_packets;
+  partial_observation_.num_lost_packets +=
+      packet_results_summary.num_lost_packets;
+  partial_observation_.size += packet_results_summary.total_size;
+
+  // This is the first packet report we have received.
+  if (!IsValid(last_send_time_most_recent_observation_)) {
+    last_send_time_most_recent_observation_ =
+        packet_results_summary.first_send_time;
+  }
+
+  const Timestamp last_send_time = packet_results_summary.last_send_time;
+  const TimeDelta observation_duration =
+      last_send_time - last_send_time_most_recent_observation_;
+
+  // Too small to be meaningful.
+  if (observation_duration < config_->observation_duration_lower_bound) {
+    return false;
+  }
+
+  last_send_time_most_recent_observation_ = last_send_time;
+
+  Observation observation;
+  observation.num_packets = partial_observation_.num_packets;
+  observation.num_lost_packets = partial_observation_.num_lost_packets;
+  observation.num_received_packets =
+      observation.num_packets - observation.num_lost_packets;
+  observation.sending_rate =
+      GetSendingRate(partial_observation_.size / observation_duration);
+  observation.id = num_observations_++;
+  observations_[observation.id % config_->observation_window_size] =
+      observation;
+
+  partial_observation_ = PartialObservation();
+
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h
new file mode 100644
index 0000000..83d6f33
--- /dev/null
+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h
@@ -0,0 +1,135 @@
+/*
+ *  Copyright 2021 The WebRTC project authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_LOSS_BASED_BWE_V2_H_
+#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_LOSS_BASED_BWE_V2_H_
+
+#include <cstddef>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "api/transport/network_types.h"
+#include "api/transport/webrtc_key_value_config.h"
+#include "api/units/data_rate.h"
+#include "api/units/data_size.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+
+namespace webrtc {
+
+class LossBasedBweV2 {
+ public:
+  // Creates a disabled `LossBasedBweV2` if the
+  // `key_value_config` is not valid.
+  explicit LossBasedBweV2(const WebRtcKeyValueConfig* key_value_config);
+
+  LossBasedBweV2(const LossBasedBweV2&) = delete;
+  LossBasedBweV2& operator=(const LossBasedBweV2&) = delete;
+
+  ~LossBasedBweV2() = default;
+
+  bool IsEnabled() const;
+  // Returns true iff a BWE can be calculated, i.e., the estimator has been
+  // initialized with a BWE and then has received enough `PacketResult`s.
+  bool IsReady() const;
+
+  // Returns `DataRate::PlusInfinity` if no BWE can be calculated.
+  DataRate GetBandwidthEstimate() const;
+
+  void SetAcknowledgedBitrate(DataRate acknowledged_bitrate);
+  void SetBandwidthEstimate(DataRate bandwidth_estimate);
+
+  void UpdateBandwidthEstimate(
+      rtc::ArrayView<const PacketResult> packet_results);
+
+ private:
+  struct ChannelParameters {
+    double inherent_loss = 0.0;
+    DataRate loss_limited_bandwidth = DataRate::MinusInfinity();
+  };
+
+  struct Config {
+    double bandwidth_rampup_upper_bound_factor = 0.0;
+    std::vector<double> candidate_factors;
+    double higher_bandwidth_bias_factor = 0.0;
+    double inherent_loss_lower_bound = 0.0;
+    DataRate inherent_loss_upper_bound_bandwidth_balance =
+        DataRate::MinusInfinity();
+    double inherent_loss_upper_bound_offset = 0.0;
+    double initial_inherent_loss_estimate = 0.0;
+    int newton_iterations = 0;
+    double newton_step_size = 0.0;
+    TimeDelta observation_duration_lower_bound = TimeDelta::Zero();
+    int observation_window_size = 0;
+    double sending_rate_smoothing_factor = 0.0;
+    double tcp_fairness_temporal_weight_factor = 0.0;
+    DataRate tcp_fairness_upper_bound_bandwidth_balance =
+        DataRate::MinusInfinity();
+    double tcp_fairness_upper_bound_loss_offset = 0.0;
+    double temporal_weight_factor = 0.0;
+  };
+
+  struct Derivatives {
+    double first = 0.0;
+    double second = 0.0;
+  };
+
+  struct Observation {
+    bool IsInitialized() const { return id != -1; }
+
+    int num_packets = 0;
+    int num_lost_packets = 0;
+    int num_received_packets = 0;
+    DataRate sending_rate = DataRate::MinusInfinity();
+    int id = -1;
+  };
+
+  struct PartialObservation {
+    int num_packets = 0;
+    int num_lost_packets = 0;
+    DataSize size = DataSize::Zero();
+  };
+
+  static absl::optional<Config> CreateConfig(
+      const WebRtcKeyValueConfig* key_value_config);
+  bool IsConfigValid() const;
+
+  // Returns `0.0` if not enough loss statistics have been received.
+  double GetAverageReportedLossRatio() const;
+  std::vector<ChannelParameters> GetCandidates() const;
+  Derivatives GetDerivatives(const ChannelParameters& channel_parameters) const;
+  double GetFeasibleInherentLoss(
+      const ChannelParameters& channel_parameters) const;
+  double GetInherentLossUpperBound(DataRate bandwidth) const;
+  double GetObjective(const ChannelParameters& channel_parameters) const;
+  DataRate GetSendingRate(DataRate instantaneous_sending_rate) const;
+  DataRate GetTcpFairnessBandwidthUpperBound() const;
+
+  void CalculateTemporalWeights();
+  void NewtonsMethodUpdate(ChannelParameters& channel_parameters) const;
+
+  // Returns false if no observation was created.
+  bool PushBackObservation(rtc::ArrayView<const PacketResult> packet_results);
+
+  absl::optional<DataRate> acknowledged_bitrate_;
+  absl::optional<Config> config_;
+  ChannelParameters current_estimate_;
+  int num_observations_ = 0;
+  std::vector<Observation> observations_;
+  PartialObservation partial_observation_;
+  Timestamp last_send_time_most_recent_observation_ = Timestamp::PlusInfinity();
+  std::vector<double> tcp_fairness_temporal_weights_;
+  std::vector<double> temporal_weights_;
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_LOSS_BASED_BWE_V2_H_
diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc b/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc
new file mode 100644
index 0000000..87ac31d
--- /dev/null
+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc
@@ -0,0 +1,299 @@
+/*
+ *  Copyright 2021 The WebRTC project authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/congestion_controller/goog_cc/loss_based_bwe_v2.h"
+
+#include <string>
+
+#include "api/transport/network_types.h"
+#include "api/units/data_rate.h"
+#include "api/units/data_size.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "rtc_base/strings/string_builder.h"
+#include "test/explicit_key_value_config.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+namespace {
+
+constexpr TimeDelta kObservationDurationLowerBound = TimeDelta::Millis(200);
+
+std::string Config(bool enabled, bool valid) {
+  char buffer[1024];
+  rtc::SimpleStringBuilder config_string(buffer);
+
+  config_string << "WebRTC-Bwe-LossBasedBweV2/";
+
+  if (enabled) {
+    config_string << "Enabled:true";
+  } else {
+    config_string << "Enabled:false";
+  }
+
+  if (valid) {
+    config_string << ",BwRampupUpperBoundFactor:1.2";
+  } else {
+    config_string << ",BwRampupUpperBoundFactor:0.0";
+  }
+
+  config_string
+      << ",CandidateFactors:0.9|1.1,HigherBwBiasFactor:0.01,"
+         "InherentLossLowerBound:0.001,InherentLossUpperBoundBwBalance:14kbps,"
+         "InherentLossUpperBoundOffset:0.9,InitialInherentLossEstimate:0.01,"
+         "NewtonIterations:2,NewtonStepSize:0.4,ObservationWindowSize:15,"
+         "SendingRateSmoothingFactor:0.01,TcpFairnessTemporalWeightFactor:0.97,"
+         "TcpFairnessUpperBoundBwBalance:90kbps,"
+         "TcpFairnessUpperBoundLossOffset:0.1,TemporalWeightFactor:0.98";
+
+  config_string.AppendFormat(
+      ",ObservationDurationLowerBound:%dms",
+      static_cast<int>(kObservationDurationLowerBound.ms()));
+
+  config_string << "/";
+
+  return config_string.str();
+}
+
+TEST(LossBasedBweV2Test, EnabledWhenGivenValidConfigurationValues) {
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/true, /*valid=*/true));
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+
+  EXPECT_TRUE(loss_based_bandwidth_estimator.IsEnabled());
+}
+
+TEST(LossBasedBweV2Test, DisabledWhenGivenDisabledConfiguration) {
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/false, /*valid=*/true));
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+
+  EXPECT_FALSE(loss_based_bandwidth_estimator.IsEnabled());
+}
+
+TEST(LossBasedBweV2Test, DisabledWhenGivenNonValidConfigurationValues) {
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/true, /*valid=*/false));
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+
+  EXPECT_FALSE(loss_based_bandwidth_estimator.IsEnabled());
+}
+
+TEST(LossBasedBweV2Test, BandwidthEstimateGivenInitializationAndThenFeedback) {
+  PacketResult enough_feedback[2];
+  enough_feedback[0].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback[1].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback[0].sent_packet.send_time = Timestamp::Zero();
+  enough_feedback[1].sent_packet.send_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback[0].receive_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback[1].receive_time =
+      Timestamp::Zero() + 2 * kObservationDurationLowerBound;
+
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/true, /*valid=*/true));
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(enough_feedback);
+
+  EXPECT_TRUE(loss_based_bandwidth_estimator.IsReady());
+  EXPECT_TRUE(loss_based_bandwidth_estimator.GetBandwidthEstimate().IsFinite());
+}
+
+TEST(LossBasedBweV2Test, NoBandwidthEstimateGivenNoInitialization) {
+  PacketResult enough_feedback[2];
+  enough_feedback[0].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback[1].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback[0].sent_packet.send_time = Timestamp::Zero();
+  enough_feedback[1].sent_packet.send_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback[0].receive_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback[1].receive_time =
+      Timestamp::Zero() + 2 * kObservationDurationLowerBound;
+
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/true, /*valid=*/true));
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(enough_feedback);
+
+  EXPECT_FALSE(loss_based_bandwidth_estimator.IsReady());
+  EXPECT_TRUE(
+      loss_based_bandwidth_estimator.GetBandwidthEstimate().IsPlusInfinity());
+}
+
+TEST(LossBasedBweV2Test, NoBandwidthEstimateGivenNotEnoughFeedback) {
+  // Create packet results where the observation duration is less than the lower
+  // bound.
+  PacketResult not_enough_feedback[2];
+  not_enough_feedback[0].sent_packet.size = DataSize::Bytes(15'000);
+  not_enough_feedback[1].sent_packet.size = DataSize::Bytes(15'000);
+  not_enough_feedback[0].sent_packet.send_time = Timestamp::Zero();
+  not_enough_feedback[1].sent_packet.send_time =
+      Timestamp::Zero() + kObservationDurationLowerBound / 2;
+  not_enough_feedback[0].receive_time =
+      Timestamp::Zero() + kObservationDurationLowerBound / 2;
+  not_enough_feedback[1].receive_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/true, /*valid=*/true));
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+
+  EXPECT_FALSE(loss_based_bandwidth_estimator.IsReady());
+  EXPECT_TRUE(
+      loss_based_bandwidth_estimator.GetBandwidthEstimate().IsPlusInfinity());
+
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(not_enough_feedback);
+
+  EXPECT_FALSE(loss_based_bandwidth_estimator.IsReady());
+  EXPECT_TRUE(
+      loss_based_bandwidth_estimator.GetBandwidthEstimate().IsPlusInfinity());
+}
+
+TEST(LossBasedBweV2Test,
+     SetValueIsTheEstimateUntilAdditionalFeedbackHasBeenReceived) {
+  PacketResult enough_feedback_1[2];
+  PacketResult enough_feedback_2[2];
+  enough_feedback_1[0].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback_1[1].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback_2[0].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback_2[1].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback_1[0].sent_packet.send_time = Timestamp::Zero();
+  enough_feedback_1[1].sent_packet.send_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback_2[0].sent_packet.send_time =
+      Timestamp::Zero() + 2 * kObservationDurationLowerBound;
+  enough_feedback_2[1].sent_packet.send_time =
+      Timestamp::Zero() + 3 * kObservationDurationLowerBound;
+  enough_feedback_1[0].receive_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback_1[1].receive_time =
+      Timestamp::Zero() + 2 * kObservationDurationLowerBound;
+  enough_feedback_2[0].receive_time =
+      Timestamp::Zero() + 3 * kObservationDurationLowerBound;
+  enough_feedback_2[1].receive_time =
+      Timestamp::Zero() + 4 * kObservationDurationLowerBound;
+
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/true, /*valid=*/true));
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(enough_feedback_1);
+
+  EXPECT_NE(loss_based_bandwidth_estimator.GetBandwidthEstimate(),
+            DataRate::KilobitsPerSec(600));
+
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+
+  EXPECT_EQ(loss_based_bandwidth_estimator.GetBandwidthEstimate(),
+            DataRate::KilobitsPerSec(600));
+
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(enough_feedback_2);
+
+  EXPECT_NE(loss_based_bandwidth_estimator.GetBandwidthEstimate(),
+            DataRate::KilobitsPerSec(600));
+}
+
+TEST(LossBasedBweV2Test,
+     SetAcknowledgedBitrateOnlyAffectsTheBweWhenAdditionalFeedbackIsGiven) {
+  PacketResult enough_feedback_1[2];
+  PacketResult enough_feedback_2[2];
+  enough_feedback_1[0].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback_1[1].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback_2[0].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback_2[1].sent_packet.size = DataSize::Bytes(15'000);
+  enough_feedback_1[0].sent_packet.send_time = Timestamp::Zero();
+  enough_feedback_1[1].sent_packet.send_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback_2[0].sent_packet.send_time =
+      Timestamp::Zero() + 2 * kObservationDurationLowerBound;
+  enough_feedback_2[1].sent_packet.send_time =
+      Timestamp::Zero() + 3 * kObservationDurationLowerBound;
+  enough_feedback_1[0].receive_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback_1[1].receive_time =
+      Timestamp::Zero() + 2 * kObservationDurationLowerBound;
+  enough_feedback_2[0].receive_time =
+      Timestamp::Zero() + 3 * kObservationDurationLowerBound;
+  enough_feedback_2[1].receive_time =
+      Timestamp::Zero() + 4 * kObservationDurationLowerBound;
+
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/true, /*valid=*/true));
+  LossBasedBweV2 loss_based_bandwidth_estimator_1(&key_value_config);
+  LossBasedBweV2 loss_based_bandwidth_estimator_2(&key_value_config);
+
+  loss_based_bandwidth_estimator_1.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+  loss_based_bandwidth_estimator_2.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+  loss_based_bandwidth_estimator_1.UpdateBandwidthEstimate(enough_feedback_1);
+  loss_based_bandwidth_estimator_2.UpdateBandwidthEstimate(enough_feedback_1);
+
+  EXPECT_EQ(loss_based_bandwidth_estimator_1.GetBandwidthEstimate(),
+            DataRate::KilobitsPerSec(660));
+
+  loss_based_bandwidth_estimator_1.SetAcknowledgedBitrate(
+      DataRate::KilobitsPerSec(600));
+
+  EXPECT_EQ(loss_based_bandwidth_estimator_1.GetBandwidthEstimate(),
+            DataRate::KilobitsPerSec(660));
+
+  loss_based_bandwidth_estimator_1.UpdateBandwidthEstimate(enough_feedback_2);
+  loss_based_bandwidth_estimator_2.UpdateBandwidthEstimate(enough_feedback_2);
+
+  EXPECT_NE(loss_based_bandwidth_estimator_1.GetBandwidthEstimate(),
+            loss_based_bandwidth_estimator_2.GetBandwidthEstimate());
+}
+
+TEST(LossBasedBweV2Test,
+     BandwidthEstimateIsCappedToBeTcpFairGivenTooHighLossRate) {
+  PacketResult enough_feedback_no_received_packets[2];
+  enough_feedback_no_received_packets[0].sent_packet.size =
+      DataSize::Bytes(15'000);
+  enough_feedback_no_received_packets[1].sent_packet.size =
+      DataSize::Bytes(15'000);
+  enough_feedback_no_received_packets[0].sent_packet.send_time =
+      Timestamp::Zero();
+  enough_feedback_no_received_packets[1].sent_packet.send_time =
+      Timestamp::Zero() + kObservationDurationLowerBound;
+  enough_feedback_no_received_packets[0].receive_time =
+      Timestamp::PlusInfinity();
+  enough_feedback_no_received_packets[1].receive_time =
+      Timestamp::PlusInfinity();
+
+  test::ExplicitKeyValueConfig key_value_config(
+      Config(/*enabled=*/true, /*valid=*/true));
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_no_received_packets);
+
+  EXPECT_EQ(loss_based_bandwidth_estimator.GetBandwidthEstimate(),
+            DataRate::KilobitsPerSec(100));
+}
+
+}  // namespace
+
+}  // namespace webrtc
diff --git a/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc b/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
index c5f51df..749b073 100644
--- a/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
+++ b/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
@@ -23,6 +23,7 @@
 #include "api/units/data_rate.h"
 #include "api/units/time_delta.h"
 #include "logging/rtc_event_log/events/rtc_event_bwe_update_loss_based.h"
+#include "modules/congestion_controller/goog_cc/loss_based_bwe_v2.h"
 #include "modules/remote_bitrate_estimator/include/bwe_defines.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
@@ -226,7 +227,8 @@
       low_loss_threshold_(kDefaultLowLossThreshold),
       high_loss_threshold_(kDefaultHighLossThreshold),
       bitrate_threshold_(kDefaultBitrateThreshold),
-      loss_based_bandwidth_estimation_(key_value_config),
+      loss_based_bandwidth_estimator_v1_(key_value_config),
+      loss_based_bandwidth_estimator_v2_(key_value_config),
       receiver_limit_caps_only_("Enabled") {
   RTC_DCHECK(event_log);
   if (BweLossExperimentIsEnabled()) {
@@ -341,18 +343,29 @@
     absl::optional<DataRate> acknowledged_rate,
     Timestamp at_time) {
   acknowledged_rate_ = acknowledged_rate;
-  if (acknowledged_rate && loss_based_bandwidth_estimation_.Enabled()) {
-    loss_based_bandwidth_estimation_.UpdateAcknowledgedBitrate(
+  if (!acknowledged_rate.has_value()) {
+    return;
+  }
+  if (LossBasedBandwidthEstimatorV1Enabled()) {
+    loss_based_bandwidth_estimator_v1_.UpdateAcknowledgedBitrate(
         *acknowledged_rate, at_time);
   }
+  if (LossBasedBandwidthEstimatorV2Enabled()) {
+    loss_based_bandwidth_estimator_v2_.SetAcknowledgedBitrate(
+        *acknowledged_rate);
+  }
 }
 
 void SendSideBandwidthEstimation::IncomingPacketFeedbackVector(
     const TransportPacketsFeedback& report) {
-  if (loss_based_bandwidth_estimation_.Enabled()) {
-    loss_based_bandwidth_estimation_.UpdateLossStatistics(
+  if (LossBasedBandwidthEstimatorV1Enabled()) {
+    loss_based_bandwidth_estimator_v1_.UpdateLossStatistics(
         report.packet_feedbacks, report.feedback_time);
   }
+  if (LossBasedBandwidthEstimatorV2Enabled()) {
+    loss_based_bandwidth_estimator_v2_.UpdateBandwidthEstimate(
+        report.packet_feedbacks);
+  }
 }
 
 void SendSideBandwidthEstimation::UpdatePacketsLost(int64_t packets_lost,
@@ -459,13 +472,16 @@
       new_bitrate = std::max(receiver_limit_, new_bitrate);
     if (delay_based_limit_.IsFinite())
       new_bitrate = std::max(delay_based_limit_, new_bitrate);
-    if (loss_based_bandwidth_estimation_.Enabled()) {
-      loss_based_bandwidth_estimation_.Initialize(new_bitrate);
+    if (LossBasedBandwidthEstimatorV1Enabled()) {
+      loss_based_bandwidth_estimator_v1_.Initialize(new_bitrate);
+    }
+    if (LossBasedBandwidthEstimatorV2Enabled()) {
+      loss_based_bandwidth_estimator_v2_.SetBandwidthEstimate(new_bitrate);
     }
 
     if (new_bitrate != current_target_) {
       min_bitrate_history_.clear();
-      if (loss_based_bandwidth_estimation_.Enabled()) {
+      if (LossBasedBandwidthEstimatorV1Enabled()) {
         min_bitrate_history_.push_back(std::make_pair(at_time, new_bitrate));
       } else {
         min_bitrate_history_.push_back(
@@ -483,14 +499,22 @@
     return;
   }
 
-  if (loss_based_bandwidth_estimation_.InUse()) {
-    DataRate new_bitrate = loss_based_bandwidth_estimation_.Update(
+  if (LossBasedBandwidthEstimatorV1ReadyForUse()) {
+    DataRate new_bitrate = loss_based_bandwidth_estimator_v1_.Update(
         at_time, min_bitrate_history_.front().second, delay_based_limit_,
         last_round_trip_time_);
     UpdateTargetBitrate(new_bitrate, at_time);
     return;
   }
 
+  if (LossBasedBandwidthEstimatorV2ReadyForUse()) {
+    DataRate new_bitrate =
+        loss_based_bandwidth_estimator_v2_.GetBandwidthEstimate();
+    new_bitrate = std::min(new_bitrate, delay_based_limit_);
+    UpdateTargetBitrate(new_bitrate, at_time);
+    return;
+  }
+
   TimeDelta time_since_loss_packet_report = at_time - last_loss_packet_report_;
   if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) {
     // We only care about loss above a given bitrate threshold.
@@ -628,4 +652,26 @@
 void SendSideBandwidthEstimation::ApplyTargetLimits(Timestamp at_time) {
   UpdateTargetBitrate(current_target_, at_time);
 }
+
+bool SendSideBandwidthEstimation::LossBasedBandwidthEstimatorV1Enabled() const {
+  return loss_based_bandwidth_estimator_v1_.Enabled() &&
+         !LossBasedBandwidthEstimatorV2Enabled();
+}
+
+bool SendSideBandwidthEstimation::LossBasedBandwidthEstimatorV1ReadyForUse()
+    const {
+  return LossBasedBandwidthEstimatorV1Enabled() &&
+         loss_based_bandwidth_estimator_v1_.InUse();
+}
+
+bool SendSideBandwidthEstimation::LossBasedBandwidthEstimatorV2Enabled() const {
+  return loss_based_bandwidth_estimator_v2_.IsEnabled();
+}
+
+bool SendSideBandwidthEstimation::LossBasedBandwidthEstimatorV2ReadyForUse()
+    const {
+  return LossBasedBandwidthEstimatorV2Enabled() &&
+         loss_based_bandwidth_estimator_v2_.IsReady();
+}
+
 }  // namespace webrtc
diff --git a/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.h b/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.h
index 5917d9b..63be1f0 100644
--- a/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.h
+++ b/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.h
@@ -26,6 +26,7 @@
 #include "api/units/time_delta.h"
 #include "api/units/timestamp.h"
 #include "modules/congestion_controller/goog_cc/loss_based_bandwidth_estimation.h"
+#include "modules/congestion_controller/goog_cc/loss_based_bwe_v2.h"
 #include "rtc_base/experiments/field_trial_parser.h"
 
 namespace webrtc {
@@ -149,6 +150,12 @@
   // should be cleaned up.
   void ApplyTargetLimits(Timestamp at_time);
 
+  bool LossBasedBandwidthEstimatorV1Enabled() const;
+  bool LossBasedBandwidthEstimatorV2Enabled() const;
+
+  bool LossBasedBandwidthEstimatorV1ReadyForUse() const;
+  bool LossBasedBandwidthEstimatorV2ReadyForUse() const;
+
   RttBasedBackoff rtt_backoff_;
   LinkCapacityTracker link_capacity_;
 
@@ -189,7 +196,8 @@
   float low_loss_threshold_;
   float high_loss_threshold_;
   DataRate bitrate_threshold_;
-  LossBasedBandwidthEstimation loss_based_bandwidth_estimation_;
+  LossBasedBandwidthEstimation loss_based_bandwidth_estimator_v1_;
+  LossBasedBweV2 loss_based_bandwidth_estimator_v2_;
   FieldTrialFlag receiver_limit_caps_only_;
 };
 }  // namespace webrtc
diff --git a/modules/congestion_controller/goog_cc/test/goog_cc_printer.cc b/modules/congestion_controller/goog_cc/test/goog_cc_printer.cc
index 52baab0..6dadf8b 100644
--- a/modules/congestion_controller/goog_cc/test/goog_cc_printer.cc
+++ b/modules/congestion_controller/goog_cc/test/goog_cc_printer.cc
@@ -87,7 +87,7 @@
   };
   auto loss_cont = [&] {
     return &controller_->bandwidth_estimation_
-                ->loss_based_bandwidth_estimation_;
+                ->loss_based_bandwidth_estimator_v1_;
   };
   std::deque<FieldLogger*> loggers({
       Log("time", [=] { return target_.at_time; }),