| /* | 
 |  *  Copyright (c) 2014 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/remote_bitrate_estimator/aimd_rate_control.h" | 
 |  | 
 | #include <inttypes.h> | 
 |  | 
 | #include <algorithm> | 
 | #include <cmath> | 
 | #include <cstdio> | 
 | #include <string> | 
 |  | 
 | #include "absl/strings/match.h" | 
 | #include "api/transport/network_types.h" | 
 | #include "api/units/data_rate.h" | 
 | #include "modules/remote_bitrate_estimator/include/bwe_defines.h" | 
 | #include "modules/remote_bitrate_estimator/overuse_detector.h" | 
 | #include "rtc_base/checks.h" | 
 | #include "rtc_base/experiments/field_trial_parser.h" | 
 | #include "rtc_base/logging.h" | 
 | #include "rtc_base/numerics/safe_minmax.h" | 
 |  | 
 | namespace webrtc { | 
 | namespace { | 
 |  | 
 | constexpr TimeDelta kDefaultRtt = TimeDelta::Millis(200); | 
 | constexpr double kDefaultBackoffFactor = 0.85; | 
 |  | 
 | constexpr char kBweBackOffFactorExperiment[] = "WebRTC-BweBackOffFactor"; | 
 |  | 
 | double ReadBackoffFactor(const FieldTrialsView& key_value_config) { | 
 |   std::string experiment_string = | 
 |       key_value_config.Lookup(kBweBackOffFactorExperiment); | 
 |   double backoff_factor; | 
 |   int parsed_values = | 
 |       sscanf(experiment_string.c_str(), "Enabled-%lf", &backoff_factor); | 
 |   if (parsed_values == 1) { | 
 |     if (backoff_factor >= 1.0) { | 
 |       RTC_LOG(LS_WARNING) << "Back-off factor must be less than 1."; | 
 |     } else if (backoff_factor <= 0.0) { | 
 |       RTC_LOG(LS_WARNING) << "Back-off factor must be greater than 0."; | 
 |     } else { | 
 |       return backoff_factor; | 
 |     } | 
 |   } | 
 |   RTC_LOG(LS_WARNING) << "Failed to parse parameters for AimdRateControl " | 
 |                          "experiment from field trial string. Using default."; | 
 |   return kDefaultBackoffFactor; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | AimdRateControl::AimdRateControl(const FieldTrialsView& key_value_config) | 
 |     : AimdRateControl(key_value_config, /* send_side =*/false) {} | 
 |  | 
 | AimdRateControl::AimdRateControl(const FieldTrialsView& key_value_config, | 
 |                                  bool send_side) | 
 |     : min_configured_bitrate_(kCongestionControllerMinBitrate), | 
 |       max_configured_bitrate_(DataRate::KilobitsPerSec(30000)), | 
 |       current_bitrate_(max_configured_bitrate_), | 
 |       latest_estimated_throughput_(current_bitrate_), | 
 |       link_capacity_(), | 
 |       rate_control_state_(RateControlState::kRcHold), | 
 |       time_last_bitrate_change_(Timestamp::MinusInfinity()), | 
 |       time_last_bitrate_decrease_(Timestamp::MinusInfinity()), | 
 |       time_first_throughput_estimate_(Timestamp::MinusInfinity()), | 
 |       bitrate_is_initialized_(false), | 
 |       beta_(key_value_config.IsEnabled(kBweBackOffFactorExperiment) | 
 |                 ? ReadBackoffFactor(key_value_config) | 
 |                 : kDefaultBackoffFactor), | 
 |       in_alr_(false), | 
 |       rtt_(kDefaultRtt), | 
 |       send_side_(send_side), | 
 |       no_bitrate_increase_in_alr_( | 
 |           key_value_config.IsEnabled("WebRTC-DontIncreaseDelayBasedBweInAlr")), | 
 |       initial_backoff_interval_("initial_backoff_interval"), | 
 |       link_capacity_fix_("link_capacity_fix") { | 
 |   ParseFieldTrial( | 
 |       {&disable_estimate_bounded_increase_}, | 
 |       key_value_config.Lookup("WebRTC-Bwe-EstimateBoundedIncrease")); | 
 |   // E.g | 
 |   // WebRTC-BweAimdRateControlConfig/initial_backoff_interval:100ms/ | 
 |   ParseFieldTrial({&initial_backoff_interval_, &link_capacity_fix_}, | 
 |                   key_value_config.Lookup("WebRTC-BweAimdRateControlConfig")); | 
 |   if (initial_backoff_interval_) { | 
 |     RTC_LOG(LS_INFO) << "Using aimd rate control with initial back-off interval" | 
 |                         " " | 
 |                      << ToString(*initial_backoff_interval_) << "."; | 
 |   } | 
 |   RTC_LOG(LS_INFO) << "Using aimd rate control with back off factor " << beta_; | 
 | } | 
 |  | 
 | AimdRateControl::~AimdRateControl() {} | 
 |  | 
 | void AimdRateControl::SetStartBitrate(DataRate start_bitrate) { | 
 |   current_bitrate_ = start_bitrate; | 
 |   latest_estimated_throughput_ = current_bitrate_; | 
 |   bitrate_is_initialized_ = true; | 
 | } | 
 |  | 
 | void AimdRateControl::SetMinBitrate(DataRate min_bitrate) { | 
 |   min_configured_bitrate_ = min_bitrate; | 
 |   current_bitrate_ = std::max(min_bitrate, current_bitrate_); | 
 | } | 
 |  | 
 | bool AimdRateControl::ValidEstimate() const { | 
 |   return bitrate_is_initialized_; | 
 | } | 
 |  | 
 | TimeDelta AimdRateControl::GetFeedbackInterval() const { | 
 |   // Estimate how often we can send RTCP if we allocate up to 5% of bandwidth | 
 |   // to feedback. | 
 |   const DataSize kRtcpSize = DataSize::Bytes(80); | 
 |   const DataRate rtcp_bitrate = current_bitrate_ * 0.05; | 
 |   const TimeDelta interval = kRtcpSize / rtcp_bitrate; | 
 |   const TimeDelta kMinFeedbackInterval = TimeDelta::Millis(200); | 
 |   const TimeDelta kMaxFeedbackInterval = TimeDelta::Millis(1000); | 
 |   return interval.Clamped(kMinFeedbackInterval, kMaxFeedbackInterval); | 
 | } | 
 |  | 
 | bool AimdRateControl::TimeToReduceFurther(Timestamp at_time, | 
 |                                           DataRate estimated_throughput) const { | 
 |   const TimeDelta bitrate_reduction_interval = | 
 |       rtt_.Clamped(TimeDelta::Millis(10), TimeDelta::Millis(200)); | 
 |   if (at_time - time_last_bitrate_change_ >= bitrate_reduction_interval) { | 
 |     return true; | 
 |   } | 
 |   if (ValidEstimate()) { | 
 |     // TODO(terelius/holmer): Investigate consequences of increasing | 
 |     // the threshold to 0.95 * LatestEstimate(). | 
 |     const DataRate threshold = 0.5 * LatestEstimate(); | 
 |     return estimated_throughput < threshold; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool AimdRateControl::InitialTimeToReduceFurther(Timestamp at_time) const { | 
 |   if (!initial_backoff_interval_) { | 
 |     return ValidEstimate() && | 
 |            TimeToReduceFurther(at_time, | 
 |                                LatestEstimate() / 2 - DataRate::BitsPerSec(1)); | 
 |   } | 
 |   // TODO(terelius): We could use the RTT (clamped to suitable limits) instead | 
 |   // of a fixed bitrate_reduction_interval. | 
 |   if (time_last_bitrate_decrease_.IsInfinite() || | 
 |       at_time - time_last_bitrate_decrease_ >= *initial_backoff_interval_) { | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | DataRate AimdRateControl::LatestEstimate() const { | 
 |   return current_bitrate_; | 
 | } | 
 |  | 
 | void AimdRateControl::SetRtt(TimeDelta rtt) { | 
 |   rtt_ = rtt; | 
 | } | 
 |  | 
 | DataRate AimdRateControl::Update(const RateControlInput& input, | 
 |                                  Timestamp at_time) { | 
 |   // Set the initial bit rate value to what we're receiving the first half | 
 |   // second. | 
 |   // TODO(bugs.webrtc.org/9379): The comment above doesn't match to the code. | 
 |   if (!bitrate_is_initialized_) { | 
 |     const TimeDelta kInitializationTime = TimeDelta::Seconds(5); | 
 |     RTC_DCHECK_LE(kBitrateWindowMs, kInitializationTime.ms()); | 
 |     if (time_first_throughput_estimate_.IsInfinite()) { | 
 |       if (input.estimated_throughput) | 
 |         time_first_throughput_estimate_ = at_time; | 
 |     } else if (at_time - time_first_throughput_estimate_ > | 
 |                    kInitializationTime && | 
 |                input.estimated_throughput) { | 
 |       current_bitrate_ = *input.estimated_throughput; | 
 |       bitrate_is_initialized_ = true; | 
 |     } | 
 |   } | 
 |  | 
 |   ChangeBitrate(input, at_time); | 
 |   return current_bitrate_; | 
 | } | 
 |  | 
 | void AimdRateControl::SetInApplicationLimitedRegion(bool in_alr) { | 
 |   in_alr_ = in_alr; | 
 | } | 
 |  | 
 | void AimdRateControl::SetEstimate(DataRate bitrate, Timestamp at_time) { | 
 |   bitrate_is_initialized_ = true; | 
 |   DataRate prev_bitrate = current_bitrate_; | 
 |   current_bitrate_ = ClampBitrate(bitrate); | 
 |   time_last_bitrate_change_ = at_time; | 
 |   if (current_bitrate_ < prev_bitrate) { | 
 |     time_last_bitrate_decrease_ = at_time; | 
 |   } | 
 | } | 
 |  | 
 | void AimdRateControl::SetNetworkStateEstimate( | 
 |     const absl::optional<NetworkStateEstimate>& estimate) { | 
 |   network_estimate_ = estimate; | 
 | } | 
 |  | 
 | double AimdRateControl::GetNearMaxIncreaseRateBpsPerSecond() const { | 
 |   RTC_DCHECK(!current_bitrate_.IsZero()); | 
 |   const TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 30; | 
 |   DataSize frame_size = current_bitrate_ * kFrameInterval; | 
 |   const DataSize kPacketSize = DataSize::Bytes(1200); | 
 |   double packets_per_frame = std::ceil(frame_size / kPacketSize); | 
 |   DataSize avg_packet_size = frame_size / packets_per_frame; | 
 |  | 
 |   // Approximate the over-use estimator delay to 100 ms. | 
 |   TimeDelta response_time = rtt_ + TimeDelta::Millis(100); | 
 |  | 
 |   response_time = response_time * 2; | 
 |   double increase_rate_bps_per_second = | 
 |       (avg_packet_size / response_time).bps<double>(); | 
 |   double kMinIncreaseRateBpsPerSecond = 4000; | 
 |   return std::max(kMinIncreaseRateBpsPerSecond, increase_rate_bps_per_second); | 
 | } | 
 |  | 
 | TimeDelta AimdRateControl::GetExpectedBandwidthPeriod() const { | 
 |   const TimeDelta kMinPeriod = TimeDelta::Seconds(2); | 
 |   const TimeDelta kDefaultPeriod = TimeDelta::Seconds(3); | 
 |   const TimeDelta kMaxPeriod = TimeDelta::Seconds(50); | 
 |  | 
 |   double increase_rate_bps_per_second = GetNearMaxIncreaseRateBpsPerSecond(); | 
 |   if (!last_decrease_) | 
 |     return kDefaultPeriod; | 
 |   double time_to_recover_decrease_seconds = | 
 |       last_decrease_->bps() / increase_rate_bps_per_second; | 
 |   TimeDelta period = TimeDelta::Seconds(time_to_recover_decrease_seconds); | 
 |   return period.Clamped(kMinPeriod, kMaxPeriod); | 
 | } | 
 |  | 
 | void AimdRateControl::ChangeBitrate(const RateControlInput& input, | 
 |                                     Timestamp at_time) { | 
 |   absl::optional<DataRate> new_bitrate; | 
 |   DataRate estimated_throughput = | 
 |       input.estimated_throughput.value_or(latest_estimated_throughput_); | 
 |   if (input.estimated_throughput) | 
 |     latest_estimated_throughput_ = *input.estimated_throughput; | 
 |  | 
 |   // An over-use should always trigger us to reduce the bitrate, even though | 
 |   // we have not yet established our first estimate. By acting on the over-use, | 
 |   // we will end up with a valid estimate. | 
 |   if (!bitrate_is_initialized_ && | 
 |       input.bw_state != BandwidthUsage::kBwOverusing) | 
 |     return; | 
 |  | 
 |   ChangeState(input, at_time); | 
 |  | 
 |   switch (rate_control_state_) { | 
 |     case RateControlState::kRcHold: | 
 |       break; | 
 |  | 
 |     case RateControlState::kRcIncrease: { | 
 |       if (estimated_throughput > link_capacity_.UpperBound()) | 
 |         link_capacity_.Reset(); | 
 |  | 
 |       // We limit the new bitrate based on the troughput to avoid unlimited | 
 |       // bitrate increases. We allow a bit more lag at very low rates to not too | 
 |       // easily get stuck if the encoder produces uneven outputs. | 
 |       DataRate increase_limit = | 
 |           1.5 * estimated_throughput + DataRate::KilobitsPerSec(10); | 
 |       if (send_side_ && in_alr_ && no_bitrate_increase_in_alr_) { | 
 |         // Do not increase the delay based estimate in alr since the estimator | 
 |         // will not be able to get transport feedback necessary to detect if | 
 |         // the new estimate is correct. | 
 |         // If we have previously increased above the limit (for instance due to | 
 |         // probing), we don't allow further changes. | 
 |         increase_limit = current_bitrate_; | 
 |       } | 
 |  | 
 |       if (current_bitrate_ < increase_limit) { | 
 |         DataRate increased_bitrate = DataRate::MinusInfinity(); | 
 |         if (link_capacity_.has_estimate()) { | 
 |           // The link_capacity estimate is reset if the measured throughput | 
 |           // is too far from the estimate. We can therefore assume that our | 
 |           // target rate is reasonably close to link capacity and use additive | 
 |           // increase. | 
 |           DataRate additive_increase = | 
 |               AdditiveRateIncrease(at_time, time_last_bitrate_change_); | 
 |           increased_bitrate = current_bitrate_ + additive_increase; | 
 |         } else { | 
 |           // If we don't have an estimate of the link capacity, use faster ramp | 
 |           // up to discover the capacity. | 
 |           DataRate multiplicative_increase = MultiplicativeRateIncrease( | 
 |               at_time, time_last_bitrate_change_, current_bitrate_); | 
 |           increased_bitrate = current_bitrate_ + multiplicative_increase; | 
 |         } | 
 |         new_bitrate = std::min(increased_bitrate, increase_limit); | 
 |       } | 
 |       time_last_bitrate_change_ = at_time; | 
 |       break; | 
 |     } | 
 |  | 
 |     case RateControlState::kRcDecrease: { | 
 |       DataRate decreased_bitrate = DataRate::PlusInfinity(); | 
 |  | 
 |       // Set bit rate to something slightly lower than the measured throughput | 
 |       // to get rid of any self-induced delay. | 
 |       decreased_bitrate = estimated_throughput * beta_; | 
 |       if (decreased_bitrate > current_bitrate_ && !link_capacity_fix_) { | 
 |         // TODO(terelius): The link_capacity estimate may be based on old | 
 |         // throughput measurements. Relying on them may lead to unnecessary | 
 |         // BWE drops. | 
 |         if (link_capacity_.has_estimate()) { | 
 |           decreased_bitrate = beta_ * link_capacity_.estimate(); | 
 |         } | 
 |       } | 
 |       // Avoid increasing the rate when over-using. | 
 |       if (decreased_bitrate < current_bitrate_) { | 
 |         new_bitrate = decreased_bitrate; | 
 |       } | 
 |  | 
 |       if (bitrate_is_initialized_ && estimated_throughput < current_bitrate_) { | 
 |         if (!new_bitrate.has_value()) { | 
 |           last_decrease_ = DataRate::Zero(); | 
 |         } else { | 
 |           last_decrease_ = current_bitrate_ - *new_bitrate; | 
 |         } | 
 |       } | 
 |       if (estimated_throughput < link_capacity_.LowerBound()) { | 
 |         // The current throughput is far from the estimated link capacity. Clear | 
 |         // the estimate to allow an immediate update in OnOveruseDetected. | 
 |         link_capacity_.Reset(); | 
 |       } | 
 |  | 
 |       bitrate_is_initialized_ = true; | 
 |       link_capacity_.OnOveruseDetected(estimated_throughput); | 
 |       // Stay on hold until the pipes are cleared. | 
 |       rate_control_state_ = RateControlState::kRcHold; | 
 |       time_last_bitrate_change_ = at_time; | 
 |       time_last_bitrate_decrease_ = at_time; | 
 |       break; | 
 |     } | 
 |     default: | 
 |       RTC_DCHECK_NOTREACHED(); | 
 |   } | 
 |  | 
 |   current_bitrate_ = ClampBitrate(new_bitrate.value_or(current_bitrate_)); | 
 | } | 
 |  | 
 | DataRate AimdRateControl::ClampBitrate(DataRate new_bitrate) const { | 
 |   if (!disable_estimate_bounded_increase_ && network_estimate_ && | 
 |       network_estimate_->link_capacity_upper.IsFinite()) { | 
 |     DataRate upper_bound = network_estimate_->link_capacity_upper; | 
 |     new_bitrate = std::min(upper_bound, new_bitrate); | 
 |   } | 
 |   if (network_estimate_ && network_estimate_->link_capacity_lower.IsFinite() && | 
 |       new_bitrate < current_bitrate_) { | 
 |     new_bitrate = std::min( | 
 |         current_bitrate_, | 
 |         std::max(new_bitrate, network_estimate_->link_capacity_lower * beta_)); | 
 |   } | 
 |   new_bitrate = std::max(new_bitrate, min_configured_bitrate_); | 
 |   return new_bitrate; | 
 | } | 
 |  | 
 | DataRate AimdRateControl::MultiplicativeRateIncrease( | 
 |     Timestamp at_time, | 
 |     Timestamp last_time, | 
 |     DataRate current_bitrate) const { | 
 |   double alpha = 1.08; | 
 |   if (last_time.IsFinite()) { | 
 |     auto time_since_last_update = at_time - last_time; | 
 |     alpha = pow(alpha, std::min(time_since_last_update.seconds<double>(), 1.0)); | 
 |   } | 
 |   DataRate multiplicative_increase = | 
 |       std::max(current_bitrate * (alpha - 1.0), DataRate::BitsPerSec(1000)); | 
 |   return multiplicative_increase; | 
 | } | 
 |  | 
 | DataRate AimdRateControl::AdditiveRateIncrease(Timestamp at_time, | 
 |                                                Timestamp last_time) const { | 
 |   double time_period_seconds = (at_time - last_time).seconds<double>(); | 
 |   double data_rate_increase_bps = | 
 |       GetNearMaxIncreaseRateBpsPerSecond() * time_period_seconds; | 
 |   return DataRate::BitsPerSec(data_rate_increase_bps); | 
 | } | 
 |  | 
 | void AimdRateControl::ChangeState(const RateControlInput& input, | 
 |                                   Timestamp at_time) { | 
 |   switch (input.bw_state) { | 
 |     case BandwidthUsage::kBwNormal: | 
 |       if (rate_control_state_ == RateControlState::kRcHold) { | 
 |         time_last_bitrate_change_ = at_time; | 
 |         rate_control_state_ = RateControlState::kRcIncrease; | 
 |       } | 
 |       break; | 
 |     case BandwidthUsage::kBwOverusing: | 
 |       if (rate_control_state_ != RateControlState::kRcDecrease) { | 
 |         rate_control_state_ = RateControlState::kRcDecrease; | 
 |       } | 
 |       break; | 
 |     case BandwidthUsage::kBwUnderusing: | 
 |       rate_control_state_ = RateControlState::kRcHold; | 
 |       break; | 
 |     default: | 
 |       RTC_DCHECK_NOTREACHED(); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace webrtc |