|  | /* | 
|  | *  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 <cassert> | 
|  | #include <cmath> | 
|  | #include <cstdio> | 
|  | #include <string> | 
|  |  | 
|  | #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/logging.h" | 
|  | #include "rtc_base/numerics/safe_minmax.h" | 
|  | #include "system_wrappers/include/field_trial.h" | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | static const int64_t kDefaultRttMs = 200; | 
|  | static const int64_t kMaxFeedbackIntervalMs = 1000; | 
|  | static const float kDefaultBackoffFactor = 0.85f; | 
|  | static const int64_t kDefaultInitialBackOffIntervalMs = 200; | 
|  |  | 
|  | const char kBweBackOffFactorExperiment[] = "WebRTC-BweBackOffFactor"; | 
|  | const char kBweInitialBackOffIntervalExperiment[] = | 
|  | "WebRTC-BweInitialBackOffInterval"; | 
|  |  | 
|  | float ReadBackoffFactor() { | 
|  | std::string experiment_string = | 
|  | webrtc::field_trial::FindFullName(kBweBackOffFactorExperiment); | 
|  | float backoff_factor; | 
|  | int parsed_values = | 
|  | sscanf(experiment_string.c_str(), "Enabled-%f", &backoff_factor); | 
|  | if (parsed_values == 1) { | 
|  | if (backoff_factor >= 1.0f) { | 
|  | RTC_LOG(WARNING) << "Back-off factor must be less than 1."; | 
|  | } else if (backoff_factor <= 0.0f) { | 
|  | RTC_LOG(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; | 
|  | } | 
|  |  | 
|  | int64_t ReadInitialBackoffIntervalMs() { | 
|  | std::string experiment_string = | 
|  | webrtc::field_trial::FindFullName(kBweInitialBackOffIntervalExperiment); | 
|  | int64_t backoff_interval; | 
|  | int parsed_values = | 
|  | sscanf(experiment_string.c_str(), "Enabled-%" SCNd64, &backoff_interval); | 
|  | if (parsed_values == 1) { | 
|  | if (10 <= backoff_interval && backoff_interval <= 200) { | 
|  | return backoff_interval; | 
|  | } | 
|  | RTC_LOG(WARNING) | 
|  | << "Initial back-off interval must be between 10 and 200 ms."; | 
|  | } | 
|  | RTC_LOG(LS_WARNING) << "Failed to parse parameters for " | 
|  | << kBweInitialBackOffIntervalExperiment | 
|  | << " experiment. Using default."; | 
|  | return kDefaultInitialBackOffIntervalMs; | 
|  | } | 
|  |  | 
|  | AimdRateControl::AimdRateControl() | 
|  | : min_configured_bitrate_bps_(congestion_controller::GetMinBitrateBps()), | 
|  | max_configured_bitrate_bps_(30000000), | 
|  | current_bitrate_bps_(max_configured_bitrate_bps_), | 
|  | latest_estimated_throughput_bps_(current_bitrate_bps_), | 
|  | avg_max_bitrate_kbps_(-1.0f), | 
|  | var_max_bitrate_kbps_(0.4f), | 
|  | rate_control_state_(kRcHold), | 
|  | rate_control_region_(kRcMaxUnknown), | 
|  | time_last_bitrate_change_(-1), | 
|  | time_last_bitrate_decrease_(-1), | 
|  | time_first_throughput_estimate_(-1), | 
|  | bitrate_is_initialized_(false), | 
|  | beta_(webrtc::field_trial::IsEnabled(kBweBackOffFactorExperiment) | 
|  | ? ReadBackoffFactor() | 
|  | : kDefaultBackoffFactor), | 
|  | rtt_(kDefaultRttMs), | 
|  | in_experiment_(!AdaptiveThresholdExperimentIsDisabled()), | 
|  | smoothing_experiment_( | 
|  | webrtc::field_trial::IsEnabled("WebRTC-Audio-BandwidthSmoothing")), | 
|  | in_initial_backoff_interval_experiment_( | 
|  | webrtc::field_trial::IsEnabled(kBweInitialBackOffIntervalExperiment)), | 
|  | initial_backoff_interval_ms_(kDefaultInitialBackOffIntervalMs) { | 
|  | if (in_initial_backoff_interval_experiment_) { | 
|  | initial_backoff_interval_ms_ = ReadInitialBackoffIntervalMs(); | 
|  | RTC_LOG(LS_INFO) << "Using aimd rate control with initial back-off interval" | 
|  | << " " << initial_backoff_interval_ms_ << " ms."; | 
|  | } | 
|  | RTC_LOG(LS_INFO) << "Using aimd rate control with back off factor " << beta_; | 
|  | } | 
|  |  | 
|  | AimdRateControl::~AimdRateControl() {} | 
|  |  | 
|  | void AimdRateControl::SetStartBitrate(int start_bitrate_bps) { | 
|  | current_bitrate_bps_ = start_bitrate_bps; | 
|  | latest_estimated_throughput_bps_ = current_bitrate_bps_; | 
|  | bitrate_is_initialized_ = true; | 
|  | } | 
|  |  | 
|  | void AimdRateControl::SetMinBitrate(int min_bitrate_bps) { | 
|  | min_configured_bitrate_bps_ = min_bitrate_bps; | 
|  | current_bitrate_bps_ = std::max<int>(min_bitrate_bps, current_bitrate_bps_); | 
|  | } | 
|  |  | 
|  | bool AimdRateControl::ValidEstimate() const { | 
|  | return bitrate_is_initialized_; | 
|  | } | 
|  |  | 
|  | int64_t AimdRateControl::GetFeedbackInterval() const { | 
|  | // Estimate how often we can send RTCP if we allocate up to 5% of bandwidth | 
|  | // to feedback. | 
|  | static const int kRtcpSize = 80; | 
|  | const int64_t interval = static_cast<int64_t>( | 
|  | kRtcpSize * 8.0 * 1000.0 / (0.05 * current_bitrate_bps_) + 0.5); | 
|  | const int64_t kMinFeedbackIntervalMs = 200; | 
|  | return rtc::SafeClamp(interval, kMinFeedbackIntervalMs, | 
|  | kMaxFeedbackIntervalMs); | 
|  | } | 
|  |  | 
|  | bool AimdRateControl::TimeToReduceFurther( | 
|  | int64_t now_ms, | 
|  | uint32_t estimated_throughput_bps) const { | 
|  | const int64_t bitrate_reduction_interval = | 
|  | std::max<int64_t>(std::min<int64_t>(rtt_, 200), 10); | 
|  | if (now_ms - 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 uint32_t threshold = static_cast<uint32_t>(0.5 * LatestEstimate()); | 
|  | return estimated_throughput_bps < threshold; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AimdRateControl::InitialTimeToReduceFurther(int64_t now_ms) const { | 
|  | if (!in_initial_backoff_interval_experiment_) { | 
|  | return ValidEstimate() && | 
|  | TimeToReduceFurther(now_ms, LatestEstimate() / 2 - 1); | 
|  | } | 
|  | // TODO(terelius): We could use the RTT (clamped to suitable limits) instead | 
|  | // of a fixed bitrate_reduction_interval. | 
|  | if (time_last_bitrate_decrease_ == -1 || | 
|  | now_ms - time_last_bitrate_decrease_ >= initial_backoff_interval_ms_) { | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32_t AimdRateControl::LatestEstimate() const { | 
|  | return current_bitrate_bps_; | 
|  | } | 
|  |  | 
|  | void AimdRateControl::SetRtt(int64_t rtt) { | 
|  | rtt_ = rtt; | 
|  | } | 
|  |  | 
|  | uint32_t AimdRateControl::Update(const RateControlInput* input, | 
|  | int64_t now_ms) { | 
|  | RTC_CHECK(input); | 
|  |  | 
|  | // 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 int64_t kInitializationTimeMs = 5000; | 
|  | RTC_DCHECK_LE(kBitrateWindowMs, kInitializationTimeMs); | 
|  | if (time_first_throughput_estimate_ < 0) { | 
|  | if (input->estimated_throughput_bps) | 
|  | time_first_throughput_estimate_ = now_ms; | 
|  | } else if (now_ms - time_first_throughput_estimate_ > | 
|  | kInitializationTimeMs && | 
|  | input->estimated_throughput_bps) { | 
|  | current_bitrate_bps_ = *input->estimated_throughput_bps; | 
|  | bitrate_is_initialized_ = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | current_bitrate_bps_ = ChangeBitrate(current_bitrate_bps_, *input, now_ms); | 
|  | return current_bitrate_bps_; | 
|  | } | 
|  |  | 
|  | void AimdRateControl::SetEstimate(int bitrate_bps, int64_t now_ms) { | 
|  | bitrate_is_initialized_ = true; | 
|  | uint32_t prev_bitrate_bps = current_bitrate_bps_; | 
|  | current_bitrate_bps_ = ClampBitrate(bitrate_bps, bitrate_bps); | 
|  | time_last_bitrate_change_ = now_ms; | 
|  | if (current_bitrate_bps_ < prev_bitrate_bps) { | 
|  | time_last_bitrate_decrease_ = now_ms; | 
|  | } | 
|  | } | 
|  |  | 
|  | int AimdRateControl::GetNearMaxIncreaseRateBps() const { | 
|  | RTC_DCHECK_GT(current_bitrate_bps_, 0); | 
|  | double bits_per_frame = static_cast<double>(current_bitrate_bps_) / 30.0; | 
|  | double packets_per_frame = std::ceil(bits_per_frame / (8.0 * 1200.0)); | 
|  | double avg_packet_size_bits = bits_per_frame / packets_per_frame; | 
|  |  | 
|  | // Approximate the over-use estimator delay to 100 ms. | 
|  | const int64_t response_time = in_experiment_ ? (rtt_ + 100) * 2 : rtt_ + 100; | 
|  | constexpr double kMinIncreaseRateBps = 4000; | 
|  | return static_cast<int>(std::max( | 
|  | kMinIncreaseRateBps, (avg_packet_size_bits * 1000) / response_time)); | 
|  | } | 
|  |  | 
|  | int AimdRateControl::GetExpectedBandwidthPeriodMs() const { | 
|  | const int kMinPeriodMs = smoothing_experiment_ ? 500 : 2000; | 
|  | constexpr int kDefaultPeriodMs = 3000; | 
|  | constexpr int kMaxPeriodMs = 50000; | 
|  |  | 
|  | int increase_rate = GetNearMaxIncreaseRateBps(); | 
|  | if (!last_decrease_) | 
|  | return smoothing_experiment_ ? kMinPeriodMs : kDefaultPeriodMs; | 
|  |  | 
|  | return std::min(kMaxPeriodMs, | 
|  | std::max<int>(1000 * static_cast<int64_t>(*last_decrease_) / | 
|  | increase_rate, | 
|  | kMinPeriodMs)); | 
|  | } | 
|  |  | 
|  | uint32_t AimdRateControl::ChangeBitrate(uint32_t new_bitrate_bps, | 
|  | const RateControlInput& input, | 
|  | int64_t now_ms) { | 
|  | uint32_t estimated_throughput_bps = | 
|  | input.estimated_throughput_bps.value_or(latest_estimated_throughput_bps_); | 
|  | if (input.estimated_throughput_bps) | 
|  | latest_estimated_throughput_bps_ = *input.estimated_throughput_bps; | 
|  |  | 
|  | // 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 current_bitrate_bps_; | 
|  |  | 
|  | ChangeState(input, now_ms); | 
|  | // Calculated here because it's used in multiple places. | 
|  | const float estimated_throughput_kbps = estimated_throughput_bps / 1000.0f; | 
|  | // Calculate the max bit rate std dev given the normalized | 
|  | // variance and the current throughput bitrate. | 
|  | const float std_max_bit_rate = | 
|  | sqrt(var_max_bitrate_kbps_ * avg_max_bitrate_kbps_); | 
|  | switch (rate_control_state_) { | 
|  | case kRcHold: | 
|  | break; | 
|  |  | 
|  | case kRcIncrease: | 
|  | if (avg_max_bitrate_kbps_ >= 0 && | 
|  | estimated_throughput_kbps > | 
|  | avg_max_bitrate_kbps_ + 3 * std_max_bit_rate) { | 
|  | rate_control_region_ = kRcMaxUnknown; | 
|  |  | 
|  | avg_max_bitrate_kbps_ = -1.0; | 
|  | } | 
|  | if (rate_control_region_ == kRcNearMax) { | 
|  | uint32_t additive_increase_bps = | 
|  | AdditiveRateIncrease(now_ms, time_last_bitrate_change_); | 
|  | new_bitrate_bps += additive_increase_bps; | 
|  | } else { | 
|  | uint32_t multiplicative_increase_bps = MultiplicativeRateIncrease( | 
|  | now_ms, time_last_bitrate_change_, new_bitrate_bps); | 
|  | new_bitrate_bps += multiplicative_increase_bps; | 
|  | } | 
|  |  | 
|  | time_last_bitrate_change_ = now_ms; | 
|  | break; | 
|  |  | 
|  | case kRcDecrease: | 
|  | // Set bit rate to something slightly lower than max | 
|  | // to get rid of any self-induced delay. | 
|  | new_bitrate_bps = | 
|  | static_cast<uint32_t>(beta_ * estimated_throughput_bps + 0.5); | 
|  | if (new_bitrate_bps > current_bitrate_bps_) { | 
|  | // Avoid increasing the rate when over-using. | 
|  | if (rate_control_region_ != kRcMaxUnknown) { | 
|  | new_bitrate_bps = static_cast<uint32_t>( | 
|  | beta_ * avg_max_bitrate_kbps_ * 1000 + 0.5f); | 
|  | } | 
|  | new_bitrate_bps = std::min(new_bitrate_bps, current_bitrate_bps_); | 
|  | } | 
|  | rate_control_region_ = kRcNearMax; | 
|  |  | 
|  | if (bitrate_is_initialized_ && | 
|  | estimated_throughput_bps < current_bitrate_bps_) { | 
|  | constexpr float kDegradationFactor = 0.9f; | 
|  | if (smoothing_experiment_ && | 
|  | new_bitrate_bps < | 
|  | kDegradationFactor * beta_ * current_bitrate_bps_) { | 
|  | // If bitrate decreases more than a normal back off after overuse, it | 
|  | // indicates a real network degradation. We do not let such a decrease | 
|  | // to determine the bandwidth estimation period. | 
|  | last_decrease_ = absl::nullopt; | 
|  | } else { | 
|  | last_decrease_ = current_bitrate_bps_ - new_bitrate_bps; | 
|  | } | 
|  | } | 
|  | if (estimated_throughput_kbps < | 
|  | avg_max_bitrate_kbps_ - 3 * std_max_bit_rate) { | 
|  | avg_max_bitrate_kbps_ = -1.0f; | 
|  | } | 
|  |  | 
|  | bitrate_is_initialized_ = true; | 
|  | UpdateMaxThroughputEstimate(estimated_throughput_kbps); | 
|  | // Stay on hold until the pipes are cleared. | 
|  | rate_control_state_ = kRcHold; | 
|  | time_last_bitrate_change_ = now_ms; | 
|  | time_last_bitrate_decrease_ = now_ms; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | assert(false); | 
|  | } | 
|  | return ClampBitrate(new_bitrate_bps, estimated_throughput_bps); | 
|  | } | 
|  |  | 
|  | uint32_t AimdRateControl::ClampBitrate( | 
|  | uint32_t new_bitrate_bps, | 
|  | uint32_t estimated_throughput_bps) const { | 
|  | // Don't change the bit rate if the send side is too far off. | 
|  | // We allow a bit more lag at very low rates to not too easily get stuck if | 
|  | // the encoder produces uneven outputs. | 
|  | const uint32_t max_bitrate_bps = | 
|  | static_cast<uint32_t>(1.5f * estimated_throughput_bps) + 10000; | 
|  | if (new_bitrate_bps > current_bitrate_bps_ && | 
|  | new_bitrate_bps > max_bitrate_bps) { | 
|  | new_bitrate_bps = std::max(current_bitrate_bps_, max_bitrate_bps); | 
|  | } | 
|  | new_bitrate_bps = std::max(new_bitrate_bps, min_configured_bitrate_bps_); | 
|  | return new_bitrate_bps; | 
|  | } | 
|  |  | 
|  | uint32_t AimdRateControl::MultiplicativeRateIncrease( | 
|  | int64_t now_ms, | 
|  | int64_t last_ms, | 
|  | uint32_t current_bitrate_bps) const { | 
|  | double alpha = 1.08; | 
|  | if (last_ms > -1) { | 
|  | auto time_since_last_update_ms = | 
|  | rtc::SafeMin<int64_t>(now_ms - last_ms, 1000); | 
|  | alpha = pow(alpha, time_since_last_update_ms / 1000.0); | 
|  | } | 
|  | uint32_t multiplicative_increase_bps = | 
|  | std::max(current_bitrate_bps * (alpha - 1.0), 1000.0); | 
|  | return multiplicative_increase_bps; | 
|  | } | 
|  |  | 
|  | uint32_t AimdRateControl::AdditiveRateIncrease(int64_t now_ms, | 
|  | int64_t last_ms) const { | 
|  | return static_cast<uint32_t>((now_ms - last_ms) * | 
|  | GetNearMaxIncreaseRateBps() / 1000); | 
|  | } | 
|  |  | 
|  | void AimdRateControl::UpdateMaxThroughputEstimate( | 
|  | float estimated_throughput_kbps) { | 
|  | const float alpha = 0.05f; | 
|  | if (avg_max_bitrate_kbps_ == -1.0f) { | 
|  | avg_max_bitrate_kbps_ = estimated_throughput_kbps; | 
|  | } else { | 
|  | avg_max_bitrate_kbps_ = | 
|  | (1 - alpha) * avg_max_bitrate_kbps_ + alpha * estimated_throughput_kbps; | 
|  | } | 
|  | // Estimate the max bit rate variance and normalize the variance | 
|  | // with the average max bit rate. | 
|  | const float norm = std::max(avg_max_bitrate_kbps_, 1.0f); | 
|  | var_max_bitrate_kbps_ = | 
|  | (1 - alpha) * var_max_bitrate_kbps_ + | 
|  | alpha * (avg_max_bitrate_kbps_ - estimated_throughput_kbps) * | 
|  | (avg_max_bitrate_kbps_ - estimated_throughput_kbps) / norm; | 
|  | // 0.4 ~= 14 kbit/s at 500 kbit/s | 
|  | if (var_max_bitrate_kbps_ < 0.4f) { | 
|  | var_max_bitrate_kbps_ = 0.4f; | 
|  | } | 
|  | // 2.5f ~= 35 kbit/s at 500 kbit/s | 
|  | if (var_max_bitrate_kbps_ > 2.5f) { | 
|  | var_max_bitrate_kbps_ = 2.5f; | 
|  | } | 
|  | } | 
|  |  | 
|  | void AimdRateControl::ChangeState(const RateControlInput& input, | 
|  | int64_t now_ms) { | 
|  | switch (input.bw_state) { | 
|  | case BandwidthUsage::kBwNormal: | 
|  | if (rate_control_state_ == kRcHold) { | 
|  | time_last_bitrate_change_ = now_ms; | 
|  | rate_control_state_ = kRcIncrease; | 
|  | } | 
|  | break; | 
|  | case BandwidthUsage::kBwOverusing: | 
|  | if (rate_control_state_ != kRcDecrease) { | 
|  | rate_control_state_ = kRcDecrease; | 
|  | } | 
|  | break; | 
|  | case BandwidthUsage::kBwUnderusing: | 
|  | rate_control_state_ = kRcHold; | 
|  | break; | 
|  | default: | 
|  | assert(false); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |