mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-27 04:35:34 +08:00
[feat] implementation for send side congestion controller
This commit is contained in:
36
src/common/limits_base.h
Normal file
36
src/common/limits_base.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-01-14
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LIMITS_BASE_H_
|
||||||
|
#define _LIMITS_BASE_H_
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool IsInfinite(const T& value) {
|
||||||
|
return value == std::numeric_limits<T>::min() ||
|
||||||
|
value == std::numeric_limits<T>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool IsFinite(const T& value) {
|
||||||
|
return !IsInfinite(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool IsPlusFinite(const T& value) {
|
||||||
|
return value == std::numeric_limits<T>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool IsMinusFinite(const T& value) {
|
||||||
|
return value == std::numeric_limits<T>::min();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define INT64_T_MAX std::numeric_limits<int64_t>::max()
|
||||||
|
#define INT64_T_MIN std::numeric_limits<int64_t>::min()
|
||||||
|
|
||||||
|
#endif
|
||||||
63
src/qos/acknowledged_bitrate_estimator.cpp
Normal file
63
src/qos/acknowledged_bitrate_estimator.cpp
Normal file
@@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "acknowledged_bitrate_estimator.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "bitrate_estimator.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "network_types.h"
|
||||||
|
|
||||||
|
AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator()
|
||||||
|
: AcknowledgedBitrateEstimator(std::make_unique<BitrateEstimator>()) {}
|
||||||
|
|
||||||
|
AcknowledgedBitrateEstimator::~AcknowledgedBitrateEstimator() {}
|
||||||
|
|
||||||
|
AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator(
|
||||||
|
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) {
|
||||||
|
if (!std::is_sorted(packet_feedback_vector.begin(),
|
||||||
|
packet_feedback_vector.end(),
|
||||||
|
PacketResult::ReceiveTimeOrder())) {
|
||||||
|
LOG_FATAL("packet_feedback_vector is not sorted");
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
int64_t acknowledged_estimate = packet.sent_packet.size;
|
||||||
|
acknowledged_estimate += packet.sent_packet.prior_unacked_data;
|
||||||
|
bitrate_estimator_->Update(packet.receive_time, acknowledged_estimate,
|
||||||
|
in_alr_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int64_t> AcknowledgedBitrateEstimator::bitrate() const {
|
||||||
|
return bitrate_estimator_->bitrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int64_t> AcknowledgedBitrateEstimator::PeekRate() const {
|
||||||
|
return bitrate_estimator_->PeekRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcknowledgedBitrateEstimator::SetAlrEndedTime(int64_t alr_ended_time) {
|
||||||
|
alr_ended_time_.emplace(alr_ended_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcknowledgedBitrateEstimator::SetAlr(bool in_alr) { in_alr_ = in_alr; }
|
||||||
38
src/qos/acknowledged_bitrate_estimator.h
Normal file
38
src/qos/acknowledged_bitrate_estimator.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-01-14
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
|
||||||
|
#define _ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "bitrate_estimator.h"
|
||||||
|
#include "network_types.h"
|
||||||
|
|
||||||
|
class AcknowledgedBitrateEstimator {
|
||||||
|
public:
|
||||||
|
AcknowledgedBitrateEstimator(
|
||||||
|
std::unique_ptr<BitrateEstimator> bitrate_estimator);
|
||||||
|
|
||||||
|
explicit AcknowledgedBitrateEstimator();
|
||||||
|
~AcknowledgedBitrateEstimator();
|
||||||
|
|
||||||
|
void IncomingPacketFeedbackVector(
|
||||||
|
const std::vector<PacketResult>& packet_feedback_vector);
|
||||||
|
std::optional<int64_t> bitrate() const;
|
||||||
|
std::optional<int64_t> PeekRate() const;
|
||||||
|
void SetAlr(bool in_alr);
|
||||||
|
void SetAlrEndedTime(int64_t alr_ended_time);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<int64_t> alr_ended_time_;
|
||||||
|
bool in_alr_;
|
||||||
|
std::unique_ptr<BitrateEstimator> bitrate_estimator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
62
src/qos/alr_detector.cpp
Normal file
62
src/qos/alr_detector.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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 "alr_detector.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
AlrDetector::AlrDetector(AlrDetectorConfig config)
|
||||||
|
: conf_(config), alr_budget_(0, true) {}
|
||||||
|
|
||||||
|
AlrDetector::AlrDetector() : alr_budget_(0, true) {}
|
||||||
|
|
||||||
|
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(
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::system_clock::now().time_since_epoch())
|
||||||
|
.count());
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlrDetector::SetEstimatedBitrate(int 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int64_t> AlrDetector::GetApplicationLimitedRegionStartTime()
|
||||||
|
const {
|
||||||
|
return alr_started_time_ms_;
|
||||||
|
}
|
||||||
60
src/qos/alr_detector.h
Normal file
60
src/qos/alr_detector.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-01-14
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ALR_DETECTOR_H_
|
||||||
|
#define _ALR_DETECTOR_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "interval_budget.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
// 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);
|
||||||
|
AlrDetector();
|
||||||
|
~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.
|
||||||
|
std::optional<int64_t> GetApplicationLimitedRegionStartTime() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class GoogCcStatePrinter;
|
||||||
|
const AlrDetectorConfig conf_;
|
||||||
|
|
||||||
|
std::optional<int64_t> last_send_time_ms_;
|
||||||
|
|
||||||
|
IntervalBudget alr_budget_;
|
||||||
|
std::optional<int64_t> alr_started_time_ms_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
137
src/qos/bitrate_estimator.cpp
Normal file
137
src/qos/bitrate_estimator.cpp
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* 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 "bitrate_estimator.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
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()
|
||||||
|
: sum_(0),
|
||||||
|
initial_window_ms_(kInitialRateWindowMs, kMinRateWindowMs,
|
||||||
|
kMaxRateWindowMs),
|
||||||
|
noninitial_window_ms_(kRateWindowMs, kMinRateWindowMs, kMaxRateWindowMs),
|
||||||
|
uncertainty_scale_(10.0),
|
||||||
|
uncertainty_scale_in_alr_(uncertainty_scale_),
|
||||||
|
small_sample_uncertainty_scale_(uncertainty_scale_),
|
||||||
|
small_sample_threshold_(0),
|
||||||
|
uncertainty_symmetry_cap_(0),
|
||||||
|
estimate_floor_(0),
|
||||||
|
current_window_ms_(0),
|
||||||
|
prev_time_ms_(-1),
|
||||||
|
bitrate_estimate_kbps_(-1.0f),
|
||||||
|
bitrate_estimate_var_(50.0f) {}
|
||||||
|
|
||||||
|
BitrateEstimator::~BitrateEstimator() = default;
|
||||||
|
|
||||||
|
void BitrateEstimator::Update(int64_t at_time, int64_t 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, amount, 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,
|
||||||
|
static_cast<float>(uncertainty_symmetry_cap_)));
|
||||||
|
|
||||||
|
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_, static_cast<float>(estimate_floor_));
|
||||||
|
bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var /
|
||||||
|
(sample_var + pred_bitrate_estimate_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
float BitrateEstimator::UpdateWindow(int64_t now_ms, int bytes,
|
||||||
|
int rate_window_ms,
|
||||||
|
bool* is_small_sample) {
|
||||||
|
// 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_;
|
||||||
|
bitrate_sample = 8.0f * sum_ / static_cast<float>(rate_window_ms);
|
||||||
|
current_window_ms_ -= rate_window_ms;
|
||||||
|
sum_ = 0;
|
||||||
|
}
|
||||||
|
sum_ += bytes;
|
||||||
|
return bitrate_sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int64_t> BitrateEstimator::bitrate() const {
|
||||||
|
if (bitrate_estimate_kbps_ < 0.f) return std::nullopt;
|
||||||
|
return static_cast<int64_t>(bitrate_estimate_kbps_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int64_t> BitrateEstimator::PeekRate() const {
|
||||||
|
if (current_window_ms_ > 0) return sum_ / current_window_ms_;
|
||||||
|
return std::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;
|
||||||
|
}
|
||||||
50
src/qos/bitrate_estimator.h
Normal file
50
src/qos/bitrate_estimator.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-01-14
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _BITRATE_ESTIMATOR_H_
|
||||||
|
#define _BITRATE_ESTIMATOR_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "constrained.h"
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
virtual ~BitrateEstimator();
|
||||||
|
virtual void Update(int64_t at_time, int64_t amount, bool in_alr);
|
||||||
|
|
||||||
|
virtual std::optional<int64_t> bitrate() const;
|
||||||
|
std::optional<int64_t> PeekRate() const;
|
||||||
|
|
||||||
|
virtual void ExpectFastRateChange();
|
||||||
|
|
||||||
|
private:
|
||||||
|
float UpdateWindow(int64_t now_ms, int bytes, int rate_window_ms,
|
||||||
|
bool* is_small_sample);
|
||||||
|
int sum_;
|
||||||
|
Constrained<int> initial_window_ms_;
|
||||||
|
Constrained<int> noninitial_window_ms_;
|
||||||
|
double uncertainty_scale_;
|
||||||
|
double uncertainty_scale_in_alr_;
|
||||||
|
double small_sample_uncertainty_scale_;
|
||||||
|
int64_t small_sample_threshold_;
|
||||||
|
int64_t uncertainty_symmetry_cap_;
|
||||||
|
int64_t estimate_floor_;
|
||||||
|
int64_t current_window_ms_;
|
||||||
|
int64_t prev_time_ms_;
|
||||||
|
float bitrate_estimate_kbps_;
|
||||||
|
float bitrate_estimate_var_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -61,16 +61,16 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback(
|
|||||||
if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow)
|
if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow)
|
||||||
feedback_max_rtts_.pop_front();
|
feedback_max_rtts_.pop_front();
|
||||||
// TODO(srte): Use time since last unacknowledged packet.
|
// TODO(srte): Use time since last unacknowledged packet.
|
||||||
// bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time,
|
bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time,
|
||||||
// min_propagation_rtt);
|
min_propagation_rtt);
|
||||||
}
|
}
|
||||||
if (packet_feedback_only_) {
|
if (packet_feedback_only_) {
|
||||||
if (!feedback_max_rtts_.empty()) {
|
if (!feedback_max_rtts_.empty()) {
|
||||||
int64_t sum_rtt_ms =
|
int64_t sum_rtt_ms =
|
||||||
std::accumulate(feedback_max_rtts_.begin(), feedback_max_rtts_.end(),
|
std::accumulate(feedback_max_rtts_.begin(), feedback_max_rtts_.end(),
|
||||||
static_cast<int64_t>(0));
|
static_cast<int64_t>(0));
|
||||||
// int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size();
|
int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size();
|
||||||
// if (delay_based_bwe_) delay_based_bwe_->OnRttUpdate(mean_rtt_ms);
|
if (delay_based_bwe_) delay_based_bwe_->OnRttUpdate(mean_rtt_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t feedback_min_rtt = std::numeric_limits<int64_t>::max();
|
int64_t feedback_min_rtt = std::numeric_limits<int64_t>::max();
|
||||||
@@ -83,8 +83,7 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback(
|
|||||||
}
|
}
|
||||||
if (feedback_min_rtt != std::numeric_limits<int64_t>::max() &&
|
if (feedback_min_rtt != std::numeric_limits<int64_t>::max() &&
|
||||||
feedback_min_rtt != std::numeric_limits<int64_t>::min()) {
|
feedback_min_rtt != std::numeric_limits<int64_t>::min()) {
|
||||||
// bandwidth_estimation_->UpdateRtt(feedback_min_rtt,
|
bandwidth_estimation_->UpdateRtt(feedback_min_rtt, report.feedback_time);
|
||||||
// report.feedback_time);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_packets_since_last_loss_update_ +=
|
expected_packets_since_last_loss_update_ +=
|
||||||
@@ -95,101 +94,101 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback(
|
|||||||
}
|
}
|
||||||
if (report.feedback_time > next_loss_update_) {
|
if (report.feedback_time > next_loss_update_) {
|
||||||
next_loss_update_ = report.feedback_time + kLossUpdateInterval;
|
next_loss_update_ = report.feedback_time + kLossUpdateInterval;
|
||||||
// bandwidth_estimation_->UpdatePacketsLost(
|
bandwidth_estimation_->UpdatePacketsLost(
|
||||||
// lost_packets_since_last_loss_update_,
|
lost_packets_since_last_loss_update_,
|
||||||
// expected_packets_since_last_loss_update_, report.feedback_time);
|
expected_packets_since_last_loss_update_, report.feedback_time);
|
||||||
expected_packets_since_last_loss_update_ = 0;
|
expected_packets_since_last_loss_update_ = 0;
|
||||||
lost_packets_since_last_loss_update_ = 0;
|
lost_packets_since_last_loss_update_ = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// std::optional<int64_t> alr_start_time =
|
std::optional<int64_t> alr_start_time =
|
||||||
// alr_detector_->GetApplicationLimitedRegionStartTime();
|
alr_detector_->GetApplicationLimitedRegionStartTime();
|
||||||
|
|
||||||
// if (previously_in_alr_ && !alr_start_time.has_value()) {
|
if (previously_in_alr_ && !alr_start_time.has_value()) {
|
||||||
// int64_t now_ms = report.feedback_time;
|
int64_t now_ms = report.feedback_time;
|
||||||
// acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time);
|
acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time);
|
||||||
// probe_controller_->SetAlrEndedTimeMs(now_ms);
|
probe_controller_->SetAlrEndedTimeMs(now_ms);
|
||||||
// }
|
}
|
||||||
// previously_in_alr_ = alr_start_time.has_value();
|
previously_in_alr_ = alr_start_time.has_value();
|
||||||
// acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(
|
acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||||
// report.SortedByReceiveTime());
|
report.SortedByReceiveTime());
|
||||||
// auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate();
|
auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate();
|
||||||
// bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate,
|
bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate,
|
||||||
// report.feedback_time);
|
report.feedback_time);
|
||||||
for (const auto& feedback : report.SortedByReceiveTime()) {
|
for (const auto& feedback : report.SortedByReceiveTime()) {
|
||||||
if (feedback.sent_packet.pacing_info.probe_cluster_id !=
|
if (feedback.sent_packet.pacing_info.probe_cluster_id !=
|
||||||
PacedPacketInfo::kNotAProbe) {
|
PacedPacketInfo::kNotAProbe) {
|
||||||
// probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback);
|
probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (network_estimator_) {
|
if (network_estimator_) {
|
||||||
// network_estimator_->OnTransportPacketsFeedback(report);
|
network_estimator_->OnTransportPacketsFeedback(report);
|
||||||
// // SetNetworkStateEstimate(network_estimator_->GetCurrentEstimate());
|
SetNetworkStateEstimate(network_estimator_->GetCurrentEstimate());
|
||||||
// }
|
}
|
||||||
// std::optional<int64_t> probe_bitrate =
|
std::optional<int64_t> probe_bitrate =
|
||||||
// probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate();
|
probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate();
|
||||||
// if (ignore_probes_lower_than_network_estimate_ && probe_bitrate &&
|
if (ignore_probes_lower_than_network_estimate_ && probe_bitrate &&
|
||||||
// estimate_ && *probe_bitrate < delay_based_bwe_->last_estimate() &&
|
estimate_ && *probe_bitrate < delay_based_bwe_->last_estimate() &&
|
||||||
// *probe_bitrate < estimate_->link_capacity_lower) {
|
*probe_bitrate < estimate_->link_capacity_lower) {
|
||||||
// probe_bitrate.reset();
|
probe_bitrate.reset();
|
||||||
// }
|
}
|
||||||
// if (limit_probes_lower_than_throughput_estimate_ && probe_bitrate &&
|
if (limit_probes_lower_than_throughput_estimate_ && probe_bitrate &&
|
||||||
// acknowledged_bitrate) {
|
acknowledged_bitrate) {
|
||||||
// Limit the backoff to something slightly below the acknowledged
|
// Limit the backoff to something slightly below the acknowledged
|
||||||
// bitrate. ("Slightly below" because we want to drain the queues
|
// bitrate. ("Slightly below" because we want to drain the queues
|
||||||
// if we are actually overusing.)
|
// if we are actually overusing.)
|
||||||
// The acknowledged bitrate shouldn't normally be higher than the delay
|
// The acknowledged bitrate shouldn't normally be higher than the delay
|
||||||
// based estimate, but it could happen e.g. due to packet bursts or
|
// 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
|
// encoder overshoot. We use std::min to ensure that a probe result
|
||||||
// below the current BWE never causes an increase.
|
// below the current BWE never causes an increase.
|
||||||
// int64_t limit =
|
int64_t limit =
|
||||||
// std::min(delay_based_bwe_->last_estimate(),
|
std::min(delay_based_bwe_->last_estimate(),
|
||||||
// *acknowledged_bitrate * kProbeDropThroughputFraction);
|
*acknowledged_bitrate * kProbeDropThroughputFraction);
|
||||||
// probe_bitrate = std::max(*probe_bitrate, limit);
|
probe_bitrate = std::max(*probe_bitrate, limit);
|
||||||
// }
|
}
|
||||||
|
|
||||||
NetworkControlUpdate update;
|
NetworkControlUpdate update;
|
||||||
bool recovered_from_overuse = false;
|
bool recovered_from_overuse = false;
|
||||||
|
|
||||||
// DelayBasedBwe::Result result;
|
DelayBasedBwe::Result result;
|
||||||
// result = delay_based_bwe_->IncomingPacketFeedbackVector(
|
result = delay_based_bwe_->IncomingPacketFeedbackVector(
|
||||||
// report, acknowledged_bitrate, probe_bitrate, estimate_,
|
report, acknowledged_bitrate, probe_bitrate, estimate_,
|
||||||
// alr_start_time.has_value());
|
alr_start_time.has_value());
|
||||||
|
|
||||||
// if (result.updated) {
|
if (result.updated) {
|
||||||
// if (result.probe) {
|
if (result.probe) {
|
||||||
// bandwidth_estimation_->SetSendBitrate(result.target_bitrate,
|
bandwidth_estimation_->SetSendBitrate(result.target_bitrate,
|
||||||
// report.feedback_time);
|
report.feedback_time);
|
||||||
// }
|
}
|
||||||
// Since SetSendBitrate now resets the delay-based estimate, we have to
|
// Since SetSendBitrate now resets the delay-based estimate, we have to
|
||||||
// call UpdateDelayBasedEstimate after SetSendBitrate.
|
// call UpdateDelayBasedEstimate after SetSendBitrate.
|
||||||
// bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time,
|
bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time,
|
||||||
// result.target_bitrate);
|
result.target_bitrate);
|
||||||
// }
|
}
|
||||||
// bandwidth_estimation_->UpdateLossBasedEstimator(
|
bandwidth_estimation_->UpdateLossBasedEstimator(
|
||||||
// report, result.delay_detector_state, probe_bitrate,
|
report, result.delay_detector_state, probe_bitrate,
|
||||||
// alr_start_time.has_value());
|
alr_start_time.has_value());
|
||||||
// if (result.updated) {
|
if (result.updated) {
|
||||||
// // Update the estimate in the ProbeController, in case we want to probe.
|
// Update the estimate in the ProbeController, in case we want to probe.
|
||||||
// MaybeTriggerOnNetworkChanged(&update, report.feedback_time);
|
MaybeTriggerOnNetworkChanged(&update, report.feedback_time);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// recovered_from_overuse = result.recovered_from_overuse;
|
recovered_from_overuse = result.recovered_from_overuse;
|
||||||
|
|
||||||
// if (recovered_from_overuse) {
|
if (recovered_from_overuse) {
|
||||||
// probe_controller_->SetAlrStartTimeMs(alr_start_time);
|
probe_controller_->SetAlrStartTimeMs(alr_start_time);
|
||||||
// auto probes = probe_controller_->RequestProbe(report.feedback_time);
|
auto probes = probe_controller_->RequestProbe(report.feedback_time);
|
||||||
// update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
|
update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
|
||||||
// probes.begin(), probes.end());
|
probes.begin(), probes.end());
|
||||||
// }
|
}
|
||||||
|
|
||||||
// No valid RTT could be because send-side BWE isn't used, in which case
|
// No valid RTT could be because send-side BWE isn't used, in which case
|
||||||
// we don't try to limit the outstanding packets.
|
// we don't try to limit the outstanding packets.
|
||||||
// if (rate_control_settings_.UseCongestionWindow() &&
|
if (rate_control_settings_.UseCongestionWindow() &&
|
||||||
// max_feedback_rtt.IsFinite()) {
|
max_feedback_rtt.IsFinite()) {
|
||||||
// UpdateCongestionWindowSize();
|
UpdateCongestionWindowSize();
|
||||||
// }
|
}
|
||||||
if (congestion_window_pushback_controller_ && current_data_window_) {
|
if (congestion_window_pushback_controller_ && current_data_window_) {
|
||||||
congestion_window_pushback_controller_->SetDataWindow(
|
congestion_window_pushback_controller_->SetDataWindow(
|
||||||
*current_data_window_);
|
*current_data_window_);
|
||||||
|
|||||||
@@ -4,8 +4,11 @@
|
|||||||
#include <deque>
|
#include <deque>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "acknowledged_bitrate_estimator.h"
|
||||||
|
#include "alr_detector.h"
|
||||||
#include "congestion_window_pushback_controller.h"
|
#include "congestion_window_pushback_controller.h"
|
||||||
#include "network_types.h"
|
#include "network_types.h"
|
||||||
|
#include "send_side_bandwidth_estimation.h"
|
||||||
|
|
||||||
class CongestionControl {
|
class CongestionControl {
|
||||||
public:
|
public:
|
||||||
@@ -22,14 +25,19 @@ class CongestionControl {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::deque<int64_t> feedback_max_rtts_;
|
std::deque<int64_t> feedback_max_rtts_;
|
||||||
// std::unique_ptr<SendSideBandwidthEstimation> bandwidth_estimation_;
|
|
||||||
int expected_packets_since_last_loss_update_ = 0;
|
int expected_packets_since_last_loss_update_ = 0;
|
||||||
int lost_packets_since_last_loss_update_ = 0;
|
int lost_packets_since_last_loss_update_ = 0;
|
||||||
int64_t next_loss_update_ = std::numeric_limits<int64_t>::min();
|
int64_t next_loss_update_ = std::numeric_limits<int64_t>::min();
|
||||||
const bool packet_feedback_only_ = false;
|
const bool packet_feedback_only_ = false;
|
||||||
bool previously_in_alr_ = false;
|
bool previously_in_alr_ = false;
|
||||||
// const bool limit_probes_lower_than_throughput_estimate_;
|
const bool limit_probes_lower_than_throughput_estimate_ = false;
|
||||||
std::optional<int64_t> current_data_window_;
|
std::optional<int64_t> current_data_window_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<SendSideBandwidthEstimation> bandwidth_estimation_;
|
||||||
|
std::unique_ptr<AlrDetector> alr_detector_;
|
||||||
|
std::unique_ptr<AcknowledgedBitrateEstimator> acknowledged_bitrate_estimator_;
|
||||||
|
std::unique_ptr<DelayBasedBwe> delay_based_bwe_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
31
src/qos/constrained.h
Normal file
31
src/qos/constrained.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-01-14
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CONSTRAIN_H_
|
||||||
|
#define _CONSTRAIN_H_
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Constrained {
|
||||||
|
public:
|
||||||
|
Constrained(T default_value, std::optional<T> lower_limit,
|
||||||
|
std::optional<T> upper_limit)
|
||||||
|
: value_(default_value),
|
||||||
|
lower_limit_(lower_limit),
|
||||||
|
upper_limit_(upper_limit) {}
|
||||||
|
T Get() const { return value_; }
|
||||||
|
operator T() const { return Get(); }
|
||||||
|
const T* operator->() const { return &value_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T value_;
|
||||||
|
std::optional<T> lower_limit_;
|
||||||
|
std::optional<T> upper_limit_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
285
src/qos/delay_based_bwe.cpp
Normal file
285
src/qos/delay_based_bwe.cpp
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
* 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 "delay_based_bwe.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "api/transport/network_types.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int64_t kStreamTimeOut = int64_t::Seconds(2);
|
||||||
|
constexpr int64_t kSendTimeGroupLength = int64_t::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(int64_t::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_(int64_t::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_(int64_t::MinusInfinity()),
|
||||||
|
uma_recorded_(false),
|
||||||
|
rate_control_(*key_value_config, /*send_side=*/true),
|
||||||
|
prev_bitrate_(int64_t::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, std::optional<int64_t> acked_bitrate,
|
||||||
|
std::optional<int64_t> probe_bitrate,
|
||||||
|
std::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,
|
||||||
|
int64_t 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;
|
||||||
|
|
||||||
|
int64_t send_delta = int64_t::Zero();
|
||||||
|
int64_t recv_delta = int64_t::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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t DelayBasedBwe::TriggerOveruse(int64_t at_time,
|
||||||
|
std::optional<int64_t> link_capacity) {
|
||||||
|
RateControlInput input(BandwidthUsage::kBwOverusing, link_capacity);
|
||||||
|
return rate_control_.Update(input, at_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
DelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate(
|
||||||
|
std::optional<int64_t> acked_bitrate, std::optional<int64_t> probe_bitrate,
|
||||||
|
std::optional<NetworkStateEstimate> /* state_estimate */,
|
||||||
|
bool recovered_from_overuse, bool /* in_alr */, int64_t 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_) {
|
||||||
|
int64_t bitrate = result.updated ? result.target_bitrate : prev_bitrate_;
|
||||||
|
|
||||||
|
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(int64_t at_time,
|
||||||
|
std::optional<int64_t> acked_bitrate,
|
||||||
|
int64_t* 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(int64_t avg_rtt) {
|
||||||
|
rate_control_.SetRtt(avg_rtt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DelayBasedBwe::LatestEstimate(std::vector<uint32_t>* ssrcs,
|
||||||
|
int64_t* 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(int64_t start_bitrate) {
|
||||||
|
RTC_LOG(LS_INFO) << "BWE Setting start bitrate to: "
|
||||||
|
<< ToString(start_bitrate);
|
||||||
|
rate_control_.SetStartBitrate(start_bitrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DelayBasedBwe::SetMinBitrate(int64_t 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t DelayBasedBwe::GetExpectedBwePeriod() const {
|
||||||
|
return rate_control_.GetExpectedBandwidthPeriod();
|
||||||
|
}
|
||||||
115
src/qos/delay_based_bwe.h
Normal file
115
src/qos/delay_based_bwe.h
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-01-14
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _DELAY_BASED_BWE_H_
|
||||||
|
#define _DELAY_BASED_BWE_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "network_types.h"
|
||||||
|
|
||||||
|
enum class BandwidthUsage {
|
||||||
|
kBwNormal = 0,
|
||||||
|
kBwUnderusing = 1,
|
||||||
|
kBwOverusing = 2,
|
||||||
|
kLast
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
int64_t time_threshold = int64_t::Seconds(1);
|
||||||
|
|
||||||
|
std::unique_ptr<StructParametersParser> Parser();
|
||||||
|
};
|
||||||
|
|
||||||
|
class DelayBasedBwe {
|
||||||
|
public:
|
||||||
|
struct Result {
|
||||||
|
Result();
|
||||||
|
~Result() = default;
|
||||||
|
bool updated;
|
||||||
|
bool probe;
|
||||||
|
int64_t target_bitrate = int64_t::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, std::optional<int64_t> acked_bitrate,
|
||||||
|
std::optional<int64_t> probe_bitrate,
|
||||||
|
std::optional<NetworkStateEstimate> network_estimate, bool in_alr);
|
||||||
|
void OnRttUpdate(int64_t avg_rtt);
|
||||||
|
bool LatestEstimate(std::vector<uint32_t>* ssrcs, int64_t* bitrate) const;
|
||||||
|
void SetStartBitrate(int64_t start_bitrate);
|
||||||
|
void SetMinBitrate(int64_t min_bitrate);
|
||||||
|
int64_t GetExpectedBwePeriod() const;
|
||||||
|
int64_t TriggerOveruse(int64_t at_time, std::optional<int64_t> link_capacity);
|
||||||
|
int64_t last_estimate() const { return prev_bitrate_; }
|
||||||
|
BandwidthUsage last_state() const { return prev_state_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class GoogCcStatePrinter;
|
||||||
|
void IncomingPacketFeedback(const PacketResult& packet_feedback,
|
||||||
|
int64_t at_time);
|
||||||
|
Result MaybeUpdateEstimate(std::optional<int64_t> acked_bitrate,
|
||||||
|
std::optional<int64_t> probe_bitrate,
|
||||||
|
std::optional<NetworkStateEstimate> state_estimate,
|
||||||
|
bool recovered_from_overuse, bool in_alr,
|
||||||
|
int64_t at_time);
|
||||||
|
// Updates the current remote rate estimate and returns true if a valid
|
||||||
|
// estimate exists.
|
||||||
|
bool UpdateEstimate(int64_t at_time, std::optional<int64_t> acked_bitrate,
|
||||||
|
int64_t* 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_;
|
||||||
|
int64_t 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_;
|
||||||
|
|
||||||
|
int64_t last_seen_packet_;
|
||||||
|
bool uma_recorded_;
|
||||||
|
AimdRateControl rate_control_;
|
||||||
|
int64_t prev_bitrate_;
|
||||||
|
BandwidthUsage prev_state_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
62
src/qos/interval_budget.cpp
Normal file
62
src/qos/interval_budget.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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 "interval_budget.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int64_t kWindowMs = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntervalBudget::IntervalBudget(int initial_target_rate_kbps)
|
||||||
|
: IntervalBudget(initial_target_rate_kbps, false) {}
|
||||||
|
|
||||||
|
IntervalBudget::IntervalBudget(int initial_target_rate_kbps,
|
||||||
|
bool can_build_up_underuse)
|
||||||
|
: bytes_remaining_(0), can_build_up_underuse_(can_build_up_underuse) {
|
||||||
|
set_target_rate_kbps(initial_target_rate_kbps);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntervalBudget::set_target_rate_kbps(int target_rate_kbps) {
|
||||||
|
target_rate_kbps_ = target_rate_kbps;
|
||||||
|
max_bytes_in_budget_ = (kWindowMs * target_rate_kbps_) / 8;
|
||||||
|
bytes_remaining_ = std::min(std::max(-max_bytes_in_budget_, bytes_remaining_),
|
||||||
|
max_bytes_in_budget_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntervalBudget::IncreaseBudget(int64_t delta_time_ms) {
|
||||||
|
int64_t bytes = target_rate_kbps_ * delta_time_ms / 8;
|
||||||
|
if (bytes_remaining_ < 0 || can_build_up_underuse_) {
|
||||||
|
// We overused last interval, compensate this interval.
|
||||||
|
bytes_remaining_ = std::min(bytes_remaining_ + bytes, max_bytes_in_budget_);
|
||||||
|
} else {
|
||||||
|
// If we underused last interval we can't use it this interval.
|
||||||
|
bytes_remaining_ = std::min(bytes, max_bytes_in_budget_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntervalBudget::UseBudget(size_t bytes) {
|
||||||
|
bytes_remaining_ = std::max(bytes_remaining_ - static_cast<int>(bytes),
|
||||||
|
-max_bytes_in_budget_);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IntervalBudget::bytes_remaining() const {
|
||||||
|
return static_cast<size_t>(std::max<int64_t>(0, bytes_remaining_));
|
||||||
|
}
|
||||||
|
|
||||||
|
double IntervalBudget::budget_ratio() const {
|
||||||
|
if (max_bytes_in_budget_ == 0) return 0.0;
|
||||||
|
return static_cast<double>(bytes_remaining_) / max_bytes_in_budget_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int IntervalBudget::target_rate_kbps() const { return target_rate_kbps_; }
|
||||||
36
src/qos/interval_budget.h
Normal file
36
src/qos/interval_budget.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-01-14
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _INTERVAL_BUDGET_H_
|
||||||
|
#define _INTERVAL_BUDGET_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// TODO(tschumim): Reflector IntervalBudget so that we can set a under- and
|
||||||
|
// over-use budget in ms.
|
||||||
|
class IntervalBudget {
|
||||||
|
public:
|
||||||
|
explicit IntervalBudget(int initial_target_rate_kbps);
|
||||||
|
IntervalBudget(int initial_target_rate_kbps, bool can_build_up_underuse);
|
||||||
|
void set_target_rate_kbps(int target_rate_kbps);
|
||||||
|
|
||||||
|
// TODO(tschumim): Unify IncreaseBudget and UseBudget to one function.
|
||||||
|
void IncreaseBudget(int64_t delta_time_ms);
|
||||||
|
void UseBudget(size_t bytes);
|
||||||
|
|
||||||
|
size_t bytes_remaining() const;
|
||||||
|
double budget_ratio() const;
|
||||||
|
int target_rate_kbps() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int target_rate_kbps_;
|
||||||
|
int64_t max_bytes_in_budget_;
|
||||||
|
int64_t bytes_remaining_;
|
||||||
|
bool can_build_up_underuse_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
505
src/qos/send_side_bandwidth_estimation.cpp
Normal file
505
src/qos/send_side_bandwidth_estimation.cpp
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
/*
|
||||||
|
* 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 "send_side_bandwidth_estimation.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int64_t kBweIncreaseInterval = 1000;
|
||||||
|
constexpr int64_t kBweDecreaseInterval = 300;
|
||||||
|
constexpr int64_t kStartPhase = 2000;
|
||||||
|
constexpr int64_t kBweConverganceTime = 20000;
|
||||||
|
constexpr int kLimitNumPackets = 20;
|
||||||
|
constexpr int64_t kDefaultMaxBitrate = 1000000000;
|
||||||
|
constexpr int64_t kLowBitrateLogPeriod = 10000;
|
||||||
|
constexpr int64_t kRtcEventLogPeriod = 5000;
|
||||||
|
// Expecting that RTCP feedback is sent uniformly within [0.5, 1.5]s intervals.
|
||||||
|
constexpr int64_t kMaxRtcpFeedbackInterval = 5000;
|
||||||
|
|
||||||
|
constexpr float kDefaultLowLossThreshold = 0.02f;
|
||||||
|
constexpr float kDefaultHighLossThreshold = 0.1f;
|
||||||
|
constexpr int64_t kDefaultBitrateThreshold = 0;
|
||||||
|
|
||||||
|
constexpr int64_t kCongestionControllerMinBitrate = 5000;
|
||||||
|
|
||||||
|
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]);
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void LinkCapacityTracker::UpdateDelayBasedEstimate(
|
||||||
|
int64_t at_time, int64_t delay_based_bitrate) {
|
||||||
|
if (delay_based_bitrate < last_delay_based_estimate_) {
|
||||||
|
capacity_estimate_bps_ = std::min(capacity_estimate_bps_,
|
||||||
|
static_cast<double>(delay_based_bitrate));
|
||||||
|
last_link_capacity_update_ = at_time;
|
||||||
|
}
|
||||||
|
last_delay_based_estimate_ = delay_based_bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkCapacityTracker::OnStartingRate(int64_t start_rate) {
|
||||||
|
if (IsInfinite(last_link_capacity_update_)) {
|
||||||
|
capacity_estimate_bps_ = start_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkCapacityTracker::OnRateUpdate(std::optional<int64_t> acknowledged,
|
||||||
|
int64_t target, int64_t at_time) {
|
||||||
|
if (!acknowledged) return;
|
||||||
|
int64_t acknowledged_target = std::min(*acknowledged, target);
|
||||||
|
if (acknowledged_target > capacity_estimate_bps_) {
|
||||||
|
int64_t delta = at_time - last_link_capacity_update_;
|
||||||
|
double alpha = IsFinite(delta) ? exp(-(delta / 10)) : 0;
|
||||||
|
capacity_estimate_bps_ =
|
||||||
|
alpha * capacity_estimate_bps_ + (1 - alpha) * acknowledged_target;
|
||||||
|
}
|
||||||
|
last_link_capacity_update_ = at_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkCapacityTracker::OnRttBackoff(int64_t backoff_rate, int64_t at_time) {
|
||||||
|
capacity_estimate_bps_ =
|
||||||
|
std::min(capacity_estimate_bps_, static_cast<double>(backoff_rate));
|
||||||
|
last_link_capacity_update_ = at_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t LinkCapacityTracker::estimate() const { return capacity_estimate_bps_; }
|
||||||
|
|
||||||
|
RttBasedBackoff::RttBasedBackoff()
|
||||||
|
: disabled_(true),
|
||||||
|
configured_limit_(3),
|
||||||
|
drop_fraction_(0.8),
|
||||||
|
drop_interval_(1),
|
||||||
|
bandwidth_floor_(5),
|
||||||
|
rtt_limit_(INT64_T_MAX),
|
||||||
|
// By initializing this to plus infinity, we make sure that we never
|
||||||
|
// trigger rtt backoff unless packet feedback is enabled.
|
||||||
|
last_propagation_rtt_update_(INT64_T_MAX),
|
||||||
|
last_propagation_rtt_(0),
|
||||||
|
last_packet_sent_(INT64_T_MIN) {
|
||||||
|
if (!disabled_) {
|
||||||
|
rtt_limit_ = configured_limit_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RttBasedBackoff::UpdatePropagationRtt(int64_t at_time,
|
||||||
|
int64_t propagation_rtt) {
|
||||||
|
last_propagation_rtt_update_ = at_time;
|
||||||
|
last_propagation_rtt_ = propagation_rtt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RttBasedBackoff::IsRttAboveLimit() const {
|
||||||
|
return CorrectedRtt() > rtt_limit_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t RttBasedBackoff::CorrectedRtt() const {
|
||||||
|
// Avoid timeout when no packets are being sent.
|
||||||
|
int64_t timeout_correction =
|
||||||
|
std::max(last_packet_sent_ - last_propagation_rtt_update_,
|
||||||
|
static_cast<int64_t>(0));
|
||||||
|
return timeout_correction + last_propagation_rtt_;
|
||||||
|
}
|
||||||
|
|
||||||
|
RttBasedBackoff::~RttBasedBackoff() = default;
|
||||||
|
|
||||||
|
SendSideBandwidthEstimation::SendSideBandwidthEstimation()
|
||||||
|
: lost_packets_since_last_loss_update_(0),
|
||||||
|
expected_packets_since_last_loss_update_(0),
|
||||||
|
current_target_(0),
|
||||||
|
last_logged_target_(0),
|
||||||
|
min_bitrate_configured_(kCongestionControllerMinBitrate),
|
||||||
|
max_bitrate_configured_(kDefaultMaxBitrate),
|
||||||
|
last_low_bitrate_log_(INT64_T_MIN),
|
||||||
|
has_decreased_since_last_fraction_loss_(false),
|
||||||
|
last_loss_feedback_(INT64_T_MIN),
|
||||||
|
last_loss_packet_report_(INT64_T_MIN),
|
||||||
|
last_fraction_loss_(0),
|
||||||
|
last_logged_fraction_loss_(0),
|
||||||
|
last_round_trip_time_(0),
|
||||||
|
receiver_limit_(INT64_T_MAX),
|
||||||
|
delay_based_limit_(INT64_T_MAX),
|
||||||
|
time_last_decrease_(INT64_T_MIN),
|
||||||
|
first_report_time_(INT64_T_MIN),
|
||||||
|
initially_lost_packets_(0),
|
||||||
|
bitrate_at_2_seconds_(0),
|
||||||
|
uma_update_state_(kNoUpdate),
|
||||||
|
uma_rtt_state_(kNoUpdate),
|
||||||
|
rampup_uma_stats_updated_(kNumUmaRampupMetrics, false),
|
||||||
|
last_rtc_event_log_(INT64_T_MIN),
|
||||||
|
low_loss_threshold_(kDefaultLowLossThreshold),
|
||||||
|
high_loss_threshold_(kDefaultHighLossThreshold),
|
||||||
|
bitrate_threshold_(kDefaultBitrateThreshold),
|
||||||
|
disable_receiver_limit_caps_only_(true) {}
|
||||||
|
|
||||||
|
SendSideBandwidthEstimation::~SendSideBandwidthEstimation() {}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::OnRouteChange() {
|
||||||
|
lost_packets_since_last_loss_update_ = 0;
|
||||||
|
expected_packets_since_last_loss_update_ = 0;
|
||||||
|
current_target_ = 0;
|
||||||
|
min_bitrate_configured_ = kCongestionControllerMinBitrate;
|
||||||
|
max_bitrate_configured_ = kDefaultMaxBitrate;
|
||||||
|
last_low_bitrate_log_ = INT64_T_MIN;
|
||||||
|
has_decreased_since_last_fraction_loss_ = false;
|
||||||
|
last_loss_feedback_ = INT64_T_MIN;
|
||||||
|
last_loss_packet_report_ = INT64_T_MIN;
|
||||||
|
last_fraction_loss_ = 0;
|
||||||
|
last_logged_fraction_loss_ = 0;
|
||||||
|
last_round_trip_time_ = 0;
|
||||||
|
receiver_limit_ = INT64_T_MAX;
|
||||||
|
delay_based_limit_ = INT64_T_MAX;
|
||||||
|
time_last_decrease_ = INT64_T_MIN;
|
||||||
|
first_report_time_ = INT64_T_MIN;
|
||||||
|
initially_lost_packets_ = 0;
|
||||||
|
bitrate_at_2_seconds_ = 0;
|
||||||
|
uma_update_state_ = kNoUpdate;
|
||||||
|
uma_rtt_state_ = kNoUpdate;
|
||||||
|
last_rtc_event_log_ = INT64_T_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::SetBitrates(
|
||||||
|
std::optional<int64_t> send_bitrate, int64_t min_bitrate,
|
||||||
|
int64_t max_bitrate, int64_t at_time) {
|
||||||
|
SetMinMaxBitrate(min_bitrate, max_bitrate);
|
||||||
|
if (send_bitrate) {
|
||||||
|
link_capacity_.OnStartingRate(*send_bitrate);
|
||||||
|
SetSendBitrate(*send_bitrate, at_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::SetSendBitrate(int64_t bitrate,
|
||||||
|
int64_t at_time) {
|
||||||
|
// Reset to avoid being capped by the estimate.
|
||||||
|
delay_based_limit_ = INT64_T_MAX;
|
||||||
|
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(int64_t min_bitrate,
|
||||||
|
int64_t max_bitrate) {
|
||||||
|
min_bitrate_configured_ =
|
||||||
|
std::max(min_bitrate, kCongestionControllerMinBitrate);
|
||||||
|
if (max_bitrate > 0 && IsFinite(max_bitrate)) {
|
||||||
|
max_bitrate_configured_ = std::max(min_bitrate_configured_, max_bitrate);
|
||||||
|
} else {
|
||||||
|
max_bitrate_configured_ = kDefaultMaxBitrate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SendSideBandwidthEstimation::GetMinBitrate() const {
|
||||||
|
return min_bitrate_configured_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t SendSideBandwidthEstimation::target_rate() const {
|
||||||
|
int64_t target = current_target_;
|
||||||
|
if (!disable_receiver_limit_caps_only_)
|
||||||
|
target = std::min(target, receiver_limit_);
|
||||||
|
return std::max(min_bitrate_configured_, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SendSideBandwidthEstimation::IsRttAboveLimit() const {
|
||||||
|
return rtt_backoff_.IsRttAboveLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t SendSideBandwidthEstimation::GetEstimatedLinkCapacity() const {
|
||||||
|
return link_capacity_.estimate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::UpdateReceiverEstimate(int64_t at_time,
|
||||||
|
int64_t bandwidth) {
|
||||||
|
// TODO(srte): Ensure caller passes PlusInfinity, not zero, to represent no
|
||||||
|
// limitation.
|
||||||
|
receiver_limit_ = !bandwidth ? INT64_T_MAX : bandwidth;
|
||||||
|
ApplyTargetLimits(at_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::UpdateDelayBasedEstimate(int64_t at_time,
|
||||||
|
int64_t bitrate) {
|
||||||
|
link_capacity_.UpdateDelayBasedEstimate(at_time, bitrate);
|
||||||
|
// TODO(srte): Ensure caller passes PlusInfinity, not zero, to represent no
|
||||||
|
// limitation.
|
||||||
|
delay_based_limit_ = !bitrate ? INT64_T_MAX : bitrate;
|
||||||
|
ApplyTargetLimits(at_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::SetAcknowledgedRate(
|
||||||
|
std::optional<int64_t> acknowledged_rate, int64_t at_time) {
|
||||||
|
acknowledged_rate_ = acknowledged_rate;
|
||||||
|
if (!acknowledged_rate.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::UpdatePacketsLost(int64_t packets_lost,
|
||||||
|
int64_t number_of_packets,
|
||||||
|
int64_t at_time) {
|
||||||
|
last_loss_feedback_ = at_time;
|
||||||
|
if (IsInfinite(first_report_time_)) {
|
||||||
|
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,
|
||||||
|
static_cast<int64_t>(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(int64_t at_time,
|
||||||
|
int packets_lost) {
|
||||||
|
int64_t bitrate_kbps = (current_target_ + 500) / 1000;
|
||||||
|
for (size_t i = 0; i < kNumUmaRampupMetrics; ++i) {
|
||||||
|
if (!rampup_uma_stats_updated_[i] &&
|
||||||
|
bitrate_kbps >= kUmaRampupMetrics[i].bitrate_kbps) {
|
||||||
|
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;
|
||||||
|
} 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_ - bitrate_kbps, static_cast<int64_t>(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::UpdateRtt(int64_t rtt, int64_t 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 > 0) last_round_trip_time_ = rtt;
|
||||||
|
|
||||||
|
if (!IsInStartPhase(at_time) && uma_rtt_state_ == kNoUpdate) {
|
||||||
|
uma_rtt_state_ = kDone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::UpdateEstimate(int64_t 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;
|
||||||
|
int64_t new_bitrate =
|
||||||
|
std::max(current_target_ * rtt_backoff_.drop_fraction_,
|
||||||
|
static_cast<double>(rtt_backoff_.bandwidth_floor_));
|
||||||
|
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)) {
|
||||||
|
int64_t new_bitrate = current_target_;
|
||||||
|
// TODO(srte): We should not allow the new_bitrate to be larger than the
|
||||||
|
// receiver limit here.
|
||||||
|
if (IsFinite(receiver_limit_)) {
|
||||||
|
new_bitrate = std::max(receiver_limit_, new_bitrate);
|
||||||
|
}
|
||||||
|
if (IsFinite(delay_based_limit_)) {
|
||||||
|
new_bitrate = std::max(delay_based_limit_, new_bitrate);
|
||||||
|
}
|
||||||
|
if (new_bitrate != current_target_) {
|
||||||
|
min_bitrate_history_.clear();
|
||||||
|
min_bitrate_history_.push_back(std::make_pair(at_time, current_target_));
|
||||||
|
UpdateTargetBitrate(new_bitrate, at_time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateMinHistory(at_time);
|
||||||
|
if (IsInfinite(last_loss_packet_report_)) {
|
||||||
|
// No feedback received.
|
||||||
|
// TODO(srte): This is likely redundant in most cases.
|
||||||
|
ApplyTargetLimits(at_time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t 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.
|
||||||
|
int64_t new_bitrate = min_bitrate_history_.front().second * 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 += 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;
|
||||||
|
int64_t new_bitrate =
|
||||||
|
(current_target_ *
|
||||||
|
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(
|
||||||
|
int64_t at_time, int64_t 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(int64_t at_time) const {
|
||||||
|
return (IsInfinite(first_report_time_)) ||
|
||||||
|
at_time - first_report_time_ < kStartPhase;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::UpdateMinHistory(int64_t 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 + 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_));
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t SendSideBandwidthEstimation::GetUpperLimit() const {
|
||||||
|
int64_t 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(int64_t bitrate,
|
||||||
|
int64_t at_time) {
|
||||||
|
if (at_time - last_low_bitrate_log_ > kLowBitrateLogPeriod) {
|
||||||
|
LOG_WARN(
|
||||||
|
"Estimated available bandwidth {} is below configured min bitrate {}",
|
||||||
|
bitrate, min_bitrate_configured_);
|
||||||
|
last_low_bitrate_log_ = at_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::MaybeLogLossBasedEvent(int64_t at_time) {
|
||||||
|
if (current_target_ != last_logged_target_ ||
|
||||||
|
last_fraction_loss_ != last_logged_fraction_loss_ ||
|
||||||
|
at_time - last_rtc_event_log_ > kRtcEventLogPeriod) {
|
||||||
|
last_logged_fraction_loss_ = last_fraction_loss_;
|
||||||
|
last_logged_target_ = current_target_;
|
||||||
|
last_rtc_event_log_ = at_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendSideBandwidthEstimation::UpdateTargetBitrate(int64_t new_bitrate,
|
||||||
|
int64_t 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(int64_t at_time) {
|
||||||
|
UpdateTargetBitrate(current_target_, at_time);
|
||||||
|
}
|
||||||
178
src/qos/send_side_bandwidth_estimation.h
Normal file
178
src/qos/send_side_bandwidth_estimation.h
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-01-13
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SEND_SIDE_BANDWIDTH_ESTIMATION_H_
|
||||||
|
#define _SEND_SIDE_BANDWIDTH_ESTIMATION_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "limits_base.h"
|
||||||
|
#include "network_types.h"
|
||||||
|
|
||||||
|
class LinkCapacityTracker {
|
||||||
|
public:
|
||||||
|
LinkCapacityTracker() = default;
|
||||||
|
~LinkCapacityTracker() = default;
|
||||||
|
// Call when a new delay-based estimate is available.
|
||||||
|
void UpdateDelayBasedEstimate(int64_t at_time, int64_t delay_based_bitrate);
|
||||||
|
void OnStartingRate(int64_t start_rate);
|
||||||
|
void OnRateUpdate(std::optional<int64_t> acknowledged, int64_t target,
|
||||||
|
int64_t at_time);
|
||||||
|
void OnRttBackoff(int64_t backoff_rate, int64_t at_time);
|
||||||
|
int64_t estimate() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double capacity_estimate_bps_ = 0;
|
||||||
|
int64_t last_link_capacity_update_ = INT64_T_MIN;
|
||||||
|
int64_t last_delay_based_estimate_ = INT64_T_MAX;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RttBasedBackoff {
|
||||||
|
public:
|
||||||
|
explicit RttBasedBackoff();
|
||||||
|
~RttBasedBackoff();
|
||||||
|
void UpdatePropagationRtt(int64_t at_time, int64_t propagation_rtt);
|
||||||
|
bool IsRttAboveLimit() const;
|
||||||
|
|
||||||
|
bool disabled_;
|
||||||
|
int64_t configured_limit_;
|
||||||
|
double drop_fraction_;
|
||||||
|
int64_t drop_interval_;
|
||||||
|
int64_t bandwidth_floor_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
int64_t rtt_limit_;
|
||||||
|
int64_t last_propagation_rtt_update_;
|
||||||
|
int64_t last_propagation_rtt_;
|
||||||
|
int64_t last_packet_sent_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int64_t CorrectedRtt() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SendSideBandwidthEstimation {
|
||||||
|
public:
|
||||||
|
SendSideBandwidthEstimation();
|
||||||
|
~SendSideBandwidthEstimation();
|
||||||
|
|
||||||
|
void OnRouteChange();
|
||||||
|
|
||||||
|
int64_t target_rate() 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_; }
|
||||||
|
int64_t round_trip_time() const { return last_round_trip_time_; }
|
||||||
|
|
||||||
|
int64_t GetEstimatedLinkCapacity() const;
|
||||||
|
// Call periodically to update estimate.
|
||||||
|
void UpdateEstimate(int64_t at_time);
|
||||||
|
void OnSentPacket(const SentPacket& sent_packet);
|
||||||
|
void UpdatePropagationRtt(int64_t at_time, int64_t propagation_rtt);
|
||||||
|
|
||||||
|
// Call when we receive a RTCP message with TMMBR or REMB.
|
||||||
|
void UpdateReceiverEstimate(int64_t at_time, int64_t bandwidth);
|
||||||
|
|
||||||
|
// Call when a new delay-based estimate is available.
|
||||||
|
void UpdateDelayBasedEstimate(int64_t at_time, int64_t bitrate);
|
||||||
|
|
||||||
|
// Call when we receive a RTCP message with a ReceiveBlock.
|
||||||
|
void UpdatePacketsLost(int64_t packets_lost, int64_t number_of_packets,
|
||||||
|
int64_t at_time);
|
||||||
|
|
||||||
|
// Call when we receive a RTCP message with a ReceiveBlock.
|
||||||
|
void UpdateRtt(int64_t rtt, int64_t at_time);
|
||||||
|
|
||||||
|
void SetBitrates(std::optional<int64_t> send_bitrate, int64_t min_bitrate,
|
||||||
|
int64_t max_bitrate, int64_t at_time);
|
||||||
|
void SetSendBitrate(int64_t bitrate, int64_t at_time);
|
||||||
|
void SetMinMaxBitrate(int64_t min_bitrate, int64_t max_bitrate);
|
||||||
|
int GetMinBitrate() const;
|
||||||
|
void SetAcknowledgedRate(std::optional<int64_t> acknowledged_rate,
|
||||||
|
int64_t at_time);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class GoogCcStatePrinter;
|
||||||
|
|
||||||
|
enum UmaState { kNoUpdate, kFirstDone, kDone };
|
||||||
|
|
||||||
|
bool IsInStartPhase(int64_t at_time) const;
|
||||||
|
|
||||||
|
void UpdateUmaStatsPacketsLost(int64_t 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(int64_t 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.
|
||||||
|
int64_t GetUpperLimit() const;
|
||||||
|
// Prints a warning if `bitrate` if sufficiently long time has past since last
|
||||||
|
// warning.
|
||||||
|
void MaybeLogLowBitrateWarning(int64_t bitrate, int64_t 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(int64_t 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(int64_t bitrate, int64_t 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(int64_t at_time);
|
||||||
|
|
||||||
|
RttBasedBackoff rtt_backoff_;
|
||||||
|
LinkCapacityTracker link_capacity_;
|
||||||
|
|
||||||
|
std::deque<std::pair<int64_t, int64_t> > min_bitrate_history_;
|
||||||
|
|
||||||
|
// incoming filters
|
||||||
|
int lost_packets_since_last_loss_update_;
|
||||||
|
int expected_packets_since_last_loss_update_;
|
||||||
|
|
||||||
|
std::optional<int64_t> acknowledged_rate_;
|
||||||
|
int64_t current_target_;
|
||||||
|
int64_t last_logged_target_;
|
||||||
|
int64_t min_bitrate_configured_;
|
||||||
|
int64_t max_bitrate_configured_;
|
||||||
|
int64_t last_low_bitrate_log_;
|
||||||
|
|
||||||
|
bool has_decreased_since_last_fraction_loss_;
|
||||||
|
int64_t last_loss_feedback_;
|
||||||
|
int64_t last_loss_packet_report_;
|
||||||
|
uint8_t last_fraction_loss_;
|
||||||
|
uint8_t last_logged_fraction_loss_;
|
||||||
|
int64_t 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.
|
||||||
|
int64_t receiver_limit_;
|
||||||
|
int64_t delay_based_limit_;
|
||||||
|
int64_t time_last_decrease_;
|
||||||
|
int64_t first_report_time_;
|
||||||
|
int initially_lost_packets_;
|
||||||
|
int64_t bitrate_at_2_seconds_;
|
||||||
|
UmaState uma_update_state_;
|
||||||
|
UmaState uma_rtt_state_;
|
||||||
|
std::vector<bool> rampup_uma_stats_updated_;
|
||||||
|
int64_t last_rtc_event_log_;
|
||||||
|
float low_loss_threshold_;
|
||||||
|
float high_loss_threshold_;
|
||||||
|
int64_t bitrate_threshold_;
|
||||||
|
bool disable_receiver_limit_caps_only_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user