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}
This commit is contained in:
parent
bc88b54d91
commit
f137b75a4d
@ -27,9 +27,9 @@ rtc_library("goog_cc") {
|
||||
":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("estimators") {
|
||||
]
|
||||
}
|
||||
|
||||
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 @@ if (rtc_include_tests) {
|
||||
"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 @@ if (rtc_include_tests) {
|
||||
":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 @@ if (rtc_include_tests) {
|
||||
"../../../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",
|
||||
|
||||
652
modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc
Normal file
652
modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc
Normal file
@ -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
|
||||
135
modules/congestion_controller/goog_cc/loss_based_bwe_v2.h
Normal file
135
modules/congestion_controller/goog_cc/loss_based_bwe_v2.h
Normal file
@ -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_
|
||||
299
modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc
Normal file
299
modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc
Normal file
@ -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
|
||||
@ -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 @@ SendSideBandwidthEstimation::SendSideBandwidthEstimation(
|
||||
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 @@ void SendSideBandwidthEstimation::SetAcknowledgedRate(
|
||||
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 @@ void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {
|
||||
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 @@ void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {
|
||||
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::UpdateTargetBitrate(DataRate new_bitrate,
|
||||
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
|
||||
|
||||
@ -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 @@ class SendSideBandwidthEstimation {
|
||||
// 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 @@ class SendSideBandwidthEstimation {
|
||||
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
|
||||
|
||||
@ -87,7 +87,7 @@ std::deque<FieldLogger*> GoogCcStatePrinter::CreateLoggers() {
|
||||
};
|
||||
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; }),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user