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:
Dan Tan 2024-08-22 17:05:13 +00:00 committed by WebRTC LUCI CQ
parent 2e376cd36d
commit 43c0cf9cf8
6 changed files with 400 additions and 26 deletions

View File

@ -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",

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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());

View File

@ -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)),