diff --git a/modules/video_coding/utility/simulcast_rate_allocator.cc b/modules/video_coding/utility/simulcast_rate_allocator.cc index f2cbe0b604..2666a16593 100644 --- a/modules/video_coding/utility/simulcast_rate_allocator.cc +++ b/modules/video_coding/utility/simulcast_rate_allocator.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -37,6 +38,33 @@ static const float kShort3TlRateAllocation[kMaxTemporalStreams] = { const uint32_t kLegacyScreenshareTl0BitrateKbps = 200; const uint32_t kLegacyScreenshareTl1BitrateKbps = 1000; + +double GetHysteresisFactor(const VideoCodec& codec) { + double factor = 1.0; + + std::string field_trial_name; + switch (codec.mode) { + case VideoCodecMode::kRealtimeVideo: + field_trial_name = "WebRTC-SimulcastUpswitchHysteresisPercent"; + // Default to no hysteresis for simulcast video. + factor = 1.0; + break; + case VideoCodecMode::kScreensharing: + field_trial_name = "WebRTC-SimulcastScreenshareUpswitchHysteresisPercent"; + // Default to 35% hysteresis for simulcast screenshare. + factor = 1.35; + break; + } + + std::string group_name = webrtc::field_trial::FindFullName(field_trial_name); + int percent = 0; + if (!group_name.empty() && sscanf(group_name.c_str(), "%d", &percent) == 1 && + percent >= 0) { + factor = 1.0 + (percent / 100.0); + } + + return factor; +} } // namespace float SimulcastRateAllocator::GetTemporalRateAllocation(int num_layers, @@ -53,7 +81,9 @@ float SimulcastRateAllocator::GetTemporalRateAllocation(int num_layers, } SimulcastRateAllocator::SimulcastRateAllocator(const VideoCodec& codec) - : codec_(codec) {} + : codec_(codec), hysteresis_factor_(GetHysteresisFactor(codec)) {} + +SimulcastRateAllocator::~SimulcastRateAllocator() = default; VideoBitrateAllocation SimulcastRateAllocator::GetAllocation( uint32_t total_bitrate_bps, @@ -67,7 +97,7 @@ VideoBitrateAllocation SimulcastRateAllocator::GetAllocation( void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers( uint32_t total_bitrate_bps, - VideoBitrateAllocation* allocated_bitrates_bps) const { + 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; @@ -103,20 +133,37 @@ void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers( // temporal layer 0. We'll then distribute this bitrate, across potential // temporal layers, when stream allocation is done. + bool first_allocation = false; + if (stream_enabled_.empty()) { + // First time allocating, this means we should not include hysteresis in + // case this is a reconfiguration of an existing enabled stream. + first_allocation = true; + stream_enabled_.resize(codec_.numberOfSimulcastStreams, false); + } + size_t top_active_layer = active_layer; // Allocate up to the target bitrate for each active simulcast layer. for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) { const SimulcastStream& stream = codec_.simulcastStream[active_layer]; if (!stream.active) { + stream_enabled_[active_layer] = false; continue; } // If we can't allocate to the current layer we can't allocate to higher // layers because they require a higher minimum bitrate. - if (left_to_allocate < stream.minBitrate * 1000) { + uint32_t min_bitrate = stream.minBitrate * 1000; + if (!first_allocation && !stream_enabled_[active_layer]) { + min_bitrate = std::min( + static_cast(hysteresis_factor_ * min_bitrate + 0.5), + stream.targetBitrate * 1000); + } + if (left_to_allocate < min_bitrate) { break; } + // We are allocating to this layer so it is the current active allocation. top_active_layer = active_layer; + stream_enabled_[active_layer] = true; uint32_t allocation = std::min(left_to_allocate, stream.targetBitrate * 1000); allocated_bitrates_bps->SetBitrate(active_layer, 0, allocation); @@ -124,6 +171,11 @@ void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers( left_to_allocate -= allocation; } + // All layers above this one are not active. + for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) { + stream_enabled_[active_layer] = false; + } + // Next, try allocate remaining bitrate, up to max bitrate, in top active // stream. // TODO(sprang): Allocate up to max bitrate for all layers once we have a diff --git a/modules/video_coding/utility/simulcast_rate_allocator.h b/modules/video_coding/utility/simulcast_rate_allocator.h index 594e88b509..de270c6cf5 100644 --- a/modules/video_coding/utility/simulcast_rate_allocator.h +++ b/modules/video_coding/utility/simulcast_rate_allocator.h @@ -27,6 +27,7 @@ namespace webrtc { class SimulcastRateAllocator : public VideoBitrateAllocator { public: explicit SimulcastRateAllocator(const VideoCodec& codec); + ~SimulcastRateAllocator() override; VideoBitrateAllocation GetAllocation(uint32_t total_bitrate_bps, uint32_t framerate) override; @@ -37,7 +38,7 @@ class SimulcastRateAllocator : public VideoBitrateAllocator { private: void DistributeAllocationToSimulcastLayers( uint32_t total_bitrate_bps, - VideoBitrateAllocation* allocated_bitrates_bps) const; + VideoBitrateAllocation* allocated_bitrates_bps); void DistributeAllocationToTemporalLayers( uint32_t framerate, VideoBitrateAllocation* allocated_bitrates_bps) const; @@ -53,6 +54,8 @@ class SimulcastRateAllocator : public VideoBitrateAllocator { int NumTemporalStreams(size_t simulcast_id) const; const VideoCodec codec_; + const double hysteresis_factor_; + 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 b1e6927e25..8166420d62 100644 --- a/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc +++ b/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc @@ -27,8 +27,11 @@ using ::testing::_; constexpr uint32_t kFramerateFps = 5; constexpr uint32_t kMinBitrateKbps = 50; // These correspond to kLegacyScreenshareTl(0|1)BitrateKbps in cc. -constexpr uint32_t kTargetBitrateKbps = 200; -constexpr uint32_t kMaxBitrateKbps = 1000; +constexpr uint32_t kLegacyScreenshareTargetBitrateKbps = 200; +constexpr uint32_t kLegacyScreenshareMaxBitrateKbps = 1000; +// Bitrates for upper simulcast screenshare layer. +constexpr uint32_t kSimulcastScreenshareMinBitrateKbps = 600; +constexpr uint32_t kSimulcastScreenshareMaxBitrateKbps = 1250; class MockTemporalLayers : public TemporalLayers { public: @@ -53,7 +56,7 @@ class SimulcastRateAllocatorTest : public ::testing::TestWithParam { memset(&codec_, 0, sizeof(VideoCodec)); codec_.codecType = kVideoCodecVP8; codec_.minBitrate = kMinBitrateKbps; - codec_.maxBitrate = kMaxBitrateKbps; + codec_.maxBitrate = kLegacyScreenshareMaxBitrateKbps; codec_.active = true; CreateAllocator(); } @@ -168,8 +171,8 @@ TEST_F(SimulcastRateAllocatorTest, NoSimulcastInactive) { CreateAllocator(); ExpectEqual(expected, GetAllocation(kMinBitrateKbps - 10)); - ExpectEqual(expected, GetAllocation(kTargetBitrateKbps)); - ExpectEqual(expected, GetAllocation(kMaxBitrateKbps + 10)); + ExpectEqual(expected, GetAllocation(kLegacyScreenshareTargetBitrateKbps)); + ExpectEqual(expected, GetAllocation(kLegacyScreenshareMaxBitrateKbps + 10)); } TEST_F(SimulcastRateAllocatorTest, SingleSimulcastBelowMin) { @@ -177,7 +180,7 @@ TEST_F(SimulcastRateAllocatorTest, SingleSimulcastBelowMin) { codec_.numberOfSimulcastStreams = 1; const uint32_t kMin = codec_.minBitrate - 10; codec_.simulcastStream[0].minBitrate = kMin; - codec_.simulcastStream[0].targetBitrate = kTargetBitrateKbps; + codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps; codec_.simulcastStream[0].active = true; CreateAllocator(); @@ -204,13 +207,13 @@ TEST_F(SimulcastRateAllocatorTest, SingleSimulcastAboveMax) { TEST_F(SimulcastRateAllocatorTest, SingleSimulcastWithinLimits) { codec_.numberOfSimulcastStreams = 1; codec_.simulcastStream[0].minBitrate = kMinBitrateKbps; - codec_.simulcastStream[0].targetBitrate = kTargetBitrateKbps; - codec_.simulcastStream[0].maxBitrate = kMaxBitrateKbps; + codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps; + codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps; codec_.simulcastStream[0].active = true; CreateAllocator(); - for (uint32_t bitrate = kMinBitrateKbps; bitrate <= kMaxBitrateKbps; - ++bitrate) { + for (uint32_t bitrate = kMinBitrateKbps; + bitrate <= kLegacyScreenshareMaxBitrateKbps; ++bitrate) { uint32_t expected[] = {bitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } @@ -219,15 +222,15 @@ TEST_F(SimulcastRateAllocatorTest, SingleSimulcastWithinLimits) { TEST_F(SimulcastRateAllocatorTest, SingleSimulcastInactive) { codec_.numberOfSimulcastStreams = 1; codec_.simulcastStream[0].minBitrate = kMinBitrateKbps; - codec_.simulcastStream[0].targetBitrate = kTargetBitrateKbps; - codec_.simulcastStream[0].maxBitrate = kMaxBitrateKbps; + codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps; + codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps; codec_.simulcastStream[0].active = false; CreateAllocator(); uint32_t expected[] = {0}; ExpectEqual(expected, GetAllocation(kMinBitrateKbps - 10)); - ExpectEqual(expected, GetAllocation(kTargetBitrateKbps)); - ExpectEqual(expected, GetAllocation(kMaxBitrateKbps + 10)); + ExpectEqual(expected, GetAllocation(kLegacyScreenshareTargetBitrateKbps)); + ExpectEqual(expected, GetAllocation(kLegacyScreenshareMaxBitrateKbps + 10)); } TEST_F(SimulcastRateAllocatorTest, OneToThreeStreams) { @@ -471,14 +474,25 @@ class ScreenshareRateAllocationTest : public SimulcastRateAllocatorTest { void SetupConferenceScreenshare(bool use_simulcast, bool active = true) { codec_.mode = VideoCodecMode::kScreensharing; codec_.minBitrate = kMinBitrateKbps; - codec_.maxBitrate = kMaxBitrateKbps; + codec_.maxBitrate = + kLegacyScreenshareMaxBitrateKbps + kSimulcastScreenshareMaxBitrateKbps; if (use_simulcast) { - codec_.numberOfSimulcastStreams = 1; + codec_.numberOfSimulcastStreams = 2; codec_.simulcastStream[0].minBitrate = kMinBitrateKbps; - codec_.simulcastStream[0].targetBitrate = kTargetBitrateKbps; - codec_.simulcastStream[0].maxBitrate = kMaxBitrateKbps; + codec_.simulcastStream[0].targetBitrate = + kLegacyScreenshareTargetBitrateKbps; + codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps; codec_.simulcastStream[0].numberOfTemporalLayers = 2; codec_.simulcastStream[0].active = active; + + codec_.simulcastStream[1].minBitrate = + kSimulcastScreenshareMinBitrateKbps; + codec_.simulcastStream[1].targetBitrate = + kSimulcastScreenshareMaxBitrateKbps; + codec_.simulcastStream[1].maxBitrate = + kSimulcastScreenshareMaxBitrateKbps; + codec_.simulcastStream[1].numberOfTemporalLayers = 2; + codec_.simulcastStream[1].active = active; } else { codec_.numberOfSimulcastStreams = 0; codec_.VP8()->numberOfTemporalLayers = 2; @@ -495,41 +509,48 @@ TEST_P(ScreenshareRateAllocationTest, BitrateBelowTl0) { SetupConferenceScreenshare(GetParam()); CreateAllocator(); - VideoBitrateAllocation allocation = - allocator_->GetAllocation(kTargetBitrateKbps * 1000, kFramerateFps); + VideoBitrateAllocation allocation = allocator_->GetAllocation( + kLegacyScreenshareTargetBitrateKbps * 1000, kFramerateFps); // All allocation should go in TL0. - EXPECT_EQ(kTargetBitrateKbps, allocation.get_sum_kbps()); - EXPECT_EQ(kTargetBitrateKbps, allocation.GetBitrate(0, 0) / 1000); + EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, allocation.get_sum_kbps()); + EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, + allocation.GetBitrate(0, 0) / 1000); } TEST_P(ScreenshareRateAllocationTest, BitrateAboveTl0) { SetupConferenceScreenshare(GetParam()); CreateAllocator(); - uint32_t target_bitrate_kbps = (kTargetBitrateKbps + kMaxBitrateKbps) / 2; + uint32_t target_bitrate_kbps = + (kLegacyScreenshareTargetBitrateKbps + kLegacyScreenshareMaxBitrateKbps) / + 2; VideoBitrateAllocation allocation = allocator_->GetAllocation(target_bitrate_kbps * 1000, kFramerateFps); // Fill TL0, then put the rest in TL1. EXPECT_EQ(target_bitrate_kbps, allocation.get_sum_kbps()); - EXPECT_EQ(kTargetBitrateKbps, allocation.GetBitrate(0, 0) / 1000); - EXPECT_EQ(target_bitrate_kbps - kTargetBitrateKbps, + EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, + allocation.GetBitrate(0, 0) / 1000); + EXPECT_EQ(target_bitrate_kbps - kLegacyScreenshareTargetBitrateKbps, allocation.GetBitrate(0, 1) / 1000); } -TEST_P(ScreenshareRateAllocationTest, BitrateAboveTl1) { - SetupConferenceScreenshare(GetParam()); +TEST_F(ScreenshareRateAllocationTest, BitrateAboveTl1) { + // This test is only for the non-simulcast case. + SetupConferenceScreenshare(false); CreateAllocator(); - VideoBitrateAllocation allocation = - allocator_->GetAllocation(kMaxBitrateKbps * 2000, kFramerateFps); + VideoBitrateAllocation allocation = allocator_->GetAllocation( + kLegacyScreenshareMaxBitrateKbps * 2000, kFramerateFps); // Fill both TL0 and TL1, but no more. - EXPECT_EQ(kMaxBitrateKbps, allocation.get_sum_kbps()); - EXPECT_EQ(kTargetBitrateKbps, allocation.GetBitrate(0, 0) / 1000); - EXPECT_EQ(kMaxBitrateKbps - kTargetBitrateKbps, - allocation.GetBitrate(0, 1) / 1000); + EXPECT_EQ(kLegacyScreenshareMaxBitrateKbps, allocation.get_sum_kbps()); + EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, + allocation.GetBitrate(0, 0) / 1000); + EXPECT_EQ( + kLegacyScreenshareMaxBitrateKbps - kLegacyScreenshareTargetBitrateKbps, + allocation.GetBitrate(0, 1) / 1000); } // This tests when the screenshare is inactive it should be allocated 0 bitrate @@ -539,10 +560,80 @@ TEST_P(ScreenshareRateAllocationTest, InactiveScreenshare) { CreateAllocator(); // Enough bitrate for TL0 and TL1. - uint32_t target_bitrate_kbps = (kTargetBitrateKbps + kMaxBitrateKbps) / 2; + uint32_t target_bitrate_kbps = + (kLegacyScreenshareTargetBitrateKbps + kLegacyScreenshareMaxBitrateKbps) / + 2; VideoBitrateAllocation allocation = allocator_->GetAllocation(target_bitrate_kbps * 1000, kFramerateFps); EXPECT_EQ(0U, allocation.get_sum_kbps()); } + +TEST_F(ScreenshareRateAllocationTest, Hysteresis) { + // This test is only for the simulcast case. + SetupConferenceScreenshare(true); + CreateAllocator(); + + // The bitrate at which we would normally enable the upper simulcast stream. + const uint32_t default_enable_rate_bps = + codec_.simulcastStream[0].targetBitrate + + codec_.simulcastStream[1].minBitrate; + const uint32_t enable_rate_with_hysteresis_bps = + (default_enable_rate_bps * 135) / 100; + + { + // On the first call to a new SimulcastRateAllocator instance, hysteresis + // is disabled. + const uint32_t bitrate = default_enable_rate_bps; + uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, + codec_.simulcastStream[1].minBitrate}; + ExpectEqual(expected, GetAllocation(bitrate)); + } + + { + // Go down to a bitrate below what is needed for two streams. + const uint32_t bitrate = default_enable_rate_bps - 1; + uint32_t expected[] = {bitrate, 0}; + ExpectEqual(expected, GetAllocation(bitrate)); + } + + { + // Don't enable stream as we need to get up above hysteresis threshold. + const uint32_t bitrate = default_enable_rate_bps; + uint32_t expected[] = {bitrate, 0}; + ExpectEqual(expected, GetAllocation(bitrate)); + } + + { + // Above threshold, enable second stream. + const uint32_t bitrate = enable_rate_with_hysteresis_bps; + uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, + enable_rate_with_hysteresis_bps - + codec_.simulcastStream[0].targetBitrate}; + ExpectEqual(expected, GetAllocation(bitrate)); + } + + { + // Go down again, still keep the second stream alive. + const uint32_t bitrate = default_enable_rate_bps; + uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, + codec_.simulcastStream[1].minBitrate}; + ExpectEqual(expected, GetAllocation(bitrate)); + } + + { + // Go down below default enable, second stream is shut down again. + const uint32_t bitrate = default_enable_rate_bps - 1; + uint32_t expected[] = {bitrate, 0}; + ExpectEqual(expected, GetAllocation(bitrate)); + } + + { + // Go up, hysteresis is blocking us again. + const uint32_t bitrate = default_enable_rate_bps; + uint32_t expected[] = {bitrate, 0}; + ExpectEqual(expected, GetAllocation(bitrate)); + } +} + } // namespace webrtc