| /* | |
| * 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 <cstddef> | |
| #include <optional> | |
| #include <string> | |
| #include <tuple> | |
| #include <vector> | |
| #include "absl/strings/string_view.h" | |
| #include "api/field_trials.h" | |
| #include "api/field_trials_view.h" | |
| #include "api/make_ref_counted.h" | |
| #include "api/video/resolution.h" | |
| #include "api/video/video_codec_type.h" | |
| #include "api/video_codecs/scalability_mode.h" | |
| #include "api/video_codecs/video_codec.h" | |
| #include "api/video_codecs/video_encoder.h" | |
| #include "call/adaptation/video_source_restrictions.h" | |
| #include "rtc_base/experiments/min_video_bitrate_experiment.h" | |
| #include "rtc_base/numerics/safe_conversions.h" | |
| #include "test/create_test_field_trials.h" | |
| #include "test/gmock.h" | |
| #include "test/gtest.h" | |
| #include "video/config/video_encoder_config.h" | |
| namespace webrtc { | |
| namespace { | |
| using ::testing::Combine; | |
| using ::testing::ElementsAre; | |
| using ::testing::IsEmpty; | |
| using ::testing::Not; | |
| using ::testing::SizeIs; | |
| using ::testing::TestWithParam; | |
| 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({.width = checked_cast<int>(s.width), | |
| .height = 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 = | |
| make_ref_counted<EncoderStreamFactory>(encoder_info, restrictions); | |
| return factory->CreateEncoderStreams(field_trials, resolution.width, | |
| resolution.height, encoder_config); | |
| } | |
| } // namespace | |
| TEST(EncoderStreamFactory, SinglecastScaleResolutionDownTo) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| 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_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 640, .height = 360})); | |
| } | |
| TEST(EncoderStreamFactory, SinglecastScaleResolutionDownToWithAdaptation) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| 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_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 320, .height = 180})); | |
| } | |
| TEST(EncoderStreamFactory, SimulcastScaleResolutionDownToUnrestricted) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| 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); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 320, .height = 180}, | |
| Resolution{.width = 640, .height = 360}, | |
| Resolution{.width = 1280, .height = 720})); | |
| } | |
| TEST(EncoderStreamFactory, SimulcastScaleResolutionDownToWith360pRestriction) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| 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); | |
| // 720p layer is dropped due to 360p restrictions. | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 320, .height = 180}, | |
| Resolution{.width = 640, .height = 360})); | |
| } | |
| TEST(EncoderStreamFactory, SimulcastScaleResolutionDownToWith90pRestriction) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| 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); | |
| // 90p restriction means all but the first layer (180p) is dropped. The one | |
| // and only layer is downsized to 90p. | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 160, .height = 90})); | |
| } | |
| TEST(EncoderStreamFactory, | |
| ReverseSimulcastScaleResolutionDownToWithRestriction) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| 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); | |
| // 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. | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 640, .height = 360}, | |
| Resolution{.width = 640, .height = 360}, | |
| Resolution{.width = 320, .height = 180})); | |
| } | |
| TEST(EncoderStreamFactory, | |
| SinglecastScaleResolutionDownToTakesPrecedenceOverBy) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| 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 = 960, | |
| .height = 650}; | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 2; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 1280, .height = 720}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 960, .height = 540})); | |
| } | |
| TEST(EncoderStreamFactory, ScaleResolutionDownBy) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 1; | |
| encoder_config.simulcast_layers.resize(1); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 2; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 1280, .height = 720}, encoder_config); | |
| EXPECT_EQ(streams[0].scale_resolution_down_by, 2); | |
| EXPECT_EQ(GetStreamResolutions(streams), (std::vector<Resolution>{ | |
| {.width = 640, .height = 360}, | |
| })); | |
| } | |
| TEST(EncoderStreamFactory, ScaleResolutionDownByWithZeroScaleFactor) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 1; | |
| encoder_config.simulcast_layers.resize(1); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 0; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 1280, .height = 720}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 1280, .height = 720})); | |
| } | |
| TEST(EncoderStreamFactory, ScaleResolutionDownByWithNegativeScaleFactor) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 1; | |
| encoder_config.simulcast_layers.resize(1); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = -2; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 1280, .height = 720}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 1280, .height = 720})); | |
| } | |
| TEST(EncoderStreamFactory, ScaleResolutionDownByWithPartiallySetScaleFactors) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 2; | |
| encoder_config.simulcast_layers.resize(2); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 2; | |
| encoder_config.simulcast_layers[1].scale_resolution_down_by = -1; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 1280, .height = 720}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 640, .height = 360}, | |
| Resolution{.width = 1280, .height = 720})); | |
| } | |
| TEST(EncoderStreamFactory, ScaleDownByWithUnalignResolutionDyadicScaling) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 2; | |
| encoder_config.simulcast_layers.resize(2); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 1; | |
| encoder_config.simulcast_layers[1].scale_resolution_down_by = 2; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 513, .height = 1025}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 512, .height = 1024}, | |
| Resolution{.width = 256, .height = 512})); | |
| } | |
| TEST(EncoderStreamFactory, ScaleDownByWithUnalignResolutionNonDyadicScaling) { | |
| // Keeps resolution unchanged if any of `scale_resolution_down_by` is not a | |
| // power of two number. | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 2; | |
| encoder_config.simulcast_layers.resize(2); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 1; | |
| encoder_config.simulcast_layers[1].scale_resolution_down_by = 3; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 513, .height = 1025}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 513, .height = 1025}, | |
| Resolution{.width = 171, .height = 342})); | |
| } | |
| TEST(EncoderStreamFactory, ScaleDownByWithUnalignedResolutionLargeScaleFactor) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 1; | |
| encoder_config.simulcast_layers.resize(1); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 1024; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 513, .height = 1025}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 16, .height = 32})); | |
| } | |
| TEST(EncoderStreamFactory, ScaleDownByWithUnalignedResolutionWithoutScaling) { | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 2; | |
| encoder_config.simulcast_layers.resize(2); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 1; | |
| encoder_config.simulcast_layers[1].scale_resolution_down_by = 1; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 513, .height = 1025}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 513, .height = 1025}, | |
| Resolution{.width = 513, .height = 1025})); | |
| } | |
| TEST(EncoderStreamFactory, ScaleDownByNormalizesResolutionUpToMaxScaleFactor) { | |
| // Without `scale_resolution_down_by` resolution normalization assumes 1/2 | |
| // scaling. When three streams are requested, resolution is normalized to be | |
| // multiple of 8. With `scale_resolution_down_by` resolution is normalized | |
| // to be multiple of the maximum scale factor. | |
| FieldTrials field_trials = CreateTestFieldTrials(); | |
| VideoEncoderConfig encoder_config; | |
| encoder_config.number_of_streams = 3; | |
| encoder_config.simulcast_layers.resize(3); | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by = 1; | |
| encoder_config.simulcast_layers[1].scale_resolution_down_by = 1; | |
| encoder_config.simulcast_layers[2].scale_resolution_down_by = 2; | |
| auto streams = CreateEncoderStreams( | |
| field_trials, {.width = 511, .height = 1027}, encoder_config); | |
| EXPECT_THAT(GetStreamResolutions(streams), | |
| ElementsAre(Resolution{.width = 510, .height = 1026}, | |
| Resolution{.width = 510, .height = 1026}, | |
| Resolution{.width = 255, .height = 513})); | |
| } | |
| 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=*/CreateTestFieldTrials(), {.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 = 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(CreateTestFieldTrials(), 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 = 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( | |
| CreateTestFieldTrials("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(CreateTestFieldTrials(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; | |
| }; | |
| using EncoderStreamFactoryOverrideStreamSettingsTest = | |
| TestWithParam<std::tuple<OverrideStreamSettingsTestParams, VideoCodecType>>; | |
| TEST_P(EncoderStreamFactoryOverrideStreamSettingsTest, 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(CreateTestFieldTrials(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( | |
| Vp8H264Screencast, | |
| EncoderStreamFactoryOverrideStreamSettingsTest, | |
| 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::kVideoCodecH264))); | |
| INSTANTIATE_TEST_SUITE_P( | |
| Av1Vp9H265Screencast, | |
| EncoderStreamFactoryOverrideStreamSettingsTest, | |
| 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 = 769'000, | |
| .target_bitrate_bps = 2'500'000, | |
| .max_bitrate_bps = 2'500'000, | |
| .scalability_mode = ScalabilityMode::kL1T2})}}), | |
| Values( | |
| #ifdef RTC_ENABLE_H265 | |
| kVideoCodecH265, | |
| #endif | |
| VideoCodecType::kVideoCodecAV1, | |
| VideoCodecType::kVideoCodecVP9))); | |
| TEST(EncoderStreamFactory, VP9TemporalLayerCountTransferToStreamSettings) { | |
| VideoEncoderConfig encoder_config; | |
| VideoCodecVP9 vp9_settings = VideoEncoder::GetDefaultVp9Settings(); | |
| encoder_config.encoder_specific_settings = | |
| 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( | |
| CreateTestFieldTrials(), {.width = 1280, .height = 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( | |
| CreateTestFieldTrials(), {.width = 1280, .height = 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( | |
| CreateTestFieldTrials(), {.width = 1280, .height = 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( | |
| CreateTestFieldTrials(), {.width = 1280, .height = 720}, encoder_config); | |
| ASSERT_THAT(streams, SizeIs(1)); | |
| EXPECT_EQ(streams[0].num_temporal_layers, 3); | |
| } | |
| #endif | |
| TEST(EncoderStreamFactory, VP9SetsMaxBitrateToConfiguredEncodingValue) { | |
| VideoEncoderConfig encoder_config; | |
| VideoCodecVP9 vp9_settings = VideoEncoder::GetDefaultVp9Settings(); | |
| encoder_config.encoder_specific_settings = | |
| make_ref_counted<VideoEncoderConfig::Vp9EncoderSpecificSettings>( | |
| vp9_settings); | |
| encoder_config.codec_type = VideoCodecType::kVideoCodecVP9; | |
| encoder_config.number_of_streams = 1; | |
| encoder_config.simulcast_layers.resize(3); | |
| encoder_config.simulcast_layers[0].max_bitrate_bps = 5000000; | |
| auto streams = CreateEncoderStreams( | |
| CreateTestFieldTrials(), {.width = 1280, .height = 720}, encoder_config); | |
| ASSERT_THAT(streams, SizeIs(1)); | |
| EXPECT_EQ(streams[0].max_bitrate_bps, 5000000); | |
| } | |
| } // namespace webrtc |