Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2017 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/acknowledged_bitrate_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "modules/congestion_controller/goog_cc/bitrate_estimator.h"
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator(
|
||||
const FieldTrialsView* key_value_config)
|
||||
: AcknowledgedBitrateEstimator(
|
||||
key_value_config,
|
||||
std::make_unique<BitrateEstimator>(key_value_config)) {}
|
||||
|
||||
AcknowledgedBitrateEstimator::~AcknowledgedBitrateEstimator() {}
|
||||
|
||||
AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator(
|
||||
const FieldTrialsView* key_value_config,
|
||||
std::unique_ptr<BitrateEstimator> bitrate_estimator)
|
||||
: in_alr_(false), bitrate_estimator_(std::move(bitrate_estimator)) {}
|
||||
|
||||
void AcknowledgedBitrateEstimator::IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketResult>& packet_feedback_vector) {
|
||||
RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(),
|
||||
packet_feedback_vector.end(),
|
||||
PacketResult::ReceiveTimeOrder()));
|
||||
for (const auto& packet : packet_feedback_vector) {
|
||||
if (alr_ended_time_ && packet.sent_packet.send_time > *alr_ended_time_) {
|
||||
bitrate_estimator_->ExpectFastRateChange();
|
||||
alr_ended_time_.reset();
|
||||
}
|
||||
DataSize acknowledged_estimate = packet.sent_packet.size;
|
||||
acknowledged_estimate += packet.sent_packet.prior_unacked_data;
|
||||
bitrate_estimator_->Update(packet.receive_time, acknowledged_estimate,
|
||||
in_alr_);
|
||||
}
|
||||
}
|
||||
|
||||
absl::optional<DataRate> AcknowledgedBitrateEstimator::bitrate() const {
|
||||
return bitrate_estimator_->bitrate();
|
||||
}
|
||||
|
||||
absl::optional<DataRate> AcknowledgedBitrateEstimator::PeekRate() const {
|
||||
return bitrate_estimator_->PeekRate();
|
||||
}
|
||||
|
||||
void AcknowledgedBitrateEstimator::SetAlrEndedTime(Timestamp alr_ended_time) {
|
||||
alr_ended_time_.emplace(alr_ended_time);
|
||||
}
|
||||
|
||||
void AcknowledgedBitrateEstimator::SetAlr(bool in_alr) {
|
||||
in_alr_ = in_alr;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2017 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_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h"
|
||||
#include "modules/congestion_controller/goog_cc/bitrate_estimator.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class AcknowledgedBitrateEstimator
|
||||
: public AcknowledgedBitrateEstimatorInterface {
|
||||
public:
|
||||
AcknowledgedBitrateEstimator(
|
||||
const FieldTrialsView* key_value_config,
|
||||
std::unique_ptr<BitrateEstimator> bitrate_estimator);
|
||||
|
||||
explicit AcknowledgedBitrateEstimator(
|
||||
const FieldTrialsView* key_value_config);
|
||||
~AcknowledgedBitrateEstimator() override;
|
||||
|
||||
void IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketResult>& packet_feedback_vector) override;
|
||||
absl::optional<DataRate> bitrate() const override;
|
||||
absl::optional<DataRate> PeekRate() const override;
|
||||
void SetAlr(bool in_alr) override;
|
||||
void SetAlrEndedTime(Timestamp alr_ended_time) override;
|
||||
|
||||
private:
|
||||
absl::optional<Timestamp> alr_ended_time_;
|
||||
bool in_alr_;
|
||||
std::unique_ptr<BitrateEstimator> bitrate_estimator_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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/acknowledged_bitrate_estimator_interface.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h"
|
||||
#include "modules/congestion_controller/goog_cc/robust_throughput_estimator.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
constexpr char RobustThroughputEstimatorSettings::kKey[];
|
||||
|
||||
RobustThroughputEstimatorSettings::RobustThroughputEstimatorSettings(
|
||||
const FieldTrialsView* key_value_config) {
|
||||
Parser()->Parse(
|
||||
key_value_config->Lookup(RobustThroughputEstimatorSettings::kKey));
|
||||
if (window_packets < 10 || 1000 < window_packets) {
|
||||
RTC_LOG(LS_WARNING) << "Window size must be between 10 and 1000 packets";
|
||||
window_packets = 20;
|
||||
}
|
||||
if (max_window_packets < 10 || 1000 < max_window_packets) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Max window size must be between 10 and 1000 packets";
|
||||
max_window_packets = 500;
|
||||
}
|
||||
max_window_packets = std::max(max_window_packets, window_packets);
|
||||
|
||||
if (required_packets < 10 || 1000 < required_packets) {
|
||||
RTC_LOG(LS_WARNING) << "Required number of initial packets must be between "
|
||||
"10 and 1000 packets";
|
||||
required_packets = 10;
|
||||
}
|
||||
required_packets = std::min(required_packets, window_packets);
|
||||
|
||||
if (min_window_duration < TimeDelta::Millis(100) ||
|
||||
TimeDelta::Millis(3000) < min_window_duration) {
|
||||
RTC_LOG(LS_WARNING) << "Window duration must be between 100 and 3000 ms";
|
||||
min_window_duration = TimeDelta::Millis(750);
|
||||
}
|
||||
if (max_window_duration < TimeDelta::Seconds(1) ||
|
||||
TimeDelta::Seconds(15) < max_window_duration) {
|
||||
RTC_LOG(LS_WARNING) << "Max window duration must be between 1 and 15 s";
|
||||
max_window_duration = TimeDelta::Seconds(5);
|
||||
}
|
||||
min_window_duration = std::min(min_window_duration, max_window_duration);
|
||||
|
||||
if (unacked_weight < 0.0 || 1.0 < unacked_weight) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Weight for prior unacked size must be between 0 and 1.";
|
||||
unacked_weight = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<StructParametersParser>
|
||||
RobustThroughputEstimatorSettings::Parser() {
|
||||
return StructParametersParser::Create(
|
||||
"enabled", &enabled, //
|
||||
"window_packets", &window_packets, //
|
||||
"max_window_packets", &max_window_packets, //
|
||||
"window_duration", &min_window_duration, //
|
||||
"max_window_duration", &max_window_duration, //
|
||||
"required_packets", &required_packets, //
|
||||
"unacked_weight", &unacked_weight);
|
||||
}
|
||||
|
||||
AcknowledgedBitrateEstimatorInterface::
|
||||
~AcknowledgedBitrateEstimatorInterface() {}
|
||||
|
||||
std::unique_ptr<AcknowledgedBitrateEstimatorInterface>
|
||||
AcknowledgedBitrateEstimatorInterface::Create(
|
||||
const FieldTrialsView* key_value_config) {
|
||||
RobustThroughputEstimatorSettings simplified_estimator_settings(
|
||||
key_value_config);
|
||||
if (simplified_estimator_settings.enabled) {
|
||||
return std::make_unique<RobustThroughputEstimator>(
|
||||
simplified_estimator_settings);
|
||||
}
|
||||
return std::make_unique<AcknowledgedBitrateEstimator>(key_value_config);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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_ACKNOWLEDGED_BITRATE_ESTIMATOR_INTERFACE_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_INTERFACE_H_
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct RobustThroughputEstimatorSettings {
|
||||
static constexpr char kKey[] = "WebRTC-Bwe-RobustThroughputEstimatorSettings";
|
||||
|
||||
RobustThroughputEstimatorSettings() = delete;
|
||||
explicit RobustThroughputEstimatorSettings(
|
||||
const FieldTrialsView* key_value_config);
|
||||
|
||||
// Set `enabled` to true to use the RobustThroughputEstimator, false to use
|
||||
// the AcknowledgedBitrateEstimator.
|
||||
bool enabled = true;
|
||||
|
||||
// The estimator keeps the smallest window containing at least
|
||||
// `window_packets` and at least the packets received during the last
|
||||
// `min_window_duration` milliseconds.
|
||||
// (This means that it may store more than `window_packets` at high bitrates,
|
||||
// and a longer duration than `min_window_duration` at low bitrates.)
|
||||
// However, if will never store more than kMaxPackets (for performance
|
||||
// reasons), and never longer than max_window_duration (to avoid very old
|
||||
// packets influencing the estimate for example when sending is paused).
|
||||
unsigned window_packets = 20;
|
||||
unsigned max_window_packets = 500;
|
||||
TimeDelta min_window_duration = TimeDelta::Seconds(1);
|
||||
TimeDelta max_window_duration = TimeDelta::Seconds(5);
|
||||
|
||||
// The estimator window requires at least `required_packets` packets
|
||||
// to produce an estimate.
|
||||
unsigned required_packets = 10;
|
||||
|
||||
// If audio packets aren't included in allocation (i.e. the
|
||||
// estimated available bandwidth is divided only among the video
|
||||
// streams), then `unacked_weight` should be set to 0.
|
||||
// If audio packets are included in allocation, but not in bandwidth
|
||||
// estimation (i.e. they don't have transport-wide sequence numbers,
|
||||
// but we nevertheless divide the estimated available bandwidth among
|
||||
// both audio and video streams), then `unacked_weight` should be set to 1.
|
||||
// If all packets have transport-wide sequence numbers, then the value
|
||||
// of `unacked_weight` doesn't matter.
|
||||
double unacked_weight = 1.0;
|
||||
|
||||
std::unique_ptr<StructParametersParser> Parser();
|
||||
};
|
||||
|
||||
class AcknowledgedBitrateEstimatorInterface {
|
||||
public:
|
||||
static std::unique_ptr<AcknowledgedBitrateEstimatorInterface> Create(
|
||||
const FieldTrialsView* key_value_config);
|
||||
virtual ~AcknowledgedBitrateEstimatorInterface();
|
||||
|
||||
virtual void IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketResult>& packet_feedback_vector) = 0;
|
||||
virtual absl::optional<DataRate> bitrate() const = 0;
|
||||
virtual absl::optional<DataRate> PeekRate() const = 0;
|
||||
virtual void SetAlr(bool in_alr) = 0;
|
||||
virtual void SetAlrEndedTime(Timestamp alr_ended_time) = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_INTERFACE_H_
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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/alr_detector.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
#include "api/rtc_event_log/rtc_event.h"
|
||||
#include "api/rtc_event_log/rtc_event_log.h"
|
||||
#include "logging/rtc_event_log/events/rtc_event_alr_state.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
AlrDetectorConfig GetConfigFromTrials(const FieldTrialsView* key_value_config) {
|
||||
RTC_CHECK(AlrExperimentSettings::MaxOneFieldTrialEnabled(*key_value_config));
|
||||
absl::optional<AlrExperimentSettings> experiment_settings =
|
||||
AlrExperimentSettings::CreateFromFieldTrial(
|
||||
*key_value_config,
|
||||
AlrExperimentSettings::kScreenshareProbingBweExperimentName);
|
||||
if (!experiment_settings) {
|
||||
experiment_settings = AlrExperimentSettings::CreateFromFieldTrial(
|
||||
*key_value_config,
|
||||
AlrExperimentSettings::kStrictPacingAndProbingExperimentName);
|
||||
}
|
||||
AlrDetectorConfig conf;
|
||||
if (experiment_settings) {
|
||||
conf.bandwidth_usage_ratio =
|
||||
experiment_settings->alr_bandwidth_usage_percent / 100.0;
|
||||
conf.start_budget_level_ratio =
|
||||
experiment_settings->alr_start_budget_level_percent / 100.0;
|
||||
conf.stop_budget_level_ratio =
|
||||
experiment_settings->alr_stop_budget_level_percent / 100.0;
|
||||
}
|
||||
conf.Parser()->Parse(
|
||||
key_value_config->Lookup("WebRTC-AlrDetectorParameters"));
|
||||
return conf;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<StructParametersParser> AlrDetectorConfig::Parser() {
|
||||
return StructParametersParser::Create( //
|
||||
"bw_usage", &bandwidth_usage_ratio, //
|
||||
"start", &start_budget_level_ratio, //
|
||||
"stop", &stop_budget_level_ratio);
|
||||
}
|
||||
|
||||
AlrDetector::AlrDetector(AlrDetectorConfig config, RtcEventLog* event_log)
|
||||
: conf_(config), alr_budget_(0, true), event_log_(event_log) {}
|
||||
|
||||
AlrDetector::AlrDetector(const FieldTrialsView* key_value_config)
|
||||
: AlrDetector(GetConfigFromTrials(key_value_config), nullptr) {}
|
||||
|
||||
AlrDetector::AlrDetector(const FieldTrialsView* key_value_config,
|
||||
RtcEventLog* event_log)
|
||||
: AlrDetector(GetConfigFromTrials(key_value_config), event_log) {}
|
||||
AlrDetector::~AlrDetector() {}
|
||||
|
||||
void AlrDetector::OnBytesSent(size_t bytes_sent, int64_t send_time_ms) {
|
||||
if (!last_send_time_ms_.has_value()) {
|
||||
last_send_time_ms_ = send_time_ms;
|
||||
// Since the duration for sending the bytes is unknwon, return without
|
||||
// updating alr state.
|
||||
return;
|
||||
}
|
||||
int64_t delta_time_ms = send_time_ms - *last_send_time_ms_;
|
||||
last_send_time_ms_ = send_time_ms;
|
||||
|
||||
alr_budget_.UseBudget(bytes_sent);
|
||||
alr_budget_.IncreaseBudget(delta_time_ms);
|
||||
bool state_changed = false;
|
||||
if (alr_budget_.budget_ratio() > conf_.start_budget_level_ratio &&
|
||||
!alr_started_time_ms_) {
|
||||
alr_started_time_ms_.emplace(rtc::TimeMillis());
|
||||
state_changed = true;
|
||||
} else if (alr_budget_.budget_ratio() < conf_.stop_budget_level_ratio &&
|
||||
alr_started_time_ms_) {
|
||||
state_changed = true;
|
||||
alr_started_time_ms_.reset();
|
||||
}
|
||||
if (event_log_ && state_changed) {
|
||||
event_log_->Log(
|
||||
std::make_unique<RtcEventAlrState>(alr_started_time_ms_.has_value()));
|
||||
}
|
||||
}
|
||||
|
||||
void AlrDetector::SetEstimatedBitrate(int bitrate_bps) {
|
||||
RTC_DCHECK(bitrate_bps);
|
||||
int target_rate_kbps =
|
||||
static_cast<double>(bitrate_bps) * conf_.bandwidth_usage_ratio / 1000;
|
||||
alr_budget_.set_target_rate_kbps(target_rate_kbps);
|
||||
}
|
||||
|
||||
absl::optional<int64_t> AlrDetector::GetApplicationLimitedRegionStartTime()
|
||||
const {
|
||||
return alr_started_time_ms_;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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_ALR_DETECTOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ALR_DETECTOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "modules/pacing/interval_budget.h"
|
||||
#include "rtc_base/experiments/alr_experiment.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RtcEventLog;
|
||||
|
||||
struct AlrDetectorConfig {
|
||||
// Sent traffic ratio as a function of network capacity used to determine
|
||||
// application-limited region. ALR region start when bandwidth usage drops
|
||||
// below kAlrStartUsageRatio and ends when it raises above
|
||||
// kAlrEndUsageRatio. NOTE: This is intentionally conservative at the moment
|
||||
// until BW adjustments of application limited region is fine tuned.
|
||||
double bandwidth_usage_ratio = 0.65;
|
||||
double start_budget_level_ratio = 0.80;
|
||||
double stop_budget_level_ratio = 0.50;
|
||||
std::unique_ptr<StructParametersParser> Parser();
|
||||
};
|
||||
// Application limited region detector is a class that utilizes signals of
|
||||
// elapsed time and bytes sent to estimate whether network traffic is
|
||||
// currently limited by the application's ability to generate traffic.
|
||||
//
|
||||
// AlrDetector provides a signal that can be utilized to adjust
|
||||
// estimate bandwidth.
|
||||
// Note: This class is not thread-safe.
|
||||
class AlrDetector {
|
||||
public:
|
||||
AlrDetector(AlrDetectorConfig config, RtcEventLog* event_log);
|
||||
explicit AlrDetector(const FieldTrialsView* key_value_config);
|
||||
AlrDetector(const FieldTrialsView* key_value_config, RtcEventLog* event_log);
|
||||
~AlrDetector();
|
||||
|
||||
void OnBytesSent(size_t bytes_sent, int64_t send_time_ms);
|
||||
|
||||
// Set current estimated bandwidth.
|
||||
void SetEstimatedBitrate(int bitrate_bps);
|
||||
|
||||
// Returns time in milliseconds when the current application-limited region
|
||||
// started or empty result if the sender is currently not application-limited.
|
||||
absl::optional<int64_t> GetApplicationLimitedRegionStartTime() const;
|
||||
|
||||
private:
|
||||
friend class GoogCcStatePrinter;
|
||||
const AlrDetectorConfig conf_;
|
||||
|
||||
absl::optional<int64_t> last_send_time_ms_;
|
||||
|
||||
IntervalBudget alr_budget_;
|
||||
absl::optional<int64_t> alr_started_time_ms_;
|
||||
|
||||
RtcEventLog* event_log_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_ALR_DETECTOR_H_
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright (c) 2017 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/bitrate_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.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/test/bwe_test_logging.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr int kInitialRateWindowMs = 500;
|
||||
constexpr int kRateWindowMs = 150;
|
||||
constexpr int kMinRateWindowMs = 150;
|
||||
constexpr int kMaxRateWindowMs = 1000;
|
||||
|
||||
const char kBweThroughputWindowConfig[] = "WebRTC-BweThroughputWindowConfig";
|
||||
|
||||
} // namespace
|
||||
|
||||
BitrateEstimator::BitrateEstimator(const FieldTrialsView* key_value_config)
|
||||
: sum_(0),
|
||||
initial_window_ms_("initial_window_ms",
|
||||
kInitialRateWindowMs,
|
||||
kMinRateWindowMs,
|
||||
kMaxRateWindowMs),
|
||||
noninitial_window_ms_("window_ms",
|
||||
kRateWindowMs,
|
||||
kMinRateWindowMs,
|
||||
kMaxRateWindowMs),
|
||||
uncertainty_scale_("scale", 10.0),
|
||||
uncertainty_scale_in_alr_("scale_alr", uncertainty_scale_),
|
||||
small_sample_uncertainty_scale_("scale_small", uncertainty_scale_),
|
||||
small_sample_threshold_("small_thresh", DataSize::Zero()),
|
||||
uncertainty_symmetry_cap_("symmetry_cap", DataRate::Zero()),
|
||||
estimate_floor_("floor", DataRate::Zero()),
|
||||
current_window_ms_(0),
|
||||
prev_time_ms_(-1),
|
||||
bitrate_estimate_kbps_(-1.0f),
|
||||
bitrate_estimate_var_(50.0f) {
|
||||
// E.g WebRTC-BweThroughputWindowConfig/initial_window_ms:350,window_ms:250/
|
||||
ParseFieldTrial(
|
||||
{&initial_window_ms_, &noninitial_window_ms_, &uncertainty_scale_,
|
||||
&uncertainty_scale_in_alr_, &small_sample_uncertainty_scale_,
|
||||
&small_sample_threshold_, &uncertainty_symmetry_cap_, &estimate_floor_},
|
||||
key_value_config->Lookup(kBweThroughputWindowConfig));
|
||||
}
|
||||
|
||||
BitrateEstimator::~BitrateEstimator() = default;
|
||||
|
||||
void BitrateEstimator::Update(Timestamp at_time, DataSize amount, bool in_alr) {
|
||||
int rate_window_ms = noninitial_window_ms_.Get();
|
||||
// We use a larger window at the beginning to get a more stable sample that
|
||||
// we can use to initialize the estimate.
|
||||
if (bitrate_estimate_kbps_ < 0.f)
|
||||
rate_window_ms = initial_window_ms_.Get();
|
||||
bool is_small_sample = false;
|
||||
float bitrate_sample_kbps = UpdateWindow(at_time.ms(), amount.bytes(),
|
||||
rate_window_ms, &is_small_sample);
|
||||
if (bitrate_sample_kbps < 0.0f)
|
||||
return;
|
||||
if (bitrate_estimate_kbps_ < 0.0f) {
|
||||
// This is the very first sample we get. Use it to initialize the estimate.
|
||||
bitrate_estimate_kbps_ = bitrate_sample_kbps;
|
||||
return;
|
||||
}
|
||||
// Optionally use higher uncertainty for very small samples to avoid dropping
|
||||
// estimate and for samples obtained in ALR.
|
||||
float scale = uncertainty_scale_;
|
||||
if (is_small_sample && bitrate_sample_kbps < bitrate_estimate_kbps_) {
|
||||
scale = small_sample_uncertainty_scale_;
|
||||
} else if (in_alr && bitrate_sample_kbps < bitrate_estimate_kbps_) {
|
||||
// Optionally use higher uncertainty for samples obtained during ALR.
|
||||
scale = uncertainty_scale_in_alr_;
|
||||
}
|
||||
// Define the sample uncertainty as a function of how far away it is from the
|
||||
// current estimate. With low values of uncertainty_symmetry_cap_ we add more
|
||||
// uncertainty to increases than to decreases. For higher values we approach
|
||||
// symmetry.
|
||||
float sample_uncertainty =
|
||||
scale * std::abs(bitrate_estimate_kbps_ - bitrate_sample_kbps) /
|
||||
(bitrate_estimate_kbps_ +
|
||||
std::min(bitrate_sample_kbps,
|
||||
uncertainty_symmetry_cap_.Get().kbps<float>()));
|
||||
|
||||
float sample_var = sample_uncertainty * sample_uncertainty;
|
||||
// Update a bayesian estimate of the rate, weighting it lower if the sample
|
||||
// uncertainty is large.
|
||||
// The bitrate estimate uncertainty is increased with each update to model
|
||||
// that the bitrate changes over time.
|
||||
float pred_bitrate_estimate_var = bitrate_estimate_var_ + 5.f;
|
||||
bitrate_estimate_kbps_ = (sample_var * bitrate_estimate_kbps_ +
|
||||
pred_bitrate_estimate_var * bitrate_sample_kbps) /
|
||||
(sample_var + pred_bitrate_estimate_var);
|
||||
bitrate_estimate_kbps_ =
|
||||
std::max(bitrate_estimate_kbps_, estimate_floor_.Get().kbps<float>());
|
||||
bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var /
|
||||
(sample_var + pred_bitrate_estimate_var);
|
||||
BWE_TEST_LOGGING_PLOT(1, "acknowledged_bitrate", at_time.ms(),
|
||||
bitrate_estimate_kbps_ * 1000);
|
||||
}
|
||||
|
||||
float BitrateEstimator::UpdateWindow(int64_t now_ms,
|
||||
int bytes,
|
||||
int rate_window_ms,
|
||||
bool* is_small_sample) {
|
||||
RTC_DCHECK(is_small_sample != nullptr);
|
||||
// Reset if time moves backwards.
|
||||
if (now_ms < prev_time_ms_) {
|
||||
prev_time_ms_ = -1;
|
||||
sum_ = 0;
|
||||
current_window_ms_ = 0;
|
||||
}
|
||||
if (prev_time_ms_ >= 0) {
|
||||
current_window_ms_ += now_ms - prev_time_ms_;
|
||||
// Reset if nothing has been received for more than a full window.
|
||||
if (now_ms - prev_time_ms_ > rate_window_ms) {
|
||||
sum_ = 0;
|
||||
current_window_ms_ %= rate_window_ms;
|
||||
}
|
||||
}
|
||||
prev_time_ms_ = now_ms;
|
||||
float bitrate_sample = -1.0f;
|
||||
if (current_window_ms_ >= rate_window_ms) {
|
||||
*is_small_sample = sum_ < small_sample_threshold_->bytes();
|
||||
bitrate_sample = 8.0f * sum_ / static_cast<float>(rate_window_ms);
|
||||
current_window_ms_ -= rate_window_ms;
|
||||
sum_ = 0;
|
||||
}
|
||||
sum_ += bytes;
|
||||
return bitrate_sample;
|
||||
}
|
||||
|
||||
absl::optional<DataRate> BitrateEstimator::bitrate() const {
|
||||
if (bitrate_estimate_kbps_ < 0.f)
|
||||
return absl::nullopt;
|
||||
return DataRate::KilobitsPerSec(bitrate_estimate_kbps_);
|
||||
}
|
||||
|
||||
absl::optional<DataRate> BitrateEstimator::PeekRate() const {
|
||||
if (current_window_ms_ > 0)
|
||||
return DataSize::Bytes(sum_) / TimeDelta::Millis(current_window_ms_);
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
void BitrateEstimator::ExpectFastRateChange() {
|
||||
// By setting the bitrate-estimate variance to a higher value we allow the
|
||||
// bitrate to change fast for the next few samples.
|
||||
bitrate_estimate_var_ += 200;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2017 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_BITRATE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_BITRATE_ESTIMATOR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Computes a bayesian estimate of the throughput given acks containing
|
||||
// the arrival time and payload size. Samples which are far from the current
|
||||
// estimate or are based on few packets are given a smaller weight, as they
|
||||
// are considered to be more likely to have been caused by, e.g., delay spikes
|
||||
// unrelated to congestion.
|
||||
class BitrateEstimator {
|
||||
public:
|
||||
explicit BitrateEstimator(const FieldTrialsView* key_value_config);
|
||||
virtual ~BitrateEstimator();
|
||||
virtual void Update(Timestamp at_time, DataSize amount, bool in_alr);
|
||||
|
||||
virtual absl::optional<DataRate> bitrate() const;
|
||||
absl::optional<DataRate> PeekRate() const;
|
||||
|
||||
virtual void ExpectFastRateChange();
|
||||
|
||||
private:
|
||||
float UpdateWindow(int64_t now_ms,
|
||||
int bytes,
|
||||
int rate_window_ms,
|
||||
bool* is_small_sample);
|
||||
int sum_;
|
||||
FieldTrialConstrained<int> initial_window_ms_;
|
||||
FieldTrialConstrained<int> noninitial_window_ms_;
|
||||
FieldTrialParameter<double> uncertainty_scale_;
|
||||
FieldTrialParameter<double> uncertainty_scale_in_alr_;
|
||||
FieldTrialParameter<double> small_sample_uncertainty_scale_;
|
||||
FieldTrialParameter<DataSize> small_sample_threshold_;
|
||||
FieldTrialParameter<DataRate> uncertainty_symmetry_cap_;
|
||||
FieldTrialParameter<DataRate> estimate_floor_;
|
||||
int64_t current_window_ms_;
|
||||
int64_t prev_time_ms_;
|
||||
float bitrate_estimate_kbps_;
|
||||
float bitrate_estimate_var_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_BITRATE_ESTIMATOR_H_
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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/congestion_window_pushback_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "rtc_base/experiments/rate_control_settings.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
CongestionWindowPushbackController::CongestionWindowPushbackController(
|
||||
const FieldTrialsView* key_value_config)
|
||||
: add_pacing_(
|
||||
absl::StartsWith(key_value_config->Lookup(
|
||||
"WebRTC-AddPacingToCongestionWindowPushback"),
|
||||
"Enabled")),
|
||||
min_pushback_target_bitrate_bps_(
|
||||
RateControlSettings::ParseFromKeyValueConfig(key_value_config)
|
||||
.CongestionWindowMinPushbackTargetBitrateBps()),
|
||||
current_data_window_(
|
||||
RateControlSettings::ParseFromKeyValueConfig(key_value_config)
|
||||
.CongestionWindowInitialDataWindow()) {}
|
||||
|
||||
void CongestionWindowPushbackController::UpdateOutstandingData(
|
||||
int64_t outstanding_bytes) {
|
||||
outstanding_bytes_ = outstanding_bytes;
|
||||
}
|
||||
void CongestionWindowPushbackController::UpdatePacingQueue(
|
||||
int64_t pacing_bytes) {
|
||||
pacing_bytes_ = pacing_bytes;
|
||||
}
|
||||
|
||||
void CongestionWindowPushbackController::SetDataWindow(DataSize data_window) {
|
||||
current_data_window_ = data_window;
|
||||
}
|
||||
|
||||
uint32_t CongestionWindowPushbackController::UpdateTargetBitrate(
|
||||
uint32_t bitrate_bps) {
|
||||
if (!current_data_window_ || current_data_window_->IsZero())
|
||||
return bitrate_bps;
|
||||
int64_t total_bytes = outstanding_bytes_;
|
||||
if (add_pacing_)
|
||||
total_bytes += pacing_bytes_;
|
||||
double fill_ratio =
|
||||
total_bytes / static_cast<double>(current_data_window_->bytes());
|
||||
if (fill_ratio > 1.5) {
|
||||
encoding_rate_ratio_ *= 0.9;
|
||||
} else if (fill_ratio > 1) {
|
||||
encoding_rate_ratio_ *= 0.95;
|
||||
} else if (fill_ratio < 0.1) {
|
||||
encoding_rate_ratio_ = 1.0;
|
||||
} else {
|
||||
encoding_rate_ratio_ *= 1.05;
|
||||
encoding_rate_ratio_ = std::min(encoding_rate_ratio_, 1.0);
|
||||
}
|
||||
uint32_t adjusted_target_bitrate_bps =
|
||||
static_cast<uint32_t>(bitrate_bps * encoding_rate_ratio_);
|
||||
|
||||
// Do not adjust below the minimum pushback bitrate but do obey if the
|
||||
// original estimate is below it.
|
||||
bitrate_bps = adjusted_target_bitrate_bps < min_pushback_target_bitrate_bps_
|
||||
? std::min(bitrate_bps, min_pushback_target_bitrate_bps_)
|
||||
: adjusted_target_bitrate_bps;
|
||||
return bitrate_bps;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/units/data_size.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// This class enables pushback from congestion window directly to video encoder.
|
||||
// When the congestion window is filling up, the video encoder target bitrate
|
||||
// will be reduced accordingly to accommodate the network changes. To avoid
|
||||
// pausing video too frequently, a minimum encoder target bitrate threshold is
|
||||
// used to prevent video pause due to a full congestion window.
|
||||
class CongestionWindowPushbackController {
|
||||
public:
|
||||
explicit CongestionWindowPushbackController(
|
||||
const FieldTrialsView* key_value_config);
|
||||
void UpdateOutstandingData(int64_t outstanding_bytes);
|
||||
void UpdatePacingQueue(int64_t pacing_bytes);
|
||||
uint32_t UpdateTargetBitrate(uint32_t bitrate_bps);
|
||||
void SetDataWindow(DataSize data_window);
|
||||
|
||||
private:
|
||||
const bool add_pacing_;
|
||||
const uint32_t min_pushback_target_bitrate_bps_;
|
||||
absl::optional<DataSize> current_data_window_;
|
||||
int64_t outstanding_bytes_ = 0;
|
||||
int64_t pacing_bytes_ = 0;
|
||||
double encoding_rate_ratio_ = 1.0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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/delay_based_bwe.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "api/rtc_event_log/rtc_event_log.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 "logging/rtc_event_log/events/rtc_event_bwe_update_delay_based.h"
|
||||
#include "modules/congestion_controller/goog_cc/delay_increase_detector_interface.h"
|
||||
#include "modules/congestion_controller/goog_cc/inter_arrival_delta.h"
|
||||
#include "modules/congestion_controller/goog_cc/trendline_estimator.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/race_checker.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
constexpr TimeDelta kStreamTimeOut = TimeDelta::Seconds(2);
|
||||
constexpr TimeDelta kSendTimeGroupLength = TimeDelta::Millis(5);
|
||||
|
||||
// This ssrc is used to fulfill the current API but will be removed
|
||||
// after the API has been changed.
|
||||
constexpr uint32_t kFixedSsrc = 0;
|
||||
} // namespace
|
||||
|
||||
constexpr char BweSeparateAudioPacketsSettings::kKey[];
|
||||
|
||||
BweSeparateAudioPacketsSettings::BweSeparateAudioPacketsSettings(
|
||||
const FieldTrialsView* key_value_config) {
|
||||
Parser()->Parse(
|
||||
key_value_config->Lookup(BweSeparateAudioPacketsSettings::kKey));
|
||||
}
|
||||
|
||||
std::unique_ptr<StructParametersParser>
|
||||
BweSeparateAudioPacketsSettings::Parser() {
|
||||
return StructParametersParser::Create( //
|
||||
"enabled", &enabled, //
|
||||
"packet_threshold", &packet_threshold, //
|
||||
"time_threshold", &time_threshold);
|
||||
}
|
||||
|
||||
DelayBasedBwe::Result::Result()
|
||||
: updated(false),
|
||||
probe(false),
|
||||
target_bitrate(DataRate::Zero()),
|
||||
recovered_from_overuse(false),
|
||||
delay_detector_state(BandwidthUsage::kBwNormal) {}
|
||||
|
||||
DelayBasedBwe::DelayBasedBwe(const FieldTrialsView* key_value_config,
|
||||
RtcEventLog* event_log,
|
||||
NetworkStatePredictor* network_state_predictor)
|
||||
: event_log_(event_log),
|
||||
key_value_config_(key_value_config),
|
||||
separate_audio_(key_value_config),
|
||||
audio_packets_since_last_video_(0),
|
||||
last_video_packet_recv_time_(Timestamp::MinusInfinity()),
|
||||
network_state_predictor_(network_state_predictor),
|
||||
video_delay_detector_(
|
||||
new TrendlineEstimator(key_value_config_, network_state_predictor_)),
|
||||
audio_delay_detector_(
|
||||
new TrendlineEstimator(key_value_config_, network_state_predictor_)),
|
||||
active_delay_detector_(video_delay_detector_.get()),
|
||||
last_seen_packet_(Timestamp::MinusInfinity()),
|
||||
uma_recorded_(false),
|
||||
rate_control_(*key_value_config, /*send_side=*/true),
|
||||
prev_bitrate_(DataRate::Zero()),
|
||||
prev_state_(BandwidthUsage::kBwNormal) {
|
||||
RTC_LOG(LS_INFO)
|
||||
<< "Initialized DelayBasedBwe with separate audio overuse detection"
|
||||
<< separate_audio_.Parser()->Encode();
|
||||
}
|
||||
|
||||
DelayBasedBwe::~DelayBasedBwe() {}
|
||||
|
||||
DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedbackVector(
|
||||
const TransportPacketsFeedback& msg,
|
||||
absl::optional<DataRate> acked_bitrate,
|
||||
absl::optional<DataRate> probe_bitrate,
|
||||
absl::optional<NetworkStateEstimate> network_estimate,
|
||||
bool in_alr) {
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&network_race_);
|
||||
|
||||
auto packet_feedback_vector = msg.SortedByReceiveTime();
|
||||
// TODO(holmer): An empty feedback vector here likely means that
|
||||
// all acks were too late and that the send time history had
|
||||
// timed out. We should reduce the rate when this occurs.
|
||||
if (packet_feedback_vector.empty()) {
|
||||
RTC_LOG(LS_WARNING) << "Very late feedback received.";
|
||||
return DelayBasedBwe::Result();
|
||||
}
|
||||
|
||||
if (!uma_recorded_) {
|
||||
RTC_HISTOGRAM_ENUMERATION(kBweTypeHistogram,
|
||||
BweNames::kSendSideTransportSeqNum,
|
||||
BweNames::kBweNamesMax);
|
||||
uma_recorded_ = true;
|
||||
}
|
||||
bool delayed_feedback = true;
|
||||
bool recovered_from_overuse = false;
|
||||
BandwidthUsage prev_detector_state = active_delay_detector_->State();
|
||||
for (const auto& packet_feedback : packet_feedback_vector) {
|
||||
delayed_feedback = false;
|
||||
IncomingPacketFeedback(packet_feedback, msg.feedback_time);
|
||||
if (prev_detector_state == BandwidthUsage::kBwUnderusing &&
|
||||
active_delay_detector_->State() == BandwidthUsage::kBwNormal) {
|
||||
recovered_from_overuse = true;
|
||||
}
|
||||
prev_detector_state = active_delay_detector_->State();
|
||||
}
|
||||
|
||||
if (delayed_feedback) {
|
||||
// TODO(bugs.webrtc.org/10125): Design a better mechanism to safe-guard
|
||||
// against building very large network queues.
|
||||
return Result();
|
||||
}
|
||||
rate_control_.SetInApplicationLimitedRegion(in_alr);
|
||||
rate_control_.SetNetworkStateEstimate(network_estimate);
|
||||
return MaybeUpdateEstimate(acked_bitrate, probe_bitrate,
|
||||
std::move(network_estimate),
|
||||
recovered_from_overuse, in_alr, msg.feedback_time);
|
||||
}
|
||||
|
||||
void DelayBasedBwe::IncomingPacketFeedback(const PacketResult& packet_feedback,
|
||||
Timestamp at_time) {
|
||||
// Reset if the stream has timed out.
|
||||
if (last_seen_packet_.IsInfinite() ||
|
||||
at_time - last_seen_packet_ > kStreamTimeOut) {
|
||||
video_inter_arrival_delta_ =
|
||||
std::make_unique<InterArrivalDelta>(kSendTimeGroupLength);
|
||||
audio_inter_arrival_delta_ =
|
||||
std::make_unique<InterArrivalDelta>(kSendTimeGroupLength);
|
||||
|
||||
video_delay_detector_.reset(
|
||||
new TrendlineEstimator(key_value_config_, network_state_predictor_));
|
||||
audio_delay_detector_.reset(
|
||||
new TrendlineEstimator(key_value_config_, network_state_predictor_));
|
||||
active_delay_detector_ = video_delay_detector_.get();
|
||||
}
|
||||
last_seen_packet_ = at_time;
|
||||
|
||||
// As an alternative to ignoring small packets, we can separate audio and
|
||||
// video packets for overuse detection.
|
||||
DelayIncreaseDetectorInterface* delay_detector_for_packet =
|
||||
video_delay_detector_.get();
|
||||
if (separate_audio_.enabled) {
|
||||
if (packet_feedback.sent_packet.audio) {
|
||||
delay_detector_for_packet = audio_delay_detector_.get();
|
||||
audio_packets_since_last_video_++;
|
||||
if (audio_packets_since_last_video_ > separate_audio_.packet_threshold &&
|
||||
packet_feedback.receive_time - last_video_packet_recv_time_ >
|
||||
separate_audio_.time_threshold) {
|
||||
active_delay_detector_ = audio_delay_detector_.get();
|
||||
}
|
||||
} else {
|
||||
audio_packets_since_last_video_ = 0;
|
||||
last_video_packet_recv_time_ =
|
||||
std::max(last_video_packet_recv_time_, packet_feedback.receive_time);
|
||||
active_delay_detector_ = video_delay_detector_.get();
|
||||
}
|
||||
}
|
||||
DataSize packet_size = packet_feedback.sent_packet.size;
|
||||
|
||||
TimeDelta send_delta = TimeDelta::Zero();
|
||||
TimeDelta recv_delta = TimeDelta::Zero();
|
||||
int size_delta = 0;
|
||||
|
||||
InterArrivalDelta* inter_arrival_for_packet =
|
||||
(separate_audio_.enabled && packet_feedback.sent_packet.audio)
|
||||
? audio_inter_arrival_delta_.get()
|
||||
: video_inter_arrival_delta_.get();
|
||||
bool calculated_deltas = inter_arrival_for_packet->ComputeDeltas(
|
||||
packet_feedback.sent_packet.send_time, packet_feedback.receive_time,
|
||||
at_time, packet_size.bytes(), &send_delta, &recv_delta, &size_delta);
|
||||
|
||||
delay_detector_for_packet->Update(recv_delta.ms<double>(),
|
||||
send_delta.ms<double>(),
|
||||
packet_feedback.sent_packet.send_time.ms(),
|
||||
packet_feedback.receive_time.ms(),
|
||||
packet_size.bytes(), calculated_deltas);
|
||||
}
|
||||
|
||||
DataRate DelayBasedBwe::TriggerOveruse(Timestamp at_time,
|
||||
absl::optional<DataRate> link_capacity) {
|
||||
RateControlInput input(BandwidthUsage::kBwOverusing, link_capacity);
|
||||
return rate_control_.Update(input, at_time);
|
||||
}
|
||||
|
||||
DelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate(
|
||||
absl::optional<DataRate> acked_bitrate,
|
||||
absl::optional<DataRate> probe_bitrate,
|
||||
absl::optional<NetworkStateEstimate> state_estimate,
|
||||
bool recovered_from_overuse,
|
||||
bool in_alr,
|
||||
Timestamp at_time) {
|
||||
Result result;
|
||||
|
||||
// Currently overusing the bandwidth.
|
||||
if (active_delay_detector_->State() == BandwidthUsage::kBwOverusing) {
|
||||
if (acked_bitrate &&
|
||||
rate_control_.TimeToReduceFurther(at_time, *acked_bitrate)) {
|
||||
result.updated =
|
||||
UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate);
|
||||
} else if (!acked_bitrate && rate_control_.ValidEstimate() &&
|
||||
rate_control_.InitialTimeToReduceFurther(at_time)) {
|
||||
// Overusing before we have a measured acknowledged bitrate. Reduce send
|
||||
// rate by 50% every 200 ms.
|
||||
// TODO(tschumim): Improve this and/or the acknowledged bitrate estimator
|
||||
// so that we (almost) always have a bitrate estimate.
|
||||
rate_control_.SetEstimate(rate_control_.LatestEstimate() / 2, at_time);
|
||||
result.updated = true;
|
||||
result.probe = false;
|
||||
result.target_bitrate = rate_control_.LatestEstimate();
|
||||
}
|
||||
} else {
|
||||
if (probe_bitrate) {
|
||||
result.probe = true;
|
||||
result.updated = true;
|
||||
rate_control_.SetEstimate(*probe_bitrate, at_time);
|
||||
result.target_bitrate = rate_control_.LatestEstimate();
|
||||
} else {
|
||||
result.updated =
|
||||
UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate);
|
||||
result.recovered_from_overuse = recovered_from_overuse;
|
||||
}
|
||||
}
|
||||
BandwidthUsage detector_state = active_delay_detector_->State();
|
||||
if ((result.updated && prev_bitrate_ != result.target_bitrate) ||
|
||||
detector_state != prev_state_) {
|
||||
DataRate bitrate = result.updated ? result.target_bitrate : prev_bitrate_;
|
||||
|
||||
BWE_TEST_LOGGING_PLOT(1, "target_bitrate_bps", at_time.ms(), bitrate.bps());
|
||||
|
||||
if (event_log_) {
|
||||
event_log_->Log(std::make_unique<RtcEventBweUpdateDelayBased>(
|
||||
bitrate.bps(), detector_state));
|
||||
}
|
||||
|
||||
prev_bitrate_ = bitrate;
|
||||
prev_state_ = detector_state;
|
||||
}
|
||||
|
||||
result.delay_detector_state = detector_state;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DelayBasedBwe::UpdateEstimate(Timestamp at_time,
|
||||
absl::optional<DataRate> acked_bitrate,
|
||||
DataRate* target_rate) {
|
||||
const RateControlInput input(active_delay_detector_->State(), acked_bitrate);
|
||||
*target_rate = rate_control_.Update(input, at_time);
|
||||
return rate_control_.ValidEstimate();
|
||||
}
|
||||
|
||||
void DelayBasedBwe::OnRttUpdate(TimeDelta avg_rtt) {
|
||||
rate_control_.SetRtt(avg_rtt);
|
||||
}
|
||||
|
||||
bool DelayBasedBwe::LatestEstimate(std::vector<uint32_t>* ssrcs,
|
||||
DataRate* bitrate) const {
|
||||
// Currently accessed from both the process thread (see
|
||||
// ModuleRtpRtcpImpl::Process()) and the configuration thread (see
|
||||
// Call::GetStats()). Should in the future only be accessed from a single
|
||||
// thread.
|
||||
RTC_DCHECK(ssrcs);
|
||||
RTC_DCHECK(bitrate);
|
||||
if (!rate_control_.ValidEstimate())
|
||||
return false;
|
||||
|
||||
*ssrcs = {kFixedSsrc};
|
||||
*bitrate = rate_control_.LatestEstimate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DelayBasedBwe::SetStartBitrate(DataRate start_bitrate) {
|
||||
RTC_LOG(LS_INFO) << "BWE Setting start bitrate to: "
|
||||
<< ToString(start_bitrate);
|
||||
rate_control_.SetStartBitrate(start_bitrate);
|
||||
}
|
||||
|
||||
void DelayBasedBwe::SetMinBitrate(DataRate min_bitrate) {
|
||||
// Called from both the configuration thread and the network thread. Shouldn't
|
||||
// be called from the network thread in the future.
|
||||
rate_control_.SetMinBitrate(min_bitrate);
|
||||
}
|
||||
|
||||
TimeDelta DelayBasedBwe::GetExpectedBwePeriod() const {
|
||||
return rate_control_.GetExpectedBandwidthPeriod();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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_DELAY_BASED_BWE_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.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/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "modules/congestion_controller/goog_cc/delay_increase_detector_interface.h"
|
||||
#include "modules/congestion_controller/goog_cc/inter_arrival_delta.h"
|
||||
#include "modules/congestion_controller/goog_cc/link_capacity_estimator.h"
|
||||
#include "modules/congestion_controller/goog_cc/probe_bitrate_estimator.h"
|
||||
#include "modules/remote_bitrate_estimator/aimd_rate_control.h"
|
||||
#include "modules/remote_bitrate_estimator/inter_arrival.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
#include "rtc_base/race_checker.h"
|
||||
|
||||
namespace webrtc {
|
||||
class RtcEventLog;
|
||||
|
||||
struct BweSeparateAudioPacketsSettings {
|
||||
static constexpr char kKey[] = "WebRTC-Bwe-SeparateAudioPackets";
|
||||
|
||||
BweSeparateAudioPacketsSettings() = default;
|
||||
explicit BweSeparateAudioPacketsSettings(
|
||||
const FieldTrialsView* key_value_config);
|
||||
|
||||
bool enabled = false;
|
||||
int packet_threshold = 10;
|
||||
TimeDelta time_threshold = TimeDelta::Seconds(1);
|
||||
|
||||
std::unique_ptr<StructParametersParser> Parser();
|
||||
};
|
||||
|
||||
class DelayBasedBwe {
|
||||
public:
|
||||
struct Result {
|
||||
Result();
|
||||
~Result() = default;
|
||||
bool updated;
|
||||
bool probe;
|
||||
DataRate target_bitrate = DataRate::Zero();
|
||||
bool recovered_from_overuse;
|
||||
BandwidthUsage delay_detector_state;
|
||||
};
|
||||
|
||||
explicit DelayBasedBwe(const FieldTrialsView* key_value_config,
|
||||
RtcEventLog* event_log,
|
||||
NetworkStatePredictor* network_state_predictor);
|
||||
|
||||
DelayBasedBwe() = delete;
|
||||
DelayBasedBwe(const DelayBasedBwe&) = delete;
|
||||
DelayBasedBwe& operator=(const DelayBasedBwe&) = delete;
|
||||
|
||||
virtual ~DelayBasedBwe();
|
||||
|
||||
Result IncomingPacketFeedbackVector(
|
||||
const TransportPacketsFeedback& msg,
|
||||
absl::optional<DataRate> acked_bitrate,
|
||||
absl::optional<DataRate> probe_bitrate,
|
||||
absl::optional<NetworkStateEstimate> network_estimate,
|
||||
bool in_alr);
|
||||
void OnRttUpdate(TimeDelta avg_rtt);
|
||||
bool LatestEstimate(std::vector<uint32_t>* ssrcs, DataRate* bitrate) const;
|
||||
void SetStartBitrate(DataRate start_bitrate);
|
||||
void SetMinBitrate(DataRate min_bitrate);
|
||||
TimeDelta GetExpectedBwePeriod() const;
|
||||
DataRate TriggerOveruse(Timestamp at_time,
|
||||
absl::optional<DataRate> link_capacity);
|
||||
DataRate last_estimate() const { return prev_bitrate_; }
|
||||
BandwidthUsage last_state() const { return prev_state_; }
|
||||
|
||||
private:
|
||||
friend class GoogCcStatePrinter;
|
||||
void IncomingPacketFeedback(const PacketResult& packet_feedback,
|
||||
Timestamp at_time);
|
||||
Result MaybeUpdateEstimate(
|
||||
absl::optional<DataRate> acked_bitrate,
|
||||
absl::optional<DataRate> probe_bitrate,
|
||||
absl::optional<NetworkStateEstimate> state_estimate,
|
||||
bool recovered_from_overuse,
|
||||
bool in_alr,
|
||||
Timestamp at_time);
|
||||
// Updates the current remote rate estimate and returns true if a valid
|
||||
// estimate exists.
|
||||
bool UpdateEstimate(Timestamp at_time,
|
||||
absl::optional<DataRate> acked_bitrate,
|
||||
DataRate* target_rate);
|
||||
|
||||
rtc::RaceChecker network_race_;
|
||||
RtcEventLog* const event_log_;
|
||||
const FieldTrialsView* const key_value_config_;
|
||||
|
||||
// Alternatively, run two separate overuse detectors for audio and video,
|
||||
// and fall back to the audio one if we haven't seen a video packet in a
|
||||
// while.
|
||||
BweSeparateAudioPacketsSettings separate_audio_;
|
||||
int64_t audio_packets_since_last_video_;
|
||||
Timestamp last_video_packet_recv_time_;
|
||||
|
||||
NetworkStatePredictor* network_state_predictor_;
|
||||
std::unique_ptr<InterArrival> video_inter_arrival_;
|
||||
std::unique_ptr<InterArrivalDelta> video_inter_arrival_delta_;
|
||||
std::unique_ptr<DelayIncreaseDetectorInterface> video_delay_detector_;
|
||||
std::unique_ptr<InterArrival> audio_inter_arrival_;
|
||||
std::unique_ptr<InterArrivalDelta> audio_inter_arrival_delta_;
|
||||
std::unique_ptr<DelayIncreaseDetectorInterface> audio_delay_detector_;
|
||||
DelayIncreaseDetectorInterface* active_delay_detector_;
|
||||
|
||||
Timestamp last_seen_packet_;
|
||||
bool uma_recorded_;
|
||||
AimdRateControl rate_control_;
|
||||
DataRate prev_bitrate_;
|
||||
BandwidthUsage prev_state_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_H_
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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/delay_based_bwe.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr int kNumProbesCluster0 = 5;
|
||||
constexpr int kNumProbesCluster1 = 8;
|
||||
const PacedPacketInfo kPacingInfo0(0, kNumProbesCluster0, 2000);
|
||||
const PacedPacketInfo kPacingInfo1(1, kNumProbesCluster1, 4000);
|
||||
constexpr float kTargetUtilizationFraction = 0.95f;
|
||||
} // namespace
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetection) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
|
||||
// First burst sent at 8 * 1000 / 10 = 800 kbps.
|
||||
for (int i = 0; i < kNumProbesCluster0; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(10);
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, now_ms, 1000, kPacingInfo0);
|
||||
}
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
|
||||
// Second burst sent at 8 * 1000 / 5 = 1600 kbps.
|
||||
for (int i = 0; i < kNumProbesCluster1; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(5);
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, now_ms, 1000, kPacingInfo1);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_GT(bitrate_observer_.latest_bitrate(), 1500000u);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetectionNonPacedPackets) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
// First burst sent at 8 * 1000 / 10 = 800 kbps, but with every other packet
|
||||
// not being paced which could mess things up.
|
||||
for (int i = 0; i < kNumProbesCluster0; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(5);
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, now_ms, 1000, kPacingInfo0);
|
||||
// Non-paced packet, arriving 5 ms after.
|
||||
clock_.AdvanceTimeMilliseconds(5);
|
||||
IncomingFeedback(now_ms, now_ms, 100, PacedPacketInfo());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_GT(bitrate_observer_.latest_bitrate(), 800000u);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetectionFasterArrival) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
// First burst sent at 8 * 1000 / 10 = 800 kbps.
|
||||
// Arriving at 8 * 1000 / 5 = 1600 kbps.
|
||||
int64_t send_time_ms = 0;
|
||||
for (int i = 0; i < kNumProbesCluster0; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(1);
|
||||
send_time_ms += 10;
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, send_time_ms, 1000, kPacingInfo0);
|
||||
}
|
||||
|
||||
EXPECT_FALSE(bitrate_observer_.updated());
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetectionSlowerArrival) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
// First burst sent at 8 * 1000 / 5 = 1600 kbps.
|
||||
// Arriving at 8 * 1000 / 7 = 1142 kbps.
|
||||
// Since the receive rate is significantly below the send rate, we expect to
|
||||
// use 95% of the estimated capacity.
|
||||
int64_t send_time_ms = 0;
|
||||
for (int i = 0; i < kNumProbesCluster1; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(7);
|
||||
send_time_ms += 5;
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, send_time_ms, 1000, kPacingInfo1);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_NEAR(bitrate_observer_.latest_bitrate(),
|
||||
kTargetUtilizationFraction * 1140000u, 10000u);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetectionSlowerArrivalHighBitrate) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
// Burst sent at 8 * 1000 / 1 = 8000 kbps.
|
||||
// Arriving at 8 * 1000 / 2 = 4000 kbps.
|
||||
// Since the receive rate is significantly below the send rate, we expect to
|
||||
// use 95% of the estimated capacity.
|
||||
int64_t send_time_ms = 0;
|
||||
for (int i = 0; i < kNumProbesCluster1; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(2);
|
||||
send_time_ms += 1;
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, send_time_ms, 1000, kPacingInfo1);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_NEAR(bitrate_observer_.latest_bitrate(),
|
||||
kTargetUtilizationFraction * 4000000u, 10000u);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, GetExpectedBwePeriodMs) {
|
||||
auto default_interval = bitrate_estimator_->GetExpectedBwePeriod();
|
||||
EXPECT_GT(default_interval.ms(), 0);
|
||||
CapacityDropTestHelper(1, true, 533, 0);
|
||||
auto interval = bitrate_estimator_->GetExpectedBwePeriod();
|
||||
EXPECT_GT(interval.ms(), 0);
|
||||
EXPECT_NE(interval.ms(), default_interval.ms());
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, InitialBehavior) {
|
||||
InitialBehaviorTestHelper(730000);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, InitializeResult) {
|
||||
DelayBasedBwe::Result result;
|
||||
EXPECT_EQ(result.delay_detector_state, BandwidthUsage::kBwNormal);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, RateIncreaseReordering) {
|
||||
RateIncreaseReorderingTestHelper(730000);
|
||||
}
|
||||
TEST_F(DelayBasedBweTest, RateIncreaseRtpTimestamps) {
|
||||
RateIncreaseRtpTimestampsTestHelper(617);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, CapacityDropOneStream) {
|
||||
CapacityDropTestHelper(1, false, 500, 0);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, CapacityDropPosOffsetChange) {
|
||||
CapacityDropTestHelper(1, false, 867, 30000);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, CapacityDropNegOffsetChange) {
|
||||
CapacityDropTestHelper(1, false, 933, -30000);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, CapacityDropOneStreamWrap) {
|
||||
CapacityDropTestHelper(1, true, 533, 0);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, TestTimestampGrouping) {
|
||||
TestTimestampGroupingTestHelper();
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, TestShortTimeoutAndWrap) {
|
||||
// Simulate a client leaving and rejoining the call after 35 seconds. This
|
||||
// will make abs send time wrap, so if streams aren't timed out properly
|
||||
// the next 30 seconds of packets will be out of order.
|
||||
TestWrappingHelper(35);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, TestLongTimeoutAndWrap) {
|
||||
// Simulate a client leaving and rejoining the call after some multiple of
|
||||
// 64 seconds later. This will cause a zero difference in abs send times due
|
||||
// to the wrap, but a big difference in arrival time, if streams aren't
|
||||
// properly timed out.
|
||||
TestWrappingHelper(10 * 64);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, TestInitialOveruse) {
|
||||
const DataRate kStartBitrate = DataRate::KilobitsPerSec(300);
|
||||
const DataRate kInitialCapacity = DataRate::KilobitsPerSec(200);
|
||||
const uint32_t kDummySsrc = 0;
|
||||
// High FPS to ensure that we send a lot of packets in a short time.
|
||||
const int kFps = 90;
|
||||
|
||||
stream_generator_->AddStream(new test::RtpStream(kFps, kStartBitrate.bps()));
|
||||
stream_generator_->set_capacity_bps(kInitialCapacity.bps());
|
||||
|
||||
// Needed to initialize the AimdRateControl.
|
||||
bitrate_estimator_->SetStartBitrate(kStartBitrate);
|
||||
|
||||
// Produce 40 frames (in 1/3 second) and give them to the estimator.
|
||||
int64_t bitrate_bps = kStartBitrate.bps();
|
||||
bool seen_overuse = false;
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
bool overuse = GenerateAndProcessFrame(kDummySsrc, bitrate_bps);
|
||||
if (overuse) {
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_LE(bitrate_observer_.latest_bitrate(), kInitialCapacity.bps());
|
||||
EXPECT_GT(bitrate_observer_.latest_bitrate(),
|
||||
0.8 * kInitialCapacity.bps());
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
seen_overuse = true;
|
||||
break;
|
||||
} else if (bitrate_observer_.updated()) {
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
bitrate_observer_.Reset();
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(seen_overuse);
|
||||
EXPECT_LE(bitrate_observer_.latest_bitrate(), kInitialCapacity.bps());
|
||||
EXPECT_GT(bitrate_observer_.latest_bitrate(), 0.8 * kInitialCapacity.bps());
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, TestTimestampPrecisionHandling) {
|
||||
// This test does some basic checks to make sure that timestamps with higher
|
||||
// than millisecond precision are handled properly and do not cause any
|
||||
// problems in the estimator. Specifically, previously reported in
|
||||
// webrtc:14023 and described in more details there, the rounding to the
|
||||
// nearest milliseconds caused discrepancy in the accumulated delay. This lead
|
||||
// to false-positive overuse detection.
|
||||
// Technical details of the test:
|
||||
// Send times(ms): 0.000, 9.725, 20.000, 29.725, 40.000, 49.725, ...
|
||||
// Recv times(ms): 0.500, 10.000, 20.500, 30.000, 40.500, 50.000, ...
|
||||
// Send deltas(ms): 9.750, 10.250, 9.750, 10.250, 9.750, ...
|
||||
// Recv deltas(ms): 9.500, 10.500, 9.500, 10.500, 9.500, ...
|
||||
// There is no delay building up between the send times and the receive times,
|
||||
// therefore this case should never lead to an overuse detection. However, if
|
||||
// the time deltas were accidentally rounded to the nearest milliseconds, then
|
||||
// all the send deltas would be equal to 10ms while some recv deltas would
|
||||
// round up to 11ms which would lead in a false illusion of delay build up.
|
||||
uint32_t last_bitrate = bitrate_observer_.latest_bitrate();
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
clock_.AdvanceTimeMicroseconds(500);
|
||||
IncomingFeedback(clock_.CurrentTime(),
|
||||
clock_.CurrentTime() - TimeDelta::Micros(500), 1000,
|
||||
PacedPacketInfo());
|
||||
clock_.AdvanceTimeMicroseconds(9500);
|
||||
IncomingFeedback(clock_.CurrentTime(),
|
||||
clock_.CurrentTime() - TimeDelta::Micros(250), 1000,
|
||||
PacedPacketInfo());
|
||||
clock_.AdvanceTimeMicroseconds(10000);
|
||||
|
||||
// The bitrate should never decrease in this test.
|
||||
EXPECT_LE(last_bitrate, bitrate_observer_.latest_bitrate());
|
||||
last_bitrate = bitrate_observer_.latest_bitrate();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,543 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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/delay_based_bwe_unittest_helper.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.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/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h"
|
||||
#include "modules/congestion_controller/goog_cc/delay_based_bwe.h"
|
||||
#include "modules/congestion_controller/goog_cc/probe_bitrate_estimator.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "test/field_trial.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
constexpr size_t kMtu = 1200;
|
||||
constexpr uint32_t kAcceptedBitrateErrorBps = 50000;
|
||||
|
||||
// Number of packets needed before we have a valid estimate.
|
||||
constexpr int kNumInitialPackets = 2;
|
||||
|
||||
constexpr int kInitialProbingPackets = 5;
|
||||
|
||||
namespace test {
|
||||
|
||||
void TestBitrateObserver::OnReceiveBitrateChanged(uint32_t bitrate) {
|
||||
latest_bitrate_ = bitrate;
|
||||
updated_ = true;
|
||||
}
|
||||
|
||||
RtpStream::RtpStream(int fps, int bitrate_bps)
|
||||
: fps_(fps), bitrate_bps_(bitrate_bps), next_rtp_time_(0) {
|
||||
RTC_CHECK_GT(fps_, 0);
|
||||
}
|
||||
|
||||
// Generates a new frame for this stream. If called too soon after the
|
||||
// previous frame, no frame will be generated. The frame is split into
|
||||
// packets.
|
||||
int64_t RtpStream::GenerateFrame(int64_t time_now_us,
|
||||
int64_t* next_sequence_number,
|
||||
std::vector<PacketResult>* packets) {
|
||||
if (time_now_us < next_rtp_time_) {
|
||||
return next_rtp_time_;
|
||||
}
|
||||
RTC_CHECK(packets != NULL);
|
||||
size_t bits_per_frame = (bitrate_bps_ + fps_ / 2) / fps_;
|
||||
size_t n_packets =
|
||||
std::max<size_t>((bits_per_frame + 4 * kMtu) / (8 * kMtu), 1u);
|
||||
size_t payload_size = (bits_per_frame + 4 * n_packets) / (8 * n_packets);
|
||||
for (size_t i = 0; i < n_packets; ++i) {
|
||||
PacketResult packet;
|
||||
packet.sent_packet.send_time =
|
||||
Timestamp::Micros(time_now_us + kSendSideOffsetUs);
|
||||
packet.sent_packet.size = DataSize::Bytes(payload_size);
|
||||
packet.sent_packet.sequence_number = (*next_sequence_number)++;
|
||||
packets->push_back(packet);
|
||||
}
|
||||
next_rtp_time_ = time_now_us + (1000000 + fps_ / 2) / fps_;
|
||||
return next_rtp_time_;
|
||||
}
|
||||
|
||||
// The send-side time when the next frame can be generated.
|
||||
int64_t RtpStream::next_rtp_time() const {
|
||||
return next_rtp_time_;
|
||||
}
|
||||
|
||||
void RtpStream::set_bitrate_bps(int bitrate_bps) {
|
||||
ASSERT_GE(bitrate_bps, 0);
|
||||
bitrate_bps_ = bitrate_bps;
|
||||
}
|
||||
|
||||
int RtpStream::bitrate_bps() const {
|
||||
return bitrate_bps_;
|
||||
}
|
||||
|
||||
bool RtpStream::Compare(const std::unique_ptr<RtpStream>& lhs,
|
||||
const std::unique_ptr<RtpStream>& rhs) {
|
||||
return lhs->next_rtp_time_ < rhs->next_rtp_time_;
|
||||
}
|
||||
|
||||
StreamGenerator::StreamGenerator(int capacity, int64_t time_now)
|
||||
: capacity_(capacity), prev_arrival_time_us_(time_now) {}
|
||||
|
||||
StreamGenerator::~StreamGenerator() = default;
|
||||
|
||||
// Add a new stream.
|
||||
void StreamGenerator::AddStream(RtpStream* stream) {
|
||||
streams_.push_back(std::unique_ptr<RtpStream>(stream));
|
||||
}
|
||||
|
||||
// Set the link capacity.
|
||||
void StreamGenerator::set_capacity_bps(int capacity_bps) {
|
||||
ASSERT_GT(capacity_bps, 0);
|
||||
capacity_ = capacity_bps;
|
||||
}
|
||||
|
||||
// Divides `bitrate_bps` among all streams. The allocated bitrate per stream
|
||||
// is decided by the current allocation ratios.
|
||||
void StreamGenerator::SetBitrateBps(int bitrate_bps) {
|
||||
ASSERT_GE(streams_.size(), 0u);
|
||||
int total_bitrate_before = 0;
|
||||
for (const auto& stream : streams_) {
|
||||
total_bitrate_before += stream->bitrate_bps();
|
||||
}
|
||||
int64_t bitrate_before = 0;
|
||||
int total_bitrate_after = 0;
|
||||
for (const auto& stream : streams_) {
|
||||
bitrate_before += stream->bitrate_bps();
|
||||
int64_t bitrate_after =
|
||||
(bitrate_before * bitrate_bps + total_bitrate_before / 2) /
|
||||
total_bitrate_before;
|
||||
stream->set_bitrate_bps(bitrate_after - total_bitrate_after);
|
||||
total_bitrate_after += stream->bitrate_bps();
|
||||
}
|
||||
ASSERT_EQ(bitrate_before, total_bitrate_before);
|
||||
EXPECT_EQ(total_bitrate_after, bitrate_bps);
|
||||
}
|
||||
|
||||
// TODO(holmer): Break out the channel simulation part from this class to make
|
||||
// it possible to simulate different types of channels.
|
||||
int64_t StreamGenerator::GenerateFrame(int64_t time_now_us,
|
||||
int64_t* next_sequence_number,
|
||||
std::vector<PacketResult>* packets) {
|
||||
RTC_CHECK(packets != NULL);
|
||||
RTC_CHECK(packets->empty());
|
||||
RTC_CHECK_GT(capacity_, 0);
|
||||
auto it =
|
||||
std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare);
|
||||
(*it)->GenerateFrame(time_now_us, next_sequence_number, packets);
|
||||
for (PacketResult& packet : *packets) {
|
||||
int capacity_bpus = capacity_ / 1000;
|
||||
int64_t required_network_time_us =
|
||||
(8 * 1000 * packet.sent_packet.size.bytes() + capacity_bpus / 2) /
|
||||
capacity_bpus;
|
||||
prev_arrival_time_us_ =
|
||||
std::max(time_now_us + required_network_time_us,
|
||||
prev_arrival_time_us_ + required_network_time_us);
|
||||
packet.receive_time = Timestamp::Micros(prev_arrival_time_us_);
|
||||
}
|
||||
it = std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare);
|
||||
return std::max((*it)->next_rtp_time(), time_now_us);
|
||||
}
|
||||
} // namespace test
|
||||
|
||||
DelayBasedBweTest::DelayBasedBweTest()
|
||||
: field_trial(std::make_unique<test::ScopedFieldTrials>(
|
||||
"WebRTC-Bwe-RobustThroughputEstimatorSettings/enabled:true/")),
|
||||
clock_(100000000),
|
||||
acknowledged_bitrate_estimator_(
|
||||
AcknowledgedBitrateEstimatorInterface::Create(&field_trial_config_)),
|
||||
probe_bitrate_estimator_(new ProbeBitrateEstimator(nullptr)),
|
||||
bitrate_estimator_(
|
||||
new DelayBasedBwe(&field_trial_config_, nullptr, nullptr)),
|
||||
stream_generator_(new test::StreamGenerator(1e6, // Capacity.
|
||||
clock_.TimeInMicroseconds())),
|
||||
arrival_time_offset_ms_(0),
|
||||
next_sequence_number_(0),
|
||||
first_update_(true) {}
|
||||
|
||||
DelayBasedBweTest::~DelayBasedBweTest() {}
|
||||
|
||||
void DelayBasedBweTest::AddDefaultStream() {
|
||||
stream_generator_->AddStream(new test::RtpStream(30, 3e5));
|
||||
}
|
||||
|
||||
const uint32_t DelayBasedBweTest::kDefaultSsrc = 0;
|
||||
|
||||
void DelayBasedBweTest::IncomingFeedback(int64_t arrival_time_ms,
|
||||
int64_t send_time_ms,
|
||||
size_t payload_size) {
|
||||
IncomingFeedback(arrival_time_ms, send_time_ms, payload_size,
|
||||
PacedPacketInfo());
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::IncomingFeedback(int64_t arrival_time_ms,
|
||||
int64_t send_time_ms,
|
||||
size_t payload_size,
|
||||
const PacedPacketInfo& pacing_info) {
|
||||
RTC_CHECK_GE(arrival_time_ms + arrival_time_offset_ms_, 0);
|
||||
IncomingFeedback(Timestamp::Millis(arrival_time_ms + arrival_time_offset_ms_),
|
||||
Timestamp::Millis(send_time_ms), payload_size, pacing_info);
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::IncomingFeedback(Timestamp receive_time,
|
||||
Timestamp send_time,
|
||||
size_t payload_size,
|
||||
const PacedPacketInfo& pacing_info) {
|
||||
PacketResult packet;
|
||||
packet.receive_time = receive_time;
|
||||
packet.sent_packet.send_time = send_time;
|
||||
packet.sent_packet.size = DataSize::Bytes(payload_size);
|
||||
packet.sent_packet.pacing_info = pacing_info;
|
||||
packet.sent_packet.sequence_number = next_sequence_number_++;
|
||||
if (packet.sent_packet.pacing_info.probe_cluster_id !=
|
||||
PacedPacketInfo::kNotAProbe)
|
||||
probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(packet);
|
||||
|
||||
TransportPacketsFeedback msg;
|
||||
msg.feedback_time = Timestamp::Millis(clock_.TimeInMilliseconds());
|
||||
msg.packet_feedbacks.push_back(packet);
|
||||
acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
msg.SortedByReceiveTime());
|
||||
DelayBasedBwe::Result result =
|
||||
bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
msg, acknowledged_bitrate_estimator_->bitrate(),
|
||||
probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate(),
|
||||
/*network_estimate*/ absl::nullopt, /*in_alr*/ false);
|
||||
if (result.updated) {
|
||||
bitrate_observer_.OnReceiveBitrateChanged(result.target_bitrate.bps());
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a frame of packets belonging to a stream at a given bitrate and
|
||||
// with a given ssrc. The stream is pushed through a very simple simulated
|
||||
// network, and is then given to the receive-side bandwidth estimator.
|
||||
// Returns true if an over-use was seen, false otherwise.
|
||||
// The StreamGenerator::updated() should be used to check for any changes in
|
||||
// target bitrate after the call to this function.
|
||||
bool DelayBasedBweTest::GenerateAndProcessFrame(uint32_t ssrc,
|
||||
uint32_t bitrate_bps) {
|
||||
stream_generator_->SetBitrateBps(bitrate_bps);
|
||||
std::vector<PacketResult> packets;
|
||||
|
||||
int64_t next_time_us = stream_generator_->GenerateFrame(
|
||||
clock_.TimeInMicroseconds(), &next_sequence_number_, &packets);
|
||||
if (packets.empty())
|
||||
return false;
|
||||
|
||||
bool overuse = false;
|
||||
bitrate_observer_.Reset();
|
||||
clock_.AdvanceTimeMicroseconds(packets.back().receive_time.us() -
|
||||
clock_.TimeInMicroseconds());
|
||||
for (auto& packet : packets) {
|
||||
RTC_CHECK_GE(packet.receive_time.ms() + arrival_time_offset_ms_, 0);
|
||||
packet.receive_time += TimeDelta::Millis(arrival_time_offset_ms_);
|
||||
|
||||
if (packet.sent_packet.pacing_info.probe_cluster_id !=
|
||||
PacedPacketInfo::kNotAProbe)
|
||||
probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(packet);
|
||||
}
|
||||
|
||||
acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(packets);
|
||||
TransportPacketsFeedback msg;
|
||||
msg.packet_feedbacks = packets;
|
||||
msg.feedback_time = Timestamp::Millis(clock_.TimeInMilliseconds());
|
||||
|
||||
DelayBasedBwe::Result result =
|
||||
bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
msg, acknowledged_bitrate_estimator_->bitrate(),
|
||||
probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate(),
|
||||
/*network_estimate*/ absl::nullopt, /*in_alr*/ false);
|
||||
if (result.updated) {
|
||||
bitrate_observer_.OnReceiveBitrateChanged(result.target_bitrate.bps());
|
||||
if (!first_update_ && result.target_bitrate.bps() < bitrate_bps)
|
||||
overuse = true;
|
||||
first_update_ = false;
|
||||
}
|
||||
|
||||
clock_.AdvanceTimeMicroseconds(next_time_us - clock_.TimeInMicroseconds());
|
||||
return overuse;
|
||||
}
|
||||
|
||||
// Run the bandwidth estimator with a stream of `number_of_frames` frames, or
|
||||
// until it reaches `target_bitrate`.
|
||||
// Can for instance be used to run the estimator for some time to get it
|
||||
// into a steady state.
|
||||
uint32_t DelayBasedBweTest::SteadyStateRun(uint32_t ssrc,
|
||||
int max_number_of_frames,
|
||||
uint32_t start_bitrate,
|
||||
uint32_t min_bitrate,
|
||||
uint32_t max_bitrate,
|
||||
uint32_t target_bitrate) {
|
||||
uint32_t bitrate_bps = start_bitrate;
|
||||
bool bitrate_update_seen = false;
|
||||
// Produce `number_of_frames` frames and give them to the estimator.
|
||||
for (int i = 0; i < max_number_of_frames; ++i) {
|
||||
bool overuse = GenerateAndProcessFrame(ssrc, bitrate_bps);
|
||||
if (overuse) {
|
||||
EXPECT_LT(bitrate_observer_.latest_bitrate(), max_bitrate);
|
||||
EXPECT_GT(bitrate_observer_.latest_bitrate(), min_bitrate);
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
bitrate_update_seen = true;
|
||||
} else if (bitrate_observer_.updated()) {
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
bitrate_observer_.Reset();
|
||||
}
|
||||
if (bitrate_update_seen && bitrate_bps > target_bitrate) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(bitrate_update_seen);
|
||||
return bitrate_bps;
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::InitialBehaviorTestHelper(
|
||||
uint32_t expected_converge_bitrate) {
|
||||
const int kFramerate = 50; // 50 fps to avoid rounding errors.
|
||||
const int kFrameIntervalMs = 1000 / kFramerate;
|
||||
const PacedPacketInfo kPacingInfo(0, 5, 5000);
|
||||
DataRate bitrate = DataRate::Zero();
|
||||
int64_t send_time_ms = 0;
|
||||
std::vector<uint32_t> ssrcs;
|
||||
EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate));
|
||||
EXPECT_EQ(0u, ssrcs.size());
|
||||
clock_.AdvanceTimeMilliseconds(1000);
|
||||
EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate));
|
||||
EXPECT_FALSE(bitrate_observer_.updated());
|
||||
bitrate_observer_.Reset();
|
||||
clock_.AdvanceTimeMilliseconds(1000);
|
||||
// Inserting packets for 5 seconds to get a valid estimate.
|
||||
for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) {
|
||||
// NOTE!!! If the following line is moved under the if case then this test
|
||||
// wont work on windows realease bots.
|
||||
PacedPacketInfo pacing_info =
|
||||
i < kInitialProbingPackets ? kPacingInfo : PacedPacketInfo();
|
||||
|
||||
if (i == kNumInitialPackets) {
|
||||
EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate));
|
||||
EXPECT_EQ(0u, ssrcs.size());
|
||||
EXPECT_FALSE(bitrate_observer_.updated());
|
||||
bitrate_observer_.Reset();
|
||||
}
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, kMtu,
|
||||
pacing_info);
|
||||
clock_.AdvanceTimeMilliseconds(1000 / kFramerate);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
EXPECT_TRUE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate));
|
||||
ASSERT_EQ(1u, ssrcs.size());
|
||||
EXPECT_EQ(kDefaultSsrc, ssrcs.front());
|
||||
EXPECT_NEAR(expected_converge_bitrate, bitrate.bps(),
|
||||
kAcceptedBitrateErrorBps);
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
bitrate_observer_.Reset();
|
||||
EXPECT_EQ(bitrate_observer_.latest_bitrate(), bitrate.bps());
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::RateIncreaseReorderingTestHelper(
|
||||
uint32_t expected_bitrate_bps) {
|
||||
const int kFramerate = 50; // 50 fps to avoid rounding errors.
|
||||
const int kFrameIntervalMs = 1000 / kFramerate;
|
||||
const PacedPacketInfo kPacingInfo(0, 5, 5000);
|
||||
int64_t send_time_ms = 0;
|
||||
// Inserting packets for five seconds to get a valid estimate.
|
||||
for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) {
|
||||
// NOTE!!! If the following line is moved under the if case then this test
|
||||
// wont work on windows realease bots.
|
||||
PacedPacketInfo pacing_info =
|
||||
i < kInitialProbingPackets ? kPacingInfo : PacedPacketInfo();
|
||||
|
||||
// TODO(sprang): Remove this hack once the single stream estimator is gone,
|
||||
// as it doesn't do anything in Process().
|
||||
if (i == kNumInitialPackets) {
|
||||
// Process after we have enough frames to get a valid input rate estimate.
|
||||
|
||||
EXPECT_FALSE(bitrate_observer_.updated()); // No valid estimate.
|
||||
}
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, kMtu,
|
||||
pacing_info);
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_NEAR(expected_bitrate_bps, bitrate_observer_.latest_bitrate(),
|
||||
kAcceptedBitrateErrorBps);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs);
|
||||
send_time_ms += 2 * kFrameIntervalMs;
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, 1000);
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(),
|
||||
send_time_ms - kFrameIntervalMs, 1000);
|
||||
}
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_NEAR(expected_bitrate_bps, bitrate_observer_.latest_bitrate(),
|
||||
kAcceptedBitrateErrorBps);
|
||||
}
|
||||
|
||||
// Make sure we initially increase the bitrate as expected.
|
||||
void DelayBasedBweTest::RateIncreaseRtpTimestampsTestHelper(
|
||||
int expected_iterations) {
|
||||
// This threshold corresponds approximately to increasing linearly with
|
||||
// bitrate(i) = 1.04 * bitrate(i-1) + 1000
|
||||
// until bitrate(i) > 500000, with bitrate(1) ~= 30000.
|
||||
uint32_t bitrate_bps = 30000;
|
||||
int iterations = 0;
|
||||
AddDefaultStream();
|
||||
// Feed the estimator with a stream of packets and verify that it reaches
|
||||
// 500 kbps at the expected time.
|
||||
while (bitrate_bps < 5e5) {
|
||||
bool overuse = GenerateAndProcessFrame(kDefaultSsrc, bitrate_bps);
|
||||
if (overuse) {
|
||||
EXPECT_GT(bitrate_observer_.latest_bitrate(), bitrate_bps);
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
bitrate_observer_.Reset();
|
||||
} else if (bitrate_observer_.updated()) {
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
bitrate_observer_.Reset();
|
||||
}
|
||||
++iterations;
|
||||
}
|
||||
ASSERT_EQ(expected_iterations, iterations);
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::CapacityDropTestHelper(
|
||||
int number_of_streams,
|
||||
bool wrap_time_stamp,
|
||||
uint32_t expected_bitrate_drop_delta,
|
||||
int64_t receiver_clock_offset_change_ms) {
|
||||
const int kFramerate = 30;
|
||||
const int kStartBitrate = 900e3;
|
||||
const int kMinExpectedBitrate = 800e3;
|
||||
const int kMaxExpectedBitrate = 1100e3;
|
||||
const uint32_t kInitialCapacityBps = 1000e3;
|
||||
const uint32_t kReducedCapacityBps = 500e3;
|
||||
|
||||
int steady_state_time = 0;
|
||||
if (number_of_streams <= 1) {
|
||||
steady_state_time = 10;
|
||||
AddDefaultStream();
|
||||
} else {
|
||||
steady_state_time = 10 * number_of_streams;
|
||||
int bitrate_sum = 0;
|
||||
int kBitrateDenom = number_of_streams * (number_of_streams - 1);
|
||||
for (int i = 0; i < number_of_streams; i++) {
|
||||
// First stream gets half available bitrate, while the rest share the
|
||||
// remaining half i.e.: 1/2 = Sum[n/(N*(N-1))] for n=1..N-1 (rounded up)
|
||||
int bitrate = kStartBitrate / 2;
|
||||
if (i > 0) {
|
||||
bitrate = (kStartBitrate * i + kBitrateDenom / 2) / kBitrateDenom;
|
||||
}
|
||||
stream_generator_->AddStream(new test::RtpStream(kFramerate, bitrate));
|
||||
bitrate_sum += bitrate;
|
||||
}
|
||||
ASSERT_EQ(bitrate_sum, kStartBitrate);
|
||||
}
|
||||
|
||||
// Run in steady state to make the estimator converge.
|
||||
stream_generator_->set_capacity_bps(kInitialCapacityBps);
|
||||
uint32_t bitrate_bps = SteadyStateRun(
|
||||
kDefaultSsrc, steady_state_time * kFramerate, kStartBitrate,
|
||||
kMinExpectedBitrate, kMaxExpectedBitrate, kInitialCapacityBps);
|
||||
EXPECT_NEAR(kInitialCapacityBps, bitrate_bps, 180000u);
|
||||
bitrate_observer_.Reset();
|
||||
|
||||
// Add an offset to make sure the BWE can handle it.
|
||||
arrival_time_offset_ms_ += receiver_clock_offset_change_ms;
|
||||
|
||||
// Reduce the capacity and verify the decrease time.
|
||||
stream_generator_->set_capacity_bps(kReducedCapacityBps);
|
||||
int64_t overuse_start_time = clock_.TimeInMilliseconds();
|
||||
int64_t bitrate_drop_time = -1;
|
||||
for (int i = 0; i < 100 * number_of_streams; ++i) {
|
||||
GenerateAndProcessFrame(kDefaultSsrc, bitrate_bps);
|
||||
if (bitrate_drop_time == -1 &&
|
||||
bitrate_observer_.latest_bitrate() <= kReducedCapacityBps) {
|
||||
bitrate_drop_time = clock_.TimeInMilliseconds();
|
||||
}
|
||||
if (bitrate_observer_.updated())
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
}
|
||||
|
||||
EXPECT_NEAR(expected_bitrate_drop_delta,
|
||||
bitrate_drop_time - overuse_start_time, 33);
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::TestTimestampGroupingTestHelper() {
|
||||
const int kFramerate = 50; // 50 fps to avoid rounding errors.
|
||||
const int kFrameIntervalMs = 1000 / kFramerate;
|
||||
int64_t send_time_ms = 0;
|
||||
// Initial set of frames to increase the bitrate. 6 seconds to have enough
|
||||
// time for the first estimate to be generated and for Process() to be called.
|
||||
for (int i = 0; i <= 6 * kFramerate; ++i) {
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, 1000);
|
||||
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_GE(bitrate_observer_.latest_bitrate(), 400000u);
|
||||
|
||||
// Insert batches of frames which were sent very close in time. Also simulate
|
||||
// capacity over-use to see that we back off correctly.
|
||||
const int kTimestampGroupLength = 15;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
for (int j = 0; j < kTimestampGroupLength; ++j) {
|
||||
// Insert `kTimestampGroupLength` frames with just 1 timestamp ticks in
|
||||
// between. Should be treated as part of the same group by the estimator.
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, 100);
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs / kTimestampGroupLength);
|
||||
send_time_ms += 1;
|
||||
}
|
||||
// Increase time until next batch to simulate over-use.
|
||||
clock_.AdvanceTimeMilliseconds(10);
|
||||
send_time_ms += kFrameIntervalMs - kTimestampGroupLength;
|
||||
}
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
// Should have reduced the estimate.
|
||||
EXPECT_LT(bitrate_observer_.latest_bitrate(), 400000u);
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::TestWrappingHelper(int silence_time_s) {
|
||||
const int kFramerate = 100;
|
||||
const int kFrameIntervalMs = 1000 / kFramerate;
|
||||
int64_t send_time_ms = 0;
|
||||
|
||||
for (size_t i = 0; i < 3000; ++i) {
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, 1000);
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
DataRate bitrate_before = DataRate::Zero();
|
||||
std::vector<uint32_t> ssrcs;
|
||||
bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_before);
|
||||
|
||||
clock_.AdvanceTimeMilliseconds(silence_time_s * 1000);
|
||||
send_time_ms += silence_time_s * 1000;
|
||||
|
||||
for (size_t i = 0; i < 24; ++i) {
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, 1000);
|
||||
clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
DataRate bitrate_after = DataRate::Zero();
|
||||
bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_after);
|
||||
EXPECT_LT(bitrate_after, bitrate_before);
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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_DELAY_BASED_BWE_UNITTEST_HELPER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_UNITTEST_HELPER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "api/transport/field_trial_based_config.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h"
|
||||
#include "modules/congestion_controller/goog_cc/delay_based_bwe.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "test/field_trial.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
class TestBitrateObserver {
|
||||
public:
|
||||
TestBitrateObserver() : updated_(false), latest_bitrate_(0) {}
|
||||
~TestBitrateObserver() {}
|
||||
|
||||
void OnReceiveBitrateChanged(uint32_t bitrate);
|
||||
|
||||
void Reset() { updated_ = false; }
|
||||
|
||||
bool updated() const { return updated_; }
|
||||
|
||||
uint32_t latest_bitrate() const { return latest_bitrate_; }
|
||||
|
||||
private:
|
||||
bool updated_;
|
||||
uint32_t latest_bitrate_;
|
||||
};
|
||||
|
||||
class RtpStream {
|
||||
public:
|
||||
enum { kSendSideOffsetUs = 1000000 };
|
||||
|
||||
RtpStream(int fps, int bitrate_bps);
|
||||
|
||||
RtpStream(const RtpStream&) = delete;
|
||||
RtpStream& operator=(const RtpStream&) = delete;
|
||||
|
||||
// Generates a new frame for this stream. If called too soon after the
|
||||
// previous frame, no frame will be generated. The frame is split into
|
||||
// packets.
|
||||
int64_t GenerateFrame(int64_t time_now_us,
|
||||
int64_t* next_sequence_number,
|
||||
std::vector<PacketResult>* packets);
|
||||
|
||||
// The send-side time when the next frame can be generated.
|
||||
int64_t next_rtp_time() const;
|
||||
|
||||
void set_bitrate_bps(int bitrate_bps);
|
||||
|
||||
int bitrate_bps() const;
|
||||
|
||||
static bool Compare(const std::unique_ptr<RtpStream>& lhs,
|
||||
const std::unique_ptr<RtpStream>& rhs);
|
||||
|
||||
private:
|
||||
int fps_;
|
||||
int bitrate_bps_;
|
||||
int64_t next_rtp_time_;
|
||||
};
|
||||
|
||||
class StreamGenerator {
|
||||
public:
|
||||
StreamGenerator(int capacity, int64_t time_now);
|
||||
~StreamGenerator();
|
||||
|
||||
StreamGenerator(const StreamGenerator&) = delete;
|
||||
StreamGenerator& operator=(const StreamGenerator&) = delete;
|
||||
|
||||
// Add a new stream.
|
||||
void AddStream(RtpStream* stream);
|
||||
|
||||
// Set the link capacity.
|
||||
void set_capacity_bps(int capacity_bps);
|
||||
|
||||
// Divides `bitrate_bps` among all streams. The allocated bitrate per stream
|
||||
// is decided by the initial allocation ratios.
|
||||
void SetBitrateBps(int bitrate_bps);
|
||||
|
||||
// Set the RTP timestamp offset for the stream identified by `ssrc`.
|
||||
void set_rtp_timestamp_offset(uint32_t ssrc, uint32_t offset);
|
||||
|
||||
// TODO(holmer): Break out the channel simulation part from this class to make
|
||||
// it possible to simulate different types of channels.
|
||||
int64_t GenerateFrame(int64_t time_now_us,
|
||||
int64_t* next_sequence_number,
|
||||
std::vector<PacketResult>* packets);
|
||||
|
||||
private:
|
||||
// Capacity of the simulated channel in bits per second.
|
||||
int capacity_;
|
||||
// The time when the last packet arrived.
|
||||
int64_t prev_arrival_time_us_;
|
||||
// All streams being transmitted on this simulated channel.
|
||||
std::vector<std::unique_ptr<RtpStream>> streams_;
|
||||
};
|
||||
} // namespace test
|
||||
|
||||
class DelayBasedBweTest : public ::testing::Test {
|
||||
public:
|
||||
DelayBasedBweTest();
|
||||
~DelayBasedBweTest() override;
|
||||
|
||||
protected:
|
||||
void AddDefaultStream();
|
||||
|
||||
// Helpers to insert a single packet into the delay-based BWE.
|
||||
void IncomingFeedback(int64_t arrival_time_ms,
|
||||
int64_t send_time_ms,
|
||||
size_t payload_size);
|
||||
void IncomingFeedback(int64_t arrival_time_ms,
|
||||
int64_t send_time_ms,
|
||||
size_t payload_size,
|
||||
const PacedPacketInfo& pacing_info);
|
||||
void IncomingFeedback(Timestamp receive_time,
|
||||
Timestamp send_time,
|
||||
size_t payload_size,
|
||||
const PacedPacketInfo& pacing_info);
|
||||
|
||||
// Generates a frame of packets belonging to a stream at a given bitrate and
|
||||
// with a given ssrc. The stream is pushed through a very simple simulated
|
||||
// network, and is then given to the receive-side bandwidth estimator.
|
||||
// Returns true if an over-use was seen, false otherwise.
|
||||
// The StreamGenerator::updated() should be used to check for any changes in
|
||||
// target bitrate after the call to this function.
|
||||
bool GenerateAndProcessFrame(uint32_t ssrc, uint32_t bitrate_bps);
|
||||
|
||||
// Run the bandwidth estimator with a stream of `number_of_frames` frames, or
|
||||
// until it reaches `target_bitrate`.
|
||||
// Can for instance be used to run the estimator for some time to get it
|
||||
// into a steady state.
|
||||
uint32_t SteadyStateRun(uint32_t ssrc,
|
||||
int number_of_frames,
|
||||
uint32_t start_bitrate,
|
||||
uint32_t min_bitrate,
|
||||
uint32_t max_bitrate,
|
||||
uint32_t target_bitrate);
|
||||
|
||||
void TestTimestampGroupingTestHelper();
|
||||
|
||||
void TestWrappingHelper(int silence_time_s);
|
||||
|
||||
void InitialBehaviorTestHelper(uint32_t expected_converge_bitrate);
|
||||
void RateIncreaseReorderingTestHelper(uint32_t expected_bitrate);
|
||||
void RateIncreaseRtpTimestampsTestHelper(int expected_iterations);
|
||||
void CapacityDropTestHelper(int number_of_streams,
|
||||
bool wrap_time_stamp,
|
||||
uint32_t expected_bitrate_drop_delta,
|
||||
int64_t receiver_clock_offset_change_ms);
|
||||
|
||||
static const uint32_t kDefaultSsrc;
|
||||
FieldTrialBasedConfig field_trial_config_;
|
||||
|
||||
std::unique_ptr<test::ScopedFieldTrials>
|
||||
field_trial; // Must be initialized first.
|
||||
SimulatedClock clock_; // Time at the receiver.
|
||||
test::TestBitrateObserver bitrate_observer_;
|
||||
std::unique_ptr<AcknowledgedBitrateEstimatorInterface>
|
||||
acknowledged_bitrate_estimator_;
|
||||
const std::unique_ptr<ProbeBitrateEstimator> probe_bitrate_estimator_;
|
||||
std::unique_ptr<DelayBasedBwe> bitrate_estimator_;
|
||||
std::unique_ptr<test::StreamGenerator> stream_generator_;
|
||||
int64_t arrival_time_offset_ms_;
|
||||
int64_t next_sequence_number_;
|
||||
bool first_update_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_UNITTEST_HELPER_H_
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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_DELAY_INCREASE_DETECTOR_INTERFACE_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_INCREASE_DETECTOR_INTERFACE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "api/network_state_predictor.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class DelayIncreaseDetectorInterface {
|
||||
public:
|
||||
DelayIncreaseDetectorInterface() {}
|
||||
virtual ~DelayIncreaseDetectorInterface() {}
|
||||
|
||||
DelayIncreaseDetectorInterface(const DelayIncreaseDetectorInterface&) =
|
||||
delete;
|
||||
DelayIncreaseDetectorInterface& operator=(
|
||||
const DelayIncreaseDetectorInterface&) = delete;
|
||||
|
||||
// Update the detector with a new sample. The deltas should represent deltas
|
||||
// between timestamp groups as defined by the InterArrival class.
|
||||
virtual void Update(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t send_time_ms,
|
||||
int64_t arrival_time_ms,
|
||||
size_t packet_size,
|
||||
bool calculated_deltas) = 0;
|
||||
|
||||
virtual BandwidthUsage State() const = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_INCREASE_DETECTOR_INTERFACE_H_
|
||||
|
|
@ -0,0 +1,747 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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/goog_cc_network_control.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "api/transport/network_control.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 "logging/rtc_event_log/events/rtc_event_remote_estimate.h"
|
||||
#include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h"
|
||||
#include "modules/congestion_controller/goog_cc/alr_detector.h"
|
||||
#include "modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h"
|
||||
#include "modules/congestion_controller/goog_cc/delay_based_bwe.h"
|
||||
#include "modules/congestion_controller/goog_cc/loss_based_bwe_v2.h"
|
||||
#include "modules/congestion_controller/goog_cc/probe_bitrate_estimator.h"
|
||||
#include "modules/congestion_controller/goog_cc/probe_controller.h"
|
||||
#include "modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/experiments/rate_control_settings.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
// From RTCPSender video report interval.
|
||||
constexpr TimeDelta kLossUpdateInterval = TimeDelta::Millis(1000);
|
||||
|
||||
// Pacing-rate relative to our target send rate.
|
||||
// Multiplicative factor that is applied to the target bitrate to calculate
|
||||
// the number of bytes that can be transmitted per interval.
|
||||
// Increasing this factor will result in lower delays in cases of bitrate
|
||||
// overshoots from the encoder.
|
||||
constexpr float kDefaultPaceMultiplier = 2.5f;
|
||||
|
||||
// If the probe result is far below the current throughput estimate
|
||||
// it's unlikely that the probe is accurate, so we don't want to drop too far.
|
||||
// However, if we actually are overusing, we want to drop to something slightly
|
||||
// below the current throughput estimate to drain the network queues.
|
||||
constexpr double kProbeDropThroughputFraction = 0.85;
|
||||
|
||||
bool IsEnabled(const FieldTrialsView* config, absl::string_view key) {
|
||||
return absl::StartsWith(config->Lookup(key), "Enabled");
|
||||
}
|
||||
|
||||
bool IsNotDisabled(const FieldTrialsView* config, absl::string_view key) {
|
||||
return !absl::StartsWith(config->Lookup(key), "Disabled");
|
||||
}
|
||||
|
||||
BandwidthLimitedCause GetBandwidthLimitedCause(LossBasedState loss_based_state,
|
||||
bool is_rtt_above_limit,
|
||||
BandwidthUsage bandwidth_usage) {
|
||||
if (bandwidth_usage == BandwidthUsage::kBwOverusing ||
|
||||
bandwidth_usage == BandwidthUsage::kBwUnderusing) {
|
||||
return BandwidthLimitedCause::kDelayBasedLimitedDelayIncreased;
|
||||
} else if (is_rtt_above_limit) {
|
||||
return BandwidthLimitedCause::kRttBasedBackOffHighRtt;
|
||||
}
|
||||
switch (loss_based_state) {
|
||||
case LossBasedState::kDecreasing:
|
||||
// Probes may not be sent in this state.
|
||||
return BandwidthLimitedCause::kLossLimitedBwe;
|
||||
case webrtc::LossBasedState::kIncreaseUsingPadding:
|
||||
// Probes may not be sent in this state.
|
||||
return BandwidthLimitedCause::kLossLimitedBwe;
|
||||
case LossBasedState::kIncreasing:
|
||||
// Probes may be sent in this state.
|
||||
return BandwidthLimitedCause::kLossLimitedBweIncreasing;
|
||||
case LossBasedState::kDelayBasedEstimate:
|
||||
return BandwidthLimitedCause::kDelayBasedLimited;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GoogCcNetworkController::GoogCcNetworkController(NetworkControllerConfig config,
|
||||
GoogCcConfig goog_cc_config)
|
||||
: key_value_config_(config.key_value_config ? config.key_value_config
|
||||
: &trial_based_config_),
|
||||
event_log_(config.event_log),
|
||||
packet_feedback_only_(goog_cc_config.feedback_only),
|
||||
safe_reset_on_route_change_("Enabled"),
|
||||
safe_reset_acknowledged_rate_("ack"),
|
||||
use_min_allocatable_as_lower_bound_(
|
||||
IsNotDisabled(key_value_config_, "WebRTC-Bwe-MinAllocAsLowerBound")),
|
||||
ignore_probes_lower_than_network_estimate_(IsNotDisabled(
|
||||
key_value_config_,
|
||||
"WebRTC-Bwe-IgnoreProbesLowerThanNetworkStateEstimate")),
|
||||
limit_probes_lower_than_throughput_estimate_(
|
||||
IsNotDisabled(key_value_config_,
|
||||
"WebRTC-Bwe-LimitProbesLowerThanThroughputEstimate")),
|
||||
rate_control_settings_(
|
||||
RateControlSettings::ParseFromKeyValueConfig(key_value_config_)),
|
||||
pace_at_max_of_bwe_and_lower_link_capacity_(
|
||||
IsEnabled(key_value_config_,
|
||||
"WebRTC-Bwe-PaceAtMaxOfBweAndLowerLinkCapacity")),
|
||||
probe_controller_(
|
||||
new ProbeController(key_value_config_, config.event_log)),
|
||||
congestion_window_pushback_controller_(
|
||||
rate_control_settings_.UseCongestionWindowPushback()
|
||||
? std::make_unique<CongestionWindowPushbackController>(
|
||||
key_value_config_)
|
||||
: nullptr),
|
||||
bandwidth_estimation_(
|
||||
std::make_unique<SendSideBandwidthEstimation>(key_value_config_,
|
||||
event_log_)),
|
||||
alr_detector_(
|
||||
std::make_unique<AlrDetector>(key_value_config_, config.event_log)),
|
||||
probe_bitrate_estimator_(new ProbeBitrateEstimator(config.event_log)),
|
||||
network_estimator_(std::move(goog_cc_config.network_state_estimator)),
|
||||
network_state_predictor_(
|
||||
std::move(goog_cc_config.network_state_predictor)),
|
||||
delay_based_bwe_(new DelayBasedBwe(key_value_config_,
|
||||
event_log_,
|
||||
network_state_predictor_.get())),
|
||||
acknowledged_bitrate_estimator_(
|
||||
AcknowledgedBitrateEstimatorInterface::Create(key_value_config_)),
|
||||
initial_config_(config),
|
||||
last_loss_based_target_rate_(*config.constraints.starting_rate),
|
||||
last_pushback_target_rate_(last_loss_based_target_rate_),
|
||||
last_stable_target_rate_(last_loss_based_target_rate_),
|
||||
last_loss_base_state_(LossBasedState::kDelayBasedEstimate),
|
||||
pacing_factor_(config.stream_based_config.pacing_factor.value_or(
|
||||
kDefaultPaceMultiplier)),
|
||||
min_total_allocated_bitrate_(
|
||||
config.stream_based_config.min_total_allocated_bitrate.value_or(
|
||||
DataRate::Zero())),
|
||||
max_padding_rate_(config.stream_based_config.max_padding_rate.value_or(
|
||||
DataRate::Zero())) {
|
||||
RTC_DCHECK(config.constraints.at_time.IsFinite());
|
||||
ParseFieldTrial(
|
||||
{&safe_reset_on_route_change_, &safe_reset_acknowledged_rate_},
|
||||
key_value_config_->Lookup("WebRTC-Bwe-SafeResetOnRouteChange"));
|
||||
if (delay_based_bwe_)
|
||||
delay_based_bwe_->SetMinBitrate(kCongestionControllerMinBitrate);
|
||||
}
|
||||
|
||||
GoogCcNetworkController::~GoogCcNetworkController() {}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnNetworkAvailability(
|
||||
NetworkAvailability msg) {
|
||||
NetworkControlUpdate update;
|
||||
update.probe_cluster_configs = probe_controller_->OnNetworkAvailability(msg);
|
||||
return update;
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnNetworkRouteChange(
|
||||
NetworkRouteChange msg) {
|
||||
if (safe_reset_on_route_change_) {
|
||||
absl::optional<DataRate> estimated_bitrate;
|
||||
if (safe_reset_acknowledged_rate_) {
|
||||
estimated_bitrate = acknowledged_bitrate_estimator_->bitrate();
|
||||
if (!estimated_bitrate)
|
||||
estimated_bitrate = acknowledged_bitrate_estimator_->PeekRate();
|
||||
} else {
|
||||
estimated_bitrate = bandwidth_estimation_->target_rate();
|
||||
}
|
||||
if (estimated_bitrate) {
|
||||
if (msg.constraints.starting_rate) {
|
||||
msg.constraints.starting_rate =
|
||||
std::min(*msg.constraints.starting_rate, *estimated_bitrate);
|
||||
} else {
|
||||
msg.constraints.starting_rate = estimated_bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
acknowledged_bitrate_estimator_ =
|
||||
AcknowledgedBitrateEstimatorInterface::Create(key_value_config_);
|
||||
probe_bitrate_estimator_.reset(new ProbeBitrateEstimator(event_log_));
|
||||
if (network_estimator_)
|
||||
network_estimator_->OnRouteChange(msg);
|
||||
delay_based_bwe_.reset(new DelayBasedBwe(key_value_config_, event_log_,
|
||||
network_state_predictor_.get()));
|
||||
bandwidth_estimation_->OnRouteChange();
|
||||
probe_controller_->Reset(msg.at_time);
|
||||
NetworkControlUpdate update;
|
||||
update.probe_cluster_configs = ResetConstraints(msg.constraints);
|
||||
MaybeTriggerOnNetworkChanged(&update, msg.at_time);
|
||||
return update;
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnProcessInterval(
|
||||
ProcessInterval msg) {
|
||||
NetworkControlUpdate update;
|
||||
if (initial_config_) {
|
||||
update.probe_cluster_configs =
|
||||
ResetConstraints(initial_config_->constraints);
|
||||
update.pacer_config = GetPacingRates(msg.at_time);
|
||||
|
||||
if (initial_config_->stream_based_config.requests_alr_probing) {
|
||||
probe_controller_->EnablePeriodicAlrProbing(
|
||||
*initial_config_->stream_based_config.requests_alr_probing);
|
||||
}
|
||||
absl::optional<DataRate> total_bitrate =
|
||||
initial_config_->stream_based_config.max_total_allocated_bitrate;
|
||||
if (total_bitrate) {
|
||||
auto probes = probe_controller_->OnMaxTotalAllocatedBitrate(
|
||||
*total_bitrate, msg.at_time);
|
||||
update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
|
||||
probes.begin(), probes.end());
|
||||
}
|
||||
initial_config_.reset();
|
||||
}
|
||||
if (congestion_window_pushback_controller_ && msg.pacer_queue) {
|
||||
congestion_window_pushback_controller_->UpdatePacingQueue(
|
||||
msg.pacer_queue->bytes());
|
||||
}
|
||||
bandwidth_estimation_->UpdateEstimate(msg.at_time);
|
||||
absl::optional<int64_t> start_time_ms =
|
||||
alr_detector_->GetApplicationLimitedRegionStartTime();
|
||||
probe_controller_->SetAlrStartTimeMs(start_time_ms);
|
||||
|
||||
auto probes = probe_controller_->Process(msg.at_time);
|
||||
update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
|
||||
probes.begin(), probes.end());
|
||||
|
||||
if (rate_control_settings_.UseCongestionWindow() &&
|
||||
!feedback_max_rtts_.empty()) {
|
||||
UpdateCongestionWindowSize();
|
||||
}
|
||||
if (congestion_window_pushback_controller_ && current_data_window_) {
|
||||
congestion_window_pushback_controller_->SetDataWindow(
|
||||
*current_data_window_);
|
||||
} else {
|
||||
update.congestion_window = current_data_window_;
|
||||
}
|
||||
MaybeTriggerOnNetworkChanged(&update, msg.at_time);
|
||||
return update;
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnRemoteBitrateReport(
|
||||
RemoteBitrateReport msg) {
|
||||
if (packet_feedback_only_) {
|
||||
RTC_LOG(LS_ERROR) << "Received REMB for packet feedback only GoogCC";
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
bandwidth_estimation_->UpdateReceiverEstimate(msg.receive_time,
|
||||
msg.bandwidth);
|
||||
BWE_TEST_LOGGING_PLOT(1, "REMB_kbps", msg.receive_time.ms(),
|
||||
msg.bandwidth.bps() / 1000);
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnRoundTripTimeUpdate(
|
||||
RoundTripTimeUpdate msg) {
|
||||
if (packet_feedback_only_ || msg.smoothed)
|
||||
return NetworkControlUpdate();
|
||||
RTC_DCHECK(!msg.round_trip_time.IsZero());
|
||||
if (delay_based_bwe_)
|
||||
delay_based_bwe_->OnRttUpdate(msg.round_trip_time);
|
||||
bandwidth_estimation_->UpdateRtt(msg.round_trip_time, msg.receive_time);
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnSentPacket(
|
||||
SentPacket sent_packet) {
|
||||
alr_detector_->OnBytesSent(sent_packet.size.bytes(),
|
||||
sent_packet.send_time.ms());
|
||||
acknowledged_bitrate_estimator_->SetAlr(
|
||||
alr_detector_->GetApplicationLimitedRegionStartTime().has_value());
|
||||
|
||||
if (!first_packet_sent_) {
|
||||
first_packet_sent_ = true;
|
||||
// Initialize feedback time to send time to allow estimation of RTT until
|
||||
// first feedback is received.
|
||||
bandwidth_estimation_->UpdatePropagationRtt(sent_packet.send_time,
|
||||
TimeDelta::Zero());
|
||||
}
|
||||
bandwidth_estimation_->OnSentPacket(sent_packet);
|
||||
|
||||
if (congestion_window_pushback_controller_) {
|
||||
congestion_window_pushback_controller_->UpdateOutstandingData(
|
||||
sent_packet.data_in_flight.bytes());
|
||||
NetworkControlUpdate update;
|
||||
MaybeTriggerOnNetworkChanged(&update, sent_packet.send_time);
|
||||
return update;
|
||||
} else {
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnReceivedPacket(
|
||||
ReceivedPacket received_packet) {
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnStreamsConfig(
|
||||
StreamsConfig msg) {
|
||||
NetworkControlUpdate update;
|
||||
if (msg.requests_alr_probing) {
|
||||
probe_controller_->EnablePeriodicAlrProbing(*msg.requests_alr_probing);
|
||||
}
|
||||
if (msg.max_total_allocated_bitrate) {
|
||||
update.probe_cluster_configs =
|
||||
probe_controller_->OnMaxTotalAllocatedBitrate(
|
||||
*msg.max_total_allocated_bitrate, msg.at_time);
|
||||
}
|
||||
|
||||
bool pacing_changed = false;
|
||||
if (msg.pacing_factor && *msg.pacing_factor != pacing_factor_) {
|
||||
pacing_factor_ = *msg.pacing_factor;
|
||||
pacing_changed = true;
|
||||
}
|
||||
if (msg.min_total_allocated_bitrate &&
|
||||
*msg.min_total_allocated_bitrate != min_total_allocated_bitrate_) {
|
||||
min_total_allocated_bitrate_ = *msg.min_total_allocated_bitrate;
|
||||
pacing_changed = true;
|
||||
|
||||
if (use_min_allocatable_as_lower_bound_) {
|
||||
ClampConstraints();
|
||||
delay_based_bwe_->SetMinBitrate(min_data_rate_);
|
||||
bandwidth_estimation_->SetMinMaxBitrate(min_data_rate_, max_data_rate_);
|
||||
}
|
||||
}
|
||||
if (msg.max_padding_rate && *msg.max_padding_rate != max_padding_rate_) {
|
||||
max_padding_rate_ = *msg.max_padding_rate;
|
||||
pacing_changed = true;
|
||||
}
|
||||
|
||||
if (pacing_changed)
|
||||
update.pacer_config = GetPacingRates(msg.at_time);
|
||||
return update;
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnTargetRateConstraints(
|
||||
TargetRateConstraints constraints) {
|
||||
NetworkControlUpdate update;
|
||||
update.probe_cluster_configs = ResetConstraints(constraints);
|
||||
MaybeTriggerOnNetworkChanged(&update, constraints.at_time);
|
||||
return update;
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::ClampConstraints() {
|
||||
// TODO(holmer): We should make sure the default bitrates are set to 10 kbps,
|
||||
// and that we don't try to set the min bitrate to 0 from any applications.
|
||||
// The congestion controller should allow a min bitrate of 0.
|
||||
min_data_rate_ = std::max(min_target_rate_, kCongestionControllerMinBitrate);
|
||||
if (use_min_allocatable_as_lower_bound_) {
|
||||
min_data_rate_ = std::max(min_data_rate_, min_total_allocated_bitrate_);
|
||||
}
|
||||
if (max_data_rate_ < min_data_rate_) {
|
||||
RTC_LOG(LS_WARNING) << "max bitrate smaller than min bitrate";
|
||||
max_data_rate_ = min_data_rate_;
|
||||
}
|
||||
if (starting_rate_ && starting_rate_ < min_data_rate_) {
|
||||
RTC_LOG(LS_WARNING) << "start bitrate smaller than min bitrate";
|
||||
starting_rate_ = min_data_rate_;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> GoogCcNetworkController::ResetConstraints(
|
||||
TargetRateConstraints new_constraints) {
|
||||
min_target_rate_ = new_constraints.min_data_rate.value_or(DataRate::Zero());
|
||||
max_data_rate_ =
|
||||
new_constraints.max_data_rate.value_or(DataRate::PlusInfinity());
|
||||
starting_rate_ = new_constraints.starting_rate;
|
||||
ClampConstraints();
|
||||
|
||||
bandwidth_estimation_->SetBitrates(starting_rate_, min_data_rate_,
|
||||
max_data_rate_, new_constraints.at_time);
|
||||
|
||||
if (starting_rate_)
|
||||
delay_based_bwe_->SetStartBitrate(*starting_rate_);
|
||||
delay_based_bwe_->SetMinBitrate(min_data_rate_);
|
||||
|
||||
return probe_controller_->SetBitrates(
|
||||
min_data_rate_, starting_rate_.value_or(DataRate::Zero()), max_data_rate_,
|
||||
new_constraints.at_time);
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnTransportLossReport(
|
||||
TransportLossReport msg) {
|
||||
if (packet_feedback_only_)
|
||||
return NetworkControlUpdate();
|
||||
int64_t total_packets_delta =
|
||||
msg.packets_received_delta + msg.packets_lost_delta;
|
||||
bandwidth_estimation_->UpdatePacketsLost(
|
||||
msg.packets_lost_delta, total_packets_delta, msg.receive_time);
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::UpdateCongestionWindowSize() {
|
||||
TimeDelta min_feedback_max_rtt = TimeDelta::Millis(
|
||||
*std::min_element(feedback_max_rtts_.begin(), feedback_max_rtts_.end()));
|
||||
|
||||
const DataSize kMinCwnd = DataSize::Bytes(2 * 1500);
|
||||
TimeDelta time_window =
|
||||
min_feedback_max_rtt +
|
||||
TimeDelta::Millis(
|
||||
rate_control_settings_.GetCongestionWindowAdditionalTimeMs());
|
||||
|
||||
DataSize data_window = last_loss_based_target_rate_ * time_window;
|
||||
if (current_data_window_) {
|
||||
data_window =
|
||||
std::max(kMinCwnd, (data_window + current_data_window_.value()) / 2);
|
||||
} else {
|
||||
data_window = std::max(kMinCwnd, data_window);
|
||||
}
|
||||
current_data_window_ = data_window;
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback(
|
||||
TransportPacketsFeedback report) {
|
||||
if (report.packet_feedbacks.empty()) {
|
||||
// TODO(bugs.webrtc.org/10125): Design a better mechanism to safe-guard
|
||||
// against building very large network queues.
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
if (congestion_window_pushback_controller_) {
|
||||
congestion_window_pushback_controller_->UpdateOutstandingData(
|
||||
report.data_in_flight.bytes());
|
||||
}
|
||||
TimeDelta max_feedback_rtt = TimeDelta::MinusInfinity();
|
||||
TimeDelta min_propagation_rtt = TimeDelta::PlusInfinity();
|
||||
Timestamp max_recv_time = Timestamp::MinusInfinity();
|
||||
|
||||
std::vector<PacketResult> feedbacks = report.ReceivedWithSendInfo();
|
||||
for (const auto& feedback : feedbacks)
|
||||
max_recv_time = std::max(max_recv_time, feedback.receive_time);
|
||||
|
||||
for (const auto& feedback : feedbacks) {
|
||||
TimeDelta feedback_rtt =
|
||||
report.feedback_time - feedback.sent_packet.send_time;
|
||||
TimeDelta min_pending_time = max_recv_time - feedback.receive_time;
|
||||
TimeDelta propagation_rtt = feedback_rtt - min_pending_time;
|
||||
max_feedback_rtt = std::max(max_feedback_rtt, feedback_rtt);
|
||||
min_propagation_rtt = std::min(min_propagation_rtt, propagation_rtt);
|
||||
}
|
||||
|
||||
if (max_feedback_rtt.IsFinite()) {
|
||||
feedback_max_rtts_.push_back(max_feedback_rtt.ms());
|
||||
const size_t kMaxFeedbackRttWindow = 32;
|
||||
if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow)
|
||||
feedback_max_rtts_.pop_front();
|
||||
// TODO(srte): Use time since last unacknowledged packet.
|
||||
bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time,
|
||||
min_propagation_rtt);
|
||||
}
|
||||
if (packet_feedback_only_) {
|
||||
if (!feedback_max_rtts_.empty()) {
|
||||
int64_t sum_rtt_ms =
|
||||
std::accumulate(feedback_max_rtts_.begin(), feedback_max_rtts_.end(),
|
||||
static_cast<int64_t>(0));
|
||||
int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size();
|
||||
if (delay_based_bwe_)
|
||||
delay_based_bwe_->OnRttUpdate(TimeDelta::Millis(mean_rtt_ms));
|
||||
}
|
||||
|
||||
TimeDelta feedback_min_rtt = TimeDelta::PlusInfinity();
|
||||
for (const auto& packet_feedback : feedbacks) {
|
||||
TimeDelta pending_time = max_recv_time - packet_feedback.receive_time;
|
||||
TimeDelta rtt = report.feedback_time -
|
||||
packet_feedback.sent_packet.send_time - pending_time;
|
||||
// Value used for predicting NACK round trip time in FEC controller.
|
||||
feedback_min_rtt = std::min(rtt, feedback_min_rtt);
|
||||
}
|
||||
if (feedback_min_rtt.IsFinite()) {
|
||||
bandwidth_estimation_->UpdateRtt(feedback_min_rtt, report.feedback_time);
|
||||
}
|
||||
|
||||
expected_packets_since_last_loss_update_ +=
|
||||
report.PacketsWithFeedback().size();
|
||||
for (const auto& packet_feedback : report.PacketsWithFeedback()) {
|
||||
if (!packet_feedback.IsReceived())
|
||||
lost_packets_since_last_loss_update_ += 1;
|
||||
}
|
||||
if (report.feedback_time > next_loss_update_) {
|
||||
next_loss_update_ = report.feedback_time + kLossUpdateInterval;
|
||||
bandwidth_estimation_->UpdatePacketsLost(
|
||||
lost_packets_since_last_loss_update_,
|
||||
expected_packets_since_last_loss_update_, report.feedback_time);
|
||||
expected_packets_since_last_loss_update_ = 0;
|
||||
lost_packets_since_last_loss_update_ = 0;
|
||||
}
|
||||
}
|
||||
absl::optional<int64_t> alr_start_time =
|
||||
alr_detector_->GetApplicationLimitedRegionStartTime();
|
||||
|
||||
if (previously_in_alr_ && !alr_start_time.has_value()) {
|
||||
int64_t now_ms = report.feedback_time.ms();
|
||||
acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time);
|
||||
probe_controller_->SetAlrEndedTimeMs(now_ms);
|
||||
}
|
||||
previously_in_alr_ = alr_start_time.has_value();
|
||||
acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
report.SortedByReceiveTime());
|
||||
auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate();
|
||||
bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate,
|
||||
report.feedback_time);
|
||||
for (const auto& feedback : report.SortedByReceiveTime()) {
|
||||
if (feedback.sent_packet.pacing_info.probe_cluster_id !=
|
||||
PacedPacketInfo::kNotAProbe) {
|
||||
probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback);
|
||||
}
|
||||
}
|
||||
|
||||
if (network_estimator_) {
|
||||
network_estimator_->OnTransportPacketsFeedback(report);
|
||||
auto prev_estimate = estimate_;
|
||||
estimate_ = network_estimator_->GetCurrentEstimate();
|
||||
// TODO(srte): Make OnTransportPacketsFeedback signal whether the state
|
||||
// changed to avoid the need for this check.
|
||||
if (estimate_ && (!prev_estimate || estimate_->last_feed_time !=
|
||||
prev_estimate->last_feed_time)) {
|
||||
event_log_->Log(std::make_unique<RtcEventRemoteEstimate>(
|
||||
estimate_->link_capacity_lower, estimate_->link_capacity_upper));
|
||||
probe_controller_->SetNetworkStateEstimate(*estimate_);
|
||||
}
|
||||
}
|
||||
absl::optional<DataRate> probe_bitrate =
|
||||
probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate();
|
||||
if (ignore_probes_lower_than_network_estimate_ && probe_bitrate &&
|
||||
estimate_ && *probe_bitrate < delay_based_bwe_->last_estimate() &&
|
||||
*probe_bitrate < estimate_->link_capacity_lower) {
|
||||
probe_bitrate.reset();
|
||||
}
|
||||
if (limit_probes_lower_than_throughput_estimate_ && probe_bitrate &&
|
||||
acknowledged_bitrate) {
|
||||
// Limit the backoff to something slightly below the acknowledged
|
||||
// bitrate. ("Slightly below" because we want to drain the queues
|
||||
// if we are actually overusing.)
|
||||
// The acknowledged bitrate shouldn't normally be higher than the delay
|
||||
// based estimate, but it could happen e.g. due to packet bursts or
|
||||
// encoder overshoot. We use std::min to ensure that a probe result
|
||||
// below the current BWE never causes an increase.
|
||||
DataRate limit =
|
||||
std::min(delay_based_bwe_->last_estimate(),
|
||||
*acknowledged_bitrate * kProbeDropThroughputFraction);
|
||||
probe_bitrate = std::max(*probe_bitrate, limit);
|
||||
}
|
||||
|
||||
NetworkControlUpdate update;
|
||||
bool recovered_from_overuse = false;
|
||||
|
||||
DelayBasedBwe::Result result;
|
||||
result = delay_based_bwe_->IncomingPacketFeedbackVector(
|
||||
report, acknowledged_bitrate, probe_bitrate, estimate_,
|
||||
alr_start_time.has_value());
|
||||
|
||||
if (result.updated) {
|
||||
if (result.probe) {
|
||||
bandwidth_estimation_->SetSendBitrate(result.target_bitrate,
|
||||
report.feedback_time);
|
||||
}
|
||||
// Since SetSendBitrate now resets the delay-based estimate, we have to
|
||||
// call UpdateDelayBasedEstimate after SetSendBitrate.
|
||||
bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time,
|
||||
result.target_bitrate);
|
||||
}
|
||||
bandwidth_estimation_->UpdateLossBasedEstimator(
|
||||
report, result.delay_detector_state, probe_bitrate,
|
||||
alr_start_time.has_value());
|
||||
if (result.updated) {
|
||||
// Update the estimate in the ProbeController, in case we want to probe.
|
||||
MaybeTriggerOnNetworkChanged(&update, report.feedback_time);
|
||||
}
|
||||
|
||||
recovered_from_overuse = result.recovered_from_overuse;
|
||||
|
||||
if (recovered_from_overuse) {
|
||||
probe_controller_->SetAlrStartTimeMs(alr_start_time);
|
||||
auto probes = probe_controller_->RequestProbe(report.feedback_time);
|
||||
update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
|
||||
probes.begin(), probes.end());
|
||||
}
|
||||
|
||||
// No valid RTT could be because send-side BWE isn't used, in which case
|
||||
// we don't try to limit the outstanding packets.
|
||||
if (rate_control_settings_.UseCongestionWindow() &&
|
||||
max_feedback_rtt.IsFinite()) {
|
||||
UpdateCongestionWindowSize();
|
||||
}
|
||||
if (congestion_window_pushback_controller_ && current_data_window_) {
|
||||
congestion_window_pushback_controller_->SetDataWindow(
|
||||
*current_data_window_);
|
||||
} else {
|
||||
update.congestion_window = current_data_window_;
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::OnNetworkStateEstimate(
|
||||
NetworkStateEstimate msg) {
|
||||
estimate_ = msg;
|
||||
return NetworkControlUpdate();
|
||||
}
|
||||
|
||||
NetworkControlUpdate GoogCcNetworkController::GetNetworkState(
|
||||
Timestamp at_time) const {
|
||||
NetworkControlUpdate update;
|
||||
update.target_rate = TargetTransferRate();
|
||||
update.target_rate->network_estimate.at_time = at_time;
|
||||
update.target_rate->network_estimate.loss_rate_ratio =
|
||||
last_estimated_fraction_loss_.value_or(0) / 255.0;
|
||||
update.target_rate->network_estimate.round_trip_time =
|
||||
last_estimated_round_trip_time_;
|
||||
update.target_rate->network_estimate.bwe_period =
|
||||
delay_based_bwe_->GetExpectedBwePeriod();
|
||||
|
||||
update.target_rate->at_time = at_time;
|
||||
update.target_rate->target_rate = last_pushback_target_rate_;
|
||||
update.target_rate->stable_target_rate =
|
||||
bandwidth_estimation_->GetEstimatedLinkCapacity();
|
||||
update.pacer_config = GetPacingRates(at_time);
|
||||
update.congestion_window = current_data_window_;
|
||||
return update;
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::MaybeTriggerOnNetworkChanged(
|
||||
NetworkControlUpdate* update,
|
||||
Timestamp at_time) {
|
||||
uint8_t fraction_loss = bandwidth_estimation_->fraction_loss();
|
||||
TimeDelta round_trip_time = bandwidth_estimation_->round_trip_time();
|
||||
DataRate loss_based_target_rate = bandwidth_estimation_->target_rate();
|
||||
LossBasedState loss_based_state = bandwidth_estimation_->loss_based_state();
|
||||
DataRate pushback_target_rate = loss_based_target_rate;
|
||||
|
||||
BWE_TEST_LOGGING_PLOT(1, "fraction_loss_%", at_time.ms(),
|
||||
(fraction_loss * 100) / 256);
|
||||
BWE_TEST_LOGGING_PLOT(1, "rtt_ms", at_time.ms(), round_trip_time.ms());
|
||||
BWE_TEST_LOGGING_PLOT(1, "Target_bitrate_kbps", at_time.ms(),
|
||||
loss_based_target_rate.kbps());
|
||||
|
||||
double cwnd_reduce_ratio = 0.0;
|
||||
if (congestion_window_pushback_controller_) {
|
||||
int64_t pushback_rate =
|
||||
congestion_window_pushback_controller_->UpdateTargetBitrate(
|
||||
loss_based_target_rate.bps());
|
||||
pushback_rate = std::max<int64_t>(bandwidth_estimation_->GetMinBitrate(),
|
||||
pushback_rate);
|
||||
pushback_target_rate = DataRate::BitsPerSec(pushback_rate);
|
||||
if (rate_control_settings_.UseCongestionWindowDropFrameOnly()) {
|
||||
cwnd_reduce_ratio = static_cast<double>(loss_based_target_rate.bps() -
|
||||
pushback_target_rate.bps()) /
|
||||
loss_based_target_rate.bps();
|
||||
}
|
||||
}
|
||||
DataRate stable_target_rate =
|
||||
bandwidth_estimation_->GetEstimatedLinkCapacity();
|
||||
stable_target_rate = std::min(stable_target_rate, pushback_target_rate);
|
||||
|
||||
if ((loss_based_target_rate != last_loss_based_target_rate_) ||
|
||||
(loss_based_state != last_loss_base_state_) ||
|
||||
(fraction_loss != last_estimated_fraction_loss_) ||
|
||||
(round_trip_time != last_estimated_round_trip_time_) ||
|
||||
(pushback_target_rate != last_pushback_target_rate_) ||
|
||||
(stable_target_rate != last_stable_target_rate_)) {
|
||||
last_loss_based_target_rate_ = loss_based_target_rate;
|
||||
last_pushback_target_rate_ = pushback_target_rate;
|
||||
last_estimated_fraction_loss_ = fraction_loss;
|
||||
last_estimated_round_trip_time_ = round_trip_time;
|
||||
last_stable_target_rate_ = stable_target_rate;
|
||||
last_loss_base_state_ = loss_based_state;
|
||||
|
||||
alr_detector_->SetEstimatedBitrate(loss_based_target_rate.bps());
|
||||
|
||||
TimeDelta bwe_period = delay_based_bwe_->GetExpectedBwePeriod();
|
||||
|
||||
TargetTransferRate target_rate_msg;
|
||||
target_rate_msg.at_time = at_time;
|
||||
if (rate_control_settings_.UseCongestionWindowDropFrameOnly()) {
|
||||
target_rate_msg.target_rate = loss_based_target_rate;
|
||||
target_rate_msg.cwnd_reduce_ratio = cwnd_reduce_ratio;
|
||||
} else {
|
||||
target_rate_msg.target_rate = pushback_target_rate;
|
||||
}
|
||||
target_rate_msg.stable_target_rate = stable_target_rate;
|
||||
target_rate_msg.network_estimate.at_time = at_time;
|
||||
target_rate_msg.network_estimate.round_trip_time = round_trip_time;
|
||||
target_rate_msg.network_estimate.loss_rate_ratio = fraction_loss / 255.0f;
|
||||
target_rate_msg.network_estimate.bwe_period = bwe_period;
|
||||
|
||||
update->target_rate = target_rate_msg;
|
||||
|
||||
auto probes = probe_controller_->SetEstimatedBitrate(
|
||||
loss_based_target_rate,
|
||||
GetBandwidthLimitedCause(bandwidth_estimation_->loss_based_state(),
|
||||
bandwidth_estimation_->IsRttAboveLimit(),
|
||||
delay_based_bwe_->last_state()),
|
||||
at_time);
|
||||
update->probe_cluster_configs.insert(update->probe_cluster_configs.end(),
|
||||
probes.begin(), probes.end());
|
||||
update->pacer_config = GetPacingRates(at_time);
|
||||
RTC_LOG(LS_VERBOSE) << "bwe " << at_time.ms() << " pushback_target_bps="
|
||||
<< last_pushback_target_rate_.bps()
|
||||
<< " estimate_bps=" << loss_based_target_rate.bps();
|
||||
}
|
||||
}
|
||||
|
||||
PacerConfig GoogCcNetworkController::GetPacingRates(Timestamp at_time) const {
|
||||
// Pacing rate is based on target rate before congestion window pushback,
|
||||
// because we don't want to build queues in the pacer when pushback occurs.
|
||||
DataRate pacing_rate = DataRate::Zero();
|
||||
if (pace_at_max_of_bwe_and_lower_link_capacity_ && estimate_ &&
|
||||
!bandwidth_estimation_->PaceAtLossBasedEstimate()) {
|
||||
pacing_rate =
|
||||
std::max({min_total_allocated_bitrate_, estimate_->link_capacity_lower,
|
||||
last_loss_based_target_rate_}) *
|
||||
pacing_factor_;
|
||||
} else {
|
||||
pacing_rate =
|
||||
std::max(min_total_allocated_bitrate_, last_loss_based_target_rate_) *
|
||||
pacing_factor_;
|
||||
}
|
||||
DataRate padding_rate =
|
||||
(last_loss_base_state_ == LossBasedState::kIncreaseUsingPadding)
|
||||
? std::max(max_padding_rate_, last_loss_based_target_rate_)
|
||||
: max_padding_rate_;
|
||||
padding_rate = std::min(padding_rate, last_pushback_target_rate_);
|
||||
PacerConfig msg;
|
||||
msg.at_time = at_time;
|
||||
msg.time_window = TimeDelta::Seconds(1);
|
||||
msg.data_window = pacing_rate * msg.time_window;
|
||||
msg.pad_window = padding_rate * msg.time_window;
|
||||
return msg;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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_GOOG_CC_NETWORK_CONTROL_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "api/rtc_event_log/rtc_event_log.h"
|
||||
#include "api/transport/field_trial_based_config.h"
|
||||
#include "api/transport/network_control.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/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h"
|
||||
#include "modules/congestion_controller/goog_cc/alr_detector.h"
|
||||
#include "modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h"
|
||||
#include "modules/congestion_controller/goog_cc/delay_based_bwe.h"
|
||||
#include "modules/congestion_controller/goog_cc/probe_bitrate_estimator.h"
|
||||
#include "modules/congestion_controller/goog_cc/probe_controller.h"
|
||||
#include "modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/experiments/rate_control_settings.h"
|
||||
|
||||
namespace webrtc {
|
||||
struct GoogCcConfig {
|
||||
std::unique_ptr<NetworkStateEstimator> network_state_estimator = nullptr;
|
||||
std::unique_ptr<NetworkStatePredictor> network_state_predictor = nullptr;
|
||||
bool feedback_only = false;
|
||||
};
|
||||
|
||||
class GoogCcNetworkController : public NetworkControllerInterface {
|
||||
public:
|
||||
GoogCcNetworkController(NetworkControllerConfig config,
|
||||
GoogCcConfig goog_cc_config);
|
||||
|
||||
GoogCcNetworkController() = delete;
|
||||
GoogCcNetworkController(const GoogCcNetworkController&) = delete;
|
||||
GoogCcNetworkController& operator=(const GoogCcNetworkController&) = delete;
|
||||
|
||||
~GoogCcNetworkController() override;
|
||||
|
||||
// NetworkControllerInterface
|
||||
NetworkControlUpdate OnNetworkAvailability(NetworkAvailability msg) override;
|
||||
NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange msg) override;
|
||||
NetworkControlUpdate OnProcessInterval(ProcessInterval msg) override;
|
||||
NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport msg) override;
|
||||
NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) override;
|
||||
NetworkControlUpdate OnSentPacket(SentPacket msg) override;
|
||||
NetworkControlUpdate OnReceivedPacket(ReceivedPacket msg) override;
|
||||
NetworkControlUpdate OnStreamsConfig(StreamsConfig msg) override;
|
||||
NetworkControlUpdate OnTargetRateConstraints(
|
||||
TargetRateConstraints msg) override;
|
||||
NetworkControlUpdate OnTransportLossReport(TransportLossReport msg) override;
|
||||
NetworkControlUpdate OnTransportPacketsFeedback(
|
||||
TransportPacketsFeedback msg) override;
|
||||
NetworkControlUpdate OnNetworkStateEstimate(
|
||||
NetworkStateEstimate msg) override;
|
||||
|
||||
NetworkControlUpdate GetNetworkState(Timestamp at_time) const;
|
||||
|
||||
private:
|
||||
friend class GoogCcStatePrinter;
|
||||
std::vector<ProbeClusterConfig> ResetConstraints(
|
||||
TargetRateConstraints new_constraints);
|
||||
void ClampConstraints();
|
||||
void MaybeTriggerOnNetworkChanged(NetworkControlUpdate* update,
|
||||
Timestamp at_time);
|
||||
void UpdateCongestionWindowSize();
|
||||
PacerConfig GetPacingRates(Timestamp at_time) const;
|
||||
const FieldTrialBasedConfig trial_based_config_;
|
||||
|
||||
const FieldTrialsView* const key_value_config_;
|
||||
RtcEventLog* const event_log_;
|
||||
const bool packet_feedback_only_;
|
||||
FieldTrialFlag safe_reset_on_route_change_;
|
||||
FieldTrialFlag safe_reset_acknowledged_rate_;
|
||||
const bool use_min_allocatable_as_lower_bound_;
|
||||
const bool ignore_probes_lower_than_network_estimate_;
|
||||
const bool limit_probes_lower_than_throughput_estimate_;
|
||||
const RateControlSettings rate_control_settings_;
|
||||
const bool pace_at_max_of_bwe_and_lower_link_capacity_;
|
||||
|
||||
const std::unique_ptr<ProbeController> probe_controller_;
|
||||
const std::unique_ptr<CongestionWindowPushbackController>
|
||||
congestion_window_pushback_controller_;
|
||||
|
||||
std::unique_ptr<SendSideBandwidthEstimation> bandwidth_estimation_;
|
||||
std::unique_ptr<AlrDetector> alr_detector_;
|
||||
std::unique_ptr<ProbeBitrateEstimator> probe_bitrate_estimator_;
|
||||
std::unique_ptr<NetworkStateEstimator> network_estimator_;
|
||||
std::unique_ptr<NetworkStatePredictor> network_state_predictor_;
|
||||
std::unique_ptr<DelayBasedBwe> delay_based_bwe_;
|
||||
std::unique_ptr<AcknowledgedBitrateEstimatorInterface>
|
||||
acknowledged_bitrate_estimator_;
|
||||
|
||||
absl::optional<NetworkControllerConfig> initial_config_;
|
||||
|
||||
DataRate min_target_rate_ = DataRate::Zero();
|
||||
DataRate min_data_rate_ = DataRate::Zero();
|
||||
DataRate max_data_rate_ = DataRate::PlusInfinity();
|
||||
absl::optional<DataRate> starting_rate_;
|
||||
|
||||
bool first_packet_sent_ = false;
|
||||
|
||||
absl::optional<NetworkStateEstimate> estimate_;
|
||||
|
||||
Timestamp next_loss_update_ = Timestamp::MinusInfinity();
|
||||
int lost_packets_since_last_loss_update_ = 0;
|
||||
int expected_packets_since_last_loss_update_ = 0;
|
||||
|
||||
std::deque<int64_t> feedback_max_rtts_;
|
||||
|
||||
DataRate last_loss_based_target_rate_;
|
||||
DataRate last_pushback_target_rate_;
|
||||
DataRate last_stable_target_rate_;
|
||||
LossBasedState last_loss_base_state_;
|
||||
|
||||
absl::optional<uint8_t> last_estimated_fraction_loss_ = 0;
|
||||
TimeDelta last_estimated_round_trip_time_ = TimeDelta::PlusInfinity();
|
||||
|
||||
double pacing_factor_;
|
||||
DataRate min_total_allocated_bitrate_;
|
||||
DataRate max_padding_rate_;
|
||||
|
||||
bool previously_in_alr_ = false;
|
||||
|
||||
absl::optional<DataSize> current_data_window_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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/inter_arrival_delta.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
static constexpr TimeDelta kBurstDeltaThreshold = TimeDelta::Millis(5);
|
||||
static constexpr TimeDelta kMaxBurstDuration = TimeDelta::Millis(100);
|
||||
constexpr TimeDelta InterArrivalDelta::kArrivalTimeOffsetThreshold;
|
||||
|
||||
InterArrivalDelta::InterArrivalDelta(TimeDelta send_time_group_length)
|
||||
: send_time_group_length_(send_time_group_length),
|
||||
current_timestamp_group_(),
|
||||
prev_timestamp_group_(),
|
||||
num_consecutive_reordered_packets_(0) {}
|
||||
|
||||
bool InterArrivalDelta::ComputeDeltas(Timestamp send_time,
|
||||
Timestamp arrival_time,
|
||||
Timestamp system_time,
|
||||
size_t packet_size,
|
||||
TimeDelta* send_time_delta,
|
||||
TimeDelta* arrival_time_delta,
|
||||
int* packet_size_delta) {
|
||||
bool calculated_deltas = false;
|
||||
if (current_timestamp_group_.IsFirstPacket()) {
|
||||
// We don't have enough data to update the filter, so we store it until we
|
||||
// have two frames of data to process.
|
||||
current_timestamp_group_.send_time = send_time;
|
||||
current_timestamp_group_.first_send_time = send_time;
|
||||
current_timestamp_group_.first_arrival = arrival_time;
|
||||
} else if (current_timestamp_group_.first_send_time > send_time) {
|
||||
// Reordered packet.
|
||||
return false;
|
||||
} else if (NewTimestampGroup(arrival_time, send_time)) {
|
||||
// First packet of a later send burst, the previous packets sample is ready.
|
||||
if (prev_timestamp_group_.complete_time.IsFinite()) {
|
||||
*send_time_delta =
|
||||
current_timestamp_group_.send_time - prev_timestamp_group_.send_time;
|
||||
*arrival_time_delta = current_timestamp_group_.complete_time -
|
||||
prev_timestamp_group_.complete_time;
|
||||
|
||||
TimeDelta system_time_delta = current_timestamp_group_.last_system_time -
|
||||
prev_timestamp_group_.last_system_time;
|
||||
|
||||
if (*arrival_time_delta - system_time_delta >=
|
||||
kArrivalTimeOffsetThreshold) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "The arrival time clock offset has changed (diff = "
|
||||
<< arrival_time_delta->ms() - system_time_delta.ms()
|
||||
<< " ms), resetting.";
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
if (*arrival_time_delta < TimeDelta::Zero()) {
|
||||
// The group of packets has been reordered since receiving its local
|
||||
// arrival timestamp.
|
||||
++num_consecutive_reordered_packets_;
|
||||
if (num_consecutive_reordered_packets_ >= kReorderedResetThreshold) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Packets between send burst arrived out of order, resetting:"
|
||||
<< " arrival_time_delta_ms=" << arrival_time_delta->ms()
|
||||
<< ", send_time_delta_ms=" << send_time_delta->ms();
|
||||
Reset();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
num_consecutive_reordered_packets_ = 0;
|
||||
}
|
||||
*packet_size_delta = static_cast<int>(current_timestamp_group_.size) -
|
||||
static_cast<int>(prev_timestamp_group_.size);
|
||||
calculated_deltas = true;
|
||||
}
|
||||
prev_timestamp_group_ = current_timestamp_group_;
|
||||
// The new timestamp is now the current frame.
|
||||
current_timestamp_group_.first_send_time = send_time;
|
||||
current_timestamp_group_.send_time = send_time;
|
||||
current_timestamp_group_.first_arrival = arrival_time;
|
||||
current_timestamp_group_.size = 0;
|
||||
} else {
|
||||
current_timestamp_group_.send_time =
|
||||
std::max(current_timestamp_group_.send_time, send_time);
|
||||
}
|
||||
// Accumulate the frame size.
|
||||
current_timestamp_group_.size += packet_size;
|
||||
current_timestamp_group_.complete_time = arrival_time;
|
||||
current_timestamp_group_.last_system_time = system_time;
|
||||
|
||||
return calculated_deltas;
|
||||
}
|
||||
|
||||
// Assumes that `timestamp` is not reordered compared to
|
||||
// `current_timestamp_group_`.
|
||||
bool InterArrivalDelta::NewTimestampGroup(Timestamp arrival_time,
|
||||
Timestamp send_time) const {
|
||||
if (current_timestamp_group_.IsFirstPacket()) {
|
||||
return false;
|
||||
} else if (BelongsToBurst(arrival_time, send_time)) {
|
||||
return false;
|
||||
} else {
|
||||
return send_time - current_timestamp_group_.first_send_time >
|
||||
send_time_group_length_;
|
||||
}
|
||||
}
|
||||
|
||||
bool InterArrivalDelta::BelongsToBurst(Timestamp arrival_time,
|
||||
Timestamp send_time) const {
|
||||
RTC_DCHECK(current_timestamp_group_.complete_time.IsFinite());
|
||||
TimeDelta arrival_time_delta =
|
||||
arrival_time - current_timestamp_group_.complete_time;
|
||||
TimeDelta send_time_delta = send_time - current_timestamp_group_.send_time;
|
||||
if (send_time_delta.IsZero())
|
||||
return true;
|
||||
TimeDelta propagation_delta = arrival_time_delta - send_time_delta;
|
||||
if (propagation_delta < TimeDelta::Zero() &&
|
||||
arrival_time_delta <= kBurstDeltaThreshold &&
|
||||
arrival_time - current_timestamp_group_.first_arrival < kMaxBurstDuration)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterArrivalDelta::Reset() {
|
||||
num_consecutive_reordered_packets_ = 0;
|
||||
current_timestamp_group_ = SendTimeGroup();
|
||||
prev_timestamp_group_ = SendTimeGroup();
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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_INTER_ARRIVAL_DELTA_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_INTER_ARRIVAL_DELTA_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Helper class to compute the inter-arrival time delta and the size delta
|
||||
// between two send bursts. This code is branched from
|
||||
// modules/remote_bitrate_estimator/inter_arrival.
|
||||
class InterArrivalDelta {
|
||||
public:
|
||||
// After this many packet groups received out of order InterArrival will
|
||||
// reset, assuming that clocks have made a jump.
|
||||
static constexpr int kReorderedResetThreshold = 3;
|
||||
static constexpr TimeDelta kArrivalTimeOffsetThreshold =
|
||||
TimeDelta::Seconds(3);
|
||||
|
||||
// A send time group is defined as all packets with a send time which are at
|
||||
// most send_time_group_length older than the first timestamp in that
|
||||
// group.
|
||||
explicit InterArrivalDelta(TimeDelta send_time_group_length);
|
||||
|
||||
InterArrivalDelta() = delete;
|
||||
InterArrivalDelta(const InterArrivalDelta&) = delete;
|
||||
InterArrivalDelta& operator=(const InterArrivalDelta&) = delete;
|
||||
|
||||
// This function returns true if a delta was computed, or false if the current
|
||||
// group is still incomplete or if only one group has been completed.
|
||||
// `send_time` is the send time.
|
||||
// `arrival_time` is the time at which the packet arrived.
|
||||
// `packet_size` is the size of the packet.
|
||||
// `timestamp_delta` (output) is the computed send time delta.
|
||||
// `arrival_time_delta` (output) is the computed arrival-time delta.
|
||||
// `packet_size_delta` (output) is the computed size delta.
|
||||
bool ComputeDeltas(Timestamp send_time,
|
||||
Timestamp arrival_time,
|
||||
Timestamp system_time,
|
||||
size_t packet_size,
|
||||
TimeDelta* send_time_delta,
|
||||
TimeDelta* arrival_time_delta,
|
||||
int* packet_size_delta);
|
||||
|
||||
private:
|
||||
struct SendTimeGroup {
|
||||
SendTimeGroup()
|
||||
: size(0),
|
||||
first_send_time(Timestamp::MinusInfinity()),
|
||||
send_time(Timestamp::MinusInfinity()),
|
||||
first_arrival(Timestamp::MinusInfinity()),
|
||||
complete_time(Timestamp::MinusInfinity()),
|
||||
last_system_time(Timestamp::MinusInfinity()) {}
|
||||
|
||||
bool IsFirstPacket() const { return complete_time.IsInfinite(); }
|
||||
|
||||
size_t size;
|
||||
Timestamp first_send_time;
|
||||
Timestamp send_time;
|
||||
Timestamp first_arrival;
|
||||
Timestamp complete_time;
|
||||
Timestamp last_system_time;
|
||||
};
|
||||
|
||||
// Returns true if the last packet was the end of the current batch and the
|
||||
// packet with `send_time` is the first of a new batch.
|
||||
bool NewTimestampGroup(Timestamp arrival_time, Timestamp send_time) const;
|
||||
|
||||
bool BelongsToBurst(Timestamp arrival_time, Timestamp send_time) const;
|
||||
|
||||
void Reset();
|
||||
|
||||
const TimeDelta send_time_group_length_;
|
||||
SendTimeGroup current_timestamp_group_;
|
||||
SendTimeGroup prev_timestamp_group_;
|
||||
int num_consecutive_reordered_packets_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_INTER_ARRIVAL_DELTA_H_
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2018 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/link_capacity_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "api/units/data_rate.h"
|
||||
#include "rtc_base/numerics/safe_minmax.h"
|
||||
|
||||
namespace webrtc {
|
||||
LinkCapacityEstimator::LinkCapacityEstimator() {}
|
||||
|
||||
DataRate LinkCapacityEstimator::UpperBound() const {
|
||||
if (estimate_kbps_.has_value())
|
||||
return DataRate::KilobitsPerSec(estimate_kbps_.value() +
|
||||
3 * deviation_estimate_kbps());
|
||||
return DataRate::Infinity();
|
||||
}
|
||||
|
||||
DataRate LinkCapacityEstimator::LowerBound() const {
|
||||
if (estimate_kbps_.has_value())
|
||||
return DataRate::KilobitsPerSec(
|
||||
std::max(0.0, estimate_kbps_.value() - 3 * deviation_estimate_kbps()));
|
||||
return DataRate::Zero();
|
||||
}
|
||||
|
||||
void LinkCapacityEstimator::Reset() {
|
||||
estimate_kbps_.reset();
|
||||
}
|
||||
|
||||
void LinkCapacityEstimator::OnOveruseDetected(DataRate acknowledged_rate) {
|
||||
Update(acknowledged_rate, 0.05);
|
||||
}
|
||||
|
||||
void LinkCapacityEstimator::OnProbeRate(DataRate probe_rate) {
|
||||
Update(probe_rate, 0.5);
|
||||
}
|
||||
|
||||
void LinkCapacityEstimator::Update(DataRate capacity_sample, double alpha) {
|
||||
double sample_kbps = capacity_sample.kbps();
|
||||
if (!estimate_kbps_.has_value()) {
|
||||
estimate_kbps_ = sample_kbps;
|
||||
} else {
|
||||
estimate_kbps_ = (1 - alpha) * estimate_kbps_.value() + alpha * sample_kbps;
|
||||
}
|
||||
// Estimate the variance of the link capacity estimate and normalize the
|
||||
// variance with the link capacity estimate.
|
||||
const double norm = std::max(estimate_kbps_.value(), 1.0);
|
||||
double error_kbps = estimate_kbps_.value() - sample_kbps;
|
||||
deviation_kbps_ =
|
||||
(1 - alpha) * deviation_kbps_ + alpha * error_kbps * error_kbps / norm;
|
||||
// 0.4 ~= 14 kbit/s at 500 kbit/s
|
||||
// 2.5f ~= 35 kbit/s at 500 kbit/s
|
||||
deviation_kbps_ = rtc::SafeClamp(deviation_kbps_, 0.4f, 2.5f);
|
||||
}
|
||||
|
||||
bool LinkCapacityEstimator::has_estimate() const {
|
||||
return estimate_kbps_.has_value();
|
||||
}
|
||||
|
||||
DataRate LinkCapacityEstimator::estimate() const {
|
||||
return DataRate::KilobitsPerSec(*estimate_kbps_);
|
||||
}
|
||||
|
||||
double LinkCapacityEstimator::deviation_estimate_kbps() const {
|
||||
// Calculate the max bit rate std dev given the normalized
|
||||
// variance and the current throughput bitrate. The standard deviation will
|
||||
// only be used if estimate_kbps_ has a value.
|
||||
return sqrt(deviation_kbps_ * estimate_kbps_.value());
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2018 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_LINK_CAPACITY_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/units/data_rate.h"
|
||||
|
||||
namespace webrtc {
|
||||
class LinkCapacityEstimator {
|
||||
public:
|
||||
LinkCapacityEstimator();
|
||||
DataRate UpperBound() const;
|
||||
DataRate LowerBound() const;
|
||||
void Reset();
|
||||
void OnOveruseDetected(DataRate acknowledged_rate);
|
||||
void OnProbeRate(DataRate probe_rate);
|
||||
bool has_estimate() const;
|
||||
DataRate estimate() const;
|
||||
|
||||
private:
|
||||
friend class GoogCcStatePrinter;
|
||||
void Update(DataRate capacity_sample, double alpha);
|
||||
|
||||
double deviation_estimate_kbps() const;
|
||||
absl::optional<double> estimate_kbps_;
|
||||
double deviation_kbps_ = 0.4;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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_bandwidth_estimation.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
const char kBweLossBasedControl[] = "WebRTC-Bwe-LossBasedControl";
|
||||
|
||||
// Expecting RTCP feedback to be sent with roughly 1s intervals, a 5s gap
|
||||
// indicates a channel outage.
|
||||
constexpr TimeDelta kMaxRtcpFeedbackInterval = TimeDelta::Millis(5000);
|
||||
|
||||
// Increase slower when RTT is high.
|
||||
double GetIncreaseFactor(const LossBasedControlConfig& config, TimeDelta rtt) {
|
||||
// Clamp the RTT
|
||||
if (rtt < config.increase_low_rtt) {
|
||||
rtt = config.increase_low_rtt;
|
||||
} else if (rtt > config.increase_high_rtt) {
|
||||
rtt = config.increase_high_rtt;
|
||||
}
|
||||
auto rtt_range = config.increase_high_rtt.Get() - config.increase_low_rtt;
|
||||
if (rtt_range <= TimeDelta::Zero()) {
|
||||
RTC_DCHECK_NOTREACHED(); // Only on misconfiguration.
|
||||
return config.min_increase_factor;
|
||||
}
|
||||
auto rtt_offset = rtt - config.increase_low_rtt;
|
||||
auto relative_offset = std::max(0.0, std::min(rtt_offset / rtt_range, 1.0));
|
||||
auto factor_range = config.max_increase_factor - config.min_increase_factor;
|
||||
return config.min_increase_factor + (1 - relative_offset) * factor_range;
|
||||
}
|
||||
|
||||
double LossFromBitrate(DataRate bitrate,
|
||||
DataRate loss_bandwidth_balance,
|
||||
double exponent) {
|
||||
if (loss_bandwidth_balance >= bitrate)
|
||||
return 1.0;
|
||||
return pow(loss_bandwidth_balance / bitrate, exponent);
|
||||
}
|
||||
|
||||
DataRate BitrateFromLoss(double loss,
|
||||
DataRate loss_bandwidth_balance,
|
||||
double exponent) {
|
||||
if (exponent <= 0) {
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return DataRate::Infinity();
|
||||
}
|
||||
if (loss < 1e-5)
|
||||
return DataRate::Infinity();
|
||||
return loss_bandwidth_balance * pow(loss, -1.0 / exponent);
|
||||
}
|
||||
|
||||
double ExponentialUpdate(TimeDelta window, TimeDelta interval) {
|
||||
// Use the convention that exponential window length (which is really
|
||||
// infinite) is the time it takes to dampen to 1/e.
|
||||
if (window <= TimeDelta::Zero()) {
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return 1.0f;
|
||||
}
|
||||
return 1.0f - exp(interval / window * -1.0);
|
||||
}
|
||||
|
||||
bool IsEnabled(const webrtc::FieldTrialsView& key_value_config,
|
||||
absl::string_view name) {
|
||||
return absl::StartsWith(key_value_config.Lookup(name), "Enabled");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LossBasedControlConfig::LossBasedControlConfig(
|
||||
const FieldTrialsView* key_value_config)
|
||||
: enabled(IsEnabled(*key_value_config, kBweLossBasedControl)),
|
||||
min_increase_factor("min_incr", 1.02),
|
||||
max_increase_factor("max_incr", 1.08),
|
||||
increase_low_rtt("incr_low_rtt", TimeDelta::Millis(200)),
|
||||
increase_high_rtt("incr_high_rtt", TimeDelta::Millis(800)),
|
||||
decrease_factor("decr", 0.99),
|
||||
loss_window("loss_win", TimeDelta::Millis(800)),
|
||||
loss_max_window("loss_max_win", TimeDelta::Millis(800)),
|
||||
acknowledged_rate_max_window("ackrate_max_win", TimeDelta::Millis(800)),
|
||||
increase_offset("incr_offset", DataRate::BitsPerSec(1000)),
|
||||
loss_bandwidth_balance_increase("balance_incr",
|
||||
DataRate::KilobitsPerSec(0.5)),
|
||||
loss_bandwidth_balance_decrease("balance_decr",
|
||||
DataRate::KilobitsPerSec(4)),
|
||||
loss_bandwidth_balance_reset("balance_reset",
|
||||
DataRate::KilobitsPerSec(0.1)),
|
||||
loss_bandwidth_balance_exponent("exponent", 0.5),
|
||||
allow_resets("resets", false),
|
||||
decrease_interval("decr_intvl", TimeDelta::Millis(300)),
|
||||
loss_report_timeout("timeout", TimeDelta::Millis(6000)) {
|
||||
ParseFieldTrial(
|
||||
{&min_increase_factor, &max_increase_factor, &increase_low_rtt,
|
||||
&increase_high_rtt, &decrease_factor, &loss_window, &loss_max_window,
|
||||
&acknowledged_rate_max_window, &increase_offset,
|
||||
&loss_bandwidth_balance_increase, &loss_bandwidth_balance_decrease,
|
||||
&loss_bandwidth_balance_reset, &loss_bandwidth_balance_exponent,
|
||||
&allow_resets, &decrease_interval, &loss_report_timeout},
|
||||
key_value_config->Lookup(kBweLossBasedControl));
|
||||
}
|
||||
LossBasedControlConfig::LossBasedControlConfig(const LossBasedControlConfig&) =
|
||||
default;
|
||||
LossBasedControlConfig::~LossBasedControlConfig() = default;
|
||||
|
||||
LossBasedBandwidthEstimation::LossBasedBandwidthEstimation(
|
||||
const FieldTrialsView* key_value_config)
|
||||
: config_(key_value_config),
|
||||
average_loss_(0),
|
||||
average_loss_max_(0),
|
||||
loss_based_bitrate_(DataRate::Zero()),
|
||||
acknowledged_bitrate_max_(DataRate::Zero()),
|
||||
acknowledged_bitrate_last_update_(Timestamp::MinusInfinity()),
|
||||
time_last_decrease_(Timestamp::MinusInfinity()),
|
||||
has_decreased_since_last_loss_report_(false),
|
||||
last_loss_packet_report_(Timestamp::MinusInfinity()),
|
||||
last_loss_ratio_(0) {}
|
||||
|
||||
void LossBasedBandwidthEstimation::UpdateLossStatistics(
|
||||
const std::vector<PacketResult>& packet_results,
|
||||
Timestamp at_time) {
|
||||
if (packet_results.empty()) {
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return;
|
||||
}
|
||||
int loss_count = 0;
|
||||
for (const auto& pkt : packet_results) {
|
||||
loss_count += !pkt.IsReceived() ? 1 : 0;
|
||||
}
|
||||
last_loss_ratio_ = static_cast<double>(loss_count) / packet_results.size();
|
||||
const TimeDelta time_passed = last_loss_packet_report_.IsFinite()
|
||||
? at_time - last_loss_packet_report_
|
||||
: TimeDelta::Seconds(1);
|
||||
last_loss_packet_report_ = at_time;
|
||||
has_decreased_since_last_loss_report_ = false;
|
||||
|
||||
average_loss_ += ExponentialUpdate(config_.loss_window, time_passed) *
|
||||
(last_loss_ratio_ - average_loss_);
|
||||
if (average_loss_ > average_loss_max_) {
|
||||
average_loss_max_ = average_loss_;
|
||||
} else {
|
||||
average_loss_max_ +=
|
||||
ExponentialUpdate(config_.loss_max_window, time_passed) *
|
||||
(average_loss_ - average_loss_max_);
|
||||
}
|
||||
}
|
||||
|
||||
void LossBasedBandwidthEstimation::UpdateAcknowledgedBitrate(
|
||||
DataRate acknowledged_bitrate,
|
||||
Timestamp at_time) {
|
||||
const TimeDelta time_passed =
|
||||
acknowledged_bitrate_last_update_.IsFinite()
|
||||
? at_time - acknowledged_bitrate_last_update_
|
||||
: TimeDelta::Seconds(1);
|
||||
acknowledged_bitrate_last_update_ = at_time;
|
||||
if (acknowledged_bitrate > acknowledged_bitrate_max_) {
|
||||
acknowledged_bitrate_max_ = acknowledged_bitrate;
|
||||
} else {
|
||||
acknowledged_bitrate_max_ -=
|
||||
ExponentialUpdate(config_.acknowledged_rate_max_window, time_passed) *
|
||||
(acknowledged_bitrate_max_ - acknowledged_bitrate);
|
||||
}
|
||||
}
|
||||
|
||||
DataRate LossBasedBandwidthEstimation::Update(Timestamp at_time,
|
||||
DataRate min_bitrate,
|
||||
DataRate wanted_bitrate,
|
||||
TimeDelta last_round_trip_time) {
|
||||
if (loss_based_bitrate_.IsZero()) {
|
||||
loss_based_bitrate_ = wanted_bitrate;
|
||||
}
|
||||
|
||||
// Only increase if loss has been low for some time.
|
||||
const double loss_estimate_for_increase = average_loss_max_;
|
||||
// Avoid multiple decreases from averaging over one loss spike.
|
||||
const double loss_estimate_for_decrease =
|
||||
std::min(average_loss_, last_loss_ratio_);
|
||||
const bool allow_decrease =
|
||||
!has_decreased_since_last_loss_report_ &&
|
||||
(at_time - time_last_decrease_ >=
|
||||
last_round_trip_time + config_.decrease_interval);
|
||||
// If packet lost reports are too old, dont increase bitrate.
|
||||
const bool loss_report_valid =
|
||||
at_time - last_loss_packet_report_ < 1.2 * kMaxRtcpFeedbackInterval;
|
||||
|
||||
if (loss_report_valid && config_.allow_resets &&
|
||||
loss_estimate_for_increase < loss_reset_threshold()) {
|
||||
loss_based_bitrate_ = wanted_bitrate;
|
||||
} else if (loss_report_valid &&
|
||||
loss_estimate_for_increase < loss_increase_threshold()) {
|
||||
// Increase bitrate by RTT-adaptive ratio.
|
||||
DataRate new_increased_bitrate =
|
||||
min_bitrate * GetIncreaseFactor(config_, last_round_trip_time) +
|
||||
config_.increase_offset;
|
||||
// The bitrate that would make the loss "just high enough".
|
||||
const DataRate new_increased_bitrate_cap = BitrateFromLoss(
|
||||
loss_estimate_for_increase, config_.loss_bandwidth_balance_increase,
|
||||
config_.loss_bandwidth_balance_exponent);
|
||||
new_increased_bitrate =
|
||||
std::min(new_increased_bitrate, new_increased_bitrate_cap);
|
||||
loss_based_bitrate_ = std::max(new_increased_bitrate, loss_based_bitrate_);
|
||||
} else if (loss_estimate_for_decrease > loss_decrease_threshold() &&
|
||||
allow_decrease) {
|
||||
// The bitrate that would make the loss "just acceptable".
|
||||
const DataRate new_decreased_bitrate_floor = BitrateFromLoss(
|
||||
loss_estimate_for_decrease, config_.loss_bandwidth_balance_decrease,
|
||||
config_.loss_bandwidth_balance_exponent);
|
||||
DataRate new_decreased_bitrate =
|
||||
std::max(decreased_bitrate(), new_decreased_bitrate_floor);
|
||||
if (new_decreased_bitrate < loss_based_bitrate_) {
|
||||
time_last_decrease_ = at_time;
|
||||
has_decreased_since_last_loss_report_ = true;
|
||||
loss_based_bitrate_ = new_decreased_bitrate;
|
||||
}
|
||||
}
|
||||
return loss_based_bitrate_;
|
||||
}
|
||||
|
||||
void LossBasedBandwidthEstimation::Initialize(DataRate bitrate) {
|
||||
loss_based_bitrate_ = bitrate;
|
||||
average_loss_ = 0;
|
||||
average_loss_max_ = 0;
|
||||
}
|
||||
|
||||
double LossBasedBandwidthEstimation::loss_reset_threshold() const {
|
||||
return LossFromBitrate(loss_based_bitrate_,
|
||||
config_.loss_bandwidth_balance_reset,
|
||||
config_.loss_bandwidth_balance_exponent);
|
||||
}
|
||||
|
||||
double LossBasedBandwidthEstimation::loss_increase_threshold() const {
|
||||
return LossFromBitrate(loss_based_bitrate_,
|
||||
config_.loss_bandwidth_balance_increase,
|
||||
config_.loss_bandwidth_balance_exponent);
|
||||
}
|
||||
|
||||
double LossBasedBandwidthEstimation::loss_decrease_threshold() const {
|
||||
return LossFromBitrate(loss_based_bitrate_,
|
||||
config_.loss_bandwidth_balance_decrease,
|
||||
config_.loss_bandwidth_balance_exponent);
|
||||
}
|
||||
|
||||
DataRate LossBasedBandwidthEstimation::decreased_bitrate() const {
|
||||
return config_.decrease_factor * acknowledged_bitrate_max_;
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (c) 2018 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_BANDWIDTH_ESTIMATION_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_LOSS_BASED_BANDWIDTH_ESTIMATION_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct LossBasedControlConfig {
|
||||
explicit LossBasedControlConfig(const FieldTrialsView* key_value_config);
|
||||
LossBasedControlConfig(const LossBasedControlConfig&);
|
||||
LossBasedControlConfig& operator=(const LossBasedControlConfig&) = default;
|
||||
~LossBasedControlConfig();
|
||||
bool enabled;
|
||||
FieldTrialParameter<double> min_increase_factor;
|
||||
FieldTrialParameter<double> max_increase_factor;
|
||||
FieldTrialParameter<TimeDelta> increase_low_rtt;
|
||||
FieldTrialParameter<TimeDelta> increase_high_rtt;
|
||||
FieldTrialParameter<double> decrease_factor;
|
||||
FieldTrialParameter<TimeDelta> loss_window;
|
||||
FieldTrialParameter<TimeDelta> loss_max_window;
|
||||
FieldTrialParameter<TimeDelta> acknowledged_rate_max_window;
|
||||
FieldTrialParameter<DataRate> increase_offset;
|
||||
FieldTrialParameter<DataRate> loss_bandwidth_balance_increase;
|
||||
FieldTrialParameter<DataRate> loss_bandwidth_balance_decrease;
|
||||
FieldTrialParameter<DataRate> loss_bandwidth_balance_reset;
|
||||
FieldTrialParameter<double> loss_bandwidth_balance_exponent;
|
||||
FieldTrialParameter<bool> allow_resets;
|
||||
FieldTrialParameter<TimeDelta> decrease_interval;
|
||||
FieldTrialParameter<TimeDelta> loss_report_timeout;
|
||||
};
|
||||
|
||||
// Estimates an upper BWE limit based on loss.
|
||||
// It requires knowledge about lost packets and acknowledged bitrate.
|
||||
// Ie, this class require transport feedback.
|
||||
class LossBasedBandwidthEstimation {
|
||||
public:
|
||||
explicit LossBasedBandwidthEstimation(
|
||||
const FieldTrialsView* key_value_config);
|
||||
// Returns the new estimate.
|
||||
DataRate Update(Timestamp at_time,
|
||||
DataRate min_bitrate,
|
||||
DataRate wanted_bitrate,
|
||||
TimeDelta last_round_trip_time);
|
||||
void UpdateAcknowledgedBitrate(DataRate acknowledged_bitrate,
|
||||
Timestamp at_time);
|
||||
void Initialize(DataRate bitrate);
|
||||
bool Enabled() const { return config_.enabled; }
|
||||
// Returns true if LossBasedBandwidthEstimation is enabled and have
|
||||
// received loss statistics. Ie, this class require transport feedback.
|
||||
bool InUse() const {
|
||||
return Enabled() && last_loss_packet_report_.IsFinite();
|
||||
}
|
||||
void UpdateLossStatistics(const std::vector<PacketResult>& packet_results,
|
||||
Timestamp at_time);
|
||||
DataRate GetEstimate() const { return loss_based_bitrate_; }
|
||||
|
||||
private:
|
||||
friend class GoogCcStatePrinter;
|
||||
void Reset(DataRate bitrate);
|
||||
double loss_increase_threshold() const;
|
||||
double loss_decrease_threshold() const;
|
||||
double loss_reset_threshold() const;
|
||||
|
||||
DataRate decreased_bitrate() const;
|
||||
|
||||
const LossBasedControlConfig config_;
|
||||
double average_loss_;
|
||||
double average_loss_max_;
|
||||
DataRate loss_based_bitrate_;
|
||||
DataRate acknowledged_bitrate_max_;
|
||||
Timestamp acknowledged_bitrate_last_update_;
|
||||
Timestamp time_last_decrease_;
|
||||
bool has_decreased_since_last_loss_report_;
|
||||
Timestamp last_loss_packet_report_;
|
||||
double last_loss_ratio_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_LOSS_BASED_BANDWIDTH_ESTIMATION_H_
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* 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 <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/array_view.h"
|
||||
#include "api/field_trials_view.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"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// State of the loss based estimate, which can be either increasing/decreasing
|
||||
// when network is loss limited, or equal to the delay based estimate.
|
||||
enum class LossBasedState {
|
||||
kIncreasing = 0,
|
||||
// TODO(bugs.webrtc.org/12707): Remove one of the increasing states once we
|
||||
// have decided if padding is usefull for ramping up when BWE is loss
|
||||
// limited.
|
||||
kIncreaseUsingPadding = 1,
|
||||
kDecreasing = 2,
|
||||
kDelayBasedEstimate = 3
|
||||
};
|
||||
|
||||
class LossBasedBweV2 {
|
||||
public:
|
||||
struct Result {
|
||||
~Result() = default;
|
||||
DataRate bandwidth_estimate = DataRate::Zero();
|
||||
// State is used by goog_cc, which later sends probe requests to probe
|
||||
// controller if state is kIncreasing.
|
||||
LossBasedState state = LossBasedState::kDelayBasedEstimate;
|
||||
};
|
||||
// Creates a disabled `LossBasedBweV2` if the
|
||||
// `key_value_config` is not valid.
|
||||
explicit LossBasedBweV2(const FieldTrialsView* 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 true if loss based BWE is ready to be used in the start phase.
|
||||
bool ReadyToUseInStartPhase() const;
|
||||
|
||||
// Returns true if loss based BWE can be used in the start phase.
|
||||
bool UseInStartPhase() const;
|
||||
|
||||
// Returns `DataRate::PlusInfinity` if no BWE can be calculated.
|
||||
Result GetLossBasedResult() const;
|
||||
|
||||
void SetAcknowledgedBitrate(DataRate acknowledged_bitrate);
|
||||
void SetMinMaxBitrate(DataRate min_bitrate, DataRate max_bitrate);
|
||||
void UpdateBandwidthEstimate(
|
||||
rtc::ArrayView<const PacketResult> packet_results,
|
||||
DataRate delay_based_estimate,
|
||||
bool in_alr);
|
||||
bool PaceAtLossBasedEstimate() const;
|
||||
|
||||
// For unit testing only.
|
||||
void SetBandwidthEstimate(DataRate bandwidth_estimate);
|
||||
|
||||
private:
|
||||
struct ChannelParameters {
|
||||
double inherent_loss = 0.0;
|
||||
DataRate loss_limited_bandwidth = DataRate::MinusInfinity();
|
||||
};
|
||||
|
||||
struct Config {
|
||||
double bandwidth_rampup_upper_bound_factor = 0.0;
|
||||
double bandwidth_rampup_upper_bound_factor_in_hold = 0;
|
||||
double bandwidth_rampup_hold_threshold = 0;
|
||||
double rampup_acceleration_max_factor = 0.0;
|
||||
TimeDelta rampup_acceleration_maxout_time = TimeDelta::Zero();
|
||||
std::vector<double> candidate_factors;
|
||||
double higher_bandwidth_bias_factor = 0.0;
|
||||
double higher_log_bandwidth_bias_factor = 0.0;
|
||||
double inherent_loss_lower_bound = 0.0;
|
||||
double loss_threshold_of_high_bandwidth_preference = 0.0;
|
||||
double bandwidth_preference_smoothing_factor = 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;
|
||||
bool append_acknowledged_rate_candidate = true;
|
||||
bool append_delay_based_estimate_candidate = false;
|
||||
bool append_upper_bound_candidate_in_alr = false;
|
||||
TimeDelta observation_duration_lower_bound = TimeDelta::Zero();
|
||||
int observation_window_size = 0;
|
||||
double sending_rate_smoothing_factor = 0.0;
|
||||
double instant_upper_bound_temporal_weight_factor = 0.0;
|
||||
DataRate instant_upper_bound_bandwidth_balance = DataRate::MinusInfinity();
|
||||
double instant_upper_bound_loss_offset = 0.0;
|
||||
double temporal_weight_factor = 0.0;
|
||||
double bandwidth_backoff_lower_bound_factor = 0.0;
|
||||
double max_increase_factor = 0.0;
|
||||
TimeDelta delayed_increase_window = TimeDelta::Zero();
|
||||
bool not_increase_if_inherent_loss_less_than_average_loss = false;
|
||||
bool not_use_acked_rate_in_alr = false;
|
||||
bool use_in_start_phase = false;
|
||||
int min_num_observations = 0;
|
||||
double lower_bound_by_acked_rate_factor = 0.0;
|
||||
double hold_duration_factor = 0.0;
|
||||
bool use_byte_loss_rate = false;
|
||||
TimeDelta padding_duration = TimeDelta::Zero();
|
||||
bool bound_best_candidate = false;
|
||||
bool pace_at_loss_based_estimate = false;
|
||||
};
|
||||
|
||||
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();
|
||||
DataSize size = DataSize::Zero();
|
||||
DataSize lost_size = DataSize::Zero();
|
||||
int id = -1;
|
||||
};
|
||||
|
||||
struct PartialObservation {
|
||||
int num_packets = 0;
|
||||
int num_lost_packets = 0;
|
||||
DataSize size = DataSize::Zero();
|
||||
DataSize lost_size = DataSize::Zero();
|
||||
};
|
||||
|
||||
struct PaddingInfo {
|
||||
DataRate padding_rate = DataRate::MinusInfinity();
|
||||
Timestamp padding_timestamp = Timestamp::MinusInfinity();
|
||||
};
|
||||
|
||||
struct HoldInfo {
|
||||
Timestamp timestamp = Timestamp::MinusInfinity();
|
||||
TimeDelta duration = TimeDelta::Zero();
|
||||
DataRate rate = DataRate::PlusInfinity();
|
||||
};
|
||||
|
||||
static absl::optional<Config> CreateConfig(
|
||||
const FieldTrialsView* key_value_config);
|
||||
bool IsConfigValid() const;
|
||||
|
||||
// Returns `0.0` if not enough loss statistics have been received.
|
||||
double GetAverageReportedLossRatio() const;
|
||||
double GetAverageReportedPacketLossRatio() const;
|
||||
double GetAverageReportedByteLossRatio() const;
|
||||
std::vector<ChannelParameters> GetCandidates(bool in_alr) const;
|
||||
DataRate GetCandidateBandwidthUpperBound() const;
|
||||
Derivatives GetDerivatives(const ChannelParameters& channel_parameters) const;
|
||||
double GetFeasibleInherentLoss(
|
||||
const ChannelParameters& channel_parameters) const;
|
||||
double GetInherentLossUpperBound(DataRate bandwidth) const;
|
||||
double AdjustBiasFactor(double loss_rate, double bias_factor) const;
|
||||
double GetHighBandwidthBias(DataRate bandwidth) const;
|
||||
double GetObjective(const ChannelParameters& channel_parameters) const;
|
||||
DataRate GetSendingRate(DataRate instantaneous_sending_rate) const;
|
||||
DataRate GetInstantUpperBound() const;
|
||||
void CalculateInstantUpperBound();
|
||||
DataRate GetInstantLowerBound() const;
|
||||
void CalculateInstantLowerBound();
|
||||
|
||||
void CalculateTemporalWeights();
|
||||
void NewtonsMethodUpdate(ChannelParameters& channel_parameters) const;
|
||||
|
||||
// Returns false if no observation was created.
|
||||
bool PushBackObservation(rtc::ArrayView<const PacketResult> packet_results);
|
||||
bool IsEstimateIncreasingWhenLossLimited(DataRate old_estimate,
|
||||
DataRate new_estimate);
|
||||
bool IsInLossLimitedState() const;
|
||||
bool CanKeepIncreasingState(DataRate estimate) const;
|
||||
|
||||
absl::optional<DataRate> acknowledged_bitrate_;
|
||||
absl::optional<Config> config_;
|
||||
ChannelParameters current_best_estimate_;
|
||||
int num_observations_ = 0;
|
||||
std::vector<Observation> observations_;
|
||||
PartialObservation partial_observation_;
|
||||
Timestamp last_send_time_most_recent_observation_ = Timestamp::PlusInfinity();
|
||||
Timestamp last_time_estimate_reduced_ = Timestamp::MinusInfinity();
|
||||
absl::optional<DataRate> cached_instant_upper_bound_;
|
||||
absl::optional<DataRate> cached_instant_lower_bound_;
|
||||
std::vector<double> instant_upper_bound_temporal_weights_;
|
||||
std::vector<double> temporal_weights_;
|
||||
Timestamp recovering_after_loss_timestamp_ = Timestamp::MinusInfinity();
|
||||
DataRate bandwidth_limit_in_current_window_ = DataRate::PlusInfinity();
|
||||
DataRate min_bitrate_ = DataRate::KilobitsPerSec(1);
|
||||
DataRate max_bitrate_ = DataRate::PlusInfinity();
|
||||
DataRate delay_based_estimate_ = DataRate::PlusInfinity();
|
||||
LossBasedBweV2::Result loss_based_result_ = LossBasedBweV2::Result();
|
||||
HoldInfo last_hold_info_ = HoldInfo();
|
||||
PaddingInfo last_padding_info_ = PaddingInfo();
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_LOSS_BASED_BWE_V2_H_
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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/probe_bitrate_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/rtc_event_log/rtc_event_log.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 "logging/rtc_event_log/events/rtc_event_probe_result_failure.h"
|
||||
#include "logging/rtc_event_log/events/rtc_event_probe_result_success.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
// The minumum number of probes we need to receive feedback about in percent
|
||||
// in order to have a valid estimate.
|
||||
constexpr double kMinReceivedProbesRatio = .80;
|
||||
|
||||
// The minumum number of bytes we need to receive feedback about in percent
|
||||
// in order to have a valid estimate.
|
||||
constexpr double kMinReceivedBytesRatio = .80;
|
||||
|
||||
// The maximum |receive rate| / |send rate| ratio for a valid estimate.
|
||||
constexpr float kMaxValidRatio = 2.0f;
|
||||
|
||||
// The minimum |receive rate| / |send rate| ratio assuming that the link is
|
||||
// not saturated, i.e. we assume that we will receive at least
|
||||
// kMinRatioForUnsaturatedLink * |send rate| if |send rate| is less than the
|
||||
// link capacity.
|
||||
constexpr float kMinRatioForUnsaturatedLink = 0.9f;
|
||||
|
||||
// The target utilization of the link. If we know true link capacity
|
||||
// we'd like to send at 95% of that rate.
|
||||
constexpr float kTargetUtilizationFraction = 0.95f;
|
||||
|
||||
// The maximum time period over which the cluster history is retained.
|
||||
// This is also the maximum time period beyond which a probing burst is not
|
||||
// expected to last.
|
||||
constexpr TimeDelta kMaxClusterHistory = TimeDelta::Seconds(1);
|
||||
|
||||
// The maximum time interval between first and the last probe on a cluster
|
||||
// on the sender side as well as the receive side.
|
||||
constexpr TimeDelta kMaxProbeInterval = TimeDelta::Seconds(1);
|
||||
|
||||
} // namespace
|
||||
|
||||
ProbeBitrateEstimator::ProbeBitrateEstimator(RtcEventLog* event_log)
|
||||
: event_log_(event_log) {}
|
||||
|
||||
ProbeBitrateEstimator::~ProbeBitrateEstimator() = default;
|
||||
|
||||
absl::optional<DataRate> ProbeBitrateEstimator::HandleProbeAndEstimateBitrate(
|
||||
const PacketResult& packet_feedback) {
|
||||
int cluster_id = packet_feedback.sent_packet.pacing_info.probe_cluster_id;
|
||||
RTC_DCHECK_NE(cluster_id, PacedPacketInfo::kNotAProbe);
|
||||
|
||||
EraseOldClusters(packet_feedback.receive_time);
|
||||
|
||||
AggregatedCluster* cluster = &clusters_[cluster_id];
|
||||
|
||||
if (packet_feedback.sent_packet.send_time < cluster->first_send) {
|
||||
cluster->first_send = packet_feedback.sent_packet.send_time;
|
||||
}
|
||||
if (packet_feedback.sent_packet.send_time > cluster->last_send) {
|
||||
cluster->last_send = packet_feedback.sent_packet.send_time;
|
||||
cluster->size_last_send = packet_feedback.sent_packet.size;
|
||||
}
|
||||
if (packet_feedback.receive_time < cluster->first_receive) {
|
||||
cluster->first_receive = packet_feedback.receive_time;
|
||||
cluster->size_first_receive = packet_feedback.sent_packet.size;
|
||||
}
|
||||
if (packet_feedback.receive_time > cluster->last_receive) {
|
||||
cluster->last_receive = packet_feedback.receive_time;
|
||||
}
|
||||
cluster->size_total += packet_feedback.sent_packet.size;
|
||||
cluster->num_probes += 1;
|
||||
|
||||
RTC_DCHECK_GT(
|
||||
packet_feedback.sent_packet.pacing_info.probe_cluster_min_probes, 0);
|
||||
RTC_DCHECK_GT(packet_feedback.sent_packet.pacing_info.probe_cluster_min_bytes,
|
||||
0);
|
||||
|
||||
int min_probes =
|
||||
packet_feedback.sent_packet.pacing_info.probe_cluster_min_probes *
|
||||
kMinReceivedProbesRatio;
|
||||
DataSize min_size =
|
||||
DataSize::Bytes(
|
||||
packet_feedback.sent_packet.pacing_info.probe_cluster_min_bytes) *
|
||||
kMinReceivedBytesRatio;
|
||||
if (cluster->num_probes < min_probes || cluster->size_total < min_size)
|
||||
return absl::nullopt;
|
||||
|
||||
TimeDelta send_interval = cluster->last_send - cluster->first_send;
|
||||
TimeDelta receive_interval = cluster->last_receive - cluster->first_receive;
|
||||
|
||||
if (send_interval <= TimeDelta::Zero() || send_interval > kMaxProbeInterval ||
|
||||
receive_interval <= TimeDelta::Zero() ||
|
||||
receive_interval > kMaxProbeInterval) {
|
||||
RTC_LOG(LS_INFO) << "Probing unsuccessful, invalid send/receive interval"
|
||||
" [cluster id: "
|
||||
<< cluster_id
|
||||
<< "] [send interval: " << ToString(send_interval)
|
||||
<< "]"
|
||||
" [receive interval: "
|
||||
<< ToString(receive_interval) << "]";
|
||||
if (event_log_) {
|
||||
event_log_->Log(std::make_unique<RtcEventProbeResultFailure>(
|
||||
cluster_id, ProbeFailureReason::kInvalidSendReceiveInterval));
|
||||
}
|
||||
return absl::nullopt;
|
||||
}
|
||||
// Since the `send_interval` does not include the time it takes to actually
|
||||
// send the last packet the size of the last sent packet should not be
|
||||
// included when calculating the send bitrate.
|
||||
RTC_DCHECK_GT(cluster->size_total, cluster->size_last_send);
|
||||
DataSize send_size = cluster->size_total - cluster->size_last_send;
|
||||
DataRate send_rate = send_size / send_interval;
|
||||
|
||||
// Since the `receive_interval` does not include the time it takes to
|
||||
// actually receive the first packet the size of the first received packet
|
||||
// should not be included when calculating the receive bitrate.
|
||||
RTC_DCHECK_GT(cluster->size_total, cluster->size_first_receive);
|
||||
DataSize receive_size = cluster->size_total - cluster->size_first_receive;
|
||||
DataRate receive_rate = receive_size / receive_interval;
|
||||
|
||||
double ratio = receive_rate / send_rate;
|
||||
if (ratio > kMaxValidRatio) {
|
||||
RTC_LOG(LS_INFO) << "Probing unsuccessful, receive/send ratio too high"
|
||||
" [cluster id: "
|
||||
<< cluster_id << "] [send: " << ToString(send_size)
|
||||
<< " / " << ToString(send_interval) << " = "
|
||||
<< ToString(send_rate)
|
||||
<< "]"
|
||||
" [receive: "
|
||||
<< ToString(receive_size) << " / "
|
||||
<< ToString(receive_interval) << " = "
|
||||
<< ToString(receive_rate)
|
||||
<< " ]"
|
||||
" [ratio: "
|
||||
<< ToString(receive_rate) << " / " << ToString(send_rate)
|
||||
<< " = " << ratio << " > kMaxValidRatio ("
|
||||
<< kMaxValidRatio << ")]";
|
||||
if (event_log_) {
|
||||
event_log_->Log(std::make_unique<RtcEventProbeResultFailure>(
|
||||
cluster_id, ProbeFailureReason::kInvalidSendReceiveRatio));
|
||||
}
|
||||
return absl::nullopt;
|
||||
}
|
||||
RTC_LOG(LS_INFO) << "Probing successful"
|
||||
" [cluster id: "
|
||||
<< cluster_id << "] [send: " << ToString(send_size) << " / "
|
||||
<< ToString(send_interval) << " = " << ToString(send_rate)
|
||||
<< " ]"
|
||||
" [receive: "
|
||||
<< ToString(receive_size) << " / "
|
||||
<< ToString(receive_interval) << " = "
|
||||
<< ToString(receive_rate) << "]";
|
||||
|
||||
DataRate res = std::min(send_rate, receive_rate);
|
||||
// If we're receiving at significantly lower bitrate than we were sending at,
|
||||
// it suggests that we've found the true capacity of the link. In this case,
|
||||
// set the target bitrate slightly lower to not immediately overuse.
|
||||
if (receive_rate < kMinRatioForUnsaturatedLink * send_rate) {
|
||||
RTC_DCHECK_GT(send_rate, receive_rate);
|
||||
res = kTargetUtilizationFraction * receive_rate;
|
||||
}
|
||||
if (event_log_) {
|
||||
event_log_->Log(
|
||||
std::make_unique<RtcEventProbeResultSuccess>(cluster_id, res.bps()));
|
||||
}
|
||||
estimated_data_rate_ = res;
|
||||
return estimated_data_rate_;
|
||||
}
|
||||
|
||||
absl::optional<DataRate>
|
||||
ProbeBitrateEstimator::FetchAndResetLastEstimatedBitrate() {
|
||||
absl::optional<DataRate> estimated_data_rate = estimated_data_rate_;
|
||||
estimated_data_rate_.reset();
|
||||
return estimated_data_rate;
|
||||
}
|
||||
|
||||
void ProbeBitrateEstimator::EraseOldClusters(Timestamp timestamp) {
|
||||
for (auto it = clusters_.begin(); it != clusters_.end();) {
|
||||
if (it->second.last_receive + kMaxClusterHistory < timestamp) {
|
||||
it = clusters_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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_PROBE_BITRATE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_BITRATE_ESTIMATOR_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/timestamp.h"
|
||||
|
||||
namespace webrtc {
|
||||
class RtcEventLog;
|
||||
|
||||
class ProbeBitrateEstimator {
|
||||
public:
|
||||
explicit ProbeBitrateEstimator(RtcEventLog* event_log);
|
||||
~ProbeBitrateEstimator();
|
||||
|
||||
// Should be called for every probe packet we receive feedback about.
|
||||
// Returns the estimated bitrate if the probe completes a valid cluster.
|
||||
absl::optional<DataRate> HandleProbeAndEstimateBitrate(
|
||||
const PacketResult& packet_feedback);
|
||||
|
||||
absl::optional<DataRate> FetchAndResetLastEstimatedBitrate();
|
||||
|
||||
private:
|
||||
struct AggregatedCluster {
|
||||
int num_probes = 0;
|
||||
Timestamp first_send = Timestamp::PlusInfinity();
|
||||
Timestamp last_send = Timestamp::MinusInfinity();
|
||||
Timestamp first_receive = Timestamp::PlusInfinity();
|
||||
Timestamp last_receive = Timestamp::MinusInfinity();
|
||||
DataSize size_last_send = DataSize::Zero();
|
||||
DataSize size_first_receive = DataSize::Zero();
|
||||
DataSize size_total = DataSize::Zero();
|
||||
};
|
||||
|
||||
// Erases old cluster data that was seen before `timestamp`.
|
||||
void EraseOldClusters(Timestamp timestamp);
|
||||
|
||||
std::map<int, AggregatedCluster> clusters_;
|
||||
RtcEventLog* const event_log_;
|
||||
absl::optional<DataRate> estimated_data_rate_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_BITRATE_ESTIMATOR_H_
|
||||
|
|
@ -0,0 +1,555 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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/probe_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/rtc_event_log/rtc_event_log.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 "logging/rtc_event_log/events/rtc_event_probe_cluster_created.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
// Maximum waiting time from the time of initiating probing to getting
|
||||
// the measured results back.
|
||||
constexpr TimeDelta kMaxWaitingTimeForProbingResult = TimeDelta::Seconds(1);
|
||||
|
||||
// Default probing bitrate limit. Applied only when the application didn't
|
||||
// specify max bitrate.
|
||||
constexpr DataRate kDefaultMaxProbingBitrate = DataRate::KilobitsPerSec(5000);
|
||||
|
||||
// If the bitrate drops to a factor `kBitrateDropThreshold` or lower
|
||||
// and we recover within `kBitrateDropTimeoutMs`, then we'll send
|
||||
// a probe at a fraction `kProbeFractionAfterDrop` of the original bitrate.
|
||||
constexpr double kBitrateDropThreshold = 0.66;
|
||||
constexpr TimeDelta kBitrateDropTimeout = TimeDelta::Seconds(5);
|
||||
constexpr double kProbeFractionAfterDrop = 0.85;
|
||||
|
||||
// Timeout for probing after leaving ALR. If the bitrate drops significantly,
|
||||
// (as determined by the delay based estimator) and we leave ALR, then we will
|
||||
// send a probe if we recover within `kLeftAlrTimeoutMs` ms.
|
||||
constexpr TimeDelta kAlrEndedTimeout = TimeDelta::Seconds(3);
|
||||
|
||||
// The expected uncertainty of probe result (as a fraction of the target probe
|
||||
// This is a limit on how often probing can be done when there is a BW
|
||||
// drop detected in ALR.
|
||||
constexpr TimeDelta kMinTimeBetweenAlrProbes = TimeDelta::Seconds(5);
|
||||
|
||||
// bitrate). Used to avoid probing if the probe bitrate is close to our current
|
||||
// estimate.
|
||||
constexpr double kProbeUncertainty = 0.05;
|
||||
|
||||
// Use probing to recover faster after large bitrate estimate drops.
|
||||
constexpr char kBweRapidRecoveryExperiment[] =
|
||||
"WebRTC-BweRapidRecoveryExperiment";
|
||||
|
||||
void MaybeLogProbeClusterCreated(RtcEventLog* event_log,
|
||||
const ProbeClusterConfig& probe) {
|
||||
RTC_DCHECK(event_log);
|
||||
if (!event_log) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataSize min_data_size = probe.target_data_rate * probe.target_duration;
|
||||
event_log->Log(std::make_unique<RtcEventProbeClusterCreated>(
|
||||
probe.id, probe.target_data_rate.bps(), probe.target_probe_count,
|
||||
min_data_size.bytes()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProbeControllerConfig::ProbeControllerConfig(
|
||||
const FieldTrialsView* key_value_config)
|
||||
: first_exponential_probe_scale("p1", 3.0),
|
||||
second_exponential_probe_scale("p2", 6.0),
|
||||
further_exponential_probe_scale("step_size", 2),
|
||||
further_probe_threshold("further_probe_threshold", 0.7),
|
||||
alr_probing_interval("alr_interval", TimeDelta::Seconds(5)),
|
||||
alr_probe_scale("alr_scale", 2),
|
||||
network_state_estimate_probing_interval("network_state_interval",
|
||||
TimeDelta::PlusInfinity()),
|
||||
probe_if_estimate_lower_than_network_state_estimate_ratio(
|
||||
"est_lower_than_network_ratio",
|
||||
0),
|
||||
estimate_lower_than_network_state_estimate_probing_interval(
|
||||
"est_lower_than_network_interval",
|
||||
TimeDelta::Seconds(3)),
|
||||
network_state_probe_scale("network_state_scale", 1.0),
|
||||
network_state_probe_duration("network_state_probe_duration",
|
||||
TimeDelta::Millis(15)),
|
||||
|
||||
probe_on_max_allocated_bitrate_change("probe_max_allocation", true),
|
||||
first_allocation_probe_scale("alloc_p1", 1),
|
||||
second_allocation_probe_scale("alloc_p2", 2),
|
||||
allocation_probe_limit_by_current_scale("alloc_current_bwe_limit"),
|
||||
min_probe_packets_sent("min_probe_packets_sent", 5),
|
||||
min_probe_duration("min_probe_duration", TimeDelta::Millis(15)),
|
||||
loss_limited_probe_scale("loss_limited_scale", 1.5),
|
||||
skip_if_estimate_larger_than_fraction_of_max(
|
||||
"skip_if_est_larger_than_fraction_of_max",
|
||||
0.0) {
|
||||
ParseFieldTrial(
|
||||
{&first_exponential_probe_scale, &second_exponential_probe_scale,
|
||||
&further_exponential_probe_scale, &further_probe_threshold,
|
||||
&alr_probing_interval, &alr_probe_scale,
|
||||
&probe_on_max_allocated_bitrate_change, &first_allocation_probe_scale,
|
||||
&second_allocation_probe_scale, &allocation_probe_limit_by_current_scale,
|
||||
&min_probe_duration, &network_state_estimate_probing_interval,
|
||||
&probe_if_estimate_lower_than_network_state_estimate_ratio,
|
||||
&estimate_lower_than_network_state_estimate_probing_interval,
|
||||
&network_state_probe_scale, &network_state_probe_duration,
|
||||
&min_probe_packets_sent, &loss_limited_probe_scale,
|
||||
&skip_if_estimate_larger_than_fraction_of_max},
|
||||
key_value_config->Lookup("WebRTC-Bwe-ProbingConfiguration"));
|
||||
|
||||
// Specialized keys overriding subsets of WebRTC-Bwe-ProbingConfiguration
|
||||
ParseFieldTrial(
|
||||
{&first_exponential_probe_scale, &second_exponential_probe_scale},
|
||||
key_value_config->Lookup("WebRTC-Bwe-InitialProbing"));
|
||||
ParseFieldTrial({&further_exponential_probe_scale, &further_probe_threshold},
|
||||
key_value_config->Lookup("WebRTC-Bwe-ExponentialProbing"));
|
||||
ParseFieldTrial(
|
||||
{&alr_probing_interval, &alr_probe_scale, &loss_limited_probe_scale},
|
||||
key_value_config->Lookup("WebRTC-Bwe-AlrProbing"));
|
||||
ParseFieldTrial(
|
||||
{&first_allocation_probe_scale, &second_allocation_probe_scale,
|
||||
&allocation_probe_limit_by_current_scale},
|
||||
key_value_config->Lookup("WebRTC-Bwe-AllocationProbing"));
|
||||
ParseFieldTrial({&min_probe_packets_sent, &min_probe_duration},
|
||||
key_value_config->Lookup("WebRTC-Bwe-ProbingBehavior"));
|
||||
}
|
||||
|
||||
ProbeControllerConfig::ProbeControllerConfig(const ProbeControllerConfig&) =
|
||||
default;
|
||||
ProbeControllerConfig::~ProbeControllerConfig() = default;
|
||||
|
||||
ProbeController::ProbeController(const FieldTrialsView* key_value_config,
|
||||
RtcEventLog* event_log)
|
||||
: network_available_(false),
|
||||
enable_periodic_alr_probing_(false),
|
||||
in_rapid_recovery_experiment_(absl::StartsWith(
|
||||
key_value_config->Lookup(kBweRapidRecoveryExperiment),
|
||||
"Enabled")),
|
||||
event_log_(event_log),
|
||||
config_(ProbeControllerConfig(key_value_config)) {
|
||||
Reset(Timestamp::Zero());
|
||||
}
|
||||
|
||||
ProbeController::~ProbeController() {}
|
||||
|
||||
std::vector<ProbeClusterConfig> ProbeController::SetBitrates(
|
||||
DataRate min_bitrate,
|
||||
DataRate start_bitrate,
|
||||
DataRate max_bitrate,
|
||||
Timestamp at_time) {
|
||||
if (start_bitrate > DataRate::Zero()) {
|
||||
start_bitrate_ = start_bitrate;
|
||||
estimated_bitrate_ = start_bitrate;
|
||||
} else if (start_bitrate_.IsZero()) {
|
||||
start_bitrate_ = min_bitrate;
|
||||
}
|
||||
|
||||
// The reason we use the variable `old_max_bitrate_pbs` is because we
|
||||
// need to set `max_bitrate_` before we call InitiateProbing.
|
||||
DataRate old_max_bitrate = max_bitrate_;
|
||||
max_bitrate_ =
|
||||
max_bitrate.IsFinite() ? max_bitrate : kDefaultMaxProbingBitrate;
|
||||
|
||||
switch (state_) {
|
||||
case State::kInit:
|
||||
if (network_available_)
|
||||
return InitiateExponentialProbing(at_time);
|
||||
break;
|
||||
|
||||
case State::kWaitingForProbingResult:
|
||||
break;
|
||||
|
||||
case State::kProbingComplete:
|
||||
// If the new max bitrate is higher than both the old max bitrate and the
|
||||
// estimate then initiate probing.
|
||||
if (!estimated_bitrate_.IsZero() && old_max_bitrate < max_bitrate_ &&
|
||||
estimated_bitrate_ < max_bitrate_) {
|
||||
return InitiateProbing(at_time, {max_bitrate_}, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return std::vector<ProbeClusterConfig>();
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> ProbeController::OnMaxTotalAllocatedBitrate(
|
||||
DataRate max_total_allocated_bitrate,
|
||||
Timestamp at_time) {
|
||||
const bool in_alr = alr_start_time_.has_value();
|
||||
const bool allow_allocation_probe = in_alr;
|
||||
|
||||
if (config_.probe_on_max_allocated_bitrate_change &&
|
||||
state_ == State::kProbingComplete &&
|
||||
max_total_allocated_bitrate != max_total_allocated_bitrate_ &&
|
||||
estimated_bitrate_ < max_bitrate_ &&
|
||||
estimated_bitrate_ < max_total_allocated_bitrate &&
|
||||
allow_allocation_probe) {
|
||||
max_total_allocated_bitrate_ = max_total_allocated_bitrate;
|
||||
|
||||
if (!config_.first_allocation_probe_scale)
|
||||
return std::vector<ProbeClusterConfig>();
|
||||
|
||||
DataRate first_probe_rate = max_total_allocated_bitrate *
|
||||
config_.first_allocation_probe_scale.Value();
|
||||
DataRate current_bwe_limit =
|
||||
!config_.allocation_probe_limit_by_current_scale
|
||||
? DataRate::PlusInfinity()
|
||||
: estimated_bitrate_ *
|
||||
config_.allocation_probe_limit_by_current_scale.Value();
|
||||
bool limited_by_current_bwe = current_bwe_limit < first_probe_rate;
|
||||
if (limited_by_current_bwe) {
|
||||
first_probe_rate = current_bwe_limit;
|
||||
}
|
||||
|
||||
std::vector<DataRate> probes = {first_probe_rate};
|
||||
if (!limited_by_current_bwe && config_.second_allocation_probe_scale) {
|
||||
DataRate second_probe_rate =
|
||||
max_total_allocated_bitrate *
|
||||
config_.second_allocation_probe_scale.Value();
|
||||
limited_by_current_bwe = current_bwe_limit < second_probe_rate;
|
||||
if (limited_by_current_bwe) {
|
||||
second_probe_rate = current_bwe_limit;
|
||||
}
|
||||
if (second_probe_rate > first_probe_rate)
|
||||
probes.push_back(second_probe_rate);
|
||||
}
|
||||
bool allow_further_probing = limited_by_current_bwe;
|
||||
|
||||
return InitiateProbing(at_time, probes, allow_further_probing);
|
||||
}
|
||||
max_total_allocated_bitrate_ = max_total_allocated_bitrate;
|
||||
return std::vector<ProbeClusterConfig>();
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> ProbeController::OnNetworkAvailability(
|
||||
NetworkAvailability msg) {
|
||||
network_available_ = msg.network_available;
|
||||
|
||||
if (!network_available_ && state_ == State::kWaitingForProbingResult) {
|
||||
state_ = State::kProbingComplete;
|
||||
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
|
||||
}
|
||||
|
||||
if (network_available_ && state_ == State::kInit && !start_bitrate_.IsZero())
|
||||
return InitiateExponentialProbing(msg.at_time);
|
||||
return std::vector<ProbeClusterConfig>();
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> ProbeController::InitiateExponentialProbing(
|
||||
Timestamp at_time) {
|
||||
RTC_DCHECK(network_available_);
|
||||
RTC_DCHECK(state_ == State::kInit);
|
||||
RTC_DCHECK_GT(start_bitrate_, DataRate::Zero());
|
||||
|
||||
// When probing at 1.8 Mbps ( 6x 300), this represents a threshold of
|
||||
// 1.2 Mbps to continue probing.
|
||||
std::vector<DataRate> probes = {config_.first_exponential_probe_scale *
|
||||
start_bitrate_};
|
||||
if (config_.second_exponential_probe_scale &&
|
||||
config_.second_exponential_probe_scale.GetOptional().value() > 0) {
|
||||
probes.push_back(config_.second_exponential_probe_scale.Value() *
|
||||
start_bitrate_);
|
||||
}
|
||||
return InitiateProbing(at_time, probes, true);
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> ProbeController::SetEstimatedBitrate(
|
||||
DataRate bitrate,
|
||||
BandwidthLimitedCause bandwidth_limited_cause,
|
||||
Timestamp at_time) {
|
||||
bandwidth_limited_cause_ = bandwidth_limited_cause;
|
||||
if (bitrate < kBitrateDropThreshold * estimated_bitrate_) {
|
||||
time_of_last_large_drop_ = at_time;
|
||||
bitrate_before_last_large_drop_ = estimated_bitrate_;
|
||||
}
|
||||
estimated_bitrate_ = bitrate;
|
||||
|
||||
if (state_ == State::kWaitingForProbingResult) {
|
||||
// Continue probing if probing results indicate channel has greater
|
||||
// capacity.
|
||||
DataRate network_state_estimate_probe_further_limit =
|
||||
config_.network_state_estimate_probing_interval->IsFinite() &&
|
||||
network_estimate_
|
||||
? network_estimate_->link_capacity_upper *
|
||||
config_.further_probe_threshold
|
||||
: DataRate::PlusInfinity();
|
||||
RTC_LOG(LS_INFO) << "Measured bitrate: " << bitrate
|
||||
<< " Minimum to probe further: "
|
||||
<< min_bitrate_to_probe_further_ << " upper limit: "
|
||||
<< network_state_estimate_probe_further_limit;
|
||||
|
||||
if (bitrate > min_bitrate_to_probe_further_ &&
|
||||
bitrate <= network_state_estimate_probe_further_limit) {
|
||||
return InitiateProbing(
|
||||
at_time, {config_.further_exponential_probe_scale * bitrate}, true);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ProbeController::EnablePeriodicAlrProbing(bool enable) {
|
||||
enable_periodic_alr_probing_ = enable;
|
||||
}
|
||||
|
||||
void ProbeController::SetAlrStartTimeMs(
|
||||
absl::optional<int64_t> alr_start_time_ms) {
|
||||
if (alr_start_time_ms) {
|
||||
alr_start_time_ = Timestamp::Millis(*alr_start_time_ms);
|
||||
} else {
|
||||
alr_start_time_ = absl::nullopt;
|
||||
}
|
||||
}
|
||||
void ProbeController::SetAlrEndedTimeMs(int64_t alr_end_time_ms) {
|
||||
alr_end_time_.emplace(Timestamp::Millis(alr_end_time_ms));
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> ProbeController::RequestProbe(
|
||||
Timestamp at_time) {
|
||||
// Called once we have returned to normal state after a large drop in
|
||||
// estimated bandwidth. The current response is to initiate a single probe
|
||||
// session (if not already probing) at the previous bitrate.
|
||||
//
|
||||
// If the probe session fails, the assumption is that this drop was a
|
||||
// real one from a competing flow or a network change.
|
||||
bool in_alr = alr_start_time_.has_value();
|
||||
bool alr_ended_recently =
|
||||
(alr_end_time_.has_value() &&
|
||||
at_time - alr_end_time_.value() < kAlrEndedTimeout);
|
||||
if (in_alr || alr_ended_recently || in_rapid_recovery_experiment_) {
|
||||
if (state_ == State::kProbingComplete) {
|
||||
DataRate suggested_probe =
|
||||
kProbeFractionAfterDrop * bitrate_before_last_large_drop_;
|
||||
DataRate min_expected_probe_result =
|
||||
(1 - kProbeUncertainty) * suggested_probe;
|
||||
TimeDelta time_since_drop = at_time - time_of_last_large_drop_;
|
||||
TimeDelta time_since_probe = at_time - last_bwe_drop_probing_time_;
|
||||
if (min_expected_probe_result > estimated_bitrate_ &&
|
||||
time_since_drop < kBitrateDropTimeout &&
|
||||
time_since_probe > kMinTimeBetweenAlrProbes) {
|
||||
RTC_LOG(LS_INFO) << "Detected big bandwidth drop, start probing.";
|
||||
// Track how often we probe in response to bandwidth drop in ALR.
|
||||
RTC_HISTOGRAM_COUNTS_10000(
|
||||
"WebRTC.BWE.BweDropProbingIntervalInS",
|
||||
(at_time - last_bwe_drop_probing_time_).seconds());
|
||||
last_bwe_drop_probing_time_ = at_time;
|
||||
return InitiateProbing(at_time, {suggested_probe}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::vector<ProbeClusterConfig>();
|
||||
}
|
||||
|
||||
void ProbeController::SetNetworkStateEstimate(
|
||||
webrtc::NetworkStateEstimate estimate) {
|
||||
network_estimate_ = estimate;
|
||||
}
|
||||
|
||||
void ProbeController::Reset(Timestamp at_time) {
|
||||
bandwidth_limited_cause_ = BandwidthLimitedCause::kDelayBasedLimited;
|
||||
state_ = State::kInit;
|
||||
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
|
||||
time_last_probing_initiated_ = Timestamp::Zero();
|
||||
estimated_bitrate_ = DataRate::Zero();
|
||||
network_estimate_ = absl::nullopt;
|
||||
start_bitrate_ = DataRate::Zero();
|
||||
max_bitrate_ = kDefaultMaxProbingBitrate;
|
||||
Timestamp now = at_time;
|
||||
last_bwe_drop_probing_time_ = now;
|
||||
alr_end_time_.reset();
|
||||
time_of_last_large_drop_ = now;
|
||||
bitrate_before_last_large_drop_ = DataRate::Zero();
|
||||
max_total_allocated_bitrate_ = DataRate::Zero();
|
||||
}
|
||||
|
||||
bool ProbeController::TimeForAlrProbe(Timestamp at_time) const {
|
||||
if (enable_periodic_alr_probing_ && alr_start_time_) {
|
||||
Timestamp next_probe_time =
|
||||
std::max(*alr_start_time_, time_last_probing_initiated_) +
|
||||
config_.alr_probing_interval;
|
||||
return at_time >= next_probe_time;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProbeController::TimeForNetworkStateProbe(Timestamp at_time) const {
|
||||
if (!network_estimate_ ||
|
||||
network_estimate_->link_capacity_upper.IsInfinite()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool probe_due_to_low_estimate =
|
||||
bandwidth_limited_cause_ == BandwidthLimitedCause::kDelayBasedLimited &&
|
||||
estimated_bitrate_ <
|
||||
config_.probe_if_estimate_lower_than_network_state_estimate_ratio *
|
||||
network_estimate_->link_capacity_upper;
|
||||
if (probe_due_to_low_estimate &&
|
||||
config_.estimate_lower_than_network_state_estimate_probing_interval
|
||||
->IsFinite()) {
|
||||
Timestamp next_probe_time =
|
||||
time_last_probing_initiated_ +
|
||||
config_.estimate_lower_than_network_state_estimate_probing_interval;
|
||||
return at_time >= next_probe_time;
|
||||
}
|
||||
|
||||
bool periodic_probe =
|
||||
estimated_bitrate_ < network_estimate_->link_capacity_upper;
|
||||
if (periodic_probe &&
|
||||
config_.network_state_estimate_probing_interval->IsFinite()) {
|
||||
Timestamp next_probe_time = time_last_probing_initiated_ +
|
||||
config_.network_state_estimate_probing_interval;
|
||||
return at_time >= next_probe_time;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> ProbeController::Process(Timestamp at_time) {
|
||||
if (at_time - time_last_probing_initiated_ >
|
||||
kMaxWaitingTimeForProbingResult) {
|
||||
if (state_ == State::kWaitingForProbingResult) {
|
||||
RTC_LOG(LS_INFO) << "kWaitingForProbingResult: timeout";
|
||||
state_ = State::kProbingComplete;
|
||||
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
|
||||
}
|
||||
}
|
||||
if (estimated_bitrate_.IsZero() || state_ != State::kProbingComplete) {
|
||||
return {};
|
||||
}
|
||||
if (TimeForAlrProbe(at_time) || TimeForNetworkStateProbe(at_time)) {
|
||||
return InitiateProbing(
|
||||
at_time, {estimated_bitrate_ * config_.alr_probe_scale}, true);
|
||||
}
|
||||
return std::vector<ProbeClusterConfig>();
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> ProbeController::InitiateProbing(
|
||||
Timestamp now,
|
||||
std::vector<DataRate> bitrates_to_probe,
|
||||
bool probe_further) {
|
||||
if (config_.skip_if_estimate_larger_than_fraction_of_max > 0) {
|
||||
DataRate network_estimate = network_estimate_
|
||||
? network_estimate_->link_capacity_upper
|
||||
: DataRate::PlusInfinity();
|
||||
DataRate max_probe_rate =
|
||||
max_total_allocated_bitrate_.IsZero()
|
||||
? max_bitrate_
|
||||
: std::min(max_total_allocated_bitrate_, max_bitrate_);
|
||||
if (std::min(network_estimate, estimated_bitrate_) >
|
||||
config_.skip_if_estimate_larger_than_fraction_of_max * max_probe_rate) {
|
||||
state_ = State::kProbingComplete;
|
||||
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
DataRate max_probe_bitrate = max_bitrate_;
|
||||
if (max_total_allocated_bitrate_ > DataRate::Zero()) {
|
||||
// If a max allocated bitrate has been configured, allow probing up to 2x
|
||||
// that rate. This allows some overhead to account for bursty streams,
|
||||
// which otherwise would have to ramp up when the overshoot is already in
|
||||
// progress.
|
||||
// It also avoids minor quality reduction caused by probes often being
|
||||
// received at slightly less than the target probe bitrate.
|
||||
max_probe_bitrate =
|
||||
std::min(max_probe_bitrate, max_total_allocated_bitrate_ * 2);
|
||||
}
|
||||
|
||||
DataRate estimate_capped_bitrate = DataRate::PlusInfinity();
|
||||
switch (bandwidth_limited_cause_) {
|
||||
case BandwidthLimitedCause::kRttBasedBackOffHighRtt:
|
||||
case BandwidthLimitedCause::kDelayBasedLimitedDelayIncreased:
|
||||
case BandwidthLimitedCause::kLossLimitedBwe:
|
||||
RTC_LOG(LS_INFO) << "Not sending probe in bandwidth limited state.";
|
||||
return {};
|
||||
case BandwidthLimitedCause::kLossLimitedBweIncreasing:
|
||||
estimate_capped_bitrate =
|
||||
std::min(max_probe_bitrate,
|
||||
estimated_bitrate_ * config_.loss_limited_probe_scale);
|
||||
break;
|
||||
case BandwidthLimitedCause::kDelayBasedLimited:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (config_.network_state_estimate_probing_interval->IsFinite() &&
|
||||
network_estimate_ && network_estimate_->link_capacity_upper.IsFinite()) {
|
||||
if (network_estimate_->link_capacity_upper.IsZero()) {
|
||||
RTC_LOG(LS_INFO) << "Not sending probe, Network state estimate is zero";
|
||||
return {};
|
||||
}
|
||||
estimate_capped_bitrate = std::min(
|
||||
{estimate_capped_bitrate, max_probe_bitrate,
|
||||
std::max(estimated_bitrate_, network_estimate_->link_capacity_upper *
|
||||
config_.network_state_probe_scale)});
|
||||
}
|
||||
|
||||
std::vector<ProbeClusterConfig> pending_probes;
|
||||
for (DataRate bitrate : bitrates_to_probe) {
|
||||
RTC_DCHECK(!bitrate.IsZero());
|
||||
bitrate = std::min(bitrate, estimate_capped_bitrate);
|
||||
if (bitrate > max_probe_bitrate) {
|
||||
bitrate = max_probe_bitrate;
|
||||
probe_further = false;
|
||||
}
|
||||
|
||||
ProbeClusterConfig config;
|
||||
config.at_time = now;
|
||||
config.target_data_rate = bitrate;
|
||||
if (network_estimate_ &&
|
||||
config_.network_state_estimate_probing_interval->IsFinite()) {
|
||||
config.target_duration = config_.network_state_probe_duration;
|
||||
} else {
|
||||
config.target_duration = config_.min_probe_duration;
|
||||
}
|
||||
|
||||
config.target_probe_count = config_.min_probe_packets_sent;
|
||||
config.id = next_probe_cluster_id_;
|
||||
next_probe_cluster_id_++;
|
||||
MaybeLogProbeClusterCreated(event_log_, config);
|
||||
pending_probes.push_back(config);
|
||||
}
|
||||
time_last_probing_initiated_ = now;
|
||||
if (probe_further) {
|
||||
state_ = State::kWaitingForProbingResult;
|
||||
// Dont expect probe results to be larger than a fraction of the actual
|
||||
// probe rate.
|
||||
min_bitrate_to_probe_further_ =
|
||||
std::min(estimate_capped_bitrate, (*(bitrates_to_probe.end() - 1))) *
|
||||
config_.further_probe_threshold;
|
||||
} else {
|
||||
state_ = State::kProbingComplete;
|
||||
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
|
||||
}
|
||||
return pending_probes;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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_PROBE_CONTROLLER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_CONTROLLER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/rtc_event_log/rtc_event_log.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct ProbeControllerConfig {
|
||||
explicit ProbeControllerConfig(const FieldTrialsView* key_value_config);
|
||||
ProbeControllerConfig(const ProbeControllerConfig&);
|
||||
ProbeControllerConfig& operator=(const ProbeControllerConfig&) = default;
|
||||
~ProbeControllerConfig();
|
||||
|
||||
// These parameters configure the initial probes. First we send one or two
|
||||
// probes of sizes p1 * start_bitrate_ and p2 * start_bitrate_.
|
||||
// Then whenever we get a bitrate estimate of at least further_probe_threshold
|
||||
// times the size of the last sent probe we'll send another one of size
|
||||
// step_size times the new estimate.
|
||||
FieldTrialParameter<double> first_exponential_probe_scale;
|
||||
FieldTrialOptional<double> second_exponential_probe_scale;
|
||||
FieldTrialParameter<double> further_exponential_probe_scale;
|
||||
FieldTrialParameter<double> further_probe_threshold;
|
||||
|
||||
// Configures how often we send ALR probes and how big they are.
|
||||
FieldTrialParameter<TimeDelta> alr_probing_interval;
|
||||
FieldTrialParameter<double> alr_probe_scale;
|
||||
|
||||
// Configures how often we send probes if NetworkStateEstimate is available.
|
||||
FieldTrialParameter<TimeDelta> network_state_estimate_probing_interval;
|
||||
// Periodically probe as long as the the ratio beteeen current estimate and
|
||||
// NetworkStateEstimate is lower then this.
|
||||
FieldTrialParameter<double>
|
||||
probe_if_estimate_lower_than_network_state_estimate_ratio;
|
||||
FieldTrialParameter<TimeDelta>
|
||||
estimate_lower_than_network_state_estimate_probing_interval;
|
||||
FieldTrialParameter<double> network_state_probe_scale;
|
||||
// Overrides min_probe_duration if network_state_estimate_probing_interval
|
||||
// is set and a network state estimate is known.
|
||||
FieldTrialParameter<TimeDelta> network_state_probe_duration;
|
||||
|
||||
// Configures the probes emitted by changed to the allocated bitrate.
|
||||
FieldTrialParameter<bool> probe_on_max_allocated_bitrate_change;
|
||||
FieldTrialOptional<double> first_allocation_probe_scale;
|
||||
FieldTrialOptional<double> second_allocation_probe_scale;
|
||||
FieldTrialOptional<double> allocation_probe_limit_by_current_scale;
|
||||
|
||||
// The minimum number probing packets used.
|
||||
FieldTrialParameter<int> min_probe_packets_sent;
|
||||
// The minimum probing duration.
|
||||
FieldTrialParameter<TimeDelta> min_probe_duration;
|
||||
FieldTrialParameter<double> loss_limited_probe_scale;
|
||||
// Dont send a probe if min(estimate, network state estimate) is larger than
|
||||
// this fraction of the set max bitrate.
|
||||
FieldTrialParameter<double> skip_if_estimate_larger_than_fraction_of_max;
|
||||
};
|
||||
|
||||
// Reason that bandwidth estimate is limited. Bandwidth estimate can be limited
|
||||
// by either delay based bwe, or loss based bwe when it increases/decreases the
|
||||
// estimate.
|
||||
enum class BandwidthLimitedCause {
|
||||
kLossLimitedBweIncreasing = 0,
|
||||
kLossLimitedBwe = 1,
|
||||
kDelayBasedLimited = 2,
|
||||
kDelayBasedLimitedDelayIncreased = 3,
|
||||
kRttBasedBackOffHighRtt = 4
|
||||
};
|
||||
|
||||
// This class controls initiation of probing to estimate initial channel
|
||||
// capacity. There is also support for probing during a session when max
|
||||
// bitrate is adjusted by an application.
|
||||
class ProbeController {
|
||||
public:
|
||||
explicit ProbeController(const FieldTrialsView* key_value_config,
|
||||
RtcEventLog* event_log);
|
||||
~ProbeController();
|
||||
|
||||
ProbeController(const ProbeController&) = delete;
|
||||
ProbeController& operator=(const ProbeController&) = delete;
|
||||
|
||||
ABSL_MUST_USE_RESULT std::vector<ProbeClusterConfig> SetBitrates(
|
||||
DataRate min_bitrate,
|
||||
DataRate start_bitrate,
|
||||
DataRate max_bitrate,
|
||||
Timestamp at_time);
|
||||
|
||||
// The total bitrate, as opposed to the max bitrate, is the sum of the
|
||||
// configured bitrates for all active streams.
|
||||
ABSL_MUST_USE_RESULT std::vector<ProbeClusterConfig>
|
||||
OnMaxTotalAllocatedBitrate(DataRate max_total_allocated_bitrate,
|
||||
Timestamp at_time);
|
||||
|
||||
ABSL_MUST_USE_RESULT std::vector<ProbeClusterConfig> OnNetworkAvailability(
|
||||
NetworkAvailability msg);
|
||||
|
||||
ABSL_MUST_USE_RESULT std::vector<ProbeClusterConfig> SetEstimatedBitrate(
|
||||
DataRate bitrate,
|
||||
BandwidthLimitedCause bandwidth_limited_cause,
|
||||
Timestamp at_time);
|
||||
|
||||
void EnablePeriodicAlrProbing(bool enable);
|
||||
|
||||
void SetAlrStartTimeMs(absl::optional<int64_t> alr_start_time);
|
||||
void SetAlrEndedTimeMs(int64_t alr_end_time);
|
||||
|
||||
ABSL_MUST_USE_RESULT std::vector<ProbeClusterConfig> RequestProbe(
|
||||
Timestamp at_time);
|
||||
|
||||
void SetNetworkStateEstimate(webrtc::NetworkStateEstimate estimate);
|
||||
|
||||
// Resets the ProbeController to a state equivalent to as if it was just
|
||||
// created EXCEPT for `enable_periodic_alr_probing_` and
|
||||
// `network_available_`.
|
||||
void Reset(Timestamp at_time);
|
||||
|
||||
ABSL_MUST_USE_RESULT std::vector<ProbeClusterConfig> Process(
|
||||
Timestamp at_time);
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
// Initial state where no probing has been triggered yet.
|
||||
kInit,
|
||||
// Waiting for probing results to continue further probing.
|
||||
kWaitingForProbingResult,
|
||||
// Probing is complete.
|
||||
kProbingComplete,
|
||||
};
|
||||
|
||||
ABSL_MUST_USE_RESULT std::vector<ProbeClusterConfig>
|
||||
InitiateExponentialProbing(Timestamp at_time);
|
||||
ABSL_MUST_USE_RESULT std::vector<ProbeClusterConfig> InitiateProbing(
|
||||
Timestamp now,
|
||||
std::vector<DataRate> bitrates_to_probe,
|
||||
bool probe_further);
|
||||
bool TimeForAlrProbe(Timestamp at_time) const;
|
||||
bool TimeForNetworkStateProbe(Timestamp at_time) const;
|
||||
|
||||
bool network_available_;
|
||||
BandwidthLimitedCause bandwidth_limited_cause_ =
|
||||
BandwidthLimitedCause::kDelayBasedLimited;
|
||||
State state_;
|
||||
DataRate min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
|
||||
Timestamp time_last_probing_initiated_ = Timestamp::MinusInfinity();
|
||||
DataRate estimated_bitrate_ = DataRate::Zero();
|
||||
absl::optional<webrtc::NetworkStateEstimate> network_estimate_;
|
||||
DataRate start_bitrate_ = DataRate::Zero();
|
||||
DataRate max_bitrate_ = DataRate::PlusInfinity();
|
||||
Timestamp last_bwe_drop_probing_time_ = Timestamp::Zero();
|
||||
absl::optional<Timestamp> alr_start_time_;
|
||||
absl::optional<Timestamp> alr_end_time_;
|
||||
bool enable_periodic_alr_probing_;
|
||||
Timestamp time_of_last_large_drop_ = Timestamp::MinusInfinity();
|
||||
DataRate bitrate_before_last_large_drop_ = DataRate::Zero();
|
||||
DataRate max_total_allocated_bitrate_ = DataRate::Zero();
|
||||
|
||||
const bool in_rapid_recovery_experiment_;
|
||||
RtcEventLog* event_log_;
|
||||
|
||||
int32_t next_probe_cluster_id_ = 1;
|
||||
|
||||
ProbeControllerConfig config_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_CONTROLLER_H_
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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/robust_throughput_estimator.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.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/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
RobustThroughputEstimator::RobustThroughputEstimator(
|
||||
const RobustThroughputEstimatorSettings& settings)
|
||||
: settings_(settings),
|
||||
latest_discarded_send_time_(Timestamp::MinusInfinity()) {
|
||||
RTC_DCHECK(settings.enabled);
|
||||
}
|
||||
|
||||
RobustThroughputEstimator::~RobustThroughputEstimator() {}
|
||||
|
||||
bool RobustThroughputEstimator::FirstPacketOutsideWindow() {
|
||||
if (window_.empty())
|
||||
return false;
|
||||
if (window_.size() > settings_.max_window_packets)
|
||||
return true;
|
||||
TimeDelta current_window_duration =
|
||||
window_.back().receive_time - window_.front().receive_time;
|
||||
if (current_window_duration > settings_.max_window_duration)
|
||||
return true;
|
||||
if (window_.size() > settings_.window_packets &&
|
||||
current_window_duration > settings_.min_window_duration) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RobustThroughputEstimator::IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketResult>& packet_feedback_vector) {
|
||||
RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(),
|
||||
packet_feedback_vector.end(),
|
||||
PacketResult::ReceiveTimeOrder()));
|
||||
for (const auto& packet : packet_feedback_vector) {
|
||||
// Ignore packets without valid send or receive times.
|
||||
// (This should not happen in production since lost packets are filtered
|
||||
// out before passing the feedback vector to the throughput estimator.
|
||||
// However, explicitly handling this case makes the estimator more robust
|
||||
// and avoids a hard-to-detect bad state.)
|
||||
if (packet.receive_time.IsInfinite() ||
|
||||
packet.sent_packet.send_time.IsInfinite()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Insert the new packet.
|
||||
window_.push_back(packet);
|
||||
window_.back().sent_packet.prior_unacked_data =
|
||||
window_.back().sent_packet.prior_unacked_data *
|
||||
settings_.unacked_weight;
|
||||
// In most cases, receive timestamps should already be in order, but in the
|
||||
// rare case where feedback packets have been reordered, we do some swaps to
|
||||
// ensure that the window is sorted.
|
||||
for (size_t i = window_.size() - 1;
|
||||
i > 0 && window_[i].receive_time < window_[i - 1].receive_time; i--) {
|
||||
std::swap(window_[i], window_[i - 1]);
|
||||
}
|
||||
constexpr TimeDelta kMaxReorderingTime = TimeDelta::Seconds(1);
|
||||
const TimeDelta receive_delta =
|
||||
(window_.back().receive_time - packet.receive_time);
|
||||
if (receive_delta > kMaxReorderingTime) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Severe packet re-ordering or timestamps offset changed: "
|
||||
<< receive_delta;
|
||||
window_.clear();
|
||||
latest_discarded_send_time_ = Timestamp::MinusInfinity();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove old packets.
|
||||
while (FirstPacketOutsideWindow()) {
|
||||
latest_discarded_send_time_ = std::max(
|
||||
latest_discarded_send_time_, window_.front().sent_packet.send_time);
|
||||
window_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
absl::optional<DataRate> RobustThroughputEstimator::bitrate() const {
|
||||
if (window_.empty() || window_.size() < settings_.required_packets)
|
||||
return absl::nullopt;
|
||||
|
||||
TimeDelta largest_recv_gap(TimeDelta::Zero());
|
||||
TimeDelta second_largest_recv_gap(TimeDelta::Zero());
|
||||
for (size_t i = 1; i < window_.size(); i++) {
|
||||
// Find receive time gaps.
|
||||
TimeDelta gap = window_[i].receive_time - window_[i - 1].receive_time;
|
||||
if (gap > largest_recv_gap) {
|
||||
second_largest_recv_gap = largest_recv_gap;
|
||||
largest_recv_gap = gap;
|
||||
} else if (gap > second_largest_recv_gap) {
|
||||
second_largest_recv_gap = gap;
|
||||
}
|
||||
}
|
||||
|
||||
Timestamp first_send_time = Timestamp::PlusInfinity();
|
||||
Timestamp last_send_time = Timestamp::MinusInfinity();
|
||||
Timestamp first_recv_time = Timestamp::PlusInfinity();
|
||||
Timestamp last_recv_time = Timestamp::MinusInfinity();
|
||||
DataSize recv_size = DataSize::Bytes(0);
|
||||
DataSize send_size = DataSize::Bytes(0);
|
||||
DataSize first_recv_size = DataSize::Bytes(0);
|
||||
DataSize last_send_size = DataSize::Bytes(0);
|
||||
size_t num_sent_packets_in_window = 0;
|
||||
for (const auto& packet : window_) {
|
||||
if (packet.receive_time < first_recv_time) {
|
||||
first_recv_time = packet.receive_time;
|
||||
first_recv_size =
|
||||
packet.sent_packet.size + packet.sent_packet.prior_unacked_data;
|
||||
}
|
||||
last_recv_time = std::max(last_recv_time, packet.receive_time);
|
||||
recv_size += packet.sent_packet.size;
|
||||
recv_size += packet.sent_packet.prior_unacked_data;
|
||||
|
||||
if (packet.sent_packet.send_time < latest_discarded_send_time_) {
|
||||
// If we have dropped packets from the window that were sent after
|
||||
// this packet, then this packet was reordered. Ignore it from
|
||||
// the send rate computation (since the send time may be very far
|
||||
// in the past, leading to underestimation of the send rate.)
|
||||
// However, ignoring packets creates a risk that we end up without
|
||||
// any packets left to compute a send rate.
|
||||
continue;
|
||||
}
|
||||
if (packet.sent_packet.send_time > last_send_time) {
|
||||
last_send_time = packet.sent_packet.send_time;
|
||||
last_send_size =
|
||||
packet.sent_packet.size + packet.sent_packet.prior_unacked_data;
|
||||
}
|
||||
first_send_time = std::min(first_send_time, packet.sent_packet.send_time);
|
||||
|
||||
send_size += packet.sent_packet.size;
|
||||
send_size += packet.sent_packet.prior_unacked_data;
|
||||
++num_sent_packets_in_window;
|
||||
}
|
||||
|
||||
// Suppose a packet of size S is sent every T milliseconds.
|
||||
// A window of N packets would contain N*S bytes, but the time difference
|
||||
// between the first and the last packet would only be (N-1)*T. Thus, we
|
||||
// need to remove the size of one packet to get the correct rate of S/T.
|
||||
// Which packet to remove (if the packets have varying sizes),
|
||||
// depends on the network model.
|
||||
// Suppose that 2 packets with sizes s1 and s2, are received at times t1
|
||||
// and t2, respectively. If the packets were transmitted back to back over
|
||||
// a bottleneck with rate capacity r, then we'd expect t2 = t1 + r * s2.
|
||||
// Thus, r = (t2-t1) / s2, so the size of the first packet doesn't affect
|
||||
// the difference between t1 and t2.
|
||||
// Analoguously, if the first packet is sent at time t1 and the sender
|
||||
// paces the packets at rate r, then the second packet can be sent at time
|
||||
// t2 = t1 + r * s1. Thus, the send rate estimate r = (t2-t1) / s1 doesn't
|
||||
// depend on the size of the last packet.
|
||||
recv_size -= first_recv_size;
|
||||
send_size -= last_send_size;
|
||||
|
||||
// Remove the largest gap by replacing it by the second largest gap.
|
||||
// This is to ensure that spurious "delay spikes" (i.e. when the
|
||||
// network stops transmitting packets for a short period, followed
|
||||
// by a burst of delayed packets), don't cause the estimate to drop.
|
||||
// This could cause an overestimation, which we guard against by
|
||||
// never returning an estimate above the send rate.
|
||||
RTC_DCHECK(first_recv_time.IsFinite());
|
||||
RTC_DCHECK(last_recv_time.IsFinite());
|
||||
TimeDelta recv_duration = (last_recv_time - first_recv_time) -
|
||||
largest_recv_gap + second_largest_recv_gap;
|
||||
recv_duration = std::max(recv_duration, TimeDelta::Millis(1));
|
||||
|
||||
if (num_sent_packets_in_window < settings_.required_packets) {
|
||||
// Too few send times to calculate a reliable send rate.
|
||||
return recv_size / recv_duration;
|
||||
}
|
||||
|
||||
RTC_DCHECK(first_send_time.IsFinite());
|
||||
RTC_DCHECK(last_send_time.IsFinite());
|
||||
TimeDelta send_duration = last_send_time - first_send_time;
|
||||
send_duration = std::max(send_duration, TimeDelta::Millis(1));
|
||||
|
||||
return std::min(send_size / send_duration, recv_size / recv_duration);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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_ROBUST_THROUGHPUT_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ROBUST_THROUGHPUT_ESTIMATOR_H_
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RobustThroughputEstimator : public AcknowledgedBitrateEstimatorInterface {
|
||||
public:
|
||||
explicit RobustThroughputEstimator(
|
||||
const RobustThroughputEstimatorSettings& settings);
|
||||
~RobustThroughputEstimator() override;
|
||||
|
||||
void IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketResult>& packet_feedback_vector) override;
|
||||
|
||||
absl::optional<DataRate> bitrate() const override;
|
||||
|
||||
absl::optional<DataRate> PeekRate() const override { return bitrate(); }
|
||||
void SetAlr(bool /*in_alr*/) override {}
|
||||
void SetAlrEndedTime(Timestamp /*alr_ended_time*/) override {}
|
||||
|
||||
private:
|
||||
bool FirstPacketOutsideWindow();
|
||||
|
||||
const RobustThroughputEstimatorSettings settings_;
|
||||
std::deque<PacketResult> window_;
|
||||
Timestamp latest_discarded_send_time_ = Timestamp::MinusInfinity();
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_ROBUST_THROUGHPUT_ESTIMATOR_H_
|
||||
|
|
@ -0,0 +1,711 @@
|
|||
/*
|
||||
* Copyright (c) 2012 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/send_side_bandwidth_estimation.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "api/rtc_event_log/rtc_event_log.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.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/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "system_wrappers/include/field_trial.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
constexpr TimeDelta kBweIncreaseInterval = TimeDelta::Millis(1000);
|
||||
constexpr TimeDelta kBweDecreaseInterval = TimeDelta::Millis(300);
|
||||
constexpr TimeDelta kStartPhase = TimeDelta::Millis(2000);
|
||||
constexpr TimeDelta kBweConverganceTime = TimeDelta::Millis(20000);
|
||||
constexpr int kLimitNumPackets = 20;
|
||||
constexpr DataRate kDefaultMaxBitrate = DataRate::BitsPerSec(1000000000);
|
||||
constexpr TimeDelta kLowBitrateLogPeriod = TimeDelta::Millis(10000);
|
||||
constexpr TimeDelta kRtcEventLogPeriod = TimeDelta::Millis(5000);
|
||||
// Expecting that RTCP feedback is sent uniformly within [0.5, 1.5]s intervals.
|
||||
constexpr TimeDelta kMaxRtcpFeedbackInterval = TimeDelta::Millis(5000);
|
||||
|
||||
constexpr float kDefaultLowLossThreshold = 0.02f;
|
||||
constexpr float kDefaultHighLossThreshold = 0.1f;
|
||||
constexpr DataRate kDefaultBitrateThreshold = DataRate::Zero();
|
||||
|
||||
struct UmaRampUpMetric {
|
||||
const char* metric_name;
|
||||
int bitrate_kbps;
|
||||
};
|
||||
|
||||
const UmaRampUpMetric kUmaRampupMetrics[] = {
|
||||
{"WebRTC.BWE.RampUpTimeTo500kbpsInMs", 500},
|
||||
{"WebRTC.BWE.RampUpTimeTo1000kbpsInMs", 1000},
|
||||
{"WebRTC.BWE.RampUpTimeTo2000kbpsInMs", 2000}};
|
||||
const size_t kNumUmaRampupMetrics =
|
||||
sizeof(kUmaRampupMetrics) / sizeof(kUmaRampupMetrics[0]);
|
||||
|
||||
const char kBweLosExperiment[] = "WebRTC-BweLossExperiment";
|
||||
|
||||
bool BweLossExperimentIsEnabled() {
|
||||
std::string experiment_string =
|
||||
webrtc::field_trial::FindFullName(kBweLosExperiment);
|
||||
// The experiment is enabled iff the field trial string begins with "Enabled".
|
||||
return absl::StartsWith(experiment_string, "Enabled");
|
||||
}
|
||||
|
||||
bool ReadBweLossExperimentParameters(float* low_loss_threshold,
|
||||
float* high_loss_threshold,
|
||||
uint32_t* bitrate_threshold_kbps) {
|
||||
RTC_DCHECK(low_loss_threshold);
|
||||
RTC_DCHECK(high_loss_threshold);
|
||||
RTC_DCHECK(bitrate_threshold_kbps);
|
||||
std::string experiment_string =
|
||||
webrtc::field_trial::FindFullName(kBweLosExperiment);
|
||||
int parsed_values =
|
||||
sscanf(experiment_string.c_str(), "Enabled-%f,%f,%u", low_loss_threshold,
|
||||
high_loss_threshold, bitrate_threshold_kbps);
|
||||
if (parsed_values == 3) {
|
||||
RTC_CHECK_GT(*low_loss_threshold, 0.0f)
|
||||
<< "Loss threshold must be greater than 0.";
|
||||
RTC_CHECK_LE(*low_loss_threshold, 1.0f)
|
||||
<< "Loss threshold must be less than or equal to 1.";
|
||||
RTC_CHECK_GT(*high_loss_threshold, 0.0f)
|
||||
<< "Loss threshold must be greater than 0.";
|
||||
RTC_CHECK_LE(*high_loss_threshold, 1.0f)
|
||||
<< "Loss threshold must be less than or equal to 1.";
|
||||
RTC_CHECK_LE(*low_loss_threshold, *high_loss_threshold)
|
||||
<< "The low loss threshold must be less than or equal to the high loss "
|
||||
"threshold.";
|
||||
RTC_CHECK_GE(*bitrate_threshold_kbps, 0)
|
||||
<< "Bitrate threshold can't be negative.";
|
||||
RTC_CHECK_LT(*bitrate_threshold_kbps,
|
||||
std::numeric_limits<int>::max() / 1000)
|
||||
<< "Bitrate must be smaller enough to avoid overflows.";
|
||||
return true;
|
||||
}
|
||||
RTC_LOG(LS_WARNING) << "Failed to parse parameters for BweLossExperiment "
|
||||
"experiment from field trial string. Using default.";
|
||||
*low_loss_threshold = kDefaultLowLossThreshold;
|
||||
*high_loss_threshold = kDefaultHighLossThreshold;
|
||||
*bitrate_threshold_kbps = kDefaultBitrateThreshold.kbps();
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
LinkCapacityTracker::LinkCapacityTracker()
|
||||
: tracking_rate("rate", TimeDelta::Seconds(10)) {
|
||||
ParseFieldTrial({&tracking_rate},
|
||||
field_trial::FindFullName("WebRTC-Bwe-LinkCapacity"));
|
||||
}
|
||||
|
||||
LinkCapacityTracker::~LinkCapacityTracker() {}
|
||||
|
||||
void LinkCapacityTracker::UpdateDelayBasedEstimate(
|
||||
Timestamp at_time,
|
||||
DataRate delay_based_bitrate) {
|
||||
if (delay_based_bitrate < last_delay_based_estimate_) {
|
||||
capacity_estimate_bps_ =
|
||||
std::min(capacity_estimate_bps_, delay_based_bitrate.bps<double>());
|
||||
last_link_capacity_update_ = at_time;
|
||||
}
|
||||
last_delay_based_estimate_ = delay_based_bitrate;
|
||||
}
|
||||
|
||||
void LinkCapacityTracker::OnStartingRate(DataRate start_rate) {
|
||||
if (last_link_capacity_update_.IsInfinite())
|
||||
capacity_estimate_bps_ = start_rate.bps<double>();
|
||||
}
|
||||
|
||||
void LinkCapacityTracker::OnRateUpdate(absl::optional<DataRate> acknowledged,
|
||||
DataRate target,
|
||||
Timestamp at_time) {
|
||||
if (!acknowledged)
|
||||
return;
|
||||
DataRate acknowledged_target = std::min(*acknowledged, target);
|
||||
if (acknowledged_target.bps() > capacity_estimate_bps_) {
|
||||
TimeDelta delta = at_time - last_link_capacity_update_;
|
||||
double alpha = delta.IsFinite() ? exp(-(delta / tracking_rate.Get())) : 0;
|
||||
capacity_estimate_bps_ = alpha * capacity_estimate_bps_ +
|
||||
(1 - alpha) * acknowledged_target.bps<double>();
|
||||
}
|
||||
last_link_capacity_update_ = at_time;
|
||||
}
|
||||
|
||||
void LinkCapacityTracker::OnRttBackoff(DataRate backoff_rate,
|
||||
Timestamp at_time) {
|
||||
capacity_estimate_bps_ =
|
||||
std::min(capacity_estimate_bps_, backoff_rate.bps<double>());
|
||||
last_link_capacity_update_ = at_time;
|
||||
}
|
||||
|
||||
DataRate LinkCapacityTracker::estimate() const {
|
||||
return DataRate::BitsPerSec(capacity_estimate_bps_);
|
||||
}
|
||||
|
||||
RttBasedBackoff::RttBasedBackoff(const FieldTrialsView* key_value_config)
|
||||
: disabled_("Disabled"),
|
||||
configured_limit_("limit", TimeDelta::Seconds(3)),
|
||||
drop_fraction_("fraction", 0.8),
|
||||
drop_interval_("interval", TimeDelta::Seconds(1)),
|
||||
bandwidth_floor_("floor", DataRate::KilobitsPerSec(5)),
|
||||
rtt_limit_(TimeDelta::PlusInfinity()),
|
||||
// By initializing this to plus infinity, we make sure that we never
|
||||
// trigger rtt backoff unless packet feedback is enabled.
|
||||
last_propagation_rtt_update_(Timestamp::PlusInfinity()),
|
||||
last_propagation_rtt_(TimeDelta::Zero()),
|
||||
last_packet_sent_(Timestamp::MinusInfinity()) {
|
||||
ParseFieldTrial({&disabled_, &configured_limit_, &drop_fraction_,
|
||||
&drop_interval_, &bandwidth_floor_},
|
||||
key_value_config->Lookup("WebRTC-Bwe-MaxRttLimit"));
|
||||
if (!disabled_) {
|
||||
rtt_limit_ = configured_limit_.Get();
|
||||
}
|
||||
}
|
||||
|
||||
void RttBasedBackoff::UpdatePropagationRtt(Timestamp at_time,
|
||||
TimeDelta propagation_rtt) {
|
||||
last_propagation_rtt_update_ = at_time;
|
||||
last_propagation_rtt_ = propagation_rtt;
|
||||
}
|
||||
|
||||
bool RttBasedBackoff::IsRttAboveLimit() const {
|
||||
return CorrectedRtt() > rtt_limit_;
|
||||
}
|
||||
|
||||
TimeDelta RttBasedBackoff::CorrectedRtt() const {
|
||||
// Avoid timeout when no packets are being sent.
|
||||
TimeDelta timeout_correction = std::max(
|
||||
last_packet_sent_ - last_propagation_rtt_update_, TimeDelta::Zero());
|
||||
return timeout_correction + last_propagation_rtt_;
|
||||
}
|
||||
|
||||
RttBasedBackoff::~RttBasedBackoff() = default;
|
||||
|
||||
SendSideBandwidthEstimation::SendSideBandwidthEstimation(
|
||||
const FieldTrialsView* key_value_config, RtcEventLog* event_log)
|
||||
: key_value_config_(key_value_config),
|
||||
rtt_backoff_(key_value_config),
|
||||
lost_packets_since_last_loss_update_(0),
|
||||
expected_packets_since_last_loss_update_(0),
|
||||
current_target_(DataRate::Zero()),
|
||||
last_logged_target_(DataRate::Zero()),
|
||||
min_bitrate_configured_(kCongestionControllerMinBitrate),
|
||||
max_bitrate_configured_(kDefaultMaxBitrate),
|
||||
last_low_bitrate_log_(Timestamp::MinusInfinity()),
|
||||
has_decreased_since_last_fraction_loss_(false),
|
||||
last_loss_feedback_(Timestamp::MinusInfinity()),
|
||||
last_loss_packet_report_(Timestamp::MinusInfinity()),
|
||||
last_fraction_loss_(0),
|
||||
last_logged_fraction_loss_(0),
|
||||
last_round_trip_time_(TimeDelta::Zero()),
|
||||
receiver_limit_(DataRate::PlusInfinity()),
|
||||
delay_based_limit_(DataRate::PlusInfinity()),
|
||||
time_last_decrease_(Timestamp::MinusInfinity()),
|
||||
first_report_time_(Timestamp::MinusInfinity()),
|
||||
initially_lost_packets_(0),
|
||||
bitrate_at_2_seconds_(DataRate::Zero()),
|
||||
uma_update_state_(kNoUpdate),
|
||||
uma_rtt_state_(kNoUpdate),
|
||||
rampup_uma_stats_updated_(kNumUmaRampupMetrics, false),
|
||||
event_log_(event_log),
|
||||
last_rtc_event_log_(Timestamp::MinusInfinity()),
|
||||
low_loss_threshold_(kDefaultLowLossThreshold),
|
||||
high_loss_threshold_(kDefaultHighLossThreshold),
|
||||
bitrate_threshold_(kDefaultBitrateThreshold),
|
||||
loss_based_bandwidth_estimator_v1_(key_value_config),
|
||||
loss_based_bandwidth_estimator_v2_(new LossBasedBweV2(key_value_config)),
|
||||
loss_based_state_(LossBasedState::kDelayBasedEstimate),
|
||||
disable_receiver_limit_caps_only_("Disabled") {
|
||||
RTC_DCHECK(event_log);
|
||||
if (BweLossExperimentIsEnabled()) {
|
||||
uint32_t bitrate_threshold_kbps;
|
||||
if (ReadBweLossExperimentParameters(&low_loss_threshold_,
|
||||
&high_loss_threshold_,
|
||||
&bitrate_threshold_kbps)) {
|
||||
RTC_LOG(LS_INFO) << "Enabled BweLossExperiment with parameters "
|
||||
<< low_loss_threshold_ << ", " << high_loss_threshold_
|
||||
<< ", " << bitrate_threshold_kbps;
|
||||
bitrate_threshold_ = DataRate::KilobitsPerSec(bitrate_threshold_kbps);
|
||||
}
|
||||
}
|
||||
ParseFieldTrial({&disable_receiver_limit_caps_only_},
|
||||
key_value_config->Lookup("WebRTC-Bwe-ReceiverLimitCapsOnly"));
|
||||
if (LossBasedBandwidthEstimatorV2Enabled()) {
|
||||
loss_based_bandwidth_estimator_v2_->SetMinMaxBitrate(
|
||||
min_bitrate_configured_, max_bitrate_configured_);
|
||||
}
|
||||
}
|
||||
|
||||
SendSideBandwidthEstimation::~SendSideBandwidthEstimation() {}
|
||||
|
||||
void SendSideBandwidthEstimation::OnRouteChange() {
|
||||
lost_packets_since_last_loss_update_ = 0;
|
||||
expected_packets_since_last_loss_update_ = 0;
|
||||
current_target_ = DataRate::Zero();
|
||||
min_bitrate_configured_ = kCongestionControllerMinBitrate;
|
||||
max_bitrate_configured_ = kDefaultMaxBitrate;
|
||||
last_low_bitrate_log_ = Timestamp::MinusInfinity();
|
||||
has_decreased_since_last_fraction_loss_ = false;
|
||||
last_loss_feedback_ = Timestamp::MinusInfinity();
|
||||
last_loss_packet_report_ = Timestamp::MinusInfinity();
|
||||
last_fraction_loss_ = 0;
|
||||
last_logged_fraction_loss_ = 0;
|
||||
last_round_trip_time_ = TimeDelta::Zero();
|
||||
receiver_limit_ = DataRate::PlusInfinity();
|
||||
delay_based_limit_ = DataRate::PlusInfinity();
|
||||
time_last_decrease_ = Timestamp::MinusInfinity();
|
||||
first_report_time_ = Timestamp::MinusInfinity();
|
||||
initially_lost_packets_ = 0;
|
||||
bitrate_at_2_seconds_ = DataRate::Zero();
|
||||
uma_update_state_ = kNoUpdate;
|
||||
uma_rtt_state_ = kNoUpdate;
|
||||
last_rtc_event_log_ = Timestamp::MinusInfinity();
|
||||
if (LossBasedBandwidthEstimatorV2Enabled() &&
|
||||
loss_based_bandwidth_estimator_v2_->UseInStartPhase()) {
|
||||
loss_based_bandwidth_estimator_v2_.reset(
|
||||
new LossBasedBweV2(key_value_config_));
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::SetBitrates(
|
||||
absl::optional<DataRate> send_bitrate,
|
||||
DataRate min_bitrate,
|
||||
DataRate max_bitrate,
|
||||
Timestamp at_time) {
|
||||
SetMinMaxBitrate(min_bitrate, max_bitrate);
|
||||
if (send_bitrate) {
|
||||
link_capacity_.OnStartingRate(*send_bitrate);
|
||||
SetSendBitrate(*send_bitrate, at_time);
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::SetSendBitrate(DataRate bitrate,
|
||||
Timestamp at_time) {
|
||||
RTC_DCHECK_GT(bitrate, DataRate::Zero());
|
||||
// Reset to avoid being capped by the estimate.
|
||||
delay_based_limit_ = DataRate::PlusInfinity();
|
||||
UpdateTargetBitrate(bitrate, at_time);
|
||||
// Clear last sent bitrate history so the new value can be used directly
|
||||
// and not capped.
|
||||
min_bitrate_history_.clear();
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::SetMinMaxBitrate(DataRate min_bitrate,
|
||||
DataRate max_bitrate) {
|
||||
min_bitrate_configured_ =
|
||||
std::max(min_bitrate, kCongestionControllerMinBitrate);
|
||||
if (max_bitrate > DataRate::Zero() && max_bitrate.IsFinite()) {
|
||||
max_bitrate_configured_ = std::max(min_bitrate_configured_, max_bitrate);
|
||||
} else {
|
||||
max_bitrate_configured_ = kDefaultMaxBitrate;
|
||||
}
|
||||
loss_based_bandwidth_estimator_v2_->SetMinMaxBitrate(min_bitrate_configured_,
|
||||
max_bitrate_configured_);
|
||||
}
|
||||
|
||||
int SendSideBandwidthEstimation::GetMinBitrate() const {
|
||||
return min_bitrate_configured_.bps<int>();
|
||||
}
|
||||
|
||||
DataRate SendSideBandwidthEstimation::target_rate() const {
|
||||
DataRate target = current_target_;
|
||||
if (!disable_receiver_limit_caps_only_)
|
||||
target = std::min(target, receiver_limit_);
|
||||
return std::max(min_bitrate_configured_, target);
|
||||
}
|
||||
|
||||
LossBasedState SendSideBandwidthEstimation::loss_based_state() const {
|
||||
return loss_based_state_;
|
||||
}
|
||||
|
||||
bool SendSideBandwidthEstimation::IsRttAboveLimit() const {
|
||||
return rtt_backoff_.IsRttAboveLimit();
|
||||
}
|
||||
|
||||
DataRate SendSideBandwidthEstimation::GetEstimatedLinkCapacity() const {
|
||||
return link_capacity_.estimate();
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdateReceiverEstimate(Timestamp at_time,
|
||||
DataRate bandwidth) {
|
||||
// TODO(srte): Ensure caller passes PlusInfinity, not zero, to represent no
|
||||
// limitation.
|
||||
receiver_limit_ = bandwidth.IsZero() ? DataRate::PlusInfinity() : bandwidth;
|
||||
ApplyTargetLimits(at_time);
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdateDelayBasedEstimate(Timestamp at_time,
|
||||
DataRate bitrate) {
|
||||
link_capacity_.UpdateDelayBasedEstimate(at_time, bitrate);
|
||||
// TODO(srte): Ensure caller passes PlusInfinity, not zero, to represent no
|
||||
// limitation.
|
||||
delay_based_limit_ = bitrate.IsZero() ? DataRate::PlusInfinity() : bitrate;
|
||||
ApplyTargetLimits(at_time);
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::SetAcknowledgedRate(
|
||||
absl::optional<DataRate> acknowledged_rate,
|
||||
Timestamp at_time) {
|
||||
acknowledged_rate_ = acknowledged_rate;
|
||||
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::UpdateLossBasedEstimator(
|
||||
const TransportPacketsFeedback& report,
|
||||
BandwidthUsage delay_detector_state,
|
||||
absl::optional<DataRate> probe_bitrate,
|
||||
bool in_alr) {
|
||||
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, delay_based_limit_, in_alr);
|
||||
UpdateEstimate(report.feedback_time);
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdatePacketsLost(int64_t packets_lost,
|
||||
int64_t number_of_packets,
|
||||
Timestamp at_time) {
|
||||
last_loss_feedback_ = at_time;
|
||||
if (first_report_time_.IsInfinite())
|
||||
first_report_time_ = at_time;
|
||||
|
||||
// Check sequence number diff and weight loss report
|
||||
if (number_of_packets > 0) {
|
||||
int64_t expected =
|
||||
expected_packets_since_last_loss_update_ + number_of_packets;
|
||||
|
||||
// Don't generate a loss rate until it can be based on enough packets.
|
||||
if (expected < kLimitNumPackets) {
|
||||
// Accumulate reports.
|
||||
expected_packets_since_last_loss_update_ = expected;
|
||||
lost_packets_since_last_loss_update_ += packets_lost;
|
||||
return;
|
||||
}
|
||||
|
||||
has_decreased_since_last_fraction_loss_ = false;
|
||||
int64_t lost_q8 =
|
||||
std::max<int64_t>(lost_packets_since_last_loss_update_ + packets_lost,
|
||||
0)
|
||||
<< 8;
|
||||
last_fraction_loss_ = std::min<int>(lost_q8 / expected, 255);
|
||||
|
||||
// Reset accumulators.
|
||||
lost_packets_since_last_loss_update_ = 0;
|
||||
expected_packets_since_last_loss_update_ = 0;
|
||||
last_loss_packet_report_ = at_time;
|
||||
UpdateEstimate(at_time);
|
||||
}
|
||||
|
||||
UpdateUmaStatsPacketsLost(at_time, packets_lost);
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdateUmaStatsPacketsLost(Timestamp at_time,
|
||||
int packets_lost) {
|
||||
DataRate bitrate_kbps =
|
||||
DataRate::KilobitsPerSec((current_target_.bps() + 500) / 1000);
|
||||
for (size_t i = 0; i < kNumUmaRampupMetrics; ++i) {
|
||||
if (!rampup_uma_stats_updated_[i] &&
|
||||
bitrate_kbps.kbps() >= kUmaRampupMetrics[i].bitrate_kbps) {
|
||||
RTC_HISTOGRAMS_COUNTS_100000(i, kUmaRampupMetrics[i].metric_name,
|
||||
(at_time - first_report_time_).ms());
|
||||
rampup_uma_stats_updated_[i] = true;
|
||||
}
|
||||
}
|
||||
if (IsInStartPhase(at_time)) {
|
||||
initially_lost_packets_ += packets_lost;
|
||||
} else if (uma_update_state_ == kNoUpdate) {
|
||||
uma_update_state_ = kFirstDone;
|
||||
bitrate_at_2_seconds_ = bitrate_kbps;
|
||||
RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitiallyLostPackets",
|
||||
initially_lost_packets_, 0, 100, 50);
|
||||
RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitialBandwidthEstimate",
|
||||
bitrate_at_2_seconds_.kbps(), 0, 2000, 50);
|
||||
} else if (uma_update_state_ == kFirstDone &&
|
||||
at_time - first_report_time_ >= kBweConverganceTime) {
|
||||
uma_update_state_ = kDone;
|
||||
int bitrate_diff_kbps = std::max(
|
||||
bitrate_at_2_seconds_.kbps<int>() - bitrate_kbps.kbps<int>(), 0);
|
||||
RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitialVsConvergedDiff", bitrate_diff_kbps,
|
||||
0, 2000, 50);
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdateRtt(TimeDelta rtt, Timestamp at_time) {
|
||||
// Update RTT if we were able to compute an RTT based on this RTCP.
|
||||
// FlexFEC doesn't send RTCP SR, which means we won't be able to compute RTT.
|
||||
if (rtt > TimeDelta::Zero())
|
||||
last_round_trip_time_ = rtt;
|
||||
|
||||
if (!IsInStartPhase(at_time) && uma_rtt_state_ == kNoUpdate) {
|
||||
uma_rtt_state_ = kDone;
|
||||
RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitialRtt", rtt.ms<int>(), 0, 2000, 50);
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {
|
||||
if (rtt_backoff_.IsRttAboveLimit()) {
|
||||
if (at_time - time_last_decrease_ >= rtt_backoff_.drop_interval_ &&
|
||||
current_target_ > rtt_backoff_.bandwidth_floor_) {
|
||||
time_last_decrease_ = at_time;
|
||||
DataRate new_bitrate =
|
||||
std::max(current_target_ * rtt_backoff_.drop_fraction_,
|
||||
rtt_backoff_.bandwidth_floor_.Get());
|
||||
link_capacity_.OnRttBackoff(new_bitrate, at_time);
|
||||
UpdateTargetBitrate(new_bitrate, at_time);
|
||||
return;
|
||||
}
|
||||
// TODO(srte): This is likely redundant in most cases.
|
||||
ApplyTargetLimits(at_time);
|
||||
return;
|
||||
}
|
||||
|
||||
// We trust the REMB and/or delay-based estimate during the first 2 seconds if
|
||||
// we haven't had any packet loss reported, to allow startup bitrate probing.
|
||||
if (last_fraction_loss_ == 0 && IsInStartPhase(at_time) &&
|
||||
!loss_based_bandwidth_estimator_v2_->ReadyToUseInStartPhase()) {
|
||||
DataRate new_bitrate = current_target_;
|
||||
// TODO(srte): We should not allow the new_bitrate to be larger than the
|
||||
// receiver limit here.
|
||||
if (receiver_limit_.IsFinite())
|
||||
new_bitrate = std::max(receiver_limit_, new_bitrate);
|
||||
if (delay_based_limit_.IsFinite())
|
||||
new_bitrate = std::max(delay_based_limit_, new_bitrate);
|
||||
if (LossBasedBandwidthEstimatorV1Enabled()) {
|
||||
loss_based_bandwidth_estimator_v1_.Initialize(new_bitrate);
|
||||
}
|
||||
|
||||
if (new_bitrate != current_target_) {
|
||||
min_bitrate_history_.clear();
|
||||
if (LossBasedBandwidthEstimatorV1Enabled()) {
|
||||
min_bitrate_history_.push_back(std::make_pair(at_time, new_bitrate));
|
||||
} else {
|
||||
min_bitrate_history_.push_back(
|
||||
std::make_pair(at_time, current_target_));
|
||||
}
|
||||
UpdateTargetBitrate(new_bitrate, at_time);
|
||||
return;
|
||||
}
|
||||
}
|
||||
UpdateMinHistory(at_time);
|
||||
if (last_loss_packet_report_.IsInfinite()) {
|
||||
// No feedback received.
|
||||
// TODO(srte): This is likely redundant in most cases.
|
||||
ApplyTargetLimits(at_time);
|
||||
return;
|
||||
}
|
||||
|
||||
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()) {
|
||||
LossBasedBweV2::Result result =
|
||||
loss_based_bandwidth_estimator_v2_->GetLossBasedResult();
|
||||
loss_based_state_ = result.state;
|
||||
UpdateTargetBitrate(result.bandwidth_estimate, 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.
|
||||
float loss = last_fraction_loss_ / 256.0f;
|
||||
// We only make decisions based on loss when the bitrate is above a
|
||||
// threshold. This is a crude way of handling loss which is uncorrelated
|
||||
// to congestion.
|
||||
if (current_target_ < bitrate_threshold_ || loss <= low_loss_threshold_) {
|
||||
// Loss < 2%: Increase rate by 8% of the min bitrate in the last
|
||||
// kBweIncreaseInterval.
|
||||
// Note that by remembering the bitrate over the last second one can
|
||||
// rampup up one second faster than if only allowed to start ramping
|
||||
// at 8% per second rate now. E.g.:
|
||||
// If sending a constant 100kbps it can rampup immediately to 108kbps
|
||||
// whenever a receiver report is received with lower packet loss.
|
||||
// If instead one would do: current_bitrate_ *= 1.08^(delta time),
|
||||
// it would take over one second since the lower packet loss to achieve
|
||||
// 108kbps.
|
||||
DataRate new_bitrate = DataRate::BitsPerSec(
|
||||
min_bitrate_history_.front().second.bps() * 1.08 + 0.5);
|
||||
|
||||
// Add 1 kbps extra, just to make sure that we do not get stuck
|
||||
// (gives a little extra increase at low rates, negligible at higher
|
||||
// rates).
|
||||
new_bitrate += DataRate::BitsPerSec(1000);
|
||||
UpdateTargetBitrate(new_bitrate, at_time);
|
||||
return;
|
||||
} else if (current_target_ > bitrate_threshold_) {
|
||||
if (loss <= high_loss_threshold_) {
|
||||
// Loss between 2% - 10%: Do nothing.
|
||||
} else {
|
||||
// Loss > 10%: Limit the rate decreases to once a kBweDecreaseInterval
|
||||
// + rtt.
|
||||
if (!has_decreased_since_last_fraction_loss_ &&
|
||||
(at_time - time_last_decrease_) >=
|
||||
(kBweDecreaseInterval + last_round_trip_time_)) {
|
||||
time_last_decrease_ = at_time;
|
||||
|
||||
// Reduce rate:
|
||||
// newRate = rate * (1 - 0.5*lossRate);
|
||||
// where packetLoss = 256*lossRate;
|
||||
DataRate new_bitrate = DataRate::BitsPerSec(
|
||||
(current_target_.bps() *
|
||||
static_cast<double>(512 - last_fraction_loss_)) /
|
||||
512.0);
|
||||
has_decreased_since_last_fraction_loss_ = true;
|
||||
UpdateTargetBitrate(new_bitrate, at_time);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(srte): This is likely redundant in most cases.
|
||||
ApplyTargetLimits(at_time);
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdatePropagationRtt(
|
||||
Timestamp at_time,
|
||||
TimeDelta propagation_rtt) {
|
||||
rtt_backoff_.UpdatePropagationRtt(at_time, propagation_rtt);
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::OnSentPacket(const SentPacket& sent_packet) {
|
||||
// Only feedback-triggering packets will be reported here.
|
||||
rtt_backoff_.last_packet_sent_ = sent_packet.send_time;
|
||||
}
|
||||
|
||||
bool SendSideBandwidthEstimation::IsInStartPhase(Timestamp at_time) const {
|
||||
return first_report_time_.IsInfinite() ||
|
||||
at_time - first_report_time_ < kStartPhase;
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdateMinHistory(Timestamp at_time) {
|
||||
// Remove old data points from history.
|
||||
// Since history precision is in ms, add one so it is able to increase
|
||||
// bitrate if it is off by as little as 0.5ms.
|
||||
while (!min_bitrate_history_.empty() &&
|
||||
at_time - min_bitrate_history_.front().first + TimeDelta::Millis(1) >
|
||||
kBweIncreaseInterval) {
|
||||
min_bitrate_history_.pop_front();
|
||||
}
|
||||
|
||||
// Typical minimum sliding-window algorithm: Pop values higher than current
|
||||
// bitrate before pushing it.
|
||||
while (!min_bitrate_history_.empty() &&
|
||||
current_target_ <= min_bitrate_history_.back().second) {
|
||||
min_bitrate_history_.pop_back();
|
||||
}
|
||||
|
||||
min_bitrate_history_.push_back(std::make_pair(at_time, current_target_));
|
||||
}
|
||||
|
||||
DataRate SendSideBandwidthEstimation::GetUpperLimit() const {
|
||||
DataRate upper_limit = delay_based_limit_;
|
||||
if (disable_receiver_limit_caps_only_)
|
||||
upper_limit = std::min(upper_limit, receiver_limit_);
|
||||
return std::min(upper_limit, max_bitrate_configured_);
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::MaybeLogLowBitrateWarning(DataRate bitrate,
|
||||
Timestamp at_time) {
|
||||
if (at_time - last_low_bitrate_log_ > kLowBitrateLogPeriod) {
|
||||
RTC_LOG(LS_WARNING) << "Estimated available bandwidth " << ToString(bitrate)
|
||||
<< " is below configured min bitrate "
|
||||
<< ToString(min_bitrate_configured_) << ".";
|
||||
last_low_bitrate_log_ = at_time;
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::MaybeLogLossBasedEvent(Timestamp at_time) {
|
||||
if (current_target_ != last_logged_target_ ||
|
||||
last_fraction_loss_ != last_logged_fraction_loss_ ||
|
||||
at_time - last_rtc_event_log_ > kRtcEventLogPeriod) {
|
||||
event_log_->Log(std::make_unique<RtcEventBweUpdateLossBased>(
|
||||
current_target_.bps(), last_fraction_loss_,
|
||||
expected_packets_since_last_loss_update_));
|
||||
last_logged_fraction_loss_ = last_fraction_loss_;
|
||||
last_logged_target_ = current_target_;
|
||||
last_rtc_event_log_ = at_time;
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideBandwidthEstimation::UpdateTargetBitrate(DataRate new_bitrate,
|
||||
Timestamp at_time) {
|
||||
new_bitrate = std::min(new_bitrate, GetUpperLimit());
|
||||
if (new_bitrate < min_bitrate_configured_) {
|
||||
MaybeLogLowBitrateWarning(new_bitrate, at_time);
|
||||
new_bitrate = min_bitrate_configured_;
|
||||
}
|
||||
current_target_ = new_bitrate;
|
||||
MaybeLogLossBasedEvent(at_time);
|
||||
link_capacity_.OnRateUpdate(acknowledged_rate_, current_target_, at_time);
|
||||
}
|
||||
|
||||
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 loss_based_bandwidth_estimator_v2_->IsReady();
|
||||
}
|
||||
|
||||
bool SendSideBandwidthEstimation::PaceAtLossBasedEstimate() const {
|
||||
return LossBasedBandwidthEstimatorV2ReadyForUse() &&
|
||||
loss_based_bandwidth_estimator_v2_->PaceAtLossBasedEstimate();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright (c) 2012 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.
|
||||
*
|
||||
* FEC and NACK added bitrate is handled outside class
|
||||
*/
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_SEND_SIDE_BANDWIDTH_ESTIMATION_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_SEND_SIDE_BANDWIDTH_ESTIMATION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.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/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 {
|
||||
|
||||
class RtcEventLog;
|
||||
|
||||
class LinkCapacityTracker {
|
||||
public:
|
||||
LinkCapacityTracker();
|
||||
~LinkCapacityTracker();
|
||||
// Call when a new delay-based estimate is available.
|
||||
void UpdateDelayBasedEstimate(Timestamp at_time,
|
||||
DataRate delay_based_bitrate);
|
||||
void OnStartingRate(DataRate start_rate);
|
||||
void OnRateUpdate(absl::optional<DataRate> acknowledged,
|
||||
DataRate target,
|
||||
Timestamp at_time);
|
||||
void OnRttBackoff(DataRate backoff_rate, Timestamp at_time);
|
||||
DataRate estimate() const;
|
||||
|
||||
private:
|
||||
FieldTrialParameter<TimeDelta> tracking_rate;
|
||||
double capacity_estimate_bps_ = 0;
|
||||
Timestamp last_link_capacity_update_ = Timestamp::MinusInfinity();
|
||||
DataRate last_delay_based_estimate_ = DataRate::PlusInfinity();
|
||||
};
|
||||
|
||||
class RttBasedBackoff {
|
||||
public:
|
||||
explicit RttBasedBackoff(const FieldTrialsView* key_value_config);
|
||||
~RttBasedBackoff();
|
||||
void UpdatePropagationRtt(Timestamp at_time, TimeDelta propagation_rtt);
|
||||
bool IsRttAboveLimit() const;
|
||||
|
||||
FieldTrialFlag disabled_;
|
||||
FieldTrialParameter<TimeDelta> configured_limit_;
|
||||
FieldTrialParameter<double> drop_fraction_;
|
||||
FieldTrialParameter<TimeDelta> drop_interval_;
|
||||
FieldTrialParameter<DataRate> bandwidth_floor_;
|
||||
|
||||
public:
|
||||
TimeDelta rtt_limit_;
|
||||
Timestamp last_propagation_rtt_update_;
|
||||
TimeDelta last_propagation_rtt_;
|
||||
Timestamp last_packet_sent_;
|
||||
|
||||
private:
|
||||
TimeDelta CorrectedRtt() const;
|
||||
};
|
||||
|
||||
class SendSideBandwidthEstimation {
|
||||
public:
|
||||
SendSideBandwidthEstimation() = delete;
|
||||
SendSideBandwidthEstimation(const FieldTrialsView* key_value_config,
|
||||
RtcEventLog* event_log);
|
||||
~SendSideBandwidthEstimation();
|
||||
|
||||
void OnRouteChange();
|
||||
|
||||
DataRate target_rate() const;
|
||||
LossBasedState loss_based_state() const;
|
||||
// Return whether the current rtt is higher than the rtt limited configured in
|
||||
// RttBasedBackoff.
|
||||
bool IsRttAboveLimit() const;
|
||||
uint8_t fraction_loss() const { return last_fraction_loss_; }
|
||||
TimeDelta round_trip_time() const { return last_round_trip_time_; }
|
||||
|
||||
DataRate GetEstimatedLinkCapacity() const;
|
||||
// Call periodically to update estimate.
|
||||
void UpdateEstimate(Timestamp at_time);
|
||||
void OnSentPacket(const SentPacket& sent_packet);
|
||||
void UpdatePropagationRtt(Timestamp at_time, TimeDelta propagation_rtt);
|
||||
|
||||
// Call when we receive a RTCP message with TMMBR or REMB.
|
||||
void UpdateReceiverEstimate(Timestamp at_time, DataRate bandwidth);
|
||||
|
||||
// Call when a new delay-based estimate is available.
|
||||
void UpdateDelayBasedEstimate(Timestamp at_time, DataRate bitrate);
|
||||
|
||||
// Call when we receive a RTCP message with a ReceiveBlock.
|
||||
void UpdatePacketsLost(int64_t packets_lost,
|
||||
int64_t number_of_packets,
|
||||
Timestamp at_time);
|
||||
|
||||
// Call when we receive a RTCP message with a ReceiveBlock.
|
||||
void UpdateRtt(TimeDelta rtt, Timestamp at_time);
|
||||
|
||||
void SetBitrates(absl::optional<DataRate> send_bitrate,
|
||||
DataRate min_bitrate,
|
||||
DataRate max_bitrate,
|
||||
Timestamp at_time);
|
||||
void SetSendBitrate(DataRate bitrate, Timestamp at_time);
|
||||
void SetMinMaxBitrate(DataRate min_bitrate, DataRate max_bitrate);
|
||||
int GetMinBitrate() const;
|
||||
void SetAcknowledgedRate(absl::optional<DataRate> acknowledged_rate,
|
||||
Timestamp at_time);
|
||||
void UpdateLossBasedEstimator(const TransportPacketsFeedback& report,
|
||||
BandwidthUsage delay_detector_state,
|
||||
absl::optional<DataRate> probe_bitrate,
|
||||
bool in_alr);
|
||||
bool PaceAtLossBasedEstimate() const;
|
||||
|
||||
private:
|
||||
friend class GoogCcStatePrinter;
|
||||
|
||||
enum UmaState { kNoUpdate, kFirstDone, kDone };
|
||||
|
||||
bool IsInStartPhase(Timestamp at_time) const;
|
||||
|
||||
void UpdateUmaStatsPacketsLost(Timestamp at_time, int packets_lost);
|
||||
|
||||
// Updates history of min bitrates.
|
||||
// After this method returns min_bitrate_history_.front().second contains the
|
||||
// min bitrate used during last kBweIncreaseIntervalMs.
|
||||
void UpdateMinHistory(Timestamp at_time);
|
||||
|
||||
// Gets the upper limit for the target bitrate. This is the minimum of the
|
||||
// delay based limit, the receiver limit and the loss based controller limit.
|
||||
DataRate GetUpperLimit() const;
|
||||
// Prints a warning if `bitrate` if sufficiently long time has past since last
|
||||
// warning.
|
||||
void MaybeLogLowBitrateWarning(DataRate bitrate, Timestamp at_time);
|
||||
// Stores an update to the event log if the loss rate has changed, the target
|
||||
// has changed, or sufficient time has passed since last stored event.
|
||||
void MaybeLogLossBasedEvent(Timestamp at_time);
|
||||
|
||||
// Cap `bitrate` to [min_bitrate_configured_, max_bitrate_configured_] and
|
||||
// set `current_bitrate_` to the capped value and updates the event log.
|
||||
void UpdateTargetBitrate(DataRate bitrate, Timestamp at_time);
|
||||
// Applies lower and upper bounds to the current target rate.
|
||||
// TODO(srte): This seems to be called even when limits haven't changed, that
|
||||
// should be cleaned up.
|
||||
void ApplyTargetLimits(Timestamp at_time);
|
||||
|
||||
bool LossBasedBandwidthEstimatorV1Enabled() const;
|
||||
bool LossBasedBandwidthEstimatorV2Enabled() const;
|
||||
|
||||
bool LossBasedBandwidthEstimatorV1ReadyForUse() const;
|
||||
bool LossBasedBandwidthEstimatorV2ReadyForUse() const;
|
||||
|
||||
const FieldTrialsView* key_value_config_;
|
||||
RttBasedBackoff rtt_backoff_;
|
||||
LinkCapacityTracker link_capacity_;
|
||||
|
||||
std::deque<std::pair<Timestamp, DataRate> > min_bitrate_history_;
|
||||
|
||||
// incoming filters
|
||||
int lost_packets_since_last_loss_update_;
|
||||
int expected_packets_since_last_loss_update_;
|
||||
|
||||
absl::optional<DataRate> acknowledged_rate_;
|
||||
DataRate current_target_;
|
||||
DataRate last_logged_target_;
|
||||
DataRate min_bitrate_configured_;
|
||||
DataRate max_bitrate_configured_;
|
||||
Timestamp last_low_bitrate_log_;
|
||||
|
||||
bool has_decreased_since_last_fraction_loss_;
|
||||
Timestamp last_loss_feedback_;
|
||||
Timestamp last_loss_packet_report_;
|
||||
uint8_t last_fraction_loss_;
|
||||
uint8_t last_logged_fraction_loss_;
|
||||
TimeDelta last_round_trip_time_;
|
||||
|
||||
// The max bitrate as set by the receiver in the call. This is typically
|
||||
// signalled using the REMB RTCP message and is used when we don't have any
|
||||
// send side delay based estimate.
|
||||
DataRate receiver_limit_;
|
||||
DataRate delay_based_limit_;
|
||||
Timestamp time_last_decrease_;
|
||||
Timestamp first_report_time_;
|
||||
int initially_lost_packets_;
|
||||
DataRate bitrate_at_2_seconds_;
|
||||
UmaState uma_update_state_;
|
||||
UmaState uma_rtt_state_;
|
||||
std::vector<bool> rampup_uma_stats_updated_;
|
||||
RtcEventLog* const event_log_;
|
||||
Timestamp last_rtc_event_log_;
|
||||
float low_loss_threshold_;
|
||||
float high_loss_threshold_;
|
||||
DataRate bitrate_threshold_;
|
||||
LossBasedBandwidthEstimation loss_based_bandwidth_estimator_v1_;
|
||||
std::unique_ptr<LossBasedBweV2> loss_based_bandwidth_estimator_v2_;
|
||||
LossBasedState loss_based_state_;
|
||||
FieldTrialFlag disable_receiver_limit_caps_only_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_SEND_SIDE_BANDWIDTH_ESTIMATION_H_
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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/trendline_estimator.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_minmax.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Parameters for linear least squares fit of regression line to noisy data.
|
||||
constexpr double kDefaultTrendlineSmoothingCoeff = 0.9;
|
||||
constexpr double kDefaultTrendlineThresholdGain = 4.0;
|
||||
const char kBweWindowSizeInPacketsExperiment[] =
|
||||
"WebRTC-BweWindowSizeInPackets";
|
||||
|
||||
size_t ReadTrendlineFilterWindowSize(const FieldTrialsView* key_value_config) {
|
||||
std::string experiment_string =
|
||||
key_value_config->Lookup(kBweWindowSizeInPacketsExperiment);
|
||||
size_t window_size;
|
||||
int parsed_values =
|
||||
sscanf(experiment_string.c_str(), "Enabled-%zu", &window_size);
|
||||
if (parsed_values == 1) {
|
||||
if (window_size > 1)
|
||||
return window_size;
|
||||
RTC_LOG(LS_WARNING) << "Window size must be greater than 1.";
|
||||
}
|
||||
RTC_LOG(LS_WARNING) << "Failed to parse parameters for BweWindowSizeInPackets"
|
||||
" experiment from field trial string. Using default.";
|
||||
return TrendlineEstimatorSettings::kDefaultTrendlineWindowSize;
|
||||
}
|
||||
|
||||
absl::optional<double> LinearFitSlope(
|
||||
const std::deque<TrendlineEstimator::PacketTiming>& packets) {
|
||||
RTC_DCHECK(packets.size() >= 2);
|
||||
// Compute the "center of mass".
|
||||
double sum_x = 0;
|
||||
double sum_y = 0;
|
||||
for (const auto& packet : packets) {
|
||||
sum_x += packet.arrival_time_ms;
|
||||
sum_y += packet.smoothed_delay_ms;
|
||||
}
|
||||
double x_avg = sum_x / packets.size();
|
||||
double y_avg = sum_y / packets.size();
|
||||
// Compute the slope k = \sum (x_i-x_avg)(y_i-y_avg) / \sum (x_i-x_avg)^2
|
||||
double numerator = 0;
|
||||
double denominator = 0;
|
||||
for (const auto& packet : packets) {
|
||||
double x = packet.arrival_time_ms;
|
||||
double y = packet.smoothed_delay_ms;
|
||||
numerator += (x - x_avg) * (y - y_avg);
|
||||
denominator += (x - x_avg) * (x - x_avg);
|
||||
}
|
||||
if (denominator == 0)
|
||||
return absl::nullopt;
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
absl::optional<double> ComputeSlopeCap(
|
||||
const std::deque<TrendlineEstimator::PacketTiming>& packets,
|
||||
const TrendlineEstimatorSettings& settings) {
|
||||
RTC_DCHECK(1 <= settings.beginning_packets &&
|
||||
settings.beginning_packets < packets.size());
|
||||
RTC_DCHECK(1 <= settings.end_packets &&
|
||||
settings.end_packets < packets.size());
|
||||
RTC_DCHECK(settings.beginning_packets + settings.end_packets <=
|
||||
packets.size());
|
||||
TrendlineEstimator::PacketTiming early = packets[0];
|
||||
for (size_t i = 1; i < settings.beginning_packets; ++i) {
|
||||
if (packets[i].raw_delay_ms < early.raw_delay_ms)
|
||||
early = packets[i];
|
||||
}
|
||||
size_t late_start = packets.size() - settings.end_packets;
|
||||
TrendlineEstimator::PacketTiming late = packets[late_start];
|
||||
for (size_t i = late_start + 1; i < packets.size(); ++i) {
|
||||
if (packets[i].raw_delay_ms < late.raw_delay_ms)
|
||||
late = packets[i];
|
||||
}
|
||||
if (late.arrival_time_ms - early.arrival_time_ms < 1) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
return (late.raw_delay_ms - early.raw_delay_ms) /
|
||||
(late.arrival_time_ms - early.arrival_time_ms) +
|
||||
settings.cap_uncertainty;
|
||||
}
|
||||
|
||||
constexpr double kMaxAdaptOffsetMs = 15.0;
|
||||
constexpr double kOverUsingTimeThreshold = 10;
|
||||
constexpr int kMinNumDeltas = 60;
|
||||
constexpr int kDeltaCounterMax = 1000;
|
||||
|
||||
} // namespace
|
||||
|
||||
constexpr char TrendlineEstimatorSettings::kKey[];
|
||||
|
||||
TrendlineEstimatorSettings::TrendlineEstimatorSettings(
|
||||
const FieldTrialsView* key_value_config) {
|
||||
if (absl::StartsWith(
|
||||
key_value_config->Lookup(kBweWindowSizeInPacketsExperiment),
|
||||
"Enabled")) {
|
||||
window_size = ReadTrendlineFilterWindowSize(key_value_config);
|
||||
}
|
||||
Parser()->Parse(key_value_config->Lookup(TrendlineEstimatorSettings::kKey));
|
||||
if (window_size < 10 || 200 < window_size) {
|
||||
RTC_LOG(LS_WARNING) << "Window size must be between 10 and 200 packets";
|
||||
window_size = kDefaultTrendlineWindowSize;
|
||||
}
|
||||
if (enable_cap) {
|
||||
if (beginning_packets < 1 || end_packets < 1 ||
|
||||
beginning_packets > window_size || end_packets > window_size) {
|
||||
RTC_LOG(LS_WARNING) << "Size of beginning and end must be between 1 and "
|
||||
<< window_size;
|
||||
enable_cap = false;
|
||||
beginning_packets = end_packets = 0;
|
||||
cap_uncertainty = 0.0;
|
||||
}
|
||||
if (beginning_packets + end_packets > window_size) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Size of beginning plus end can't exceed the window size";
|
||||
enable_cap = false;
|
||||
beginning_packets = end_packets = 0;
|
||||
cap_uncertainty = 0.0;
|
||||
}
|
||||
if (cap_uncertainty < 0.0 || 0.025 < cap_uncertainty) {
|
||||
RTC_LOG(LS_WARNING) << "Cap uncertainty must be between 0 and 0.025";
|
||||
cap_uncertainty = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<StructParametersParser> TrendlineEstimatorSettings::Parser() {
|
||||
return StructParametersParser::Create("sort", &enable_sort, //
|
||||
"cap", &enable_cap, //
|
||||
"beginning_packets",
|
||||
&beginning_packets, //
|
||||
"end_packets", &end_packets, //
|
||||
"cap_uncertainty", &cap_uncertainty, //
|
||||
"window_size", &window_size);
|
||||
}
|
||||
|
||||
TrendlineEstimator::TrendlineEstimator(
|
||||
const FieldTrialsView* key_value_config,
|
||||
NetworkStatePredictor* network_state_predictor)
|
||||
: settings_(key_value_config),
|
||||
smoothing_coef_(kDefaultTrendlineSmoothingCoeff),
|
||||
threshold_gain_(kDefaultTrendlineThresholdGain),
|
||||
num_of_deltas_(0),
|
||||
first_arrival_time_ms_(-1),
|
||||
accumulated_delay_(0),
|
||||
smoothed_delay_(0),
|
||||
delay_hist_(),
|
||||
k_up_(0.0087),
|
||||
k_down_(0.039),
|
||||
overusing_time_threshold_(kOverUsingTimeThreshold),
|
||||
threshold_(12.5),
|
||||
prev_modified_trend_(NAN),
|
||||
last_update_ms_(-1),
|
||||
prev_trend_(0.0),
|
||||
time_over_using_(-1),
|
||||
overuse_counter_(0),
|
||||
hypothesis_(BandwidthUsage::kBwNormal),
|
||||
hypothesis_predicted_(BandwidthUsage::kBwNormal),
|
||||
network_state_predictor_(network_state_predictor) {
|
||||
RTC_LOG(LS_INFO)
|
||||
<< "Using Trendline filter for delay change estimation with settings "
|
||||
<< settings_.Parser()->Encode() << " and "
|
||||
<< (network_state_predictor_ ? "injected" : "no")
|
||||
<< " network state predictor";
|
||||
}
|
||||
|
||||
TrendlineEstimator::~TrendlineEstimator() {}
|
||||
|
||||
void TrendlineEstimator::UpdateTrendline(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t send_time_ms,
|
||||
int64_t arrival_time_ms,
|
||||
size_t packet_size) {
|
||||
const double delta_ms = recv_delta_ms - send_delta_ms;
|
||||
++num_of_deltas_;
|
||||
num_of_deltas_ = std::min(num_of_deltas_, kDeltaCounterMax);
|
||||
if (first_arrival_time_ms_ == -1)
|
||||
first_arrival_time_ms_ = arrival_time_ms;
|
||||
|
||||
// Exponential backoff filter.
|
||||
accumulated_delay_ += delta_ms;
|
||||
BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms,
|
||||
accumulated_delay_);
|
||||
smoothed_delay_ = smoothing_coef_ * smoothed_delay_ +
|
||||
(1 - smoothing_coef_) * accumulated_delay_;
|
||||
BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms,
|
||||
smoothed_delay_);
|
||||
|
||||
// Maintain packet window
|
||||
delay_hist_.emplace_back(
|
||||
static_cast<double>(arrival_time_ms - first_arrival_time_ms_),
|
||||
smoothed_delay_, accumulated_delay_);
|
||||
if (settings_.enable_sort) {
|
||||
for (size_t i = delay_hist_.size() - 1;
|
||||
i > 0 &&
|
||||
delay_hist_[i].arrival_time_ms < delay_hist_[i - 1].arrival_time_ms;
|
||||
--i) {
|
||||
std::swap(delay_hist_[i], delay_hist_[i - 1]);
|
||||
}
|
||||
}
|
||||
if (delay_hist_.size() > settings_.window_size)
|
||||
delay_hist_.pop_front();
|
||||
|
||||
// Simple linear regression.
|
||||
double trend = prev_trend_;
|
||||
if (delay_hist_.size() == settings_.window_size) {
|
||||
// Update trend_ if it is possible to fit a line to the data. The delay
|
||||
// trend can be seen as an estimate of (send_rate - capacity)/capacity.
|
||||
// 0 < trend < 1 -> the delay increases, queues are filling up
|
||||
// trend == 0 -> the delay does not change
|
||||
// trend < 0 -> the delay decreases, queues are being emptied
|
||||
trend = LinearFitSlope(delay_hist_).value_or(trend);
|
||||
if (settings_.enable_cap) {
|
||||
absl::optional<double> cap = ComputeSlopeCap(delay_hist_, settings_);
|
||||
// We only use the cap to filter out overuse detections, not
|
||||
// to detect additional underuses.
|
||||
if (trend >= 0 && cap.has_value() && trend > cap.value()) {
|
||||
trend = cap.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trend);
|
||||
|
||||
Detect(trend, send_delta_ms, arrival_time_ms);
|
||||
}
|
||||
|
||||
void TrendlineEstimator::Update(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t send_time_ms,
|
||||
int64_t arrival_time_ms,
|
||||
size_t packet_size,
|
||||
bool calculated_deltas) {
|
||||
if (calculated_deltas) {
|
||||
UpdateTrendline(recv_delta_ms, send_delta_ms, send_time_ms, arrival_time_ms,
|
||||
packet_size);
|
||||
}
|
||||
if (network_state_predictor_) {
|
||||
hypothesis_predicted_ = network_state_predictor_->Update(
|
||||
send_time_ms, arrival_time_ms, hypothesis_);
|
||||
}
|
||||
}
|
||||
|
||||
BandwidthUsage TrendlineEstimator::State() const {
|
||||
return network_state_predictor_ ? hypothesis_predicted_ : hypothesis_;
|
||||
}
|
||||
|
||||
void TrendlineEstimator::Detect(double trend, double ts_delta, int64_t now_ms) {
|
||||
if (num_of_deltas_ < 2) {
|
||||
hypothesis_ = BandwidthUsage::kBwNormal;
|
||||
return;
|
||||
}
|
||||
const double modified_trend =
|
||||
std::min(num_of_deltas_, kMinNumDeltas) * trend * threshold_gain_;
|
||||
prev_modified_trend_ = modified_trend;
|
||||
BWE_TEST_LOGGING_PLOT(1, "T", now_ms, modified_trend);
|
||||
BWE_TEST_LOGGING_PLOT(1, "threshold", now_ms, threshold_);
|
||||
if (modified_trend > threshold_) {
|
||||
if (time_over_using_ == -1) {
|
||||
// Initialize the timer. Assume that we've been
|
||||
// over-using half of the time since the previous
|
||||
// sample.
|
||||
time_over_using_ = ts_delta / 2;
|
||||
} else {
|
||||
// Increment timer
|
||||
time_over_using_ += ts_delta;
|
||||
}
|
||||
overuse_counter_++;
|
||||
if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) {
|
||||
if (trend >= prev_trend_) {
|
||||
time_over_using_ = 0;
|
||||
overuse_counter_ = 0;
|
||||
hypothesis_ = BandwidthUsage::kBwOverusing;
|
||||
}
|
||||
}
|
||||
} else if (modified_trend < -threshold_) {
|
||||
time_over_using_ = -1;
|
||||
overuse_counter_ = 0;
|
||||
hypothesis_ = BandwidthUsage::kBwUnderusing;
|
||||
} else {
|
||||
time_over_using_ = -1;
|
||||
overuse_counter_ = 0;
|
||||
hypothesis_ = BandwidthUsage::kBwNormal;
|
||||
}
|
||||
prev_trend_ = trend;
|
||||
UpdateThreshold(modified_trend, now_ms);
|
||||
}
|
||||
|
||||
void TrendlineEstimator::UpdateThreshold(double modified_trend,
|
||||
int64_t now_ms) {
|
||||
if (last_update_ms_ == -1)
|
||||
last_update_ms_ = now_ms;
|
||||
|
||||
if (fabs(modified_trend) > threshold_ + kMaxAdaptOffsetMs) {
|
||||
// Avoid adapting the threshold to big latency spikes, caused e.g.,
|
||||
// by a sudden capacity drop.
|
||||
last_update_ms_ = now_ms;
|
||||
return;
|
||||
}
|
||||
|
||||
const double k = fabs(modified_trend) < threshold_ ? k_down_ : k_up_;
|
||||
const int64_t kMaxTimeDeltaMs = 100;
|
||||
int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs);
|
||||
threshold_ += k * (fabs(modified_trend) - threshold_) * time_delta_ms;
|
||||
threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f);
|
||||
last_update_ms_ = now_ms;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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_TRENDLINE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_TRENDLINE_ESTIMATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "modules/congestion_controller/goog_cc/delay_increase_detector_interface.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct TrendlineEstimatorSettings {
|
||||
static constexpr char kKey[] = "WebRTC-Bwe-TrendlineEstimatorSettings";
|
||||
static constexpr unsigned kDefaultTrendlineWindowSize = 20;
|
||||
|
||||
TrendlineEstimatorSettings() = delete;
|
||||
explicit TrendlineEstimatorSettings(const FieldTrialsView* key_value_config);
|
||||
|
||||
// Sort the packets in the window. Should be redundant,
|
||||
// but then almost no cost.
|
||||
bool enable_sort = false;
|
||||
|
||||
// Cap the trendline slope based on the minimum delay seen
|
||||
// in the beginning_packets and end_packets respectively.
|
||||
bool enable_cap = false;
|
||||
unsigned beginning_packets = 7;
|
||||
unsigned end_packets = 7;
|
||||
double cap_uncertainty = 0.0;
|
||||
|
||||
// Size (in packets) of the window.
|
||||
unsigned window_size = kDefaultTrendlineWindowSize;
|
||||
|
||||
std::unique_ptr<StructParametersParser> Parser();
|
||||
};
|
||||
|
||||
class TrendlineEstimator : public DelayIncreaseDetectorInterface {
|
||||
public:
|
||||
TrendlineEstimator(const FieldTrialsView* key_value_config,
|
||||
NetworkStatePredictor* network_state_predictor);
|
||||
|
||||
~TrendlineEstimator() override;
|
||||
|
||||
TrendlineEstimator(const TrendlineEstimator&) = delete;
|
||||
TrendlineEstimator& operator=(const TrendlineEstimator&) = delete;
|
||||
|
||||
// Update the estimator with a new sample. The deltas should represent deltas
|
||||
// between timestamp groups as defined by the InterArrival class.
|
||||
void Update(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t send_time_ms,
|
||||
int64_t arrival_time_ms,
|
||||
size_t packet_size,
|
||||
bool calculated_deltas) override;
|
||||
|
||||
void UpdateTrendline(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t send_time_ms,
|
||||
int64_t arrival_time_ms,
|
||||
size_t packet_size);
|
||||
|
||||
BandwidthUsage State() const override;
|
||||
|
||||
struct PacketTiming {
|
||||
PacketTiming(double arrival_time_ms,
|
||||
double smoothed_delay_ms,
|
||||
double raw_delay_ms)
|
||||
: arrival_time_ms(arrival_time_ms),
|
||||
smoothed_delay_ms(smoothed_delay_ms),
|
||||
raw_delay_ms(raw_delay_ms) {}
|
||||
double arrival_time_ms;
|
||||
double smoothed_delay_ms;
|
||||
double raw_delay_ms;
|
||||
};
|
||||
|
||||
private:
|
||||
friend class GoogCcStatePrinter;
|
||||
void Detect(double trend, double ts_delta, int64_t now_ms);
|
||||
|
||||
void UpdateThreshold(double modified_offset, int64_t now_ms);
|
||||
|
||||
// Parameters.
|
||||
TrendlineEstimatorSettings settings_;
|
||||
const double smoothing_coef_;
|
||||
const double threshold_gain_;
|
||||
// Used by the existing threshold.
|
||||
int num_of_deltas_;
|
||||
// Keep the arrival times small by using the change from the first packet.
|
||||
int64_t first_arrival_time_ms_;
|
||||
// Exponential backoff filtering.
|
||||
double accumulated_delay_;
|
||||
double smoothed_delay_;
|
||||
// Linear least squares regression.
|
||||
std::deque<PacketTiming> delay_hist_;
|
||||
|
||||
const double k_up_;
|
||||
const double k_down_;
|
||||
double overusing_time_threshold_;
|
||||
double threshold_;
|
||||
double prev_modified_trend_;
|
||||
int64_t last_update_ms_;
|
||||
double prev_trend_;
|
||||
double time_over_using_;
|
||||
int overuse_counter_;
|
||||
BandwidthUsage hypothesis_;
|
||||
BandwidthUsage hypothesis_predicted_;
|
||||
NetworkStatePredictor* network_state_predictor_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_TRENDLINE_ESTIMATOR_H_
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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/trendline_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "api/transport/field_trial_based_config.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
class PacketTimeGenerator {
|
||||
public:
|
||||
PacketTimeGenerator(int64_t initial_clock, double time_between_packets)
|
||||
: initial_clock_(initial_clock),
|
||||
time_between_packets_(time_between_packets),
|
||||
packets_(0) {}
|
||||
int64_t operator()() {
|
||||
return initial_clock_ + time_between_packets_ * packets_++;
|
||||
}
|
||||
|
||||
private:
|
||||
const int64_t initial_clock_;
|
||||
const double time_between_packets_;
|
||||
size_t packets_;
|
||||
};
|
||||
|
||||
class TrendlineEstimatorTest : public testing::Test {
|
||||
public:
|
||||
TrendlineEstimatorTest()
|
||||
: send_times(kPacketCount),
|
||||
recv_times(kPacketCount),
|
||||
packet_sizes(kPacketCount),
|
||||
config(),
|
||||
estimator(&config, nullptr),
|
||||
count(1) {
|
||||
std::fill(packet_sizes.begin(), packet_sizes.end(), kPacketSizeBytes);
|
||||
}
|
||||
|
||||
void RunTestUntilStateChange() {
|
||||
RTC_DCHECK_EQ(send_times.size(), kPacketCount);
|
||||
RTC_DCHECK_EQ(recv_times.size(), kPacketCount);
|
||||
RTC_DCHECK_EQ(packet_sizes.size(), kPacketCount);
|
||||
RTC_DCHECK_GE(count, 1);
|
||||
RTC_DCHECK_LT(count, kPacketCount);
|
||||
|
||||
auto initial_state = estimator.State();
|
||||
for (; count < kPacketCount; count++) {
|
||||
double recv_delta = recv_times[count] - recv_times[count - 1];
|
||||
double send_delta = send_times[count] - send_times[count - 1];
|
||||
estimator.Update(recv_delta, send_delta, send_times[count],
|
||||
recv_times[count], packet_sizes[count], true);
|
||||
if (estimator.State() != initial_state) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
const size_t kPacketCount = 25;
|
||||
const size_t kPacketSizeBytes = 1200;
|
||||
std::vector<int64_t> send_times;
|
||||
std::vector<int64_t> recv_times;
|
||||
std::vector<size_t> packet_sizes;
|
||||
const FieldTrialBasedConfig config;
|
||||
TrendlineEstimator estimator;
|
||||
size_t count;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(TrendlineEstimatorTest, Normal) {
|
||||
PacketTimeGenerator send_time_generator(123456789 /*initial clock*/,
|
||||
20 /*20 ms between sent packets*/);
|
||||
std::generate(send_times.begin(), send_times.end(), send_time_generator);
|
||||
|
||||
PacketTimeGenerator recv_time_generator(987654321 /*initial clock*/,
|
||||
20 /*delivered at the same pace*/);
|
||||
std::generate(recv_times.begin(), recv_times.end(), recv_time_generator);
|
||||
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal);
|
||||
RunTestUntilStateChange();
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal);
|
||||
EXPECT_EQ(count, kPacketCount); // All packets processed
|
||||
}
|
||||
|
||||
TEST_F(TrendlineEstimatorTest, Overusing) {
|
||||
PacketTimeGenerator send_time_generator(123456789 /*initial clock*/,
|
||||
20 /*20 ms between sent packets*/);
|
||||
std::generate(send_times.begin(), send_times.end(), send_time_generator);
|
||||
|
||||
PacketTimeGenerator recv_time_generator(987654321 /*initial clock*/,
|
||||
1.1 * 20 /*10% slower delivery*/);
|
||||
std::generate(recv_times.begin(), recv_times.end(), recv_time_generator);
|
||||
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal);
|
||||
RunTestUntilStateChange();
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwOverusing);
|
||||
RunTestUntilStateChange();
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwOverusing);
|
||||
EXPECT_EQ(count, kPacketCount); // All packets processed
|
||||
}
|
||||
|
||||
TEST_F(TrendlineEstimatorTest, Underusing) {
|
||||
PacketTimeGenerator send_time_generator(123456789 /*initial clock*/,
|
||||
20 /*20 ms between sent packets*/);
|
||||
std::generate(send_times.begin(), send_times.end(), send_time_generator);
|
||||
|
||||
PacketTimeGenerator recv_time_generator(987654321 /*initial clock*/,
|
||||
0.85 * 20 /*15% faster delivery*/);
|
||||
std::generate(recv_times.begin(), recv_times.end(), recv_time_generator);
|
||||
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal);
|
||||
RunTestUntilStateChange();
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwUnderusing);
|
||||
RunTestUntilStateChange();
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwUnderusing);
|
||||
EXPECT_EQ(count, kPacketCount); // All packets processed
|
||||
}
|
||||
|
||||
TEST_F(TrendlineEstimatorTest, IncludesSmallPacketsByDefault) {
|
||||
PacketTimeGenerator send_time_generator(123456789 /*initial clock*/,
|
||||
20 /*20 ms between sent packets*/);
|
||||
std::generate(send_times.begin(), send_times.end(), send_time_generator);
|
||||
|
||||
PacketTimeGenerator recv_time_generator(987654321 /*initial clock*/,
|
||||
1.1 * 20 /*10% slower delivery*/);
|
||||
std::generate(recv_times.begin(), recv_times.end(), recv_time_generator);
|
||||
|
||||
std::fill(packet_sizes.begin(), packet_sizes.end(), 100);
|
||||
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal);
|
||||
RunTestUntilStateChange();
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwOverusing);
|
||||
RunTestUntilStateChange();
|
||||
EXPECT_EQ(estimator.State(), BandwidthUsage::kBwOverusing);
|
||||
EXPECT_EQ(count, kPacketCount); // All packets processed
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
Loading…
Add table
Add a link
Reference in a new issue