Christoffer Rodbro | 3a83748 | 2018-11-19 15:30:23 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | |
| 11 | #include "modules/bitrate_controller/loss_based_bandwidth_estimation.h" |
| 12 | |
| 13 | #include <algorithm> |
| 14 | #include <string> |
| 15 | #include <vector> |
| 16 | |
| 17 | #include "api/units/data_rate.h" |
| 18 | #include "api/units/time_delta.h" |
| 19 | #include "system_wrappers/include/field_trial.h" |
| 20 | |
| 21 | namespace webrtc { |
| 22 | namespace { |
| 23 | const char kBweLossBasedControl[] = "WebRTC-Bwe-LossBasedControl"; |
| 24 | |
| 25 | // Increase slower when RTT is high. |
| 26 | double GetIncreaseFactor(const LossBasedControlConfig& config, TimeDelta rtt) { |
| 27 | // Clamp the RTT |
| 28 | if (rtt < config.increase_low_rtt) { |
| 29 | rtt = config.increase_low_rtt; |
| 30 | } else if (rtt > config.increase_high_rtt) { |
| 31 | rtt = config.increase_high_rtt; |
| 32 | } |
| 33 | auto rtt_range = config.increase_high_rtt.Get() - config.increase_low_rtt; |
| 34 | if (rtt_range <= TimeDelta::Zero()) { |
| 35 | RTC_DCHECK(false); // Only on misconfiguration. |
| 36 | return config.min_increase_factor; |
| 37 | } |
| 38 | auto rtt_offset = rtt - config.increase_low_rtt; |
| 39 | auto relative_offset = std::max(0.0, std::min(rtt_offset / rtt_range, 1.0)); |
| 40 | auto factor_range = config.max_increase_factor - config.min_increase_factor; |
| 41 | return config.min_increase_factor + (1 - relative_offset) * factor_range; |
| 42 | } |
| 43 | |
| 44 | double LossFromBitrate(DataRate bitrate, |
| 45 | DataRate loss_bandwidth_balance, |
| 46 | double exponent) { |
| 47 | if (loss_bandwidth_balance >= bitrate) |
| 48 | return 1.0; |
| 49 | return pow(loss_bandwidth_balance / bitrate, exponent); |
| 50 | } |
| 51 | |
| 52 | DataRate BitrateFromLoss(double loss, |
| 53 | DataRate loss_bandwidth_balance, |
| 54 | double exponent) { |
| 55 | if (exponent <= 0) { |
| 56 | RTC_DCHECK(false); |
| 57 | return DataRate::Infinity(); |
| 58 | } |
| 59 | if (loss < 1e-5) |
| 60 | return DataRate::Infinity(); |
| 61 | return loss_bandwidth_balance * pow(loss, -1.0 / exponent); |
| 62 | } |
| 63 | |
| 64 | double ExponentialUpdate(TimeDelta window, TimeDelta interval) { |
| 65 | // Use the convention that exponential window length (which is really |
| 66 | // infinite) is the time it takes to dampen to 1/e. |
| 67 | if (window <= TimeDelta::Zero()) { |
| 68 | RTC_DCHECK(false); |
| 69 | return 1.0f; |
| 70 | } |
| 71 | return 1.0f - exp(interval / window * -1.0); |
| 72 | } |
| 73 | |
| 74 | } // namespace |
| 75 | |
| 76 | LossBasedControlConfig::LossBasedControlConfig() |
| 77 | : enabled(field_trial::IsEnabled(kBweLossBasedControl)), |
| 78 | min_increase_factor("min_incr", 1.02), |
| 79 | max_increase_factor("max_incr", 1.08), |
| 80 | increase_low_rtt("incr_low_rtt", TimeDelta::ms(200)), |
| 81 | increase_high_rtt("incr_high_rtt", TimeDelta::ms(800)), |
| 82 | decrease_factor("decr", 0.99), |
| 83 | loss_window("loss_win", TimeDelta::ms(800)), |
| 84 | loss_max_window("loss_max_win", TimeDelta::ms(800)), |
| 85 | acknowledged_rate_max_window("ackrate_max_win", TimeDelta::ms(800)), |
| 86 | increase_offset("incr_offset", DataRate::bps(1000)), |
| 87 | loss_bandwidth_balance_increase("balance_incr", DataRate::kbps(0.5)), |
| 88 | loss_bandwidth_balance_decrease("balance_decr", DataRate::kbps(4)), |
| 89 | loss_bandwidth_balance_exponent("exponent", 0.5), |
| 90 | allow_resets("resets", false), |
| 91 | decrease_interval("decr_intvl", TimeDelta::ms(300)), |
| 92 | loss_report_timeout("timeout", TimeDelta::ms(6000)) { |
| 93 | std::string trial_string = field_trial::FindFullName(kBweLossBasedControl); |
| 94 | ParseFieldTrial( |
| 95 | {&min_increase_factor, &max_increase_factor, &increase_low_rtt, |
| 96 | &increase_high_rtt, &decrease_factor, &loss_window, &loss_max_window, |
| 97 | &acknowledged_rate_max_window, &increase_offset, |
| 98 | &loss_bandwidth_balance_increase, &loss_bandwidth_balance_decrease, |
| 99 | &loss_bandwidth_balance_exponent, &allow_resets, &decrease_interval, |
| 100 | &loss_report_timeout}, |
| 101 | trial_string); |
| 102 | } |
| 103 | LossBasedControlConfig::LossBasedControlConfig(const LossBasedControlConfig&) = |
| 104 | default; |
| 105 | LossBasedControlConfig::~LossBasedControlConfig() = default; |
| 106 | |
| 107 | LossBasedBandwidthEstimation::LossBasedBandwidthEstimation() |
| 108 | : config_(LossBasedControlConfig()), |
| 109 | average_loss_(0), |
| 110 | average_loss_max_(0), |
| 111 | loss_based_bitrate_(DataRate::Zero()), |
| 112 | acknowledged_bitrate_max_(DataRate::Zero()), |
| 113 | acknowledged_bitrate_last_update_(Timestamp::MinusInfinity()), |
| 114 | time_last_decrease_(Timestamp::MinusInfinity()), |
| 115 | has_decreased_since_last_loss_report_(false), |
| 116 | last_loss_packet_report_(Timestamp::MinusInfinity()), |
| 117 | last_loss_ratio_(0) {} |
| 118 | |
| 119 | void LossBasedBandwidthEstimation::UpdateLossStatistics( |
| 120 | const std::vector<PacketResult>& packet_results, |
| 121 | Timestamp at_time) { |
| 122 | if (packet_results.empty()) { |
| 123 | RTC_DCHECK(false); |
| 124 | return; |
| 125 | } |
| 126 | int loss_count = 0; |
Mirko Bonadei | 739baf0 | 2019-01-27 17:29:42 +0100 | [diff] [blame^] | 127 | for (const auto& pkt : packet_results) { |
Christoffer Rodbro | 3a83748 | 2018-11-19 15:30:23 +0100 | [diff] [blame] | 128 | loss_count += pkt.receive_time.IsInfinite() ? 1 : 0; |
| 129 | } |
| 130 | last_loss_ratio_ = static_cast<double>(loss_count) / packet_results.size(); |
| 131 | const TimeDelta time_passed = last_loss_packet_report_.IsFinite() |
| 132 | ? at_time - last_loss_packet_report_ |
| 133 | : TimeDelta::seconds(1); |
| 134 | last_loss_packet_report_ = at_time; |
| 135 | has_decreased_since_last_loss_report_ = false; |
| 136 | |
| 137 | average_loss_ += ExponentialUpdate(config_.loss_window, time_passed) * |
| 138 | (last_loss_ratio_ - average_loss_); |
| 139 | if (average_loss_ > average_loss_max_) { |
| 140 | average_loss_max_ = average_loss_; |
| 141 | } else { |
| 142 | average_loss_max_ += |
| 143 | ExponentialUpdate(config_.loss_max_window, time_passed) * |
| 144 | (average_loss_ - average_loss_max_); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | void LossBasedBandwidthEstimation::UpdateAcknowledgedBitrate( |
| 149 | DataRate acknowledged_bitrate, |
| 150 | Timestamp at_time) { |
| 151 | const TimeDelta time_passed = |
| 152 | acknowledged_bitrate_last_update_.IsFinite() |
| 153 | ? at_time - acknowledged_bitrate_last_update_ |
| 154 | : TimeDelta::seconds(1); |
| 155 | acknowledged_bitrate_last_update_ = at_time; |
| 156 | if (acknowledged_bitrate > acknowledged_bitrate_max_) { |
| 157 | acknowledged_bitrate_max_ = acknowledged_bitrate; |
| 158 | } else { |
Christoffer Rodbro | 5976bde | 2018-11-29 17:12:52 +0100 | [diff] [blame] | 159 | acknowledged_bitrate_max_ -= |
Christoffer Rodbro | 3a83748 | 2018-11-19 15:30:23 +0100 | [diff] [blame] | 160 | ExponentialUpdate(config_.acknowledged_rate_max_window, time_passed) * |
Christoffer Rodbro | 5976bde | 2018-11-29 17:12:52 +0100 | [diff] [blame] | 161 | (acknowledged_bitrate_max_ - acknowledged_bitrate); |
Christoffer Rodbro | 3a83748 | 2018-11-19 15:30:23 +0100 | [diff] [blame] | 162 | } |
| 163 | } |
| 164 | |
| 165 | void LossBasedBandwidthEstimation::Update(Timestamp at_time, |
| 166 | DataRate min_bitrate, |
| 167 | TimeDelta last_round_trip_time) { |
| 168 | // Only increase if loss has been low for some time. |
| 169 | const double loss_estimate_for_increase = average_loss_max_; |
| 170 | // Avoid multiple decreases from averaging over one loss spike. |
| 171 | const double loss_estimate_for_decrease = |
| 172 | std::min(average_loss_, last_loss_ratio_); |
| 173 | |
| 174 | const double loss_increase_threshold = LossFromBitrate( |
| 175 | loss_based_bitrate_, config_.loss_bandwidth_balance_increase, |
| 176 | config_.loss_bandwidth_balance_exponent); |
| 177 | const double loss_decrease_threshold = LossFromBitrate( |
| 178 | loss_based_bitrate_, config_.loss_bandwidth_balance_decrease, |
| 179 | config_.loss_bandwidth_balance_exponent); |
| 180 | const bool allow_decrease = |
| 181 | !has_decreased_since_last_loss_report_ && |
| 182 | (at_time - time_last_decrease_ >= |
| 183 | last_round_trip_time + config_.decrease_interval); |
| 184 | |
| 185 | if (loss_estimate_for_increase < loss_increase_threshold) { |
| 186 | // Increase bitrate by RTT-adaptive ratio. |
| 187 | DataRate new_increased_bitrate = |
| 188 | min_bitrate * GetIncreaseFactor(config_, last_round_trip_time) + |
| 189 | config_.increase_offset; |
| 190 | // The bitrate that would make the loss "just high enough". |
| 191 | const DataRate new_increased_bitrate_cap = BitrateFromLoss( |
| 192 | loss_estimate_for_increase, config_.loss_bandwidth_balance_increase, |
| 193 | config_.loss_bandwidth_balance_exponent); |
| 194 | new_increased_bitrate = |
| 195 | std::min(new_increased_bitrate, new_increased_bitrate_cap); |
| 196 | loss_based_bitrate_ = std::max(new_increased_bitrate, loss_based_bitrate_); |
| 197 | } else if (loss_estimate_for_decrease > loss_decrease_threshold && |
| 198 | allow_decrease) { |
| 199 | DataRate new_decreased_bitrate = |
| 200 | config_.decrease_factor * acknowledged_bitrate_max_; |
| 201 | // The bitrate that would make the loss "just acceptable". |
| 202 | const DataRate new_decreased_bitrate_floor = BitrateFromLoss( |
| 203 | loss_estimate_for_decrease, config_.loss_bandwidth_balance_decrease, |
| 204 | config_.loss_bandwidth_balance_exponent); |
| 205 | new_decreased_bitrate = |
| 206 | std::max(new_decreased_bitrate, new_decreased_bitrate_floor); |
| 207 | if (new_decreased_bitrate < loss_based_bitrate_) { |
| 208 | time_last_decrease_ = at_time; |
| 209 | has_decreased_since_last_loss_report_ = true; |
| 210 | loss_based_bitrate_ = new_decreased_bitrate; |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | |
Christoffer Rodbro | 982639c | 2019-01-18 15:34:59 +0100 | [diff] [blame] | 215 | void LossBasedBandwidthEstimation::Reset(DataRate bitrate) { |
| 216 | loss_based_bitrate_ = bitrate; |
| 217 | average_loss_ = 0; |
| 218 | average_loss_max_ = 0; |
| 219 | } |
| 220 | |
| 221 | void LossBasedBandwidthEstimation::MaybeReset(DataRate bitrate) { |
| 222 | if (config_.allow_resets) |
| 223 | Reset(bitrate); |
| 224 | } |
| 225 | |
| 226 | void LossBasedBandwidthEstimation::SetInitialBitrate(DataRate bitrate) { |
| 227 | Reset(bitrate); |
| 228 | } |
| 229 | |
Christoffer Rodbro | 3a83748 | 2018-11-19 15:30:23 +0100 | [diff] [blame] | 230 | } // namespace webrtc |