blob: 4d1a097a639abbc878af279b155cdb08b9b0c92f [file] [log] [blame]
/*
* 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 <vector>
#include "absl/algorithm/container.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/field_trials_view.h"
#include "api/network_state_predictor.h"
#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 "modules/remote_bitrate_estimator/include/bwe_defines.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(absl::optional<DataRate> datarate) {
return datarate.has_value() && IsValid(datarate.value());
}
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);
}
double loss_probability = inherent_loss;
if (IsValid(sending_rate) && IsValid(loss_limited_bandwidth) &&
(sending_rate > loss_limited_bandwidth)) {
loss_probability += (1 - inherent_loss) *
(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 FieldTrialsView* 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);
instant_upper_bound_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;
}
LossBasedBweV2::Result LossBasedBweV2::GetLossBasedResult() const {
Result result;
result.state = current_state_;
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.";
}
}
result.bandwidth_estimate = IsValid(delay_based_estimate_)
? delay_based_estimate_
: DataRate::PlusInfinity();
return result;
}
if (IsValid(delay_based_estimate_)) {
result.bandwidth_estimate =
std::min({current_estimate_.loss_limited_bandwidth,
GetInstantUpperBound(), delay_based_estimate_});
} else {
result.bandwidth_estimate = std::min(
current_estimate_.loss_limited_bandwidth, GetInstantUpperBound());
}
return result;
}
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::SetMinMaxBitrate(DataRate min_bitrate,
DataRate max_bitrate) {
if (IsValid(min_bitrate)) {
min_bitrate_ = min_bitrate;
} else {
RTC_LOG(LS_WARNING) << "The min bitrate must be finite: "
<< ToString(min_bitrate);
}
if (IsValid(max_bitrate)) {
max_bitrate_ = max_bitrate;
} else {
RTC_LOG(LS_WARNING) << "The max bitrate must be finite: "
<< ToString(max_bitrate);
}
}
void LossBasedBweV2::SetProbeBitrate(absl::optional<DataRate> probe_bitrate) {
if (probe_bitrate.has_value() && IsValid(probe_bitrate.value())) {
probe_bitrate_ = probe_bitrate.value();
last_probe_timestamp_ = last_send_time_most_recent_observation_;
}
}
void LossBasedBweV2::UpdateBandwidthEstimate(
rtc::ArrayView<const PacketResult> packet_results,
DataRate delay_based_estimate,
BandwidthUsage delay_detector_state,
absl::optional<DataRate> probe_bitrate,
DataRate upper_link_capacity,
bool in_alr) {
delay_based_estimate_ = delay_based_estimate;
upper_link_capacity_ = upper_link_capacity;
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, delay_detector_state)) {
return;
}
SetProbeBitrate(probe_bitrate);
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(in_alr)) {
NewtonsMethodUpdate(candidate);
const double candidate_objective = GetObjective(candidate);
if (candidate_objective > objective_max) {
objective_max = candidate_objective;
best_candidate = candidate;
}
}
if (best_candidate.loss_limited_bandwidth <
current_estimate_.loss_limited_bandwidth) {
last_time_estimate_reduced_ = last_send_time_most_recent_observation_;
}
// Do not increase the estimate if the average loss is greater than current
// inherent loss.
if (GetAverageReportedLossRatio() > best_candidate.inherent_loss &&
config_->not_increase_if_inherent_loss_less_than_average_loss &&
current_estimate_.loss_limited_bandwidth <
best_candidate.loss_limited_bandwidth) {
best_candidate.loss_limited_bandwidth =
current_estimate_.loss_limited_bandwidth;
}
if (IsBandwidthLimitedDueToLoss()) {
// Bound the estimate increase if:
// 1. The estimate has been increased for less than
// `delayed_increase_window` ago, and
// 2. The best candidate is greater than bandwidth_limit_in_current_window.
if (recovering_after_loss_timestamp_.IsFinite() &&
recovering_after_loss_timestamp_ + config_->delayed_increase_window >
last_send_time_most_recent_observation_ &&
best_candidate.loss_limited_bandwidth >
bandwidth_limit_in_current_window_) {
best_candidate.loss_limited_bandwidth =
bandwidth_limit_in_current_window_;
}
bool increasing_when_loss_limited =
IsEstimateIncreasingWhenLossLimited(best_candidate);
// Bound the best candidate by the acked bitrate unless there is a recent
// probe result.
if (increasing_when_loss_limited && !IsValid(probe_bitrate_) &&
IsValid(acknowledged_bitrate_)) {
best_candidate.loss_limited_bandwidth =
IsValid(best_candidate.loss_limited_bandwidth)
? std::min(best_candidate.loss_limited_bandwidth,
config_->bandwidth_rampup_upper_bound_factor *
(*acknowledged_bitrate_))
: config_->bandwidth_rampup_upper_bound_factor *
(*acknowledged_bitrate_);
}
}
if (IsEstimateIncreasingWhenLossLimited(best_candidate) &&
best_candidate.loss_limited_bandwidth < delay_based_estimate_) {
current_state_ = LossBasedState::kIncreasing;
} else if (best_candidate.loss_limited_bandwidth < delay_based_estimate_) {
current_state_ = LossBasedState::kDecreasing;
} else if (best_candidate.loss_limited_bandwidth >= delay_based_estimate_) {
current_state_ = LossBasedState::kDelayBasedEstimate;
}
// Use probe bitrate as the estimate limit when probes are requested.
if (config_->probe_integration_enabled && IsValid(probe_bitrate_) &&
IsRequestingProbe()) {
if (last_probe_timestamp_ + config_->probe_expiration >=
last_send_time_most_recent_observation_) {
best_candidate.loss_limited_bandwidth =
std::min(probe_bitrate_, best_candidate.loss_limited_bandwidth);
}
}
current_estimate_ = best_candidate;
if (IsBandwidthLimitedDueToLoss() &&
(recovering_after_loss_timestamp_.IsInfinite() ||
recovering_after_loss_timestamp_ + config_->delayed_increase_window <
last_send_time_most_recent_observation_)) {
bandwidth_limit_in_current_window_ =
std::max(kCongestionControllerMinBitrate,
current_estimate_.loss_limited_bandwidth *
config_->max_increase_factor);
recovering_after_loss_timestamp_ = last_send_time_most_recent_observation_;
}
}
bool LossBasedBweV2::IsEstimateIncreasingWhenLossLimited(
const ChannelParameters& best_candidate) {
return (current_estimate_.loss_limited_bandwidth <
best_candidate.loss_limited_bandwidth ||
(current_estimate_.loss_limited_bandwidth ==
best_candidate.loss_limited_bandwidth &&
current_state_ == LossBasedState::kIncreasing)) &&
IsBandwidthLimitedDueToLoss();
}
// 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 FieldTrialsView* key_value_config) {
FieldTrialParameter<bool> enabled("Enabled", true);
FieldTrialParameter<double> bandwidth_rampup_upper_bound_factor(
"BwRampupUpperBoundFactor", 1000000.0);
FieldTrialParameter<double> rampup_acceleration_max_factor(
"BwRampupAccelMaxFactor", 0.0);
FieldTrialParameter<TimeDelta> rampup_acceleration_maxout_time(
"BwRampupAccelMaxoutTime", TimeDelta::Seconds(60));
FieldTrialList<double> candidate_factors("CandidateFactors",
{1.02, 1.0, 0.95});
FieldTrialParameter<double> higher_bandwidth_bias_factor("HigherBwBiasFactor",
0.0002);
FieldTrialParameter<double> higher_log_bandwidth_bias_factor(
"HigherLogBwBiasFactor", 0.02);
FieldTrialParameter<double> inherent_loss_lower_bound(
"InherentLossLowerBound", 1.0e-3);
FieldTrialParameter<double> loss_threshold_of_high_bandwidth_preference(
"LossThresholdOfHighBandwidthPreference", 0.15);
FieldTrialParameter<double> bandwidth_preference_smoothing_factor(
"BandwidthPreferenceSmoothingFactor", 0.002);
FieldTrialParameter<DataRate> inherent_loss_upper_bound_bandwidth_balance(
"InherentLossUpperBoundBwBalance", DataRate::KilobitsPerSec(75.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.75);
FieldTrialParameter<bool> append_acknowledged_rate_candidate(
"AckedRateCandidate", true);
FieldTrialParameter<bool> append_delay_based_estimate_candidate(
"DelayBasedCandidate", true);
FieldTrialParameter<TimeDelta> observation_duration_lower_bound(
"ObservationDurationLowerBound", TimeDelta::Millis(250));
FieldTrialParameter<int> observation_window_size("ObservationWindowSize", 20);
FieldTrialParameter<double> sending_rate_smoothing_factor(
"SendingRateSmoothingFactor", 0.0);
FieldTrialParameter<double> instant_upper_bound_temporal_weight_factor(
"InstantUpperBoundTemporalWeightFactor", 0.9);
FieldTrialParameter<DataRate> instant_upper_bound_bandwidth_balance(
"InstantUpperBoundBwBalance", DataRate::KilobitsPerSec(75.0));
FieldTrialParameter<double> instant_upper_bound_loss_offset(
"InstantUpperBoundLossOffset", 0.05);
FieldTrialParameter<double> temporal_weight_factor("TemporalWeightFactor",
0.9);
FieldTrialParameter<double> bandwidth_backoff_lower_bound_factor(
"BwBackoffLowerBoundFactor", 1.0);
FieldTrialParameter<bool> trendline_integration_enabled(
"TrendlineIntegrationEnabled", false);
FieldTrialParameter<int> trendline_observations_window_size(
"TrendlineObservationsWindowSize", 20);
FieldTrialParameter<double> max_increase_factor("MaxIncreaseFactor", 1.3);
FieldTrialParameter<TimeDelta> delayed_increase_window(
"DelayedIncreaseWindow", TimeDelta::Millis(300));
FieldTrialParameter<bool> use_acked_bitrate_only_when_overusing(
"UseAckedBitrateOnlyWhenOverusing", false);
FieldTrialParameter<bool>
not_increase_if_inherent_loss_less_than_average_loss(
"NotIncreaseIfInherentLossLessThanAverageLoss", true);
FieldTrialParameter<double> high_loss_rate_threshold("HighLossRateThreshold",
1.0);
FieldTrialParameter<DataRate> bandwidth_cap_at_high_loss_rate(
"BandwidthCapAtHighLossRate", DataRate::KilobitsPerSec(500.0));
FieldTrialParameter<double> slope_of_bwe_high_loss_func(
"SlopeOfBweHighLossFunc", 1000);
FieldTrialParameter<bool> probe_integration_enabled("ProbeIntegrationEnabled",
false);
FieldTrialParameter<TimeDelta> probe_expiration("ProbeExpiration",
TimeDelta::Seconds(10));
FieldTrialParameter<bool> bound_by_upper_link_capacity_when_loss_limited(
"BoundByUpperLinkCapacityWhenLossLimited", true);
FieldTrialParameter<bool> not_use_acked_rate_in_alr("NotUseAckedRateInAlr",
false);
if (key_value_config) {
ParseFieldTrial({&enabled,
&bandwidth_rampup_upper_bound_factor,
&rampup_acceleration_max_factor,
&rampup_acceleration_maxout_time,
&candidate_factors,
&higher_bandwidth_bias_factor,
&higher_log_bandwidth_bias_factor,
&inherent_loss_lower_bound,
&loss_threshold_of_high_bandwidth_preference,
&bandwidth_preference_smoothing_factor,
&inherent_loss_upper_bound_bandwidth_balance,
&inherent_loss_upper_bound_offset,
&initial_inherent_loss_estimate,
&newton_iterations,
&newton_step_size,
&append_acknowledged_rate_candidate,
&append_delay_based_estimate_candidate,
&observation_duration_lower_bound,
&observation_window_size,
&sending_rate_smoothing_factor,
&instant_upper_bound_temporal_weight_factor,
&instant_upper_bound_bandwidth_balance,
&instant_upper_bound_loss_offset,
&temporal_weight_factor,
&bandwidth_backoff_lower_bound_factor,
&trendline_integration_enabled,
&trendline_observations_window_size,
&max_increase_factor,
&delayed_increase_window,
&use_acked_bitrate_only_when_overusing,
&not_increase_if_inherent_loss_less_than_average_loss,
&probe_integration_enabled,
&probe_expiration,
&high_loss_rate_threshold,
&bandwidth_cap_at_high_loss_rate,
&slope_of_bwe_high_loss_func,
&bound_by_upper_link_capacity_when_loss_limited,
&not_use_acked_rate_in_alr},
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->rampup_acceleration_max_factor = rampup_acceleration_max_factor.Get();
config->rampup_acceleration_maxout_time =
rampup_acceleration_maxout_time.Get();
config->candidate_factors = candidate_factors.Get();
config->higher_bandwidth_bias_factor = higher_bandwidth_bias_factor.Get();
config->higher_log_bandwidth_bias_factor =
higher_log_bandwidth_bias_factor.Get();
config->inherent_loss_lower_bound = inherent_loss_lower_bound.Get();
config->loss_threshold_of_high_bandwidth_preference =
loss_threshold_of_high_bandwidth_preference.Get();
config->bandwidth_preference_smoothing_factor =
bandwidth_preference_smoothing_factor.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->append_acknowledged_rate_candidate =
append_acknowledged_rate_candidate.Get();
config->append_delay_based_estimate_candidate =
append_delay_based_estimate_candidate.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->instant_upper_bound_temporal_weight_factor =
instant_upper_bound_temporal_weight_factor.Get();
config->instant_upper_bound_bandwidth_balance =
instant_upper_bound_bandwidth_balance.Get();
config->instant_upper_bound_loss_offset =
instant_upper_bound_loss_offset.Get();
config->temporal_weight_factor = temporal_weight_factor.Get();
config->bandwidth_backoff_lower_bound_factor =
bandwidth_backoff_lower_bound_factor.Get();
config->trendline_integration_enabled = trendline_integration_enabled.Get();
config->trendline_observations_window_size =
trendline_observations_window_size.Get();
config->max_increase_factor = max_increase_factor.Get();
config->delayed_increase_window = delayed_increase_window.Get();
config->use_acked_bitrate_only_when_overusing =
use_acked_bitrate_only_when_overusing.Get();
config->not_increase_if_inherent_loss_less_than_average_loss =
not_increase_if_inherent_loss_less_than_average_loss.Get();
config->high_loss_rate_threshold = high_loss_rate_threshold.Get();
config->bandwidth_cap_at_high_loss_rate =
bandwidth_cap_at_high_loss_rate.Get();
config->slope_of_bwe_high_loss_func = slope_of_bwe_high_loss_func.Get();
config->probe_integration_enabled = probe_integration_enabled.Get();
config->probe_expiration = probe_expiration.Get();
config->bound_by_upper_link_capacity_when_loss_limited =
bound_by_upper_link_capacity_when_loss_limited.Get();
config->not_use_acked_rate_in_alr = not_use_acked_rate_in_alr.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_->rampup_acceleration_max_factor < 0.0) {
RTC_LOG(LS_WARNING)
<< "The rampup acceleration max factor must be non-negative.: "
<< config_->rampup_acceleration_max_factor;
valid = false;
}
if (config_->rampup_acceleration_maxout_time <= TimeDelta::Zero()) {
RTC_LOG(LS_WARNING)
<< "The rampup acceleration maxout time must be above zero: "
<< config_->rampup_acceleration_maxout_time.seconds();
valid = false;
}
for (double candidate_factor : config_->candidate_factors) {
if (candidate_factor <= 0.0) {
RTC_LOG(LS_WARNING) << "All candidate factors must be greater than zero: "
<< candidate_factor;
valid = false;
}
}
// Ensure that the configuration allows generation of at least one candidate
// other than the current estimate.
if (!config_->append_acknowledged_rate_candidate &&
!config_->append_delay_based_estimate_candidate &&
!absl::c_any_of(config_->candidate_factors,
[](double cf) { return cf != 1.0; })) {
RTC_LOG(LS_WARNING)
<< "The configuration does not allow generating candidates. Specify "
"a candidate factor other than 1.0, allow the acknowledged rate "
"to be a candidate, and/or allow the delay based estimate to be a "
"candidate.";
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_->loss_threshold_of_high_bandwidth_preference < 0.0 ||
config_->loss_threshold_of_high_bandwidth_preference >= 1.0) {
RTC_LOG(LS_WARNING)
<< "The loss threshold of high bandwidth preference must be in [0, 1): "
<< config_->loss_threshold_of_high_bandwidth_preference;
valid = false;
}
if (config_->bandwidth_preference_smoothing_factor <= 0.0 ||
config_->bandwidth_preference_smoothing_factor > 1.0) {
RTC_LOG(LS_WARNING)
<< "The bandwidth preference smoothing factor must be in (0, 1]: "
<< config_->bandwidth_preference_smoothing_factor;
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_->instant_upper_bound_temporal_weight_factor <= 0.0 ||
config_->instant_upper_bound_temporal_weight_factor > 1.0) {
RTC_LOG(LS_WARNING)
<< "The instant upper bound temporal weight factor must be in (0, 1]"
<< config_->instant_upper_bound_temporal_weight_factor;
valid = false;
}
if (config_->instant_upper_bound_bandwidth_balance <= DataRate::Zero()) {
RTC_LOG(LS_WARNING)
<< "The instant upper bound bandwidth balance must be positive: "
<< ToString(config_->instant_upper_bound_bandwidth_balance);
valid = false;
}
if (config_->instant_upper_bound_loss_offset < 0.0 ||
config_->instant_upper_bound_loss_offset >= 1.0) {
RTC_LOG(LS_WARNING)
<< "The instant upper bound loss offset must be in [0, 1): "
<< config_->instant_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;
}
if (config_->bandwidth_backoff_lower_bound_factor > 1.0) {
RTC_LOG(LS_WARNING)
<< "The bandwidth backoff lower bound factor must not be greater than "
"1: "
<< config_->bandwidth_backoff_lower_bound_factor;
valid = false;
}
if (config_->trendline_observations_window_size < 1) {
RTC_LOG(LS_WARNING) << "The trendline window size must be at least 1: "
<< config_->trendline_observations_window_size;
valid = false;
}
if (config_->max_increase_factor <= 0.0) {
RTC_LOG(LS_WARNING) << "The maximum increase factor must be positive: "
<< config_->max_increase_factor;
valid = false;
}
if (config_->delayed_increase_window <= TimeDelta::Zero()) {
RTC_LOG(LS_WARNING) << "The delayed increase window must be positive: "
<< config_->delayed_increase_window.ms();
valid = false;
}
if (config_->high_loss_rate_threshold <= 0.0 ||
config_->high_loss_rate_threshold > 1.0) {
RTC_LOG(LS_WARNING) << "The high loss rate threshold must be in (0, 1]: "
<< config_->high_loss_rate_threshold;
valid = false;
}
return valid;
}
double LossBasedBweV2::GetAverageReportedLossRatio() const {
if (num_observations_ <= 0) {
return 0.0;
}
double num_packets = 0;
double num_lost_packets = 0;
for (const Observation& observation : observations_) {
if (!observation.IsInitialized()) {
continue;
}
double instant_temporal_weight =
instant_upper_bound_temporal_weights_[(num_observations_ - 1) -
observation.id];
num_packets += instant_temporal_weight * observation.num_packets;
num_lost_packets += instant_temporal_weight * observation.num_lost_packets;
}
return num_lost_packets / num_packets;
}
DataRate LossBasedBweV2::GetCandidateBandwidthUpperBound() const {
DataRate candidate_bandwidth_upper_bound = max_bitrate_;
if (IsBandwidthLimitedDueToLoss() &&
IsValid(bandwidth_limit_in_current_window_)) {
candidate_bandwidth_upper_bound = bandwidth_limit_in_current_window_;
}
if (config_->trendline_integration_enabled) {
candidate_bandwidth_upper_bound =
std::min(GetInstantUpperBound(), candidate_bandwidth_upper_bound);
if (IsValid(delay_based_estimate_)) {
candidate_bandwidth_upper_bound =
std::min(delay_based_estimate_, candidate_bandwidth_upper_bound);
}
}
if (!acknowledged_bitrate_.has_value())
return candidate_bandwidth_upper_bound;
if (config_->rampup_acceleration_max_factor > 0.0) {
const TimeDelta time_since_bandwidth_reduced = std::min(
config_->rampup_acceleration_maxout_time,
std::max(TimeDelta::Zero(), last_send_time_most_recent_observation_ -
last_time_estimate_reduced_));
const double rampup_acceleration = config_->rampup_acceleration_max_factor *
time_since_bandwidth_reduced /
config_->rampup_acceleration_maxout_time;
candidate_bandwidth_upper_bound +=
rampup_acceleration * (*acknowledged_bitrate_);
}
return candidate_bandwidth_upper_bound;
}
std::vector<LossBasedBweV2::ChannelParameters> LossBasedBweV2::GetCandidates(
bool in_alr) const {
std::vector<DataRate> bandwidths;
bool can_increase_bitrate = TrendlineEsimateAllowBitrateIncrease();
for (double candidate_factor : config_->candidate_factors) {
if (!can_increase_bitrate && candidate_factor > 1.0) {
continue;
}
bandwidths.push_back(candidate_factor *
current_estimate_.loss_limited_bandwidth);
}
if (acknowledged_bitrate_.has_value() &&
config_->append_acknowledged_rate_candidate &&
TrendlineEsimateAllowEmergencyBackoff()) {
if (!(config_->not_use_acked_rate_in_alr && in_alr)) {
bandwidths.push_back(*acknowledged_bitrate_ *
config_->bandwidth_backoff_lower_bound_factor);
}
}
if (IsValid(delay_based_estimate_) &&
config_->append_delay_based_estimate_candidate) {
if (can_increase_bitrate &&
delay_based_estimate_ > current_estimate_.loss_limited_bandwidth) {
bandwidths.push_back(delay_based_estimate_);
}
}
const DataRate candidate_bandwidth_upper_bound =
GetCandidateBandwidthUpperBound();
std::vector<ChannelParameters> candidates;
candidates.resize(bandwidths.size());
for (size_t i = 0; i < bandwidths.size(); ++i) {
ChannelParameters candidate = current_estimate_;
if (config_->trendline_integration_enabled) {
candidate.loss_limited_bandwidth =
std::min(bandwidths[i], candidate_bandwidth_upper_bound);
} else {
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::AdjustBiasFactor(double loss_rate,
double bias_factor) const {
return bias_factor *
(config_->loss_threshold_of_high_bandwidth_preference - loss_rate) /
(config_->bandwidth_preference_smoothing_factor +
std::abs(config_->loss_threshold_of_high_bandwidth_preference -
loss_rate));
}
double LossBasedBweV2::GetHighBandwidthBias(DataRate bandwidth) const {
if (IsValid(bandwidth)) {
const double average_reported_loss_ratio = GetAverageReportedLossRatio();
return AdjustBiasFactor(average_reported_loss_ratio,
config_->higher_bandwidth_bias_factor) *
bandwidth.kbps() +
AdjustBiasFactor(average_reported_loss_ratio,
config_->higher_log_bandwidth_bias_factor) *
std::log(1.0 + bandwidth.kbps());
}
return 0.0;
}
double LossBasedBweV2::GetObjective(
const ChannelParameters& channel_parameters) const {
double objective = 0.0;
const double high_bandwidth_bias =
GetHighBandwidthBias(channel_parameters.loss_limited_bandwidth);
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 * high_bandwidth_bias * 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::GetInstantUpperBound() const {
return cached_instant_upper_bound_.value_or(max_bitrate_);
}
void LossBasedBweV2::CalculateInstantUpperBound() {
DataRate instant_limit = max_bitrate_;
const double average_reported_loss_ratio = GetAverageReportedLossRatio();
if (average_reported_loss_ratio > config_->instant_upper_bound_loss_offset) {
instant_limit = config_->instant_upper_bound_bandwidth_balance /
(average_reported_loss_ratio -
config_->instant_upper_bound_loss_offset);
if (average_reported_loss_ratio > config_->high_loss_rate_threshold) {
instant_limit = std::min(
instant_limit, DataRate::KilobitsPerSec(std::max(
static_cast<double>(min_bitrate_.kbps()),
config_->bandwidth_cap_at_high_loss_rate.kbps() -
config_->slope_of_bwe_high_loss_func *
average_reported_loss_ratio)));
}
}
if (IsBandwidthLimitedDueToLoss()) {
if (IsValid(upper_link_capacity_) &&
config_->bound_by_upper_link_capacity_when_loss_limited) {
instant_limit = std::min(instant_limit, upper_link_capacity_);
}
}
cached_instant_upper_bound_ = instant_limit;
}
void LossBasedBweV2::CalculateTemporalWeights() {
for (int i = 0; i < config_->observation_window_size; ++i) {
temporal_weights_[i] = std::pow(config_->temporal_weight_factor, i);
instant_upper_bound_temporal_weights_[i] =
std::pow(config_->instant_upper_bound_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::TrendlineEsimateAllowBitrateIncrease() const {
if (!config_->trendline_integration_enabled) {
return true;
}
for (const auto& detector_state : delay_detector_states_) {
if (detector_state == BandwidthUsage::kBwOverusing ||
detector_state == BandwidthUsage::kBwUnderusing) {
return false;
}
}
return true;
}
bool LossBasedBweV2::TrendlineEsimateAllowEmergencyBackoff() const {
if (!config_->trendline_integration_enabled) {
return true;
}
if (!config_->use_acked_bitrate_only_when_overusing) {
return true;
}
for (const auto& detector_state : delay_detector_states_) {
if (detector_state == BandwidthUsage::kBwOverusing) {
return true;
}
}
return false;
}
bool LossBasedBweV2::PushBackObservation(
rtc::ArrayView<const PacketResult> packet_results,
BandwidthUsage delay_detector_state) {
delay_detector_states_.push_front(delay_detector_state);
if (static_cast<int>(delay_detector_states_.size()) >
config_->trendline_observations_window_size) {
delay_detector_states_.pop_back();
}
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 <= TimeDelta::Zero() ||
(observation_duration < config_->observation_duration_lower_bound &&
(delay_detector_state != BandwidthUsage::kBwOverusing ||
!config_->trendline_integration_enabled))) {
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();
CalculateInstantUpperBound();
return true;
}
bool LossBasedBweV2::IsBandwidthLimitedDueToLoss() const {
return current_state_ != LossBasedState::kDelayBasedEstimate;
}
bool LossBasedBweV2::IsRequestingProbe() const {
return current_state_ == LossBasedState::kIncreasing;
}
} // namespace webrtc