diff --git a/video/config/BUILD.gn b/video/config/BUILD.gn index 4c20e02f55..9c1c0642dc 100644 --- a/video/config/BUILD.gn +++ b/video/config/BUILD.gn @@ -18,8 +18,10 @@ rtc_library("streams_config") { deps = [ ":encoder_config", + "../../api:array_view", "../../api:field_trials_view", "../../api/units:data_rate", + "../../api/video:resolution", "../../api/video:video_codec_constants", "../../api/video_codecs:video_codecs_api", "../../call/adaptation:resource_adaptation", @@ -69,6 +71,7 @@ if (rtc_include_tests) { ] deps = [ ":streams_config", + "../../api/video_codecs:scalability_mode", "../../call/adaptation:resource_adaptation", "../../media:media_constants", "../../rtc_base/experiments:min_video_bitrate_experiment", diff --git a/video/config/encoder_stream_factory.cc b/video/config/encoder_stream_factory.cc index 7dd53ab389..96931e1b09 100644 --- a/video/config/encoder_stream_factory.cc +++ b/video/config/encoder_stream_factory.cc @@ -111,6 +111,22 @@ int GetDefaultMaxQp(webrtc::VideoCodecType codec_type) { } } +// Round size to nearest simulcast-friendly size. +// Simulcast stream width and height must both be dividable by +// |2 ^ (simulcast_layers - 1)|. +int NormalizeSimulcastSize(const FieldTrialsView& field_trials, + int size, + size_t simulcast_layers) { + int base2_exponent = static_cast(simulcast_layers) - 1; + const absl::optional experimental_base2_exponent = + webrtc::NormalizeSimulcastSizeExperiment::GetBase2Exponent(field_trials); + if (experimental_base2_exponent && + (size > (1 << *experimental_base2_exponent))) { + base2_exponent = *experimental_base2_exponent; + } + return ((size >> base2_exponent) << base2_exponent); +} + } // namespace EncoderStreamFactory::EncoderStreamFactory( @@ -322,16 +338,17 @@ EncoderStreamFactory::CreateSimulcastOrConferenceModeScreenshareStreams( webrtc::VideoEncoderConfig::ContentType::kScreen; const bool is_legacy_screencast = webrtc::SimulcastUtility::IsConferenceModeScreenshare(encoder_config); - std::vector layers; + + std::vector resolutions = + GetStreamResolutions(trials, width, height, encoder_config); const bool temporal_layers_supported = IsTemporalLayersSupported(encoder_config.codec_type); // Use legacy simulcast screenshare if conference mode is explicitly enabled // or use the regular simulcast configuration path which is generic. - layers = GetSimulcastConfig(FindRequiredActiveLayers(encoder_config), - encoder_config.number_of_streams, width, height, - is_legacy_screencast, temporal_layers_supported, - trials, encoder_config.codec_type); + std::vector layers = GetSimulcastConfig( + resolutions, is_legacy_screencast, temporal_layers_supported, trials, + encoder_config.codec_type); // Allow an experiment to override the minimum bitrate for the lowest // spatial layer. The experiment's configuration has the lowest priority. layers[0].min_bitrate_bps = experimental_min_bitrate @@ -339,31 +356,6 @@ EncoderStreamFactory::CreateSimulcastOrConferenceModeScreenshareStreams( webrtc::kDefaultMinVideoBitrateBps)) .bps(); - const bool has_scale_resolution_down_by = absl::c_any_of( - encoder_config.simulcast_layers, [](const webrtc::VideoStream& layer) { - return layer.scale_resolution_down_by != -1.; - }); - - bool default_scale_factors_used = true; - if (has_scale_resolution_down_by) { - default_scale_factors_used = IsScaleFactorsPowerOfTwo(encoder_config); - } - const bool norm_size_configured = - webrtc::NormalizeSimulcastSizeExperiment::GetBase2Exponent(trials) - .has_value(); - const int normalized_width = - (default_scale_factors_used || norm_size_configured) && - (width >= kMinLayerSize) - ? NormalizeSimulcastSize(trials, width, - encoder_config.number_of_streams) - : width; - const int normalized_height = - (default_scale_factors_used || norm_size_configured) && - (height >= kMinLayerSize) - ? NormalizeSimulcastSize(trials, height, - encoder_config.number_of_streams) - : height; - // Update the active simulcast layers and configured bitrates. for (size_t i = 0; i < layers.size(); ++i) { layers[i].active = encoder_config.simulcast_layers[i].active; @@ -381,20 +373,6 @@ EncoderStreamFactory::CreateSimulcastOrConferenceModeScreenshareStreams( layers[i].max_framerate = encoder_config.simulcast_layers[i].max_framerate; } - if (encoder_config.simulcast_layers[i].requested_resolution.has_value()) { - auto res = GetLayerResolutionFromRequestedResolution( - normalized_width, normalized_height, - *encoder_config.simulcast_layers[i].requested_resolution); - layers[i].width = res.width; - layers[i].height = res.height; - } else if (has_scale_resolution_down_by) { - const double scale_resolution_down_by = std::max( - encoder_config.simulcast_layers[i].scale_resolution_down_by, 1.0); - layers[i].width = ScaleDownResolution( - normalized_width, scale_resolution_down_by, kMinLayerSize); - layers[i].height = ScaleDownResolution( - normalized_height, scale_resolution_down_by, kMinLayerSize); - } // Update simulcast bitrates with configured min and max bitrate. if (encoder_config.simulcast_layers[i].min_bitrate_bps > 0) { layers[i].min_bitrate_bps = @@ -516,4 +494,69 @@ EncoderStreamFactory::GetLayerResolutionFromRequestedResolution( return {.width = out_width, .height = out_height}; } +std::vector EncoderStreamFactory::GetStreamResolutions( + const webrtc::FieldTrialsView& trials, + int width, + int height, + const webrtc::VideoEncoderConfig& encoder_config) const { + std::vector resolutions; + if (webrtc::SimulcastUtility::IsConferenceModeScreenshare(encoder_config)) { + for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { + resolutions.push_back({.width = width, .height = height}); + } + } else { + size_t min_num_layers = FindRequiredActiveLayers(encoder_config); + size_t max_num_layers = LimitSimulcastLayerCount( + min_num_layers, encoder_config.number_of_streams, width, height, trials, + encoder_config.codec_type); + RTC_DCHECK_LE(max_num_layers, encoder_config.number_of_streams); + + const bool has_scale_resolution_down_by = absl::c_any_of( + encoder_config.simulcast_layers, [](const webrtc::VideoStream& layer) { + return layer.scale_resolution_down_by != -1.; + }); + + bool default_scale_factors_used = true; + if (has_scale_resolution_down_by) { + default_scale_factors_used = IsScaleFactorsPowerOfTwo(encoder_config); + } + + const bool norm_size_configured = + webrtc::NormalizeSimulcastSizeExperiment::GetBase2Exponent(trials) + .has_value(); + const int normalized_width = + (default_scale_factors_used || norm_size_configured) && + (width >= kMinLayerSize) + ? NormalizeSimulcastSize(trials, width, max_num_layers) + : width; + const int normalized_height = + (default_scale_factors_used || norm_size_configured) && + (height >= kMinLayerSize) + ? NormalizeSimulcastSize(trials, height, max_num_layers) + : height; + + resolutions.resize(max_num_layers); + for (size_t i = 0; i < max_num_layers; i++) { + if (encoder_config.simulcast_layers[i].requested_resolution.has_value()) { + resolutions[i] = GetLayerResolutionFromRequestedResolution( + normalized_width, normalized_height, + *encoder_config.simulcast_layers[i].requested_resolution); + } else if (has_scale_resolution_down_by) { + const double scale_resolution_down_by = std::max( + encoder_config.simulcast_layers[i].scale_resolution_down_by, 1.0); + resolutions[i].width = ScaleDownResolution( + normalized_width, scale_resolution_down_by, kMinLayerSize); + resolutions[i].height = ScaleDownResolution( + normalized_height, scale_resolution_down_by, kMinLayerSize); + } else { + // Resolutions with default 1/2 scale factor, from low to high. + resolutions[i].width = normalized_width >> (max_num_layers - i - 1); + resolutions[i].height = normalized_height >> (max_num_layers - i - 1); + } + } + } + + return resolutions; +} + } // namespace cricket diff --git a/video/config/encoder_stream_factory.h b/video/config/encoder_stream_factory.h index ef27fb87cf..7792a81c6a 100644 --- a/video/config/encoder_stream_factory.h +++ b/video/config/encoder_stream_factory.h @@ -54,6 +54,12 @@ class EncoderStreamFactory int in_frame_height, webrtc::Resolution requested_resolution) const; + std::vector GetStreamResolutions( + const webrtc::FieldTrialsView& trials, + int width, + int height, + const webrtc::VideoEncoderConfig& encoder_config) const; + const int encoder_info_requested_resolution_alignment_; const absl::optional restrictions_; }; diff --git a/video/config/encoder_stream_factory_unittest.cc b/video/config/encoder_stream_factory_unittest.cc index 7daf0b2eea..b20adb5aee 100644 --- a/video/config/encoder_stream_factory_unittest.cc +++ b/video/config/encoder_stream_factory_unittest.cc @@ -10,6 +10,9 @@ #include "video/config/encoder_stream_factory.h" +#include + +#include "api/video_codecs/scalability_mode.h" #include "call/adaptation/video_source_restrictions.h" #include "rtc_base/experiments/min_video_bitrate_experiment.h" #include "test/explicit_key_value_config.h" @@ -20,39 +23,71 @@ namespace webrtc { namespace { using ::cricket::EncoderStreamFactory; using test::ExplicitKeyValueConfig; +using ::testing::Combine; +using ::testing::ElementsAre; using ::testing::IsEmpty; using ::testing::Not; +using ::testing::SizeIs; +using ::testing::Values; + +struct CreateVideoStreamParams { + int width = 0; + int height = 0; + int max_framerate_fps = -1; + int min_bitrate_bps = -1; + int target_bitrate_bps = -1; + int max_bitrate_bps = -1; + int scale_resolution_down_by = -1; + std::optional scalability_mode; +}; + +// A helper function that creates `VideoStream` with given settings. +VideoStream CreateVideoStream(const CreateVideoStreamParams& params) { + VideoStream stream; + stream.width = params.width; + stream.height = params.height; + stream.max_framerate = params.max_framerate_fps; + stream.min_bitrate_bps = params.min_bitrate_bps; + stream.target_bitrate_bps = params.target_bitrate_bps; + stream.max_bitrate_bps = params.max_bitrate_bps; + stream.scale_resolution_down_by = params.scale_resolution_down_by; + stream.scalability_mode = params.scalability_mode; + return stream; +} std::vector GetStreamResolutions( const std::vector& streams) { std::vector res; for (const auto& s : streams) { - if (s.active) { - res.push_back( - {rtc::checked_cast(s.width), rtc::checked_cast(s.height)}); - } + res.push_back( + {rtc::checked_cast(s.width), rtc::checked_cast(s.height)}); } return res; } -VideoStream LayerWithRequestedResolution(Resolution res) { - VideoStream s; - s.requested_resolution = res; - return s; +std::vector CreateEncoderStreams( + const FieldTrialsView& field_trials, + const Resolution& resolution, + const VideoEncoderConfig& encoder_config, + absl::optional restrictions = absl::nullopt) { + VideoEncoder::EncoderInfo encoder_info; + auto factory = + rtc::make_ref_counted(encoder_info, restrictions); + return factory->CreateEncoderStreams(field_trials, resolution.width, + resolution.height, encoder_config); } } // namespace TEST(EncoderStreamFactory, SinglecastRequestedResolution) { ExplicitKeyValueConfig field_trials(""); - VideoEncoder::EncoderInfo encoder_info; - auto factory = rtc::make_ref_counted(encoder_info); VideoEncoderConfig encoder_config; encoder_config.number_of_streams = 1; - encoder_config.simulcast_layers.push_back( - LayerWithRequestedResolution({.width = 640, .height = 360})); - auto streams = - factory->CreateEncoderStreams(field_trials, 1280, 720, encoder_config); + encoder_config.simulcast_layers.resize(1); + encoder_config.simulcast_layers[0].requested_resolution = {.width = 640, + .height = 360}; + auto streams = CreateEncoderStreams( + field_trials, {.width = 1280, .height = 720}, encoder_config); EXPECT_EQ(streams[0].requested_resolution, (Resolution{.width = 640, .height = 360})); EXPECT_EQ(GetStreamResolutions(streams), (std::vector{ @@ -66,15 +101,14 @@ TEST(EncoderStreamFactory, SinglecastRequestedResolutionWithAdaptation) { /* max_pixels_per_frame= */ (320 * 320), /* target_pixels_per_frame= */ absl::nullopt, /* max_frame_rate= */ absl::nullopt); - VideoEncoder::EncoderInfo encoder_info; - auto factory = - rtc::make_ref_counted(encoder_info, restrictions); VideoEncoderConfig encoder_config; encoder_config.number_of_streams = 1; - encoder_config.simulcast_layers.push_back( - LayerWithRequestedResolution({.width = 640, .height = 360})); + encoder_config.simulcast_layers.resize(1); + encoder_config.simulcast_layers[0].requested_resolution = {.width = 640, + .height = 360}; auto streams = - factory->CreateEncoderStreams(field_trials, 1280, 720, encoder_config); + CreateEncoderStreams(field_trials, {.width = 1280, .height = 720}, + encoder_config, restrictions); EXPECT_EQ(streams[0].requested_resolution, (Resolution{.width = 640, .height = 360})); EXPECT_EQ(GetStreamResolutions(streams), (std::vector{ @@ -84,18 +118,14 @@ TEST(EncoderStreamFactory, SinglecastRequestedResolutionWithAdaptation) { TEST(EncoderStreamFactory, BitratePriority) { constexpr double kBitratePriority = 0.123; - ExplicitKeyValueConfig field_trials(""); - VideoEncoder::EncoderInfo encoder_info; - auto factory = rtc::make_ref_counted(encoder_info); VideoEncoderConfig encoder_config; encoder_config.number_of_streams = 2; + encoder_config.simulcast_layers.resize(encoder_config.number_of_streams); encoder_config.bitrate_priority = kBitratePriority; - encoder_config.simulcast_layers = { - LayerWithRequestedResolution({.width = 320, .height = 180}), - LayerWithRequestedResolution({.width = 640, .height = 360})}; - auto streams = - factory->CreateEncoderStreams(field_trials, 640, 360, encoder_config); - ASSERT_EQ(streams.size(), 2u); + auto streams = CreateEncoderStreams( + /*field_trials=*/ExplicitKeyValueConfig(""), + {.width = 640, .height = 360}, encoder_config); + ASSERT_THAT(streams, SizeIs(2)); EXPECT_EQ(streams[0].bitrate_priority, kBitratePriority); EXPECT_FALSE(streams[1].bitrate_priority); } @@ -125,4 +155,182 @@ TEST(EncoderStreamFactory, SetsMinBitrateToExperimentalValue) { EXPECT_NE(streams[0].min_bitrate_bps, kDefaultMinVideoBitrateBps); EXPECT_EQ(streams[0].min_bitrate_bps, 1000); } + +struct StreamResolutionTestParams { + absl::string_view field_trials; + size_t number_of_streams = 1; + Resolution resolution = {.width = 640, .height = 480}; + bool is_legacy_screencast = false; + size_t first_active_layer_idx = 0; +}; + +std::vector CreateStreamResolutions( + const StreamResolutionTestParams& test_params) { + VideoEncoderConfig encoder_config; + encoder_config.codec_type = VideoCodecType::kVideoCodecVP8; + encoder_config.number_of_streams = test_params.number_of_streams; + encoder_config.simulcast_layers.resize(test_params.number_of_streams); + for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { + encoder_config.simulcast_layers[i].active = + (i >= test_params.first_active_layer_idx); + } + if (test_params.is_legacy_screencast) { + encoder_config.content_type = VideoEncoderConfig::ContentType::kScreen; + encoder_config.legacy_conference_mode = true; + } + return GetStreamResolutions( + CreateEncoderStreams(ExplicitKeyValueConfig(test_params.field_trials), + test_params.resolution, encoder_config)); +} + +TEST(EncoderStreamFactory, KeepsResolutionUnchangedWhenAligned) { + EXPECT_THAT( + CreateStreamResolutions({.number_of_streams = 2, + .resolution = {.width = 516, .height = 526}}), + ElementsAre(Resolution{.width = 516 / 2, .height = 526 / 2}, + Resolution{.width = 516, .height = 526})); +} + +TEST(EncoderStreamFactory, AdjustsResolutionWhenUnaligned) { + // By default width and height of the smallest simulcast stream are required + // to be whole numbers. To achieve that, the resolution of the highest + // simulcast stream is adjusted to be multiple of (2 ^ (number_of_streams - + // 1)) by rounding down. + EXPECT_THAT( + CreateStreamResolutions({.number_of_streams = 2, + .resolution = {.width = 515, .height = 517}}), + ElementsAre(Resolution{.width = 514 / 2, .height = 516 / 2}, + Resolution{.width = 514, .height = 516})); +} + +TEST(EncoderStreamFactory, MakesResolutionDivisibleBy4) { + EXPECT_THAT( + CreateStreamResolutions( + {.field_trials = "WebRTC-NormalizeSimulcastResolution/Enabled-2/", + .number_of_streams = 2, + .resolution = {.width = 515, .height = 517}}), + ElementsAre(Resolution{.width = 512 / 2, .height = 516 / 2}, + Resolution{.width = 512, .height = 516})); +} + +TEST(EncoderStreamFactory, KeepsStreamCountUnchangedWhenResolutionIsHigh) { + EXPECT_THAT( + CreateStreamResolutions({.number_of_streams = 3, + .resolution = {.width = 1000, .height = 1000}}), + SizeIs(3)); +} + +TEST(EncoderStreamFactory, ReducesStreamCountWhenResolutionIsLow) { + EXPECT_THAT( + CreateStreamResolutions({.number_of_streams = 3, + .resolution = {.width = 100, .height = 100}}), + SizeIs(1)); +} + +TEST(EncoderStreamFactory, ReducesStreamCountDownToFirstActiveStream) { + EXPECT_THAT( + CreateStreamResolutions({.number_of_streams = 3, + .resolution = {.width = 100, .height = 100}, + .first_active_layer_idx = 1}), + SizeIs(2)); +} + +TEST(EncoderStreamFactory, + ReducesLegacyScreencastStreamCountWhenResolutionIsLow) { + // At least 2 streams are expected to be configured in legacy screencast mode. + EXPECT_THAT( + CreateStreamResolutions({.number_of_streams = 3, + .resolution = {.width = 100, .height = 100}, + .is_legacy_screencast = true}), + SizeIs(2)); +} + +TEST(EncoderStreamFactory, KeepsStreamCountUnchangedWhenLegacyLimitIsDisabled) { + EXPECT_THAT(CreateStreamResolutions( + {.field_trials = "WebRTC-LegacySimulcastLayerLimit/Disabled/", + .number_of_streams = 3, + .resolution = {.width = 100, .height = 100}}), + SizeIs(3)); +} + +TEST(EncoderStreamFactory, KeepsHighResolutionWhenStreamCountIsReduced) { + EXPECT_THAT( + CreateStreamResolutions({.number_of_streams = 3, + .resolution = {.width = 640, .height = 360}}), + ElementsAre(Resolution{.width = 320, .height = 180}, + Resolution{.width = 640, .height = 360})); +} + +struct OverrideStreamSettingsTestParams { + std::string field_trials; + Resolution input_resolution; + VideoEncoderConfig::ContentType content_type; + std::vector requested_streams; + std::vector expected_streams; +}; + +class EncoderStreamFactoryOverrideStreamSettinsTest + : public ::testing::TestWithParam< + std::tuple> {}; + +TEST_P(EncoderStreamFactoryOverrideStreamSettinsTest, OverrideStreamSettings) { + OverrideStreamSettingsTestParams test_params = std::get<0>(GetParam()); + VideoEncoderConfig encoder_config; + encoder_config.codec_type = std::get<1>(GetParam()); + encoder_config.number_of_streams = test_params.requested_streams.size(); + encoder_config.simulcast_layers = test_params.requested_streams; + encoder_config.content_type = test_params.content_type; + auto streams = + CreateEncoderStreams(ExplicitKeyValueConfig(test_params.field_trials), + test_params.input_resolution, encoder_config); + ASSERT_EQ(streams.size(), test_params.expected_streams.size()); + for (size_t i = 0; i < streams.size(); ++i) { + SCOPED_TRACE(i); + const VideoStream& expected = test_params.expected_streams[i]; + EXPECT_EQ(streams[i].width, expected.width); + EXPECT_EQ(streams[i].height, expected.height); + EXPECT_EQ(streams[i].max_framerate, expected.max_framerate); + EXPECT_EQ(streams[i].min_bitrate_bps, expected.min_bitrate_bps); + EXPECT_EQ(streams[i].target_bitrate_bps, expected.target_bitrate_bps); + EXPECT_EQ(streams[i].max_bitrate_bps, expected.max_bitrate_bps); + EXPECT_EQ(streams[i].scalability_mode, expected.scalability_mode); + } +} + +INSTANTIATE_TEST_SUITE_P( + Screencast, + EncoderStreamFactoryOverrideStreamSettinsTest, + Combine(Values(OverrideStreamSettingsTestParams{ + .input_resolution = {.width = 1920, .height = 1080}, + .content_type = VideoEncoderConfig::ContentType::kScreen, + .requested_streams = + {CreateVideoStream( + {.max_framerate_fps = 5, + .max_bitrate_bps = 420'000, + .scale_resolution_down_by = 1, + .scalability_mode = ScalabilityMode::kL1T2}), + CreateVideoStream( + {.max_framerate_fps = 30, + .max_bitrate_bps = 2'500'000, + .scale_resolution_down_by = 1, + .scalability_mode = ScalabilityMode::kL1T2})}, + .expected_streams = + {CreateVideoStream( + {.width = 1920, + .height = 1080, + .max_framerate_fps = 5, + .min_bitrate_bps = 30'000, + .target_bitrate_bps = 420'000, + .max_bitrate_bps = 420'000, + .scalability_mode = ScalabilityMode::kL1T2}), + CreateVideoStream( + {.width = 1920, + .height = 1080, + .max_framerate_fps = 30, + .min_bitrate_bps = 800'000, + .target_bitrate_bps = 2'500'000, + .max_bitrate_bps = 2'500'000, + .scalability_mode = ScalabilityMode::kL1T2})}}), + Values(VideoCodecType::kVideoCodecVP8, + VideoCodecType::kVideoCodecAV1))); } // namespace webrtc diff --git a/video/config/simulcast.cc b/video/config/simulcast.cc index 809845762d..a655c2e9d6 100644 --- a/video/config/simulcast.cc +++ b/video/config/simulcast.cc @@ -229,31 +229,25 @@ SimulcastFormat InterpolateSimulcastFormat( } std::vector GetNormalSimulcastLayers( - size_t layer_count, - int width, - int height, + rtc::ArrayView resolutions, bool temporal_layers_supported, bool base_heavy_tl3_rate_alloc, const webrtc::FieldTrialsView& trials, webrtc::VideoCodecType codec) { - std::vector layers(layer_count); const bool enable_lowres_bitrate_interpolation = EnableLowresBitrateInterpolation(trials); const int num_temporal_layers = temporal_layers_supported ? kDefaultNumTemporalLayers : 1; - // Format width and height has to be divisible by |2 ^ num_simulcast_layers - - // 1|. - width = NormalizeSimulcastSize(trials, width, layer_count); - height = NormalizeSimulcastSize(trials, height, layer_count); // Add simulcast streams, from highest resolution (`s` = num_simulcast_layers // -1) to lowest resolution at `s` = 0. - for (size_t s = layer_count - 1;; --s) { - layers[s].width = width; - layers[s].height = height; + std::vector layers(resolutions.size()); + for (size_t s = 0; s < resolutions.size(); ++s) { + layers[s].width = resolutions[s].width; + layers[s].height = resolutions[s].height; layers[s].num_temporal_layers = num_temporal_layers; SimulcastFormat interpolated_format = InterpolateSimulcastFormat( - width, height, /*max_roundup_rate=*/absl::nullopt, + layers[s].width, layers[s].height, /*max_roundup_rate=*/absl::nullopt, enable_lowres_bitrate_interpolation, codec); layers[s].max_bitrate_bps = interpolated_format.max_bitrate.bps(); @@ -293,13 +287,6 @@ std::vector GetNormalSimulcastLayers( std::max(layers[s].min_bitrate_bps, layers[s].target_bitrate_bps); layers[s].max_framerate = kDefaultVideoMaxFramerate; - - width /= 2; - height /= 2; - - if (s == 0) { - break; - } } return layers; @@ -362,10 +349,12 @@ std::vector GetScreenshareLayers( return layers; } -size_t LimitSimulcastLayerCount(int width, +} // namespace + +size_t LimitSimulcastLayerCount(size_t min_num_layers, + size_t max_num_layers, + int width, int height, - size_t need_layers, - size_t layer_count, const webrtc::FieldTrialsView& trials, webrtc::VideoCodecType codec) { if (!absl::StartsWith(trials.Lookup(kUseLegacySimulcastLayerLimitFieldTrial), @@ -378,36 +367,19 @@ size_t LimitSimulcastLayerCount(int width, webrtc::ParseFieldTrial({&max_ratio}, trials.Lookup("WebRTC-SimulcastLayerLimitRoundUp")); - size_t adaptive_layer_count = std::max( - need_layers, InterpolateSimulcastFormat( - width, height, max_ratio.GetOptional(), - /*enable_lowres_bitrate_interpolation=*/false, codec) - .max_layers); - if (layer_count > adaptive_layer_count) { + size_t reduced_num_layers = + std::max(min_num_layers, + InterpolateSimulcastFormat( + width, height, max_ratio.GetOptional(), + /*enable_lowres_bitrate_interpolation=*/false, codec) + .max_layers); + if (max_num_layers > reduced_num_layers) { RTC_LOG(LS_WARNING) << "Reducing simulcast layer count from " - << layer_count << " to " << adaptive_layer_count; - layer_count = adaptive_layer_count; + << max_num_layers << " to " << reduced_num_layers; + return reduced_num_layers; } } - return layer_count; -} - -} // namespace - -// Round size to nearest simulcast-friendly size. -// Simulcast stream width and height must both be dividable by -// |2 ^ (simulcast_layers - 1)|. -int NormalizeSimulcastSize(const FieldTrialsView& field_trials, - int size, - size_t simulcast_layers) { - int base2_exponent = static_cast(simulcast_layers) - 1; - const absl::optional experimental_base2_exponent = - webrtc::NormalizeSimulcastSizeExperiment::GetBase2Exponent(field_trials); - if (experimental_base2_exponent && - (size > (1 << *experimental_base2_exponent))) { - base2_exponent = *experimental_base2_exponent; - } - return ((size >> base2_exponent) << base2_exponent); + return max_num_layers; } void BoostMaxSimulcastLayer(webrtc::DataRate max_bitrate, @@ -439,32 +411,21 @@ webrtc::DataRate GetTotalMaxBitrate( } std::vector GetSimulcastConfig( - size_t min_layers, - size_t max_layers, - int width, - int height, + rtc::ArrayView resolutions, bool is_screenshare_with_conference_mode, bool temporal_layers_supported, const webrtc::FieldTrialsView& trials, webrtc::VideoCodecType codec) { - RTC_DCHECK_LE(min_layers, max_layers); - RTC_DCHECK(max_layers > 1 || is_screenshare_with_conference_mode); + RTC_DCHECK(!resolutions.empty()); const bool base_heavy_tl3_rate_alloc = webrtc::RateControlSettings(trials).Vp8BaseHeavyTl3RateAllocation(); if (is_screenshare_with_conference_mode) { - return GetScreenshareLayers(max_layers, width, height, - temporal_layers_supported, - base_heavy_tl3_rate_alloc, trials); + return GetScreenshareLayers( + resolutions.size(), resolutions[0].width, resolutions[0].height, + temporal_layers_supported, base_heavy_tl3_rate_alloc, trials); } else { - // Some applications rely on the old behavior limiting the simulcast layer - // count based on the resolution automatically, which they can get through - // the WebRTC-LegacySimulcastLayerLimit field trial until they update. - max_layers = LimitSimulcastLayerCount(width, height, min_layers, max_layers, - trials, codec); - - return GetNormalSimulcastLayers(max_layers, width, height, - temporal_layers_supported, + return GetNormalSimulcastLayers(resolutions, temporal_layers_supported, base_heavy_tl3_rate_alloc, trials, codec); } } diff --git a/video/config/simulcast.h b/video/config/simulcast.h index 480cf4bb78..75b4e28417 100644 --- a/video/config/simulcast.h +++ b/video/config/simulcast.h @@ -15,8 +15,10 @@ #include +#include "api/array_view.h" #include "api/field_trials_view.h" #include "api/units/data_rate.h" +#include "api/video/resolution.h" #include "video/config/video_encoder_config.h" namespace cricket { @@ -30,17 +32,19 @@ webrtc::DataRate GetTotalMaxBitrate( void BoostMaxSimulcastLayer(webrtc::DataRate max_bitrate, std::vector* layers); -// Round size to nearest simulcast-friendly size -int NormalizeSimulcastSize(const webrtc::FieldTrialsView& field_trials, - int size, - size_t simulcast_layers); +// Returns number of simulcast streams. The value depends on the resolution and +// is restricted to the range from `min_num_layers` to `max_num_layers`, +// inclusive. +size_t LimitSimulcastLayerCount(size_t min_num_layers, + size_t max_num_layers, + int width, + int height, + const webrtc::FieldTrialsView& trials, + webrtc::VideoCodecType codec); // Gets simulcast settings. std::vector GetSimulcastConfig( - size_t min_layers, - size_t max_layers, - int width, - int height, + rtc::ArrayView resolutions, bool is_screenshare_with_conference_mode, bool temporal_layers_supported, const webrtc::FieldTrialsView& trials, diff --git a/video/config/simulcast_unittest.cc b/video/config/simulcast_unittest.cc index 87859208e7..3dd4417374 100644 --- a/video/config/simulcast_unittest.cc +++ b/video/config/simulcast_unittest.cc @@ -12,12 +12,13 @@ #include "media/base/media_constants.h" #include "test/explicit_key_value_config.h" +#include "test/gmock.h" #include "test/gtest.h" namespace webrtc { namespace { - using test::ExplicitKeyValueConfig; +using ::testing::SizeIs; constexpr bool kScreenshare = true; constexpr int kDefaultTemporalLayers = 3; // Value from simulcast.cc. @@ -36,6 +37,19 @@ const std::vector GetSimulcastBitrates720p() { streams[2].max_bitrate_bps = 2500000; return streams; } + +// Creates a vector of resolutions scaled down with 1/2 factor ordered from low +// to high. +std::vector CreateResolutions(int max_width, + int max_height, + int num_streams) { + std::vector resolutions(num_streams); + for (int i = 0; i < num_streams; ++i) { + resolutions[i].width = max_width >> (num_streams - i - 1); + resolutions[i].height = max_height >> (num_streams - i - 1); + } + return resolutions; +} } // namespace TEST(SimulcastTest, TotalMaxBitrateIsZeroForNoStreams) { @@ -83,13 +97,12 @@ TEST(SimulcastTest, GetConfig) { const std::vector kExpected = GetSimulcastBitrates720p(); - const size_t kMinLayers = 1; const size_t kMaxLayers = 3; std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 1280, 720, !kScreenshare, true, trials, + CreateResolutions(1280, 720, kMaxLayers), !kScreenshare, true, trials, webrtc::kVideoCodecVP8); - EXPECT_EQ(kMaxLayers, streams.size()); + ASSERT_THAT(streams, SizeIs(kMaxLayers)); EXPECT_EQ(320u, streams[0].width); EXPECT_EQ(180u, streams[0].height); EXPECT_EQ(640u, streams[1].width); @@ -114,12 +127,12 @@ TEST(SimulcastTest, GetConfigWithBaseHeavyVP8TL3RateAllocation) { const std::vector kExpected = GetSimulcastBitrates720p(); - const size_t kMinLayers = 1; const size_t kMaxLayers = 3; std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 1280, 720, !kScreenshare, true, trials, + CreateResolutions(1280, 720, kMaxLayers), !kScreenshare, true, trials, webrtc::kVideoCodecVP8); + ASSERT_THAT(streams, SizeIs(kMaxLayers)); EXPECT_EQ(kExpected[0].min_bitrate_bps, streams[0].min_bitrate_bps); EXPECT_EQ(static_cast(0.4 * kExpected[0].target_bitrate_bps / 0.6), streams[0].target_bitrate_bps); @@ -135,176 +148,27 @@ TEST(SimulcastTest, GetConfigWithBaseHeavyVP8TL3RateAllocation) { TEST(SimulcastTest, GetConfigWithLimitedMaxLayers) { ExplicitKeyValueConfig trials(""); - const size_t kMinLayers = 1; const size_t kMaxLayers = 2; std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 1280, 720, !kScreenshare, true, trials, + CreateResolutions(1280, 720, kMaxLayers), !kScreenshare, true, trials, webrtc::kVideoCodecVP8); - EXPECT_EQ(kMaxLayers, streams.size()); + ASSERT_THAT(streams, SizeIs(kMaxLayers)); EXPECT_EQ(640u, streams[0].width); EXPECT_EQ(360u, streams[0].height); EXPECT_EQ(1280u, streams[1].width); EXPECT_EQ(720u, streams[1].height); } -TEST(SimulcastTest, GetConfigWithLimitedMaxLayersForResolution) { - ExplicitKeyValueConfig trials( - "WebRTC-LegacySimulcastLayerLimit/Enabled/"); - - const size_t kMinLayers = 1; - const size_t kMaxLayers = 3; - std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 800, 600, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - - EXPECT_EQ(2u, streams.size()); - EXPECT_EQ(400u, streams[0].width); - EXPECT_EQ(300u, streams[0].height); - EXPECT_EQ(800u, streams[1].width); - EXPECT_EQ(600u, streams[1].height); -} - -TEST(SimulcastTest, GetConfigWithLowResolutionScreenshare) { - ExplicitKeyValueConfig trials( - "WebRTC-LegacySimulcastLayerLimit/Enabled/"); - - const size_t kMinLayers = 1; - const size_t kMaxLayers = 3; - std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 100, 100, kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - - // Simulcast streams number is never decreased for screenshare, - // even for very low resolution. - EXPECT_GT(streams.size(), 1u); -} - -TEST(SimulcastTest, GetConfigWithNotLimitedMaxLayersForResolution) { - ExplicitKeyValueConfig trials( - "WebRTC-LegacySimulcastLayerLimit/Disabled/"); - - const size_t kMinLayers = 1; - const size_t kMaxLayers = 3; - std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 800, 600, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - - EXPECT_EQ(kMaxLayers, streams.size()); - EXPECT_EQ(200u, streams[0].width); - EXPECT_EQ(150u, streams[0].height); - EXPECT_EQ(400u, streams[1].width); - EXPECT_EQ(300u, streams[1].height); - EXPECT_EQ(800u, streams[2].width); - EXPECT_EQ(600u, streams[2].height); -} - -TEST(SimulcastTest, GetConfigWithNormalizedResolution) { - ExplicitKeyValueConfig trials(""); - - const size_t kMinLayers = 1; - const size_t kMaxLayers = 2; - std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 640 + 1, 360 + 1, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - - // Must be divisible by |2 ^ (num_layers - 1)|. - EXPECT_EQ(kMaxLayers, streams.size()); - EXPECT_EQ(320u, streams[0].width); - EXPECT_EQ(180u, streams[0].height); - EXPECT_EQ(640u, streams[1].width); - EXPECT_EQ(360u, streams[1].height); -} - -TEST(SimulcastTest, GetConfigWithNormalizedResolutionDivisibleBy4) { - ExplicitKeyValueConfig trials( - "WebRTC-NormalizeSimulcastResolution/Enabled-2/"); - - const size_t kMinLayers = 1; - const size_t kMaxLayers = 2; - std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 709, 501, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - - // Must be divisible by |2 ^ 2|. - EXPECT_EQ(kMaxLayers, streams.size()); - EXPECT_EQ(354u, streams[0].width); - EXPECT_EQ(250u, streams[0].height); - EXPECT_EQ(708u, streams[1].width); - EXPECT_EQ(500u, streams[1].height); -} - -TEST(SimulcastTest, GetConfigWithNormalizedResolutionDivisibleBy8) { - ExplicitKeyValueConfig trials( - "WebRTC-NormalizeSimulcastResolution/Enabled-3/"); - - const size_t kMinLayers = 1; - const size_t kMaxLayers = 2; - std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 709, 501, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - - // Must be divisible by |2 ^ 3|. - EXPECT_EQ(kMaxLayers, streams.size()); - EXPECT_EQ(352u, streams[0].width); - EXPECT_EQ(248u, streams[0].height); - EXPECT_EQ(704u, streams[1].width); - EXPECT_EQ(496u, streams[1].height); -} - -TEST(SimulcastTest, GetConfigForLegacyLayerLimit) { - ExplicitKeyValueConfig trials( - "WebRTC-LegacySimulcastLayerLimit/Enabled/"); - - const size_t kMinLayers = 1; - const int kMaxLayers = 3; - std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 320, 180, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(1u, streams.size()); - - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 640, 360, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(2u, streams.size()); - - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 1920, 1080, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(3u, streams.size()); -} - -TEST(SimulcastTest, GetConfigForLegacyLayerLimitWithRequiredHD) { - ExplicitKeyValueConfig trials( - "WebRTC-LegacySimulcastLayerLimit/Enabled/"); - - const size_t kMinLayers = 3; // "HD" layer must be present! - const int kMaxLayers = 3; - std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 320, 180, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(3u, streams.size()); - - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 640, 360, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(3u, streams.size()); - - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 1920, 1080, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(3u, streams.size()); -} - TEST(SimulcastTest, GetConfigForScreenshareSimulcast) { ExplicitKeyValueConfig trials(""); - const size_t kMinLayers = 1; - const size_t kMaxLayers = 3; std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 1400, 800, kScreenshare, true, trials, - webrtc::kVideoCodecVP8); + std::vector{{.width = 1400, .height = 800}, + {.width = 1400, .height = 800}, + {.width = 1400, .height = 800}}, + kScreenshare, true, trials, webrtc::kVideoCodecVP8); - EXPECT_GT(streams.size(), 1u); + EXPECT_THAT(streams, SizeIs(2)); for (size_t i = 0; i < streams.size(); ++i) { EXPECT_EQ(1400u, streams[i].width) << "Screen content never scaled."; EXPECT_EQ(800u, streams[i].height) << "Screen content never scaled."; @@ -320,35 +184,29 @@ TEST(SimulcastTest, GetConfigForScreenshareSimulcast) { TEST(SimulcastTest, GetConfigForScreenshareSimulcastWithLimitedMaxLayers) { ExplicitKeyValueConfig trials(""); - const size_t kMinLayers = 1; - const size_t kMaxLayers = 1; std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 1400, 800, kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - - EXPECT_EQ(kMaxLayers, streams.size()); + std::vector{{.width = 1400, .height = 800}}, kScreenshare, + true, trials, webrtc::kVideoCodecVP8); + EXPECT_THAT(streams, SizeIs(1)); } TEST(SimulcastTest, AveragesBitratesForNonStandardResolution) { ExplicitKeyValueConfig trials(""); - const size_t kMinLayers = 1; - const size_t kMaxLayers = 3; std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, 900, 800, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); + std::vector{{.width = 900, .height = 800}}, !kScreenshare, + true, trials, webrtc::kVideoCodecVP8); - EXPECT_EQ(kMaxLayers, streams.size()); - EXPECT_EQ(900u, streams[2].width); - EXPECT_EQ(800u, streams[2].height); - EXPECT_EQ(1850000, streams[2].max_bitrate_bps); - EXPECT_EQ(1850000, streams[2].target_bitrate_bps); - EXPECT_EQ(475000, streams[2].min_bitrate_bps); + ASSERT_THAT(streams, SizeIs(1)); + EXPECT_EQ(900u, streams[0].width); + EXPECT_EQ(800u, streams[0].height); + EXPECT_EQ(1850000, streams[0].max_bitrate_bps); + EXPECT_EQ(1850000, streams[0].target_bitrate_bps); + EXPECT_EQ(475000, streams[0].min_bitrate_bps); } TEST(SimulcastTest, BitratesForCloseToStandardResolution) { ExplicitKeyValueConfig trials(""); - const size_t kMinLayers = 1; const size_t kMaxLayers = 3; // Resolution very close to 720p in number of pixels const size_t kWidth = 1280; @@ -356,10 +214,10 @@ TEST(SimulcastTest, BitratesForCloseToStandardResolution) { const std::vector kExpectedNear = GetSimulcastBitrates720p(); std::vector streams = cricket::GetSimulcastConfig( - kMinLayers, kMaxLayers, kWidth, kHeight, !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); + CreateResolutions(kWidth, kHeight, kMaxLayers), !kScreenshare, true, + trials, webrtc::kVideoCodecVP8); - EXPECT_EQ(kMaxLayers, streams.size()); + ASSERT_THAT(streams, SizeIs(kMaxLayers)); EXPECT_EQ(kWidth, streams[2].width); EXPECT_EQ(kHeight, streams[2].height); for (size_t i = 0; i < streams.size(); ++i) { @@ -379,25 +237,20 @@ TEST(SimulcastTest, MaxLayersWithRoundUpDisabled) { const size_t kMinLayers = 1; const int kMaxLayers = 3; - std::vector streams; - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 540, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(3u, streams.size()); + size_t num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 960, 540, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 3u); // <960x540: 2 layers - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 539, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(2u, streams.size()); - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 270, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(2u, streams.size()); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 960, 539, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 2u); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 480, 270, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 2u); // <480x270: 1 layer - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 269, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(1u, streams.size()); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 480, 269, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 1u); } TEST(SimulcastTest, MaxLayersWithDefaultRoundUpRatio) { @@ -406,33 +259,26 @@ TEST(SimulcastTest, MaxLayersWithDefaultRoundUpRatio) { const size_t kMinLayers = 1; const int kMaxLayers = 3; - std::vector streams; - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 540, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(3u, streams.size()); + size_t num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 960, 540, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 3u); // Lowest cropped height where max layers from higher resolution is used. - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 512, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(3u, streams.size()); - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 508, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(2u, streams.size()); - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 270, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(2u, streams.size()); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 960, 512, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 3u); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 960, 508, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 2u); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 480, 270, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 2u); // Lowest cropped height where max layers from higher resolution is used. - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 256, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(2u, streams.size()); - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 254, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(1u, streams.size()); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 480, 256, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 2u); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 480, 254, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 1u); } TEST(SimulcastTest, MaxLayersWithRoundUpRatio) { @@ -442,20 +288,16 @@ TEST(SimulcastTest, MaxLayersWithRoundUpRatio) { const size_t kMinLayers = 1; const int kMaxLayers = 3; - std::vector streams; - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 270, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(2u, streams.size()); + size_t num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 480, 270, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 2u); // Lowest cropped height where max layers from higher resolution is used. - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 252, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(2u, streams.size()); - streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 250, - !kScreenshare, true, trials, - webrtc::kVideoCodecVP8); - EXPECT_EQ(1u, streams.size()); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 480, 252, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 2u); + num_layers = cricket::LimitSimulcastLayerCount( + kMinLayers, kMaxLayers, 480, 250, trials, webrtc::kVideoCodecVP8); + EXPECT_EQ(num_layers, 1u); } TEST(SimulcastTest, BitratesInterpolatedForResBelow180p) { @@ -465,10 +307,10 @@ TEST(SimulcastTest, BitratesInterpolatedForResBelow180p) { const size_t kMaxLayers = 3; std::vector streams = cricket::GetSimulcastConfig( - /* min_layers = */ 1, kMaxLayers, /* width = */ 960, /* height = */ 540, + CreateResolutions(/*max_width=*/960, /*max_height=*/540, kMaxLayers), !kScreenshare, true, trials, webrtc::kVideoCodecVP8); - ASSERT_EQ(streams.size(), kMaxLayers); + ASSERT_THAT(streams, SizeIs(kMaxLayers)); EXPECT_EQ(240u, streams[0].width); EXPECT_EQ(135u, streams[0].height); EXPECT_EQ(streams[0].max_bitrate_bps, 112500); @@ -482,10 +324,10 @@ TEST(SimulcastTest, BitratesConsistentForVerySmallRes) { "WebRTC-LowresSimulcastBitrateInterpolation/Enabled/"); std::vector streams = cricket::GetSimulcastConfig( - /* min_layers = */ 1, /* max_layers = */ 3, /* width = */ 1, - /* height = */ 1, !kScreenshare, true, trials, webrtc::kVideoCodecVP8); + std::vector{{.width = 1, .height = 1}}, !kScreenshare, true, + trials, webrtc::kVideoCodecVP8); - ASSERT_TRUE(!streams.empty()); + ASSERT_THAT(streams, SizeIs(1)); EXPECT_EQ(1u, streams[0].width); EXPECT_EQ(1u, streams[0].height); EXPECT_EQ(streams[0].max_bitrate_bps, 30000); @@ -500,10 +342,10 @@ TEST(SimulcastTest, const size_t kMaxLayers = 3; std::vector streams = cricket::GetSimulcastConfig( - /* min_layers = */ 1, kMaxLayers, /* width = */ 960, /* height = */ 540, + CreateResolutions(/*max_width=*/960, /*max_height=*/540, kMaxLayers), !kScreenshare, true, trials, webrtc::kVideoCodecVP8); - ASSERT_EQ(streams.size(), kMaxLayers); + ASSERT_THAT(streams, SizeIs(kMaxLayers)); EXPECT_EQ(240u, streams[0].width); EXPECT_EQ(135u, streams[0].height); EXPECT_EQ(streams[0].max_bitrate_bps, 200000); @@ -516,15 +358,15 @@ TEST(SimulcastTest, BitratesBasedOnCodec) { const size_t kMaxLayers = 3; std::vector streams_vp8 = cricket::GetSimulcastConfig( - /* min_layers = */ 1, /* max_layers = */ 3, /* width = */ 1280, - /* height = */ 720, !kScreenshare, true, trials, webrtc::kVideoCodecVP8); + CreateResolutions(/*max_width=*/1280, /*max_height=*/720, kMaxLayers), + !kScreenshare, true, trials, webrtc::kVideoCodecVP8); std::vector streams_vp9 = cricket::GetSimulcastConfig( - /* min_layers = */ 1, /* max_layers = */ 3, /* width = */ 1280, - /* height = */ 720, !kScreenshare, true, trials, webrtc::kVideoCodecVP9); + CreateResolutions(/*max_width=*/1280, /*max_height=*/720, kMaxLayers), + !kScreenshare, true, trials, webrtc::kVideoCodecVP9); - ASSERT_EQ(streams_vp8.size(), kMaxLayers); - ASSERT_EQ(streams_vp9.size(), kMaxLayers); + ASSERT_THAT(streams_vp8, SizeIs(kMaxLayers)); + ASSERT_THAT(streams_vp9, SizeIs(kMaxLayers)); EXPECT_EQ(streams_vp9[0].width, streams_vp8[0].width); EXPECT_EQ(streams_vp9[0].height, streams_vp8[0].height); @@ -548,12 +390,11 @@ TEST(SimulcastTest, BitratesForVP9) { ExplicitKeyValueConfig trials(""); const size_t kMaxLayers = 3; - std::vector streams = cricket::GetSimulcastConfig( - /* min_layers = */ 1, kMaxLayers, /* width = */ 1280, /* height = */ 720, + CreateResolutions(/*max_width=*/1280, /*max_height=*/720, kMaxLayers), !kScreenshare, true, trials, webrtc::kVideoCodecVP9); - ASSERT_EQ(streams.size(), kMaxLayers); + ASSERT_THAT(streams, SizeIs(kMaxLayers)); EXPECT_EQ(1280u, streams[2].width); EXPECT_EQ(720u, streams[2].height); EXPECT_EQ(streams[2].max_bitrate_bps, 1524000); @@ -561,10 +402,10 @@ TEST(SimulcastTest, BitratesForVP9) { EXPECT_EQ(streams[2].min_bitrate_bps, 481000); streams = cricket::GetSimulcastConfig( - /* min_layers = */ 1, kMaxLayers, /* width = */ 1276, /* height = */ 716, + CreateResolutions(/*max_width=*/1276, /*max_height=*/716, kMaxLayers), !kScreenshare, true, trials, webrtc::kVideoCodecVP9); - ASSERT_EQ(streams.size(), kMaxLayers); + ASSERT_THAT(streams, SizeIs(kMaxLayers)); EXPECT_EQ(1276u, streams[2].width); EXPECT_EQ(716u, streams[2].height); EXPECT_NEAR(streams[2].max_bitrate_bps, 1524000, 20000); diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc index ac4aa3ef0c..9aade033d2 100644 --- a/video/video_stream_encoder_unittest.cc +++ b/video/video_stream_encoder_unittest.cc @@ -2666,9 +2666,11 @@ TEST_P(ResolutionAlignmentTest, SinkWantsAlignmentApplied) { config.video_stream_factory = nullptr; video_stream_encoder_->ConfigureEncoder(std::move(config), kMaxPayloadLength); + // We can get up to 3 streams of 1280x720 resolution each in this test. Make + // available bitrate large enough to get all streams encoded. + const DataRate kAvailableBitrate = 3 * kSimulcastTargetBitrate; video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( - kSimulcastTargetBitrate, kSimulcastTargetBitrate, kSimulcastTargetBitrate, - 0, 0, 0); + kAvailableBitrate, kAvailableBitrate, kAvailableBitrate, 0, 0, 0); // Wait for all layers before triggering event. sink_.SetNumExpectedLayers(num_streams);