diff --git a/media/engine/simulcast.cc b/media/engine/simulcast.cc index f74d4adfbe..375572f374 100644 --- a/media/engine/simulcast.cc +++ b/media/engine/simulcast.cc @@ -23,6 +23,7 @@ #include "modules/video_coding/utility/simulcast_rate_allocator.h" #include "rtc_base/arraysize.h" #include "rtc_base/checks.h" +#include "rtc_base/experiments/field_trial_parser.h" #include "rtc_base/experiments/min_video_bitrate_experiment.h" #include "rtc_base/experiments/normalize_simulcast_size_experiment.h" #include "rtc_base/experiments/rate_control_settings.h" @@ -61,7 +62,7 @@ struct SimulcastFormat { int width; int height; // The maximum number of simulcast layers can be used for - // resolutions at |widthxheigh| for legacy applications. + // resolutions at |widthxheight| for legacy applications. size_t max_layers; // The maximum bitrate for encoding stream at |widthxheight|, when we are // not sending the next higher spatial stream. @@ -162,7 +163,10 @@ int NormalizeSimulcastSize(int size, size_t simulcast_layers) { return ((size >> base2_exponent) << base2_exponent); } -SimulcastFormat InterpolateSimulcastFormat(int width, int height) { +SimulcastFormat InterpolateSimulcastFormat( + int width, + int height, + absl::optional max_roundup_rate) { const int index = FindSimulcastFormatIndex(width, height); if (index == 0) return kSimulcastFormats[index]; @@ -174,7 +178,10 @@ SimulcastFormat InterpolateSimulcastFormat(int width, int height) { const float rate = (total_pixels_up - total_pixels) / static_cast(total_pixels_up - total_pixels_down); - size_t max_layers = kSimulcastFormats[index].max_layers; + // Use upper resolution if |rate| is below the configured threshold. + size_t max_layers = (max_roundup_rate && rate < max_roundup_rate.value()) + ? kSimulcastFormats[index - 1].max_layers + : kSimulcastFormats[index].max_layers; webrtc::DataRate max_bitrate = Interpolate(kSimulcastFormats[index - 1].max_bitrate, kSimulcastFormats[index].max_bitrate, rate); @@ -188,6 +195,10 @@ SimulcastFormat InterpolateSimulcastFormat(int width, int height) { return {width, height, max_layers, max_bitrate, target_bitrate, min_bitrate}; } +SimulcastFormat InterpolateSimulcastFormat(int width, int height) { + return InterpolateSimulcastFormat(width, height, absl::nullopt); +} + webrtc::DataRate FindSimulcastMaxBitrate(int width, int height) { return InterpolateSimulcastFormat(width, height).max_bitrate; } @@ -235,9 +246,18 @@ size_t LimitSimulcastLayerCount(int width, const webrtc::WebRtcKeyValueConfig& trials) { if (!absl::StartsWith(trials.Lookup(kUseLegacySimulcastLayerLimitFieldTrial), "Disabled")) { + // Max layers from one higher resolution in kSimulcastFormats will be used + // if the ratio (pixels_up - pixels) / (pixels_up - pixels_down) is less + // than configured |max_ratio|. pixels_down is the selected index in + // kSimulcastFormats based on pixels. + webrtc::FieldTrialOptional max_ratio("max_ratio"); + webrtc::ParseFieldTrial({&max_ratio}, + trials.Lookup("WebRTC-SimulcastLayerLimitRoundUp")); + size_t adaptive_layer_count = std::max( need_layers, - kSimulcastFormats[FindSimulcastFormatIndex(width, height)].max_layers); + InterpolateSimulcastFormat(width, height, max_ratio.GetOptional()) + .max_layers); if (layer_count > adaptive_layer_count) { RTC_LOG(LS_WARNING) << "Reducing simulcast layer count from " << layer_count << " to " << adaptive_layer_count; diff --git a/media/engine/simulcast_unittest.cc b/media/engine/simulcast_unittest.cc index 27b1574456..193f8c0259 100644 --- a/media/engine/simulcast_unittest.cc +++ b/media/engine/simulcast_unittest.cc @@ -378,4 +378,66 @@ TEST(SimulcastTest, BitratesForCloseToStandardResolution) { } } +TEST(SimulcastTest, MaxLayers) { + FieldTrialBasedConfig trials; + const size_t kMinLayers = 1; + const int kMaxLayers = 3; + + std::vector streams; + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 540, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(3u, streams.size()); + // <960x540: 2 layers + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 539, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(2u, streams.size()); + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 270, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(2u, streams.size()); + // <480x270: 1 layer + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 269, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(1u, streams.size()); +} + +TEST(SimulcastTest, MaxLayersWithFieldTrial) { + test::ScopedFieldTrials field_trials( + "WebRTC-SimulcastLayerLimitRoundUp/max_ratio:0.1/"); + FieldTrialBasedConfig trials; + const size_t kMinLayers = 1; + const int kMaxLayers = 3; + + std::vector streams; + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 540, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(3u, streams.size()); + // Lowest cropped height where max layers from higher resolution is used. + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 512, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(3u, streams.size()); + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 510, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(2u, streams.size()); + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 270, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(2u, streams.size()); + // Lowest cropped height where max layers from higher resolution is used. + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 256, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(2u, streams.size()); + streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 254, + kBitratePriority, kQpMax, !kScreenshare, + true, trials); + EXPECT_EQ(1u, streams.size()); +} + } // namespace webrtc