diff --git a/modules/video_coding/BUILD.gn b/modules/video_coding/BUILD.gn index eafbd39ed2..dc9f018942 100644 --- a/modules/video_coding/BUILD.gn +++ b/modules/video_coding/BUILD.gn @@ -264,6 +264,7 @@ rtc_source_set("video_coding_utility") { "../../rtc_base/experiments:quality_scaler_settings", "../../rtc_base/experiments:quality_scaling_experiment", "../../rtc_base/experiments:rate_control_settings", + "../../rtc_base/experiments:stable_target_rate_experiment", "../../rtc_base/synchronization:sequence_checker", "../../rtc_base/system:arch", "../../rtc_base/system:file_wrapper", diff --git a/modules/video_coding/codecs/vp9/svc_rate_allocator.cc b/modules/video_coding/codecs/vp9/svc_rate_allocator.cc index 45fc9864ed..8513b432c3 100644 --- a/modules/video_coding/codecs/vp9/svc_rate_allocator.cc +++ b/modules/video_coding/codecs/vp9/svc_rate_allocator.cc @@ -200,15 +200,11 @@ VideoBitrateAllocation SvcRateAllocator::Allocate( // Figure out how many spatial layers should be active. if (experiment_settings_.IsEnabled() && parameters.stable_bitrate > DataRate::Zero()) { - double hysteresis_factor = 1.0; + double hysteresis_factor; if (codec_.mode == VideoCodecMode::kScreensharing) { - hysteresis_factor = - experiment_settings_.GetScreenshareHysteresisFactor().value_or( - hysteresis_factor); + hysteresis_factor = experiment_settings_.GetScreenshareHysteresisFactor(); } else { - hysteresis_factor = - experiment_settings_.GetVideoHysteresisFactor().value_or( - hysteresis_factor); + hysteresis_factor = experiment_settings_.GetVideoHysteresisFactor(); } DataRate stable_rate = diff --git a/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc b/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc index f721608365..f4d0924ffa 100644 --- a/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc +++ b/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc @@ -283,7 +283,9 @@ TEST_P(SvcRateAllocatorTestParametrizedContentType, PaddingBitrate) { } TEST_P(SvcRateAllocatorTestParametrizedContentType, StableBitrate) { - ScopedFieldTrials field_trial("WebRTC-StableTargetRate/enabled:true/"); + ScopedFieldTrials field_trial( + "WebRTC-StableTargetRate/enabled:true,video_hysteresis_factor:1.0," + "screenshare_hysteresis_factor:1.0/"); const VideoCodec codec = Configure(1280, 720, 3, 1, is_screen_sharing_); const auto start_rates = SvcRateAllocator::GetLayerStartBitrates(codec); diff --git a/modules/video_coding/utility/simulcast_rate_allocator.cc b/modules/video_coding/utility/simulcast_rate_allocator.cc index 5929572099..15b8e543a1 100644 --- a/modules/video_coding/utility/simulcast_rate_allocator.cc +++ b/modules/video_coding/utility/simulcast_rate_allocator.cc @@ -59,32 +59,45 @@ float SimulcastRateAllocator::GetTemporalRateAllocation(int num_layers, SimulcastRateAllocator::SimulcastRateAllocator(const VideoCodec& codec) : codec_(codec), - hysteresis_factor_(RateControlSettings::ParseFromFieldTrials() - .GetSimulcastHysteresisFactor(codec.mode)) {} + stable_rate_settings_( + StableTargetRateExperiment::ParseFromFieldTrials()) {} SimulcastRateAllocator::~SimulcastRateAllocator() = default; VideoBitrateAllocation SimulcastRateAllocator::Allocate( VideoBitrateAllocationParameters parameters) { - VideoBitrateAllocation allocated_bitrates_bps; - DistributeAllocationToSimulcastLayers(parameters.total_bitrate.bps(), - &allocated_bitrates_bps); - DistributeAllocationToTemporalLayers(&allocated_bitrates_bps); - return allocated_bitrates_bps; + VideoBitrateAllocation allocated_bitrates; + DataRate stable_rate = parameters.total_bitrate; + if (stable_rate_settings_.IsEnabled() && + parameters.stable_bitrate > DataRate::Zero()) { + stable_rate = std::min(parameters.stable_bitrate, parameters.total_bitrate); + } + DistributeAllocationToSimulcastLayers(parameters.total_bitrate, stable_rate, + &allocated_bitrates); + DistributeAllocationToTemporalLayers(&allocated_bitrates); + return allocated_bitrates; } void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers( - uint32_t total_bitrate_bps, - VideoBitrateAllocation* allocated_bitrates_bps) { - uint32_t left_to_allocate = total_bitrate_bps; - if (codec_.maxBitrate && codec_.maxBitrate * 1000 < left_to_allocate) - left_to_allocate = codec_.maxBitrate * 1000; + DataRate total_bitrate, + DataRate stable_bitrate, + VideoBitrateAllocation* allocated_bitrates) { + DataRate left_in_total_allocation = total_bitrate; + DataRate left_in_stable_allocation = stable_bitrate; + + if (codec_.maxBitrate) { + DataRate max_rate = DataRate::kbps(codec_.maxBitrate); + left_in_total_allocation = std::min(left_in_total_allocation, max_rate); + left_in_stable_allocation = std::min(left_in_stable_allocation, max_rate); + } if (codec_.numberOfSimulcastStreams == 0) { // No simulcast, just set the target as this has been capped already. if (codec_.active) { - allocated_bitrates_bps->SetBitrate( - 0, 0, std::max(codec_.minBitrate * 1000, left_to_allocate)); + allocated_bitrates->SetBitrate( + 0, 0, + std::max(DataRate::kbps(codec_.minBitrate), left_in_total_allocation) + .bps()); } return; } @@ -115,9 +128,10 @@ void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers( // Always allocate enough bitrate for the minimum bitrate of the first // active layer. Suspending below min bitrate is controlled outside the // codec implementation and is not overridden by this. - left_to_allocate = std::max( - codec_.simulcastStream[layer_index[active_layer]].minBitrate * 1000, - left_to_allocate); + DataRate min_rate = DataRate::kbps( + codec_.simulcastStream[layer_index[active_layer]].minBitrate); + left_in_total_allocation = std::max(left_in_total_allocation, min_rate); + left_in_stable_allocation = std::max(left_in_stable_allocation, min_rate); // Begin by allocating bitrate to simulcast streams, putting all bitrate in // temporal layer 0. We'll then distribute this bitrate, across potential @@ -142,25 +156,28 @@ void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers( } // If we can't allocate to the current layer we can't allocate to higher // layers because they require a higher minimum bitrate. - uint32_t min_bitrate = stream.minBitrate * 1000; + DataRate min_bitrate = DataRate::kbps(stream.minBitrate); + DataRate target_bitrate = DataRate::kbps(stream.targetBitrate); + double hysteresis_factor = + codec_.mode == VideoCodecMode::kRealtimeVideo + ? stable_rate_settings_.GetVideoHysteresisFactor() + : stable_rate_settings_.GetScreenshareHysteresisFactor(); if (!first_allocation && !stream_enabled_[layer_index[active_layer]]) { - min_bitrate = std::min( - static_cast(hysteresis_factor_ * min_bitrate + 0.5), - stream.targetBitrate * 1000); + min_bitrate = std::min(hysteresis_factor * min_bitrate, target_bitrate); } - if (left_to_allocate < min_bitrate) { + if (left_in_stable_allocation < min_bitrate) { break; } // We are allocating to this layer so it is the current active allocation. top_active_layer = layer_index[active_layer]; stream_enabled_[layer_index[active_layer]] = true; - uint32_t allocation = - std::min(left_to_allocate, stream.targetBitrate * 1000); - allocated_bitrates_bps->SetBitrate(layer_index[active_layer], 0, - allocation); - RTC_DCHECK_LE(allocation, left_to_allocate); - left_to_allocate -= allocation; + DataRate layer_rate = std::min(left_in_total_allocation, target_bitrate); + allocated_bitrates->SetBitrate(layer_index[active_layer], 0, + layer_rate.bps()); + left_in_total_allocation -= layer_rate; + left_in_stable_allocation -= + std::min(left_in_stable_allocation, target_bitrate); } // All layers above this one are not active. @@ -172,16 +189,16 @@ void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers( // stream. // TODO(sprang): Allocate up to max bitrate for all layers once we have a // better idea of possible performance implications. - if (left_to_allocate > 0) { + if (left_in_total_allocation > DataRate::Zero()) { const SimulcastStream& stream = codec_.simulcastStream[top_active_layer]; - uint32_t bitrate_bps = - allocated_bitrates_bps->GetSpatialLayerSum(top_active_layer); - uint32_t allocation = - std::min(left_to_allocate, stream.maxBitrate * 1000 - bitrate_bps); - bitrate_bps += allocation; - RTC_DCHECK_LE(allocation, left_to_allocate); - left_to_allocate -= allocation; - allocated_bitrates_bps->SetBitrate(top_active_layer, 0, bitrate_bps); + DataRate initial_layer_rate = + DataRate::bps(allocated_bitrates->GetSpatialLayerSum(top_active_layer)); + DataRate additional_allocation = + std::min(left_in_total_allocation, + DataRate::kbps(stream.maxBitrate) - initial_layer_rate); + allocated_bitrates->SetBitrate( + top_active_layer, 0, + (initial_layer_rate + additional_allocation).bps()); } } diff --git a/modules/video_coding/utility/simulcast_rate_allocator.h b/modules/video_coding/utility/simulcast_rate_allocator.h index efbe5149b7..97d50df401 100644 --- a/modules/video_coding/utility/simulcast_rate_allocator.h +++ b/modules/video_coding/utility/simulcast_rate_allocator.h @@ -20,6 +20,7 @@ #include "api/video/video_bitrate_allocator.h" #include "api/video_codecs/video_codec.h" #include "rtc_base/constructor_magic.h" +#include "rtc_base/experiments/stable_target_rate_experiment.h" namespace webrtc { @@ -36,10 +37,11 @@ class SimulcastRateAllocator : public VideoBitrateAllocator { private: void DistributeAllocationToSimulcastLayers( - uint32_t total_bitrate_bps, - VideoBitrateAllocation* allocated_bitrates_bps); + DataRate total_bitrate, + DataRate stable_bitrate, + VideoBitrateAllocation* allocated_bitrates); void DistributeAllocationToTemporalLayers( - VideoBitrateAllocation* allocated_bitrates_bps) const; + VideoBitrateAllocation* allocated_bitrates) const; std::vector DefaultTemporalLayerAllocation(int bitrate_kbps, int max_bitrate_kbps, int simulcast_id) const; @@ -50,7 +52,7 @@ class SimulcastRateAllocator : public VideoBitrateAllocator { int NumTemporalStreams(size_t simulcast_id) const; const VideoCodec codec_; - const double hysteresis_factor_; + const StableTargetRateExperiment stable_rate_settings_; std::vector stream_enabled_; RTC_DISALLOW_COPY_AND_ASSIGN(SimulcastRateAllocator); diff --git a/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc b/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc index 2c2b7c7e65..eb01481646 100644 --- a/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc +++ b/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc @@ -136,6 +136,24 @@ class SimulcastRateAllocatorTest : public ::testing::TestWithParam { DataRate::kbps(target_bitrate), kDefaultFrameRate)); } + VideoBitrateAllocation GetAllocation(DataRate target_rate, + DataRate stable_rate) { + return allocator_->Allocate(VideoBitrateAllocationParameters( + target_rate, stable_rate, kDefaultFrameRate)); + } + + DataRate MinRate(size_t layer_index) const { + return DataRate::kbps(codec_.simulcastStream[layer_index].minBitrate); + } + + DataRate TargetRate(size_t layer_index) const { + return DataRate::kbps(codec_.simulcastStream[layer_index].targetBitrate); + } + + DataRate MaxRate(size_t layer_index) const { + return DataRate::kbps(codec_.simulcastStream[layer_index].maxBitrate); + } + protected: static const int kDefaultFrameRate = 30; VideoCodec codec_; @@ -524,6 +542,71 @@ TEST_F(SimulcastRateAllocatorTest, NonConferenceModeScreenshare) { EXPECT_EQ(alloc.GetTemporalLayerAllocation(2).size(), 3u); } +TEST_F(SimulcastRateAllocatorTest, StableRate) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-StableTargetRate/" + "enabled:true," + "video_hysteresis_factor:1.1/"); + + SetupCodec3SL3TL({true, true, true}); + CreateAllocator(); + + // Let the volatile rate always be be enough for all streams, in this test we + // are only interested in how the stable rate affects enablement. + const DataRate volatile_rate = + (TargetRate(0) + TargetRate(1) + MinRate(2)) * 1.1; + + { + // On the first call to a new SimulcastRateAllocator instance, hysteresis + // is disabled, but stable rate still caps layers. + uint32_t expected[] = {TargetRate(0).kbps(), + MaxRate(1).kbps()}; + ExpectEqual(expected, + GetAllocation(volatile_rate, TargetRate(0) + MinRate(1))); + } + + { + // Let stable rate go to a bitrate below what is needed for two streams. + uint32_t expected[] = {MaxRate(0).kbps(), 0}; + ExpectEqual(expected, + GetAllocation(volatile_rate, + TargetRate(0) + MinRate(1) - DataRate::bps(1))); + } + + { + // Don't enable stream as we need to get up above hysteresis threshold. + uint32_t expected[] = {MaxRate(0).kbps(), 0}; + ExpectEqual(expected, + GetAllocation(volatile_rate, TargetRate(0) + MinRate(1))); + } + + { + // Above threshold with hysteresis, enable second stream. + uint32_t expected[] = {TargetRate(0).kbps(), + MaxRate(1).kbps()}; + ExpectEqual(expected, GetAllocation(volatile_rate, + (TargetRate(0) + MinRate(1)) * 1.1)); + } + + { + // Enough to enable all thee layers. + uint32_t expected[] = { + TargetRate(0).kbps(), TargetRate(1).kbps(), + (volatile_rate - TargetRate(0) - TargetRate(1)).kbps()}; + ExpectEqual(expected, GetAllocation(volatile_rate, volatile_rate)); + } + + { + // Drop hysteresis, all three still on. + uint32_t expected[] = { + TargetRate(0).kbps(), TargetRate(1).kbps(), + (volatile_rate - TargetRate(0) - TargetRate(1)).kbps()}; + ExpectEqual(expected, + GetAllocation(volatile_rate, + TargetRate(0) + TargetRate(1) + MinRate(2))); + } +} + class ScreenshareRateAllocationTest : public SimulcastRateAllocatorTest { public: void SetupConferenceScreenshare(bool use_simulcast, bool active = true) { diff --git a/rtc_base/experiments/stable_target_rate_experiment.cc b/rtc_base/experiments/stable_target_rate_experiment.cc index 185bd40960..fa7a97b51f 100644 --- a/rtc_base/experiments/stable_target_rate_experiment.cc +++ b/rtc_base/experiments/stable_target_rate_experiment.cc @@ -20,8 +20,8 @@ constexpr char kFieldTrialName[] = "WebRTC-StableTargetRate"; StableTargetRateExperiment::StableTargetRateExperiment( const WebRtcKeyValueConfig* const key_value_config, - absl::optional default_video_hysteresis, - absl::optional default_screenshare_hysteresis) + double default_video_hysteresis, + double default_screenshare_hysteresis) : enabled_("enabled", false), video_hysteresis_factor_("video_hysteresis_factor", default_video_hysteresis), @@ -44,31 +44,25 @@ StableTargetRateExperiment StableTargetRateExperiment::ParseFromFieldTrials() { StableTargetRateExperiment StableTargetRateExperiment::ParseFromKeyValueConfig( const WebRtcKeyValueConfig* const key_value_config) { - if (key_value_config->Lookup("WebRTC-VideoRateControl") != "") { - RateControlSettings rate_control = - RateControlSettings::ParseFromKeyValueConfig(key_value_config); - return StableTargetRateExperiment(key_value_config, - rate_control.GetSimulcastHysteresisFactor( - VideoCodecMode::kRealtimeVideo), - rate_control.GetSimulcastHysteresisFactor( - VideoCodecMode::kScreensharing)); - } - return StableTargetRateExperiment(key_value_config, absl::nullopt, - absl::nullopt); + RateControlSettings rate_control = + RateControlSettings::ParseFromKeyValueConfig(key_value_config); + return StableTargetRateExperiment( + key_value_config, + rate_control.GetSimulcastHysteresisFactor(VideoCodecMode::kRealtimeVideo), + rate_control.GetSimulcastHysteresisFactor( + VideoCodecMode::kScreensharing)); } bool StableTargetRateExperiment::IsEnabled() const { return enabled_.Get(); } -absl::optional StableTargetRateExperiment::GetVideoHysteresisFactor() - const { - return video_hysteresis_factor_.GetOptional(); +double StableTargetRateExperiment::GetVideoHysteresisFactor() const { + return video_hysteresis_factor_.Get(); } -absl::optional -StableTargetRateExperiment::GetScreenshareHysteresisFactor() const { - return screenshare_hysteresis_factor_.GetOptional(); +double StableTargetRateExperiment::GetScreenshareHysteresisFactor() const { + return screenshare_hysteresis_factor_.Get(); } } // namespace webrtc diff --git a/rtc_base/experiments/stable_target_rate_experiment.h b/rtc_base/experiments/stable_target_rate_experiment.h index 7a2c06c4ba..299299ce87 100644 --- a/rtc_base/experiments/stable_target_rate_experiment.h +++ b/rtc_base/experiments/stable_target_rate_experiment.h @@ -25,18 +25,18 @@ class StableTargetRateExperiment { const WebRtcKeyValueConfig* const key_value_config); bool IsEnabled() const; - absl::optional GetVideoHysteresisFactor() const; - absl::optional GetScreenshareHysteresisFactor() const; + double GetVideoHysteresisFactor() const; + double GetScreenshareHysteresisFactor() const; private: explicit StableTargetRateExperiment( const WebRtcKeyValueConfig* const key_value_config, - absl::optional default_video_hysteresis, - absl::optional default_screenshare_hysteresis); + double default_video_hysteresis, + double default_screenshare_hysteresis); FieldTrialParameter enabled_; - FieldTrialOptional video_hysteresis_factor_; - FieldTrialOptional screenshare_hysteresis_factor_; + FieldTrialParameter video_hysteresis_factor_; + FieldTrialParameter screenshare_hysteresis_factor_; }; } // namespace webrtc diff --git a/rtc_base/experiments/stable_target_rate_experiment_unittest.cc b/rtc_base/experiments/stable_target_rate_experiment_unittest.cc index 86629f4e87..71e757d68c 100644 --- a/rtc_base/experiments/stable_target_rate_experiment_unittest.cc +++ b/rtc_base/experiments/stable_target_rate_experiment_unittest.cc @@ -19,8 +19,8 @@ TEST(StableBweExperimentTest, Default) { StableTargetRateExperiment config = StableTargetRateExperiment::ParseFromFieldTrials(); EXPECT_FALSE(config.IsEnabled()); - EXPECT_FALSE(config.GetVideoHysteresisFactor()); - EXPECT_FALSE(config.GetScreenshareHysteresisFactor()); + EXPECT_EQ(config.GetVideoHysteresisFactor(), 1.0); + EXPECT_EQ(config.GetScreenshareHysteresisFactor(), 1.35); } TEST(StableBweExperimentTest, EnabledNoHysteresis) { @@ -30,8 +30,8 @@ TEST(StableBweExperimentTest, EnabledNoHysteresis) { StableTargetRateExperiment config = StableTargetRateExperiment::ParseFromFieldTrials(); EXPECT_TRUE(config.IsEnabled()); - EXPECT_FALSE(config.GetVideoHysteresisFactor()); - EXPECT_FALSE(config.GetScreenshareHysteresisFactor()); + EXPECT_EQ(config.GetVideoHysteresisFactor(), 1.0); + EXPECT_EQ(config.GetScreenshareHysteresisFactor(), 1.35); } TEST(StableBweExperimentTest, EnabledWithHysteresis) {