| /* |
| * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "video/config/encoder_stream_factory.h" |
| |
| #include <tuple> |
| |
| #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" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| |
| 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<ScalabilityMode> 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<Resolution> GetStreamResolutions( |
| const std::vector<VideoStream>& streams) { |
| std::vector<Resolution> res; |
| for (const auto& s : streams) { |
| res.push_back( |
| {rtc::checked_cast<int>(s.width), rtc::checked_cast<int>(s.height)}); |
| } |
| return res; |
| } |
| |
| std::vector<VideoStream> CreateEncoderStreams( |
| const FieldTrialsView& field_trials, |
| const Resolution& resolution, |
| const VideoEncoderConfig& encoder_config, |
| std::optional<VideoSourceRestrictions> restrictions = std::nullopt) { |
| VideoEncoder::EncoderInfo encoder_info; |
| auto factory = |
| rtc::make_ref_counted<EncoderStreamFactory>(encoder_info, restrictions); |
| return factory->CreateEncoderStreams(field_trials, resolution.width, |
| resolution.height, encoder_config); |
| } |
| |
| } // namespace |
| |
| TEST(EncoderStreamFactory, SinglecastScaleResolutionDownTo) { |
| ExplicitKeyValueConfig field_trials(""); |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 1; |
| encoder_config.simulcast_layers.resize(1); |
| encoder_config.simulcast_layers[0].scale_resolution_down_to = {.width = 640, |
| .height = 360}; |
| auto streams = CreateEncoderStreams( |
| field_trials, {.width = 1280, .height = 720}, encoder_config); |
| EXPECT_EQ(streams[0].scale_resolution_down_to, |
| (Resolution{.width = 640, .height = 360})); |
| EXPECT_EQ(GetStreamResolutions(streams), (std::vector<Resolution>{ |
| {.width = 640, .height = 360}, |
| })); |
| } |
| |
| TEST(EncoderStreamFactory, SinglecastScaleResolutionDownToWithAdaptation) { |
| ExplicitKeyValueConfig field_trials(""); |
| VideoSourceRestrictions restrictions( |
| /* max_pixels_per_frame= */ (320 * 320), |
| /* target_pixels_per_frame= */ std::nullopt, |
| /* max_frame_rate= */ std::nullopt); |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 1; |
| encoder_config.simulcast_layers.resize(1); |
| encoder_config.simulcast_layers[0].scale_resolution_down_to = {.width = 640, |
| .height = 360}; |
| auto streams = |
| CreateEncoderStreams(field_trials, {.width = 1280, .height = 720}, |
| encoder_config, restrictions); |
| EXPECT_EQ(streams[0].scale_resolution_down_to, |
| (Resolution{.width = 640, .height = 360})); |
| EXPECT_EQ(GetStreamResolutions(streams), (std::vector<Resolution>{ |
| {.width = 320, .height = 180}, |
| })); |
| } |
| |
| TEST(EncoderStreamFactory, SimulcastScaleResolutionDownToUnrestricted) { |
| ExplicitKeyValueConfig field_trials(""); |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 3; |
| encoder_config.simulcast_layers.resize(3); |
| encoder_config.simulcast_layers[0].scale_resolution_down_to = {.width = 320, |
| .height = 180}; |
| encoder_config.simulcast_layers[1].scale_resolution_down_to = {.width = 640, |
| .height = 360}; |
| encoder_config.simulcast_layers[2].scale_resolution_down_to = {.width = 1280, |
| .height = 720}; |
| auto streams = CreateEncoderStreams( |
| field_trials, {.width = 1280, .height = 720}, encoder_config); |
| std::vector<Resolution> stream_resolutions = GetStreamResolutions(streams); |
| ASSERT_THAT(stream_resolutions, SizeIs(3)); |
| EXPECT_EQ(stream_resolutions[0], (Resolution{.width = 320, .height = 180})); |
| EXPECT_EQ(stream_resolutions[1], (Resolution{.width = 640, .height = 360})); |
| EXPECT_EQ(stream_resolutions[2], (Resolution{.width = 1280, .height = 720})); |
| } |
| |
| TEST(EncoderStreamFactory, SimulcastScaleResolutionDownToWith360pRestriction) { |
| ExplicitKeyValueConfig field_trials(""); |
| VideoSourceRestrictions restrictions( |
| /* max_pixels_per_frame= */ (640 * 360), |
| /* target_pixels_per_frame= */ std::nullopt, |
| /* max_frame_rate= */ std::nullopt); |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 3; |
| encoder_config.simulcast_layers.resize(3); |
| encoder_config.simulcast_layers[0].scale_resolution_down_to = {.width = 320, |
| .height = 180}; |
| encoder_config.simulcast_layers[1].scale_resolution_down_to = {.width = 640, |
| .height = 360}; |
| encoder_config.simulcast_layers[2].scale_resolution_down_to = {.width = 1280, |
| .height = 720}; |
| auto streams = |
| CreateEncoderStreams(field_trials, {.width = 1280, .height = 720}, |
| encoder_config, restrictions); |
| std::vector<Resolution> stream_resolutions = GetStreamResolutions(streams); |
| // 720p layer is dropped due to 360p restrictions. |
| ASSERT_THAT(stream_resolutions, SizeIs(2)); |
| EXPECT_EQ(stream_resolutions[0], (Resolution{.width = 320, .height = 180})); |
| EXPECT_EQ(stream_resolutions[1], (Resolution{.width = 640, .height = 360})); |
| } |
| |
| TEST(EncoderStreamFactory, SimulcastScaleResolutionDownToWith90pRestriction) { |
| ExplicitKeyValueConfig field_trials(""); |
| VideoSourceRestrictions restrictions( |
| /* max_pixels_per_frame= */ (160 * 90), |
| /* target_pixels_per_frame= */ std::nullopt, |
| /* max_frame_rate= */ std::nullopt); |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 3; |
| encoder_config.simulcast_layers.resize(3); |
| encoder_config.simulcast_layers[0].scale_resolution_down_to = {.width = 320, |
| .height = 180}; |
| encoder_config.simulcast_layers[1].scale_resolution_down_to = {.width = 640, |
| .height = 360}; |
| encoder_config.simulcast_layers[2].scale_resolution_down_to = {.width = 1280, |
| .height = 720}; |
| auto streams = |
| CreateEncoderStreams(field_trials, {.width = 1280, .height = 720}, |
| encoder_config, restrictions); |
| std::vector<Resolution> stream_resolutions = GetStreamResolutions(streams); |
| ASSERT_THAT(stream_resolutions, SizeIs(1)); |
| // 90p restriction means all but the first layer (180p) is dropped. The one |
| // and only layer is downsized to 90p. |
| EXPECT_EQ(stream_resolutions[0], (Resolution{.width = 160, .height = 90})); |
| } |
| |
| TEST(EncoderStreamFactory, |
| ReverseSimulcastScaleResolutionDownToWithRestriction) { |
| ExplicitKeyValueConfig field_trials(""); |
| VideoSourceRestrictions restrictions( |
| /* max_pixels_per_frame= */ (640 * 360), |
| /* target_pixels_per_frame= */ std::nullopt, |
| /* max_frame_rate= */ std::nullopt); |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 3; |
| encoder_config.simulcast_layers.resize(3); |
| // 720p, 360p, 180p (instead of the usual 180p, 360p, 720p). |
| encoder_config.simulcast_layers[0].scale_resolution_down_to = {.width = 1280, |
| .height = 720}; |
| encoder_config.simulcast_layers[1].scale_resolution_down_to = {.width = 640, |
| .height = 360}; |
| encoder_config.simulcast_layers[2].scale_resolution_down_to = {.width = 320, |
| .height = 180}; |
| auto streams = |
| CreateEncoderStreams(field_trials, {.width = 1280, .height = 720}, |
| encoder_config, restrictions); |
| std::vector<Resolution> stream_resolutions = GetStreamResolutions(streams); |
| // The layer dropping that is performed for lower-to-higher ordered simulcast |
| // streams is not applicable when higher-to-lower order is used. In this case |
| // the 360p restriction is applied to all layers. |
| ASSERT_THAT(stream_resolutions, SizeIs(3)); |
| EXPECT_EQ(stream_resolutions[0], (Resolution{.width = 640, .height = 360})); |
| EXPECT_EQ(stream_resolutions[1], (Resolution{.width = 640, .height = 360})); |
| EXPECT_EQ(stream_resolutions[2], (Resolution{.width = 320, .height = 180})); |
| } |
| |
| TEST(EncoderStreamFactory, BitratePriority) { |
| constexpr double kBitratePriority = 0.123; |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 2; |
| encoder_config.simulcast_layers.resize(encoder_config.number_of_streams); |
| encoder_config.bitrate_priority = kBitratePriority; |
| 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); |
| } |
| |
| TEST(EncoderStreamFactory, SetsMinBitrateToDefaultValue) { |
| VideoEncoder::EncoderInfo encoder_info; |
| auto factory = rtc::make_ref_counted<EncoderStreamFactory>(encoder_info); |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 2; |
| encoder_config.simulcast_layers.resize(encoder_config.number_of_streams); |
| auto streams = factory->CreateEncoderStreams(ExplicitKeyValueConfig(""), 1920, |
| 1080, encoder_config); |
| ASSERT_THAT(streams, Not(IsEmpty())); |
| EXPECT_EQ(streams[0].min_bitrate_bps, kDefaultMinVideoBitrateBps); |
| } |
| |
| TEST(EncoderStreamFactory, SetsMinBitrateToExperimentalValue) { |
| VideoEncoder::EncoderInfo encoder_info; |
| auto factory = rtc::make_ref_counted<EncoderStreamFactory>(encoder_info); |
| VideoEncoderConfig encoder_config; |
| encoder_config.number_of_streams = 2; |
| encoder_config.simulcast_layers.resize(encoder_config.number_of_streams); |
| auto streams = factory->CreateEncoderStreams( |
| ExplicitKeyValueConfig("WebRTC-Video-MinVideoBitrate/Enabled,br:1kbps/"), |
| 1920, 1080, encoder_config); |
| ASSERT_THAT(streams, Not(IsEmpty())); |
| 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<Resolution> 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<VideoStream> requested_streams; |
| std::vector<VideoStream> expected_streams; |
| }; |
| |
| class EncoderStreamFactoryOverrideStreamSettinsTest |
| : public ::testing::TestWithParam< |
| std::tuple<OverrideStreamSettingsTestParams, VideoCodecType>> {}; |
| |
| 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))); |
| |
| TEST(EncoderStreamFactory, VP9TemporalLayerCountTransferToStreamSettings) { |
| VideoEncoderConfig encoder_config; |
| VideoCodecVP9 vp9_settings = VideoEncoder::GetDefaultVp9Settings(); |
| encoder_config.encoder_specific_settings = |
| rtc::make_ref_counted<VideoEncoderConfig::Vp9EncoderSpecificSettings>( |
| vp9_settings); |
| encoder_config.codec_type = VideoCodecType::kVideoCodecVP9; |
| encoder_config.number_of_streams = 1; |
| encoder_config.simulcast_layers.resize(1); |
| encoder_config.simulcast_layers[0].num_temporal_layers = 3; |
| auto streams = CreateEncoderStreams(ExplicitKeyValueConfig(""), {1280, 720}, |
| encoder_config); |
| ASSERT_THAT(streams, SizeIs(1)); |
| EXPECT_EQ(streams[0].num_temporal_layers, 3); |
| } |
| |
| TEST(EncoderStreamFactory, AV1TemporalLayerCountTransferToStreamSettings) { |
| VideoEncoderConfig encoder_config; |
| encoder_config.codec_type = VideoCodecType::kVideoCodecAV1; |
| encoder_config.number_of_streams = 1; |
| encoder_config.simulcast_layers.resize(1); |
| encoder_config.simulcast_layers[0].num_temporal_layers = 3; |
| auto streams = CreateEncoderStreams(ExplicitKeyValueConfig(""), {1280, 720}, |
| encoder_config); |
| ASSERT_THAT(streams, SizeIs(1)); |
| EXPECT_EQ(streams[0].num_temporal_layers, 3); |
| } |
| |
| TEST(EncoderStreamFactory, H264TemporalLayerCountTransferToStreamSettings) { |
| VideoEncoderConfig encoder_config; |
| encoder_config.codec_type = VideoCodecType::kVideoCodecH264; |
| encoder_config.number_of_streams = 1; |
| encoder_config.simulcast_layers.resize(1); |
| encoder_config.simulcast_layers[0].num_temporal_layers = 3; |
| auto streams = CreateEncoderStreams(ExplicitKeyValueConfig(""), {1280, 720}, |
| encoder_config); |
| ASSERT_THAT(streams, SizeIs(1)); |
| EXPECT_EQ(streams[0].num_temporal_layers, std::nullopt); |
| } |
| |
| #ifdef RTC_ENABLE_H265 |
| TEST(EncoderStreamFactory, H265TemporalLayerCountTransferToStreamSettings) { |
| VideoEncoderConfig encoder_config; |
| encoder_config.codec_type = VideoCodecType::kVideoCodecH265; |
| encoder_config.number_of_streams = 1; |
| encoder_config.simulcast_layers.resize(1); |
| encoder_config.simulcast_layers[0].num_temporal_layers = 3; |
| auto streams = CreateEncoderStreams(ExplicitKeyValueConfig(""), {1280, 720}, |
| encoder_config); |
| ASSERT_THAT(streams, SizeIs(1)); |
| EXPECT_EQ(streams[0].num_temporal_layers, 3); |
| } |
| #endif |
| |
| } // namespace webrtc |