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 = [
|
||||
"../api:bitrate_allocation",
|
||||
"../api:field_trials_view",
|
||||
"../api:sequence_checker",
|
||||
"../api/transport:network_control",
|
||||
"../api/units:data_rate",
|
||||
@ -256,6 +257,7 @@ rtc_library("bitrate_allocator") {
|
||||
"../rtc_base:checks",
|
||||
"../rtc_base:logging",
|
||||
"../rtc_base:safe_minmax",
|
||||
"../rtc_base/experiments:field_trial_parser",
|
||||
"../rtc_base/system:no_unique_address",
|
||||
"../system_wrappers",
|
||||
"../system_wrappers:field_trial",
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_minmax.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
@ -317,9 +318,103 @@ std::map<BitrateAllocatorObserver*, int> ZeroRateAllocation(
|
||||
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(
|
||||
const std::vector<AllocatableTrack>& allocatable_tracks,
|
||||
uint32_t bitrate) {
|
||||
uint32_t bitrate,
|
||||
DataRate upper_elastic_limit) {
|
||||
if (allocatable_tracks.empty())
|
||||
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
|
||||
// share is allocated to each observer based on its bitrate_priority.
|
||||
if (bitrate <= sum_max_bitrates)
|
||||
return NormalRateAllocation(allocatable_tracks, bitrate, sum_min_bitrates);
|
||||
if (bitrate <= sum_max_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.
|
||||
return MaxRateAllocation(allocatable_tracks, bitrate, sum_max_bitrates);
|
||||
@ -351,7 +451,8 @@ std::map<BitrateAllocatorObserver*, int> AllocateBitrates(
|
||||
|
||||
} // namespace
|
||||
|
||||
BitrateAllocator::BitrateAllocator(LimitObserver* limit_observer)
|
||||
BitrateAllocator::BitrateAllocator(LimitObserver* limit_observer,
|
||||
DataRate upper_elastic_rate_limit)
|
||||
: limit_observer_(limit_observer),
|
||||
last_target_bps_(0),
|
||||
last_stable_target_bps_(0),
|
||||
@ -360,7 +461,8 @@ BitrateAllocator::BitrateAllocator(LimitObserver* limit_observer)
|
||||
last_rtt_(0),
|
||||
last_bwe_period_ms_(1000),
|
||||
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();
|
||||
}
|
||||
|
||||
@ -394,9 +496,10 @@ void BitrateAllocator::OnNetworkEstimateChanged(TargetTransferRate msg) {
|
||||
last_bwe_log_time_ = now;
|
||||
}
|
||||
|
||||
auto allocation = AllocateBitrates(allocatable_tracks_, last_target_bps_);
|
||||
auto stable_bitrate_allocation =
|
||||
AllocateBitrates(allocatable_tracks_, last_stable_target_bps_);
|
||||
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_) {
|
||||
uint32_t allocated_bitrate = allocation[config.observer];
|
||||
@ -439,6 +542,7 @@ void BitrateAllocator::OnNetworkEstimateChanged(TargetTransferRate msg) {
|
||||
if (allocated_bitrate > 0)
|
||||
config.media_ratio = MediaRatio(allocated_bitrate, protection_bitrate);
|
||||
config.allocated_bitrate_bps = allocated_bitrate;
|
||||
config.last_used_bitrate = config.observer->GetUsedRate();
|
||||
}
|
||||
UpdateAllocationLimits();
|
||||
}
|
||||
@ -461,9 +565,10 @@ void BitrateAllocator::AddObserver(BitrateAllocatorObserver* observer,
|
||||
if (last_target_bps_ > 0) {
|
||||
// Calculate a new allocation and update all observers.
|
||||
|
||||
auto allocation = AllocateBitrates(allocatable_tracks_, last_target_bps_);
|
||||
auto stable_bitrate_allocation =
|
||||
AllocateBitrates(allocatable_tracks_, last_stable_target_bps_);
|
||||
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_) {
|
||||
uint32_t allocated_bitrate = allocation[config.observer];
|
||||
uint32_t allocated_stable_bitrate =
|
||||
@ -477,6 +582,7 @@ void BitrateAllocator::AddObserver(BitrateAllocatorObserver* observer,
|
||||
update.bwe_period = TimeDelta::Millis(last_bwe_period_ms_);
|
||||
uint32_t protection_bitrate = config.observer->OnBitrateUpdated(update);
|
||||
config.allocated_bitrate_bps = allocated_bitrate;
|
||||
config.last_used_bitrate = config.observer->GetUsedRate();
|
||||
if (allocated_bitrate > 0)
|
||||
config.media_ratio = MediaRatio(allocated_bitrate, protection_bitrate);
|
||||
}
|
||||
@ -496,6 +602,78 @@ void BitrateAllocator::AddObserver(BitrateAllocatorObserver* observer,
|
||||
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() {
|
||||
BitrateAllocationLimits limits;
|
||||
for (const auto& config : allocatable_tracks_) {
|
||||
@ -590,4 +768,15 @@ uint32_t bitrate_allocator_impl::AllocatableTrack::MinBitrateWithHysteresis()
|
||||
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
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "api/call/bitrate_allocation.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/sequence_checker.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
@ -92,12 +93,11 @@ struct AllocatableTrack {
|
||||
: observer(observer),
|
||||
config(allocation_config),
|
||||
allocated_bitrate_bps(-1),
|
||||
last_used_bitrate_bps(-1),
|
||||
media_ratio(1.0) {}
|
||||
BitrateAllocatorObserver* observer;
|
||||
MediaStreamAllocationConfig config;
|
||||
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].
|
||||
|
||||
uint32_t LastAllocatedBitrate() const;
|
||||
@ -122,7 +122,11 @@ class BitrateAllocator : public BitrateAllocatorInterface {
|
||||
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;
|
||||
|
||||
void UpdateStartRate(uint32_t start_rate_bps);
|
||||
@ -140,6 +144,11 @@ class BitrateAllocator : public BitrateAllocatorInterface {
|
||||
void AddObserver(BitrateAllocatorObserver* observer,
|
||||
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
|
||||
// allocation.
|
||||
void RemoveObserver(BitrateAllocatorObserver* observer) override;
|
||||
@ -176,7 +185,12 @@ class BitrateAllocator : public BitrateAllocatorInterface {
|
||||
int num_pause_events_ RTC_GUARDED_BY(&sequenced_checker_);
|
||||
int64_t last_bwe_log_time_ 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
|
||||
#endif // CALL_BITRATE_ALLOCATOR_H_
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "test/explicit_key_value_config.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
@ -85,6 +86,13 @@ class TestBitrateObserver : public BitrateAllocatorObserver {
|
||||
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;
|
||||
const double kDefaultBitratePriority = 1.0;
|
||||
|
||||
@ -108,21 +116,24 @@ TargetTransferRate CreateTargetRateMessage(uint32_t target_bitrate_bps,
|
||||
|
||||
class BitrateAllocatorTest : public ::testing::Test {
|
||||
protected:
|
||||
BitrateAllocatorTest() : allocator_(new BitrateAllocator(&limit_observer_)) {
|
||||
BitrateAllocatorTest()
|
||||
: allocator_(new BitrateAllocator(&limit_observer_, DataRate::Zero())) {
|
||||
allocator_->OnNetworkEstimateChanged(
|
||||
CreateTargetRateMessage(300000u, 0, 0, kDefaultProbingIntervalMs));
|
||||
}
|
||||
~BitrateAllocatorTest() {}
|
||||
void AddObserver(BitrateAllocatorObserver* observer,
|
||||
uint32_t min_bitrate_bps,
|
||||
uint32_t max_bitrate_bps,
|
||||
uint32_t pad_up_bitrate_bps,
|
||||
bool enforce_min_bitrate,
|
||||
double bitrate_priority) {
|
||||
void AddObserver(
|
||||
BitrateAllocatorObserver* observer,
|
||||
uint32_t min_bitrate_bps,
|
||||
uint32_t max_bitrate_bps,
|
||||
uint32_t pad_up_bitrate_bps,
|
||||
bool enforce_min_bitrate,
|
||||
double bitrate_priority,
|
||||
absl::optional<TrackRateElasticity> rate_elasticity = absl::nullopt) {
|
||||
allocator_->AddObserver(
|
||||
observer,
|
||||
{min_bitrate_bps, max_bitrate_bps, pad_up_bitrate_bps,
|
||||
/* priority_bitrate */ 0, enforce_min_bitrate, bitrate_priority});
|
||||
observer, {min_bitrate_bps, max_bitrate_bps, pad_up_bitrate_bps,
|
||||
/* priority_bitrate */ 0, enforce_min_bitrate,
|
||||
bitrate_priority, rate_elasticity});
|
||||
}
|
||||
MediaStreamAllocationConfig DefaultConfig() const {
|
||||
MediaStreamAllocationConfig default_config;
|
||||
@ -134,6 +145,12 @@ class BitrateAllocatorTest : public ::testing::Test {
|
||||
default_config.bitrate_priority = kDefaultBitratePriority;
|
||||
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_;
|
||||
std::unique_ptr<BitrateAllocator> allocator_;
|
||||
@ -300,7 +317,7 @@ TEST_F(BitrateAllocatorTest, RemoveObserverTriggersLimitObserver) {
|
||||
class BitrateAllocatorTestNoEnforceMin : public ::testing::Test {
|
||||
protected:
|
||||
BitrateAllocatorTestNoEnforceMin()
|
||||
: allocator_(new BitrateAllocator(&limit_observer_)) {
|
||||
: allocator_(new BitrateAllocator(&limit_observer_, DataRate::Zero())) {
|
||||
allocator_->OnNetworkEstimateChanged(
|
||||
CreateTargetRateMessage(300000u, 0, 0, kDefaultProbingIntervalMs));
|
||||
}
|
||||
@ -1037,4 +1054,132 @@ TEST_F(BitrateAllocatorTest, PriorityRateThreeObserversTwoAllocatedToMax) {
|
||||
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
|
||||
|
||||
23
call/call.cc
23
call/call.cc
@ -29,6 +29,7 @@
|
||||
#include "api/sequence_checker.h"
|
||||
#include "api/task_queue/pending_task_safety_flag.h"
|
||||
#include "api/transport/network_control.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "audio/audio_receive_stream.h"
|
||||
#include "audio/audio_send_stream.h"
|
||||
#include "audio/audio_state.h"
|
||||
@ -420,6 +421,7 @@ class Call final : public webrtc::Call,
|
||||
|
||||
ReceiveSideCongestionController receive_side_cc_;
|
||||
RepeatingTaskHandle receive_side_cc_periodic_task_;
|
||||
RepeatingTaskHandle elastic_bandwidth_allocation_task_;
|
||||
|
||||
const std::unique_ptr<ReceiveTimeCalculator> receive_time_calculator_;
|
||||
|
||||
@ -660,7 +662,9 @@ Call::Call(CallConfig config,
|
||||
: nullptr),
|
||||
num_cpu_cores_(CpuInfo::DetectNumberOfCores()),
|
||||
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)),
|
||||
audio_network_state_(kNetworkDown),
|
||||
video_network_state_(kNetworkDown),
|
||||
@ -697,6 +701,22 @@ Call::Call(CallConfig config,
|
||||
worker_thread_,
|
||||
[receive_side_cc] { return receive_side_cc->MaybeProcess(); },
|
||||
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() {
|
||||
@ -709,6 +729,7 @@ Call::~Call() {
|
||||
RTC_CHECK(video_receive_streams_.empty());
|
||||
|
||||
receive_side_cc_periodic_task_.Stop();
|
||||
elastic_bandwidth_allocation_task_.Stop();
|
||||
call_stats_->DeregisterStatsObserver(&receive_side_cc_);
|
||||
send_stats_.SetFirstPacketTime(transport_send_->GetFirstPacketTime());
|
||||
|
||||
|
||||
@ -74,6 +74,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([
|
||||
FieldTrial('WebRTC-DisableRtxRateLimiter',
|
||||
42225500,
|
||||
date(2024, 4, 1)),
|
||||
FieldTrial('WebRTC-ElasticBitrateAllocation',
|
||||
350555527,
|
||||
date(2025, 3, 1)),
|
||||
FieldTrial('WebRTC-EncoderDataDumpDirectory',
|
||||
296242528,
|
||||
date(2024, 4, 1)),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user