Support borrowing of underused audio bitrate.
Controlled via added field trial WebRTC-ElasticBitrateAllocation. Bug: webrtc:350555527 Change-Id: If57552144bd4a50421d618fd8bdab31d7c4afc35 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/359506 Reviewed-by: Erik Språng <sprang@webrtc.org> Commit-Queue: Dan Tan <dwtan@google.com> Cr-Commit-Position: refs/heads/main@{#42834}
This commit is contained in:
parent
2e376cd36d
commit
43c0cf9cf8
@ -249,6 +249,7 @@ rtc_library("bitrate_allocator") {
|
|||||||
]
|
]
|
||||||
deps = [
|
deps = [
|
||||||
"../api:bitrate_allocation",
|
"../api:bitrate_allocation",
|
||||||
|
"../api:field_trials_view",
|
||||||
"../api:sequence_checker",
|
"../api:sequence_checker",
|
||||||
"../api/transport:network_control",
|
"../api/transport:network_control",
|
||||||
"../api/units:data_rate",
|
"../api/units:data_rate",
|
||||||
@ -256,6 +257,7 @@ rtc_library("bitrate_allocator") {
|
|||||||
"../rtc_base:checks",
|
"../rtc_base:checks",
|
||||||
"../rtc_base:logging",
|
"../rtc_base:logging",
|
||||||
"../rtc_base:safe_minmax",
|
"../rtc_base:safe_minmax",
|
||||||
|
"../rtc_base/experiments:field_trial_parser",
|
||||||
"../rtc_base/system:no_unique_address",
|
"../rtc_base/system:no_unique_address",
|
||||||
"../system_wrappers",
|
"../system_wrappers",
|
||||||
"../system_wrappers:field_trial",
|
"../system_wrappers:field_trial",
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
#include "api/units/data_rate.h"
|
#include "api/units/data_rate.h"
|
||||||
#include "api/units/time_delta.h"
|
#include "api/units/time_delta.h"
|
||||||
#include "rtc_base/checks.h"
|
#include "rtc_base/checks.h"
|
||||||
|
#include "rtc_base/experiments/field_trial_parser.h"
|
||||||
#include "rtc_base/logging.h"
|
#include "rtc_base/logging.h"
|
||||||
#include "rtc_base/numerics/safe_minmax.h"
|
#include "rtc_base/numerics/safe_minmax.h"
|
||||||
#include "system_wrappers/include/clock.h"
|
#include "system_wrappers/include/clock.h"
|
||||||
@ -317,9 +318,103 @@ std::map<BitrateAllocatorObserver*, int> ZeroRateAllocation(
|
|||||||
return allocation;
|
return allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns new allocation if modified, absl::nullopt otherwise.
|
||||||
|
absl::optional<std::map<BitrateAllocatorObserver*, int>> MaybeApplySurplus(
|
||||||
|
const std::map<BitrateAllocatorObserver*, int>& allocation,
|
||||||
|
const std::vector<AllocatableTrack>& allocatable_tracks,
|
||||||
|
DataRate bitrate,
|
||||||
|
DataRate upper_elastic_limit) {
|
||||||
|
if (upper_elastic_limit.IsZero())
|
||||||
|
return absl::nullopt;
|
||||||
|
|
||||||
|
// In this first pass looping over all `allocatable_tracks`, we aggregates
|
||||||
|
// - `surplus`: sum of unused rates for all kCanContribute* tracks,
|
||||||
|
// - `sum_demand`: sum of `bitrate_priority` for all tracks that can consume
|
||||||
|
// more bitrate to allow proportional sharing of surplus later,
|
||||||
|
// - `sum_allocated`: sum of allocated bitrates for all tracks, which might
|
||||||
|
// be larger than `bitrate` e.g. when min_bitrate_bps are enforced.
|
||||||
|
DataRate surplus = DataRate::Zero();
|
||||||
|
double sum_demand = 0.0;
|
||||||
|
DataRate sum_allocated = DataRate::Zero();
|
||||||
|
|
||||||
|
for (const auto& observer_config : allocatable_tracks) {
|
||||||
|
const auto it = allocation.find(observer_config.observer);
|
||||||
|
if (it == allocation.end()) {
|
||||||
|
// No allocation for this track.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const DataRate allocated = DataRate::BitsPerSec(it->second);
|
||||||
|
sum_allocated += allocated;
|
||||||
|
if (const absl::optional<TrackRateElasticity> elasticity =
|
||||||
|
observer_config.config.rate_elasticity) {
|
||||||
|
bool inactive_can_contribute_and_consume = false;
|
||||||
|
if (elasticity == TrackRateElasticity::kCanContributeUnusedRate ||
|
||||||
|
elasticity == TrackRateElasticity::kCanContributeAndConsume) {
|
||||||
|
if (const absl::optional<DataRate> used =
|
||||||
|
observer_config.observer->GetUsedRate()) {
|
||||||
|
if (*used < allocated) {
|
||||||
|
surplus += allocated - *used;
|
||||||
|
if (elasticity == TrackRateElasticity::kCanContributeAndConsume &&
|
||||||
|
*used < allocated / 2) {
|
||||||
|
inactive_can_contribute_and_consume = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!inactive_can_contribute_and_consume &&
|
||||||
|
(elasticity == TrackRateElasticity::kCanConsumeExtraRate ||
|
||||||
|
elasticity == TrackRateElasticity::kCanContributeAndConsume)) {
|
||||||
|
sum_demand += observer_config.config.bitrate_priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `sum_allocated` can exceed `bitrate` if sum minBitrates exceeds
|
||||||
|
// estimated rate. The real `surplus` should cover the difference.
|
||||||
|
DataRate overshoot =
|
||||||
|
(sum_allocated >= bitrate) ? (sum_allocated - bitrate) : DataRate::Zero();
|
||||||
|
if (sum_demand < 0.0001 || overshoot > surplus) {
|
||||||
|
// No demand for extra bitrate or no available surplus.
|
||||||
|
return absl::nullopt;
|
||||||
|
}
|
||||||
|
surplus -= overshoot;
|
||||||
|
|
||||||
|
auto new_allocation = allocation;
|
||||||
|
// We loop over all allocatable_tracks again, and proportionally assign
|
||||||
|
// `surplus` to each track according to `bitrate_priority`.
|
||||||
|
for (const auto& observer_config : allocatable_tracks) {
|
||||||
|
auto it = new_allocation.find(observer_config.observer);
|
||||||
|
if (it == new_allocation.end()) {
|
||||||
|
// No allocation for this track.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
absl::optional<TrackRateElasticity> elasticity =
|
||||||
|
observer_config.config.rate_elasticity;
|
||||||
|
if (elasticity == TrackRateElasticity::kCanConsumeExtraRate ||
|
||||||
|
elasticity == TrackRateElasticity::kCanContributeAndConsume) {
|
||||||
|
DataRate allocated = DataRate::BitsPerSec(it->second);
|
||||||
|
if (allocated < upper_elastic_limit) {
|
||||||
|
allocated +=
|
||||||
|
surplus * (observer_config.config.bitrate_priority / sum_demand);
|
||||||
|
if (allocated > upper_elastic_limit)
|
||||||
|
allocated = upper_elastic_limit;
|
||||||
|
}
|
||||||
|
DataRate max_bitrate =
|
||||||
|
DataRate::BitsPerSec(observer_config.config.max_bitrate_bps);
|
||||||
|
if (allocated > max_bitrate) {
|
||||||
|
allocated = max_bitrate;
|
||||||
|
}
|
||||||
|
// Save new allocated rate back to `new_allocation`.
|
||||||
|
it->second = allocated.bps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new_allocation;
|
||||||
|
}
|
||||||
|
|
||||||
std::map<BitrateAllocatorObserver*, int> AllocateBitrates(
|
std::map<BitrateAllocatorObserver*, int> AllocateBitrates(
|
||||||
const std::vector<AllocatableTrack>& allocatable_tracks,
|
const std::vector<AllocatableTrack>& allocatable_tracks,
|
||||||
uint32_t bitrate) {
|
uint32_t bitrate,
|
||||||
|
DataRate upper_elastic_limit) {
|
||||||
if (allocatable_tracks.empty())
|
if (allocatable_tracks.empty())
|
||||||
return std::map<BitrateAllocatorObserver*, int>();
|
return std::map<BitrateAllocatorObserver*, int>();
|
||||||
|
|
||||||
@ -342,8 +437,13 @@ std::map<BitrateAllocatorObserver*, int> AllocateBitrates(
|
|||||||
|
|
||||||
// All observers will get their min bitrate plus a share of the rest. This
|
// All observers will get their min bitrate plus a share of the rest. This
|
||||||
// share is allocated to each observer based on its bitrate_priority.
|
// share is allocated to each observer based on its bitrate_priority.
|
||||||
if (bitrate <= sum_max_bitrates)
|
if (bitrate <= sum_max_bitrates) {
|
||||||
return NormalRateAllocation(allocatable_tracks, bitrate, sum_min_bitrates);
|
auto allocation =
|
||||||
|
NormalRateAllocation(allocatable_tracks, bitrate, sum_min_bitrates);
|
||||||
|
return MaybeApplySurplus(allocation, allocatable_tracks,
|
||||||
|
DataRate::BitsPerSec(bitrate), upper_elastic_limit)
|
||||||
|
.value_or(allocation);
|
||||||
|
}
|
||||||
|
|
||||||
// All observers will get up to transmission_max_bitrate_multiplier_ x max.
|
// All observers will get up to transmission_max_bitrate_multiplier_ x max.
|
||||||
return MaxRateAllocation(allocatable_tracks, bitrate, sum_max_bitrates);
|
return MaxRateAllocation(allocatable_tracks, bitrate, sum_max_bitrates);
|
||||||
@ -351,7 +451,8 @@ std::map<BitrateAllocatorObserver*, int> AllocateBitrates(
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
BitrateAllocator::BitrateAllocator(LimitObserver* limit_observer)
|
BitrateAllocator::BitrateAllocator(LimitObserver* limit_observer,
|
||||||
|
DataRate upper_elastic_rate_limit)
|
||||||
: limit_observer_(limit_observer),
|
: limit_observer_(limit_observer),
|
||||||
last_target_bps_(0),
|
last_target_bps_(0),
|
||||||
last_stable_target_bps_(0),
|
last_stable_target_bps_(0),
|
||||||
@ -360,7 +461,8 @@ BitrateAllocator::BitrateAllocator(LimitObserver* limit_observer)
|
|||||||
last_rtt_(0),
|
last_rtt_(0),
|
||||||
last_bwe_period_ms_(1000),
|
last_bwe_period_ms_(1000),
|
||||||
num_pause_events_(0),
|
num_pause_events_(0),
|
||||||
last_bwe_log_time_(0) {
|
last_bwe_log_time_(0),
|
||||||
|
upper_elastic_rate_limit_(upper_elastic_rate_limit) {
|
||||||
sequenced_checker_.Detach();
|
sequenced_checker_.Detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,9 +496,10 @@ void BitrateAllocator::OnNetworkEstimateChanged(TargetTransferRate msg) {
|
|||||||
last_bwe_log_time_ = now;
|
last_bwe_log_time_ = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto allocation = AllocateBitrates(allocatable_tracks_, last_target_bps_);
|
auto allocation = AllocateBitrates(allocatable_tracks_, last_target_bps_,
|
||||||
auto stable_bitrate_allocation =
|
upper_elastic_rate_limit_);
|
||||||
AllocateBitrates(allocatable_tracks_, last_stable_target_bps_);
|
auto stable_bitrate_allocation = AllocateBitrates(
|
||||||
|
allocatable_tracks_, last_stable_target_bps_, DataRate::Zero());
|
||||||
|
|
||||||
for (auto& config : allocatable_tracks_) {
|
for (auto& config : allocatable_tracks_) {
|
||||||
uint32_t allocated_bitrate = allocation[config.observer];
|
uint32_t allocated_bitrate = allocation[config.observer];
|
||||||
@ -439,6 +542,7 @@ void BitrateAllocator::OnNetworkEstimateChanged(TargetTransferRate msg) {
|
|||||||
if (allocated_bitrate > 0)
|
if (allocated_bitrate > 0)
|
||||||
config.media_ratio = MediaRatio(allocated_bitrate, protection_bitrate);
|
config.media_ratio = MediaRatio(allocated_bitrate, protection_bitrate);
|
||||||
config.allocated_bitrate_bps = allocated_bitrate;
|
config.allocated_bitrate_bps = allocated_bitrate;
|
||||||
|
config.last_used_bitrate = config.observer->GetUsedRate();
|
||||||
}
|
}
|
||||||
UpdateAllocationLimits();
|
UpdateAllocationLimits();
|
||||||
}
|
}
|
||||||
@ -461,9 +565,10 @@ void BitrateAllocator::AddObserver(BitrateAllocatorObserver* observer,
|
|||||||
if (last_target_bps_ > 0) {
|
if (last_target_bps_ > 0) {
|
||||||
// Calculate a new allocation and update all observers.
|
// Calculate a new allocation and update all observers.
|
||||||
|
|
||||||
auto allocation = AllocateBitrates(allocatable_tracks_, last_target_bps_);
|
auto allocation = AllocateBitrates(allocatable_tracks_, last_target_bps_,
|
||||||
auto stable_bitrate_allocation =
|
upper_elastic_rate_limit_);
|
||||||
AllocateBitrates(allocatable_tracks_, last_stable_target_bps_);
|
auto stable_bitrate_allocation = AllocateBitrates(
|
||||||
|
allocatable_tracks_, last_stable_target_bps_, DataRate::Zero());
|
||||||
for (auto& config : allocatable_tracks_) {
|
for (auto& config : allocatable_tracks_) {
|
||||||
uint32_t allocated_bitrate = allocation[config.observer];
|
uint32_t allocated_bitrate = allocation[config.observer];
|
||||||
uint32_t allocated_stable_bitrate =
|
uint32_t allocated_stable_bitrate =
|
||||||
@ -477,6 +582,7 @@ void BitrateAllocator::AddObserver(BitrateAllocatorObserver* observer,
|
|||||||
update.bwe_period = TimeDelta::Millis(last_bwe_period_ms_);
|
update.bwe_period = TimeDelta::Millis(last_bwe_period_ms_);
|
||||||
uint32_t protection_bitrate = config.observer->OnBitrateUpdated(update);
|
uint32_t protection_bitrate = config.observer->OnBitrateUpdated(update);
|
||||||
config.allocated_bitrate_bps = allocated_bitrate;
|
config.allocated_bitrate_bps = allocated_bitrate;
|
||||||
|
config.last_used_bitrate = config.observer->GetUsedRate();
|
||||||
if (allocated_bitrate > 0)
|
if (allocated_bitrate > 0)
|
||||||
config.media_ratio = MediaRatio(allocated_bitrate, protection_bitrate);
|
config.media_ratio = MediaRatio(allocated_bitrate, protection_bitrate);
|
||||||
}
|
}
|
||||||
@ -496,6 +602,78 @@ void BitrateAllocator::AddObserver(BitrateAllocatorObserver* observer,
|
|||||||
UpdateAllocationLimits();
|
UpdateAllocationLimits();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BitrateAllocator::RecomputeAllocationIfNeeded() {
|
||||||
|
RTC_DCHECK_RUN_ON(&sequenced_checker_);
|
||||||
|
|
||||||
|
if (upper_elastic_rate_limit_.IsZero()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool need_recompute = false;
|
||||||
|
bool has_contributor = false;
|
||||||
|
bool has_consumer = false;
|
||||||
|
|
||||||
|
// Recomputes if there is a kCanContribute* track whose current bitrate usage
|
||||||
|
// has a jump (i.e., increase only) larger than 20% of allocated_bitrate.
|
||||||
|
constexpr double kUsageJumpRatioThreshold = 0.2;
|
||||||
|
for (auto& config : allocatable_tracks_) {
|
||||||
|
if (config.config.rate_elasticity.has_value()) {
|
||||||
|
const TrackRateElasticity elasticity = *config.config.rate_elasticity;
|
||||||
|
if (elasticity == TrackRateElasticity::kCanContributeUnusedRate ||
|
||||||
|
elasticity == TrackRateElasticity::kCanContributeAndConsume) {
|
||||||
|
DataRate current_usage =
|
||||||
|
config.observer->GetUsedRate().value_or(DataRate::Zero());
|
||||||
|
DataRate last_usage =
|
||||||
|
config.last_used_bitrate.value_or(DataRate::Zero());
|
||||||
|
if (!last_usage.IsZero()) {
|
||||||
|
has_contributor = true;
|
||||||
|
DataRate recompute_threshold =
|
||||||
|
DataRate::BitsPerSec(config.LastAllocatedBitrate()) *
|
||||||
|
kUsageJumpRatioThreshold;
|
||||||
|
if (current_usage > last_usage + recompute_threshold) {
|
||||||
|
need_recompute = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (elasticity == TrackRateElasticity::kCanConsumeExtraRate ||
|
||||||
|
elasticity == TrackRateElasticity::kCanContributeAndConsume) {
|
||||||
|
has_consumer = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_contributor == false || has_consumer == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (need_recompute && last_target_bps_ > 0) {
|
||||||
|
// Calculate a new allocation and update all observers.
|
||||||
|
auto allocation = AllocateBitrates(allocatable_tracks_, last_target_bps_,
|
||||||
|
upper_elastic_rate_limit_);
|
||||||
|
auto stable_bitrate_allocation = AllocateBitrates(
|
||||||
|
allocatable_tracks_, last_stable_target_bps_, DataRate::Zero());
|
||||||
|
for (auto& config : allocatable_tracks_) {
|
||||||
|
DataRate allocated_bitrate =
|
||||||
|
DataRate::BitsPerSec(allocation[config.observer]);
|
||||||
|
DataRate allocated_stable_bitrate =
|
||||||
|
DataRate::BitsPerSec(stable_bitrate_allocation[config.observer]);
|
||||||
|
BitrateAllocationUpdate update;
|
||||||
|
update.target_bitrate = allocated_bitrate;
|
||||||
|
update.stable_target_bitrate = allocated_stable_bitrate;
|
||||||
|
update.packet_loss_ratio = last_fraction_loss_ / 256.0;
|
||||||
|
update.round_trip_time = TimeDelta::Millis(last_rtt_);
|
||||||
|
update.bwe_period = TimeDelta::Millis(last_bwe_period_ms_);
|
||||||
|
DataRate protection_bitrate =
|
||||||
|
DataRate::BitsPerSec(config.observer->OnBitrateUpdated(update));
|
||||||
|
config.allocated_bitrate_bps = allocated_bitrate.bps();
|
||||||
|
config.last_used_bitrate = config.observer->GetUsedRate();
|
||||||
|
if (allocated_bitrate.bps() > 0)
|
||||||
|
config.media_ratio =
|
||||||
|
MediaRatio(allocated_bitrate.bps(), protection_bitrate.bps());
|
||||||
|
}
|
||||||
|
UpdateAllocationLimits();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void BitrateAllocator::UpdateAllocationLimits() {
|
void BitrateAllocator::UpdateAllocationLimits() {
|
||||||
BitrateAllocationLimits limits;
|
BitrateAllocationLimits limits;
|
||||||
for (const auto& config : allocatable_tracks_) {
|
for (const auto& config : allocatable_tracks_) {
|
||||||
@ -590,4 +768,15 @@ uint32_t bitrate_allocator_impl::AllocatableTrack::MinBitrateWithHysteresis()
|
|||||||
return min_bitrate;
|
return min_bitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(b/350555527): Remove after experiment
|
||||||
|
const char kElasticBitrateAllocator[] = "WebRTC-ElasticBitrateAllocation";
|
||||||
|
DataRate GetElasticRateAllocationFieldTrialParameter(
|
||||||
|
const FieldTrialsView& field_trials) {
|
||||||
|
FieldTrialParameter<DataRate> elastic_rate_limit("upper_limit",
|
||||||
|
DataRate::Zero());
|
||||||
|
std::string trial_string = field_trials.Lookup(kElasticBitrateAllocator);
|
||||||
|
ParseFieldTrial({&elastic_rate_limit}, trial_string);
|
||||||
|
return elastic_rate_limit.Get();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "api/call/bitrate_allocation.h"
|
#include "api/call/bitrate_allocation.h"
|
||||||
|
#include "api/field_trials_view.h"
|
||||||
#include "api/sequence_checker.h"
|
#include "api/sequence_checker.h"
|
||||||
#include "api/transport/network_types.h"
|
#include "api/transport/network_types.h"
|
||||||
#include "api/units/data_rate.h"
|
#include "api/units/data_rate.h"
|
||||||
@ -92,12 +93,11 @@ struct AllocatableTrack {
|
|||||||
: observer(observer),
|
: observer(observer),
|
||||||
config(allocation_config),
|
config(allocation_config),
|
||||||
allocated_bitrate_bps(-1),
|
allocated_bitrate_bps(-1),
|
||||||
last_used_bitrate_bps(-1),
|
|
||||||
media_ratio(1.0) {}
|
media_ratio(1.0) {}
|
||||||
BitrateAllocatorObserver* observer;
|
BitrateAllocatorObserver* observer;
|
||||||
MediaStreamAllocationConfig config;
|
MediaStreamAllocationConfig config;
|
||||||
int64_t allocated_bitrate_bps;
|
int64_t allocated_bitrate_bps;
|
||||||
int64_t last_used_bitrate_bps;
|
absl::optional<DataRate> last_used_bitrate;
|
||||||
double media_ratio; // Part of the total bitrate used for media [0.0, 1.0].
|
double media_ratio; // Part of the total bitrate used for media [0.0, 1.0].
|
||||||
|
|
||||||
uint32_t LastAllocatedBitrate() const;
|
uint32_t LastAllocatedBitrate() const;
|
||||||
@ -122,7 +122,11 @@ class BitrateAllocator : public BitrateAllocatorInterface {
|
|||||||
virtual ~LimitObserver() = default;
|
virtual ~LimitObserver() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit BitrateAllocator(LimitObserver* limit_observer);
|
// `upper_elastic_rate_limit` specifies the rate ceiling an observer can
|
||||||
|
// reach when unused bits are added. A value of zero disables borrowing of
|
||||||
|
// unused rates.
|
||||||
|
BitrateAllocator(LimitObserver* limit_observer,
|
||||||
|
DataRate upper_elastic_rate_limit);
|
||||||
~BitrateAllocator() override;
|
~BitrateAllocator() override;
|
||||||
|
|
||||||
void UpdateStartRate(uint32_t start_rate_bps);
|
void UpdateStartRate(uint32_t start_rate_bps);
|
||||||
@ -140,6 +144,11 @@ class BitrateAllocator : public BitrateAllocatorInterface {
|
|||||||
void AddObserver(BitrateAllocatorObserver* observer,
|
void AddObserver(BitrateAllocatorObserver* observer,
|
||||||
MediaStreamAllocationConfig config) override;
|
MediaStreamAllocationConfig config) override;
|
||||||
|
|
||||||
|
// Checks and recomputes bitrate allocation if necessary (when an
|
||||||
|
// elastic/audio bitrate increases significantly). Returns whether there is an
|
||||||
|
// active contributing and active consuming stream.
|
||||||
|
bool RecomputeAllocationIfNeeded();
|
||||||
|
|
||||||
// Removes a previously added observer, but will not trigger a new bitrate
|
// Removes a previously added observer, but will not trigger a new bitrate
|
||||||
// allocation.
|
// allocation.
|
||||||
void RemoveObserver(BitrateAllocatorObserver* observer) override;
|
void RemoveObserver(BitrateAllocatorObserver* observer) override;
|
||||||
@ -176,7 +185,12 @@ class BitrateAllocator : public BitrateAllocatorInterface {
|
|||||||
int num_pause_events_ RTC_GUARDED_BY(&sequenced_checker_);
|
int num_pause_events_ RTC_GUARDED_BY(&sequenced_checker_);
|
||||||
int64_t last_bwe_log_time_ RTC_GUARDED_BY(&sequenced_checker_);
|
int64_t last_bwe_log_time_ RTC_GUARDED_BY(&sequenced_checker_);
|
||||||
BitrateAllocationLimits current_limits_ RTC_GUARDED_BY(&sequenced_checker_);
|
BitrateAllocationLimits current_limits_ RTC_GUARDED_BY(&sequenced_checker_);
|
||||||
|
const DataRate upper_elastic_rate_limit_ RTC_GUARDED_BY(&sequenced_checker_);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO(b/350555527): Remove after experiment
|
||||||
|
DataRate GetElasticRateAllocationFieldTrialParameter(
|
||||||
|
const FieldTrialsView& field_trials);
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
#endif // CALL_BITRATE_ALLOCATOR_H_
|
#endif // CALL_BITRATE_ALLOCATOR_H_
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include "absl/strings/string_view.h"
|
#include "absl/strings/string_view.h"
|
||||||
#include "system_wrappers/include/clock.h"
|
#include "system_wrappers/include/clock.h"
|
||||||
|
#include "test/explicit_key_value_config.h"
|
||||||
#include "test/gmock.h"
|
#include "test/gmock.h"
|
||||||
#include "test/gtest.h"
|
#include "test/gtest.h"
|
||||||
|
|
||||||
@ -85,6 +86,13 @@ class TestBitrateObserver : public BitrateAllocatorObserver {
|
|||||||
double protection_ratio_;
|
double protection_ratio_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TestContributingBitrateObserver : public TestBitrateObserver {
|
||||||
|
public:
|
||||||
|
TestContributingBitrateObserver() : rate_usage_(DataRate::Zero()) {}
|
||||||
|
absl::optional<DataRate> GetUsedRate() const override { return rate_usage_; }
|
||||||
|
DataRate rate_usage_;
|
||||||
|
};
|
||||||
|
|
||||||
constexpr int64_t kDefaultProbingIntervalMs = 3000;
|
constexpr int64_t kDefaultProbingIntervalMs = 3000;
|
||||||
const double kDefaultBitratePriority = 1.0;
|
const double kDefaultBitratePriority = 1.0;
|
||||||
|
|
||||||
@ -108,21 +116,24 @@ TargetTransferRate CreateTargetRateMessage(uint32_t target_bitrate_bps,
|
|||||||
|
|
||||||
class BitrateAllocatorTest : public ::testing::Test {
|
class BitrateAllocatorTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
BitrateAllocatorTest() : allocator_(new BitrateAllocator(&limit_observer_)) {
|
BitrateAllocatorTest()
|
||||||
|
: allocator_(new BitrateAllocator(&limit_observer_, DataRate::Zero())) {
|
||||||
allocator_->OnNetworkEstimateChanged(
|
allocator_->OnNetworkEstimateChanged(
|
||||||
CreateTargetRateMessage(300000u, 0, 0, kDefaultProbingIntervalMs));
|
CreateTargetRateMessage(300000u, 0, 0, kDefaultProbingIntervalMs));
|
||||||
}
|
}
|
||||||
~BitrateAllocatorTest() {}
|
~BitrateAllocatorTest() {}
|
||||||
void AddObserver(BitrateAllocatorObserver* observer,
|
void AddObserver(
|
||||||
uint32_t min_bitrate_bps,
|
BitrateAllocatorObserver* observer,
|
||||||
uint32_t max_bitrate_bps,
|
uint32_t min_bitrate_bps,
|
||||||
uint32_t pad_up_bitrate_bps,
|
uint32_t max_bitrate_bps,
|
||||||
bool enforce_min_bitrate,
|
uint32_t pad_up_bitrate_bps,
|
||||||
double bitrate_priority) {
|
bool enforce_min_bitrate,
|
||||||
|
double bitrate_priority,
|
||||||
|
absl::optional<TrackRateElasticity> rate_elasticity = absl::nullopt) {
|
||||||
allocator_->AddObserver(
|
allocator_->AddObserver(
|
||||||
observer,
|
observer, {min_bitrate_bps, max_bitrate_bps, pad_up_bitrate_bps,
|
||||||
{min_bitrate_bps, max_bitrate_bps, pad_up_bitrate_bps,
|
/* priority_bitrate */ 0, enforce_min_bitrate,
|
||||||
/* priority_bitrate */ 0, enforce_min_bitrate, bitrate_priority});
|
bitrate_priority, rate_elasticity});
|
||||||
}
|
}
|
||||||
MediaStreamAllocationConfig DefaultConfig() const {
|
MediaStreamAllocationConfig DefaultConfig() const {
|
||||||
MediaStreamAllocationConfig default_config;
|
MediaStreamAllocationConfig default_config;
|
||||||
@ -134,6 +145,12 @@ class BitrateAllocatorTest : public ::testing::Test {
|
|||||||
default_config.bitrate_priority = kDefaultBitratePriority;
|
default_config.bitrate_priority = kDefaultBitratePriority;
|
||||||
return default_config;
|
return default_config;
|
||||||
}
|
}
|
||||||
|
void ReconfigureAllocator(DataRate elastic_rate_upper_limit) {
|
||||||
|
allocator_.reset(
|
||||||
|
new BitrateAllocator(&limit_observer_, elastic_rate_upper_limit));
|
||||||
|
allocator_->OnNetworkEstimateChanged(
|
||||||
|
CreateTargetRateMessage(300000u, 0, 0, kDefaultProbingIntervalMs));
|
||||||
|
}
|
||||||
|
|
||||||
NiceMock<MockLimitObserver> limit_observer_;
|
NiceMock<MockLimitObserver> limit_observer_;
|
||||||
std::unique_ptr<BitrateAllocator> allocator_;
|
std::unique_ptr<BitrateAllocator> allocator_;
|
||||||
@ -300,7 +317,7 @@ TEST_F(BitrateAllocatorTest, RemoveObserverTriggersLimitObserver) {
|
|||||||
class BitrateAllocatorTestNoEnforceMin : public ::testing::Test {
|
class BitrateAllocatorTestNoEnforceMin : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
BitrateAllocatorTestNoEnforceMin()
|
BitrateAllocatorTestNoEnforceMin()
|
||||||
: allocator_(new BitrateAllocator(&limit_observer_)) {
|
: allocator_(new BitrateAllocator(&limit_observer_, DataRate::Zero())) {
|
||||||
allocator_->OnNetworkEstimateChanged(
|
allocator_->OnNetworkEstimateChanged(
|
||||||
CreateTargetRateMessage(300000u, 0, 0, kDefaultProbingIntervalMs));
|
CreateTargetRateMessage(300000u, 0, 0, kDefaultProbingIntervalMs));
|
||||||
}
|
}
|
||||||
@ -1037,4 +1054,132 @@ TEST_F(BitrateAllocatorTest, PriorityRateThreeObserversTwoAllocatedToMax) {
|
|||||||
allocator_->RemoveObserver(&observer_high);
|
allocator_->RemoveObserver(&observer_high);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(BitrateAllocatorTest, ElasticRateAllocationCanBorrowUnsedRate) {
|
||||||
|
test::ExplicitKeyValueConfig field_trials(
|
||||||
|
"WebRTC-ElasticBitrateAllocation/upper_limit:200bps/");
|
||||||
|
ReconfigureAllocator(
|
||||||
|
GetElasticRateAllocationFieldTrialParameter(field_trials));
|
||||||
|
TestBitrateObserver observer_consume;
|
||||||
|
TestContributingBitrateObserver observer_contribute;
|
||||||
|
AddObserver(&observer_consume, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanConsumeExtraRate);
|
||||||
|
AddObserver(&observer_contribute, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanContributeUnusedRate);
|
||||||
|
|
||||||
|
observer_contribute.rate_usage_ = DataRate::BitsPerSec(20);
|
||||||
|
allocator_->OnNetworkEstimateChanged(
|
||||||
|
CreateTargetRateMessage(100, 0, 0, kDefaultProbingIntervalMs));
|
||||||
|
|
||||||
|
// observer_contribute is allocated 50 but only used 20, so 30 is borrowed to
|
||||||
|
// observer_consume who gets 50+30=80.
|
||||||
|
EXPECT_EQ(80u, observer_consume.last_bitrate_bps_);
|
||||||
|
EXPECT_EQ(50u, observer_contribute.last_bitrate_bps_);
|
||||||
|
|
||||||
|
allocator_->RemoveObserver(&observer_consume);
|
||||||
|
allocator_->RemoveObserver(&observer_contribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BitrateAllocatorTest, ElasticRateAllocationDefaultsInactive) {
|
||||||
|
test::ExplicitKeyValueConfig field_trials("");
|
||||||
|
ReconfigureAllocator(
|
||||||
|
GetElasticRateAllocationFieldTrialParameter(field_trials));
|
||||||
|
TestBitrateObserver observer_consume;
|
||||||
|
TestContributingBitrateObserver observer_contribute;
|
||||||
|
AddObserver(&observer_consume, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanConsumeExtraRate);
|
||||||
|
AddObserver(&observer_contribute, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanContributeUnusedRate);
|
||||||
|
|
||||||
|
observer_contribute.rate_usage_ = DataRate::BitsPerSec(20);
|
||||||
|
allocator_->OnNetworkEstimateChanged(
|
||||||
|
CreateTargetRateMessage(100, 0, 0, kDefaultProbingIntervalMs));
|
||||||
|
|
||||||
|
EXPECT_EQ(50u, observer_consume.last_bitrate_bps_);
|
||||||
|
EXPECT_EQ(50u, observer_contribute.last_bitrate_bps_);
|
||||||
|
|
||||||
|
allocator_->RemoveObserver(&observer_consume);
|
||||||
|
allocator_->RemoveObserver(&observer_contribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BitrateAllocatorTest, ElasticRateAllocationDontExceedMaxBitrate) {
|
||||||
|
test::ExplicitKeyValueConfig field_trials(
|
||||||
|
"WebRTC-ElasticBitrateAllocation/upper_limit:200bps/");
|
||||||
|
ReconfigureAllocator(
|
||||||
|
GetElasticRateAllocationFieldTrialParameter(field_trials));
|
||||||
|
TestBitrateObserver observer_consume;
|
||||||
|
TestContributingBitrateObserver observer_contribute;
|
||||||
|
AddObserver(&observer_consume, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanConsumeExtraRate);
|
||||||
|
AddObserver(&observer_contribute, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanContributeUnusedRate);
|
||||||
|
|
||||||
|
observer_contribute.rate_usage_ = DataRate::BitsPerSec(20);
|
||||||
|
allocator_->OnNetworkEstimateChanged(
|
||||||
|
CreateTargetRateMessage(140, 0, 0, kDefaultProbingIntervalMs));
|
||||||
|
|
||||||
|
// observer_contribute is allocated 70 but only used 20, so 50 is borrowed to
|
||||||
|
// observer_consume who could get 70+50=120, but is capped by max-bitrate to
|
||||||
|
// 100.
|
||||||
|
EXPECT_EQ(100u, observer_consume.last_bitrate_bps_);
|
||||||
|
EXPECT_EQ(70u, observer_contribute.last_bitrate_bps_);
|
||||||
|
|
||||||
|
allocator_->RemoveObserver(&observer_consume);
|
||||||
|
allocator_->RemoveObserver(&observer_contribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BitrateAllocatorTest, ElasticRateAllocationStayWithinUpperLimit) {
|
||||||
|
uint32_t upper_limit = 70;
|
||||||
|
test::ExplicitKeyValueConfig field_trials(
|
||||||
|
"WebRTC-ElasticBitrateAllocation/upper_limit:" +
|
||||||
|
std::to_string(upper_limit) + "bps/");
|
||||||
|
ReconfigureAllocator(
|
||||||
|
GetElasticRateAllocationFieldTrialParameter(field_trials));
|
||||||
|
TestBitrateObserver observer_consume;
|
||||||
|
TestContributingBitrateObserver observer_contribute;
|
||||||
|
AddObserver(&observer_consume, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanConsumeExtraRate);
|
||||||
|
AddObserver(&observer_contribute, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanContributeUnusedRate);
|
||||||
|
|
||||||
|
observer_contribute.rate_usage_ = DataRate::BitsPerSec(20);
|
||||||
|
allocator_->OnNetworkEstimateChanged(
|
||||||
|
CreateTargetRateMessage(100, 0, 0, kDefaultProbingIntervalMs));
|
||||||
|
|
||||||
|
// observer_contribute is allocated 50 but only used 20, so 30 is borrowed to
|
||||||
|
// observer_consume who could get 30+50=80, but is capped by upper_limit.
|
||||||
|
EXPECT_EQ(upper_limit, observer_consume.last_bitrate_bps_);
|
||||||
|
EXPECT_EQ(50u, observer_contribute.last_bitrate_bps_);
|
||||||
|
|
||||||
|
allocator_->RemoveObserver(&observer_consume);
|
||||||
|
allocator_->RemoveObserver(&observer_contribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BitrateAllocatorTest, ElasticRateAllocationDontReduceAllocation) {
|
||||||
|
uint32_t upper_limit = 70;
|
||||||
|
test::ExplicitKeyValueConfig field_trials(
|
||||||
|
"WebRTC-ElasticBitrateAllocation/upper_limit:" +
|
||||||
|
std::to_string(upper_limit) + "bps/");
|
||||||
|
ReconfigureAllocator(
|
||||||
|
GetElasticRateAllocationFieldTrialParameter(field_trials));
|
||||||
|
TestBitrateObserver observer_consume;
|
||||||
|
TestContributingBitrateObserver observer_contribute;
|
||||||
|
AddObserver(&observer_consume, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanConsumeExtraRate);
|
||||||
|
AddObserver(&observer_contribute, 10, 100, 0, false, 1.0,
|
||||||
|
TrackRateElasticity::kCanContributeUnusedRate);
|
||||||
|
|
||||||
|
observer_contribute.rate_usage_ = DataRate::BitsPerSec(20);
|
||||||
|
allocator_->OnNetworkEstimateChanged(
|
||||||
|
CreateTargetRateMessage(200, 0, 0, kDefaultProbingIntervalMs));
|
||||||
|
|
||||||
|
// observer_contribute is allocated 100 but only used 20, so 80 can be
|
||||||
|
// borrowed to observer_consume. But observer_consume already has 100
|
||||||
|
// (above upper_limit), so no bitrate is borrowed.
|
||||||
|
EXPECT_EQ(100u, observer_consume.last_bitrate_bps_);
|
||||||
|
EXPECT_EQ(100u, observer_contribute.last_bitrate_bps_);
|
||||||
|
|
||||||
|
allocator_->RemoveObserver(&observer_consume);
|
||||||
|
allocator_->RemoveObserver(&observer_contribute);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
23
call/call.cc
23
call/call.cc
@ -29,6 +29,7 @@
|
|||||||
#include "api/sequence_checker.h"
|
#include "api/sequence_checker.h"
|
||||||
#include "api/task_queue/pending_task_safety_flag.h"
|
#include "api/task_queue/pending_task_safety_flag.h"
|
||||||
#include "api/transport/network_control.h"
|
#include "api/transport/network_control.h"
|
||||||
|
#include "api/units/time_delta.h"
|
||||||
#include "audio/audio_receive_stream.h"
|
#include "audio/audio_receive_stream.h"
|
||||||
#include "audio/audio_send_stream.h"
|
#include "audio/audio_send_stream.h"
|
||||||
#include "audio/audio_state.h"
|
#include "audio/audio_state.h"
|
||||||
@ -420,6 +421,7 @@ class Call final : public webrtc::Call,
|
|||||||
|
|
||||||
ReceiveSideCongestionController receive_side_cc_;
|
ReceiveSideCongestionController receive_side_cc_;
|
||||||
RepeatingTaskHandle receive_side_cc_periodic_task_;
|
RepeatingTaskHandle receive_side_cc_periodic_task_;
|
||||||
|
RepeatingTaskHandle elastic_bandwidth_allocation_task_;
|
||||||
|
|
||||||
const std::unique_ptr<ReceiveTimeCalculator> receive_time_calculator_;
|
const std::unique_ptr<ReceiveTimeCalculator> receive_time_calculator_;
|
||||||
|
|
||||||
@ -660,7 +662,9 @@ Call::Call(CallConfig config,
|
|||||||
: nullptr),
|
: nullptr),
|
||||||
num_cpu_cores_(CpuInfo::DetectNumberOfCores()),
|
num_cpu_cores_(CpuInfo::DetectNumberOfCores()),
|
||||||
call_stats_(new CallStats(&env_.clock(), worker_thread_)),
|
call_stats_(new CallStats(&env_.clock(), worker_thread_)),
|
||||||
bitrate_allocator_(new BitrateAllocator(this)),
|
bitrate_allocator_(new BitrateAllocator(
|
||||||
|
this,
|
||||||
|
GetElasticRateAllocationFieldTrialParameter(env_.field_trials()))),
|
||||||
config_(std::move(config)),
|
config_(std::move(config)),
|
||||||
audio_network_state_(kNetworkDown),
|
audio_network_state_(kNetworkDown),
|
||||||
video_network_state_(kNetworkDown),
|
video_network_state_(kNetworkDown),
|
||||||
@ -697,6 +701,22 @@ Call::Call(CallConfig config,
|
|||||||
worker_thread_,
|
worker_thread_,
|
||||||
[receive_side_cc] { return receive_side_cc->MaybeProcess(); },
|
[receive_side_cc] { return receive_side_cc->MaybeProcess(); },
|
||||||
TaskQueueBase::DelayPrecision::kLow, &env_.clock());
|
TaskQueueBase::DelayPrecision::kLow, &env_.clock());
|
||||||
|
|
||||||
|
// TODO(b/350555527): Remove after experiment
|
||||||
|
if (GetElasticRateAllocationFieldTrialParameter(env_.field_trials()) !=
|
||||||
|
DataRate::Zero()) {
|
||||||
|
elastic_bandwidth_allocation_task_ = RepeatingTaskHandle::Start(
|
||||||
|
worker_thread_,
|
||||||
|
[this] {
|
||||||
|
TimeDelta next_schedule_interval = TimeDelta::Millis(25);
|
||||||
|
if (bitrate_allocator_) {
|
||||||
|
if (!bitrate_allocator_->RecomputeAllocationIfNeeded())
|
||||||
|
next_schedule_interval = TimeDelta::Millis(300);
|
||||||
|
}
|
||||||
|
return next_schedule_interval;
|
||||||
|
},
|
||||||
|
TaskQueueBase::DelayPrecision::kLow, &env_.clock());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Call::~Call() {
|
Call::~Call() {
|
||||||
@ -709,6 +729,7 @@ Call::~Call() {
|
|||||||
RTC_CHECK(video_receive_streams_.empty());
|
RTC_CHECK(video_receive_streams_.empty());
|
||||||
|
|
||||||
receive_side_cc_periodic_task_.Stop();
|
receive_side_cc_periodic_task_.Stop();
|
||||||
|
elastic_bandwidth_allocation_task_.Stop();
|
||||||
call_stats_->DeregisterStatsObserver(&receive_side_cc_);
|
call_stats_->DeregisterStatsObserver(&receive_side_cc_);
|
||||||
send_stats_.SetFirstPacketTime(transport_send_->GetFirstPacketTime());
|
send_stats_.SetFirstPacketTime(transport_send_->GetFirstPacketTime());
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([
|
|||||||
FieldTrial('WebRTC-DisableRtxRateLimiter',
|
FieldTrial('WebRTC-DisableRtxRateLimiter',
|
||||||
42225500,
|
42225500,
|
||||||
date(2024, 4, 1)),
|
date(2024, 4, 1)),
|
||||||
|
FieldTrial('WebRTC-ElasticBitrateAllocation',
|
||||||
|
350555527,
|
||||||
|
date(2025, 3, 1)),
|
||||||
FieldTrial('WebRTC-EncoderDataDumpDirectory',
|
FieldTrial('WebRTC-EncoderDataDumpDirectory',
|
||||||
296242528,
|
296242528,
|
||||||
date(2024, 4, 1)),
|
date(2024, 4, 1)),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user