Add option to make first scale factor depend on input resolution.
Scale factors are 3/4, 2/3, 3/4, 2/3, ...
Adds possibly to start with:
- 2/3 (if width/height multiple of 3)
- 2/3, 2/3 (if width/height multiple of 9)
Bug: none
Change-Id: Idbeddfec4baea893c240bbb897d01ac1cff3b435
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/157105
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29538}
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 59c2bfc..371f6cd 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -106,6 +106,7 @@
"../rtc_base/system:file_wrapper",
"../rtc_base/system:rtc_export",
"../rtc_base/third_party/sigslot",
+ "../system_wrappers:field_trial",
"//third_party/abseil-cpp/absl/algorithm:container",
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
diff --git a/media/base/video_adapter.cc b/media/base/video_adapter.cc
index 4f1181e..75c1a47 100644
--- a/media/base/video_adapter.cc
+++ b/media/base/video_adapter.cc
@@ -21,12 +21,31 @@
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
+#include "system_wrappers/include/field_trial.h"
namespace {
+int Gcd(int a, int b) {
+ RTC_DCHECK_GE(a, 0);
+ RTC_DCHECK_GT(b, 0);
+ int c = a % b;
+ while (c != 0) {
+ a = b;
+ b = c;
+ c = a % b;
+ }
+ return b;
+}
+
struct Fraction {
int numerator;
int denominator;
+ void DivideByGcd() {
+ int g = Gcd(numerator, denominator);
+ numerator /= g;
+ denominator /= g;
+ }
+
// Determines number of output pixels if both width and height of an input of
// |input_pixels| pixels is scaled with the fraction numerator / denominator.
int scale_pixel_count(int input_pixels) {
@@ -45,18 +64,37 @@
// Generates a scale factor that makes |input_pixels| close to |target_pixels|,
// but no higher than |max_pixels|.
-Fraction FindScale(int input_pixels, int target_pixels, int max_pixels) {
+Fraction FindScale(int input_width,
+ int input_height,
+ int target_pixels,
+ int max_pixels,
+ bool variable_start_scale_factor) {
// This function only makes sense for a positive target.
RTC_DCHECK_GT(target_pixels, 0);
RTC_DCHECK_GT(max_pixels, 0);
RTC_DCHECK_GE(max_pixels, target_pixels);
+ const int input_pixels = input_width * input_height;
+
// Don't scale up original.
if (target_pixels >= input_pixels)
return Fraction{1, 1};
Fraction current_scale = Fraction{1, 1};
Fraction best_scale = Fraction{1, 1};
+
+ if (variable_start_scale_factor) {
+ // Start scaling down by 2/3 depending on |input_width| and |input_height|.
+ if (input_width % 3 == 0 && input_height % 3 == 0) {
+ // 2/3 (then alternates 3/4, 2/3, 3/4,...).
+ current_scale = Fraction{6, 6};
+ }
+ if (input_width % 9 == 0 && input_height % 9 == 0) {
+ // 2/3, 2/3 (then alternates 3/4, 2/3, 3/4,...).
+ current_scale = Fraction{36, 36};
+ }
+ }
+
// The minimum (absolute) difference between the number of output pixels and
// the target pixel count.
int min_pixel_diff = std::numeric_limits<int>::max();
@@ -65,7 +103,7 @@
min_pixel_diff = std::abs(input_pixels - target_pixels);
}
- // Alternately scale down by 2/3 and 3/4. This results in fractions which are
+ // Alternately scale down by 3/4 and 2/3. This results in fractions which are
// effectively scalable. For instance, starting at 1280x720 will result in
// the series (3/4) => 960x540, (1/2) => 640x360, (3/8) => 480x270,
// (1/4) => 320x180, (3/16) => 240x125, (1/8) => 160x90.
@@ -90,6 +128,7 @@
}
}
}
+ best_scale.DivideByGcd();
return best_scale;
}
@@ -104,6 +143,8 @@
adaption_changes_(0),
previous_width_(0),
previous_height_(0),
+ variable_start_scale_factor_(webrtc::field_trial::IsEnabled(
+ "WebRTC-Video-VariableStartScaleFactor")),
required_resolution_alignment_(required_resolution_alignment),
resolution_request_target_pixel_count_(std::numeric_limits<int>::max()),
resolution_request_max_pixel_count_(std::numeric_limits<int>::max()),
@@ -217,8 +258,9 @@
*cropped_height =
std::min(in_height, static_cast<int>(in_width / requested_aspect));
}
- const Fraction scale = FindScale((*cropped_width) * (*cropped_height),
- target_pixel_count, max_pixel_count);
+ const Fraction scale =
+ FindScale(*cropped_width, *cropped_height, target_pixel_count,
+ max_pixel_count, variable_start_scale_factor_);
// Adjust cropping slightly to get even integer output size and a perfect
// scale factor. Make sure the resulting dimensions are aligned correctly
// to be nice to hardware encoders.
diff --git a/media/base/video_adapter.h b/media/base/video_adapter.h
index bbe0886..a846cc0 100644
--- a/media/base/video_adapter.h
+++ b/media/base/video_adapter.h
@@ -105,6 +105,7 @@
int adaption_changes_; // Number of changes in scale factor.
int previous_width_; // Previous adapter output width.
int previous_height_; // Previous adapter output height.
+ const bool variable_start_scale_factor_;
// Resolution must be divisible by this factor.
const int required_resolution_alignment_;
// The target timestamp for the next frame based on requested format.
diff --git a/media/base/video_adapter_unittest.cc b/media/base/video_adapter_unittest.cc
index 2bfe803..6529ee7 100644
--- a/media/base/video_adapter_unittest.cc
+++ b/media/base/video_adapter_unittest.cc
@@ -12,11 +12,14 @@
#include <limits>
#include <memory>
+#include <string>
#include <utility>
#include "api/video/video_frame.h"
#include "media/base/fake_frame_source.h"
+#include "rtc_base/arraysize.h"
#include "rtc_base/time_utils.h"
+#include "test/field_trial.h"
#include "test/gtest.h"
namespace cricket {
@@ -29,8 +32,10 @@
class VideoAdapterTest : public ::testing::Test,
public ::testing::WithParamInterface<bool> {
public:
- VideoAdapterTest()
- : frame_source_(std::make_unique<FakeFrameSource>(
+ VideoAdapterTest() : VideoAdapterTest("") {}
+ explicit VideoAdapterTest(const std::string& field_trials)
+ : override_field_trials_(field_trials),
+ frame_source_(std::make_unique<FakeFrameSource>(
kWidth,
kHeight,
VideoFormat::FpsToInterval(kDefaultFps) /
@@ -115,6 +120,7 @@
cricket::FOURCC_I420));
}
+ webrtc::test::ScopedFieldTrials override_field_trials_;
const std::unique_ptr<FakeFrameSource> frame_source_;
VideoAdapter adapter_;
int cropped_width_;
@@ -125,10 +131,20 @@
const bool use_new_format_request_;
};
+class VideoAdapterTestVariableStartScale : public VideoAdapterTest {
+ public:
+ VideoAdapterTestVariableStartScale()
+ : VideoAdapterTest("WebRTC-Video-VariableStartScaleFactor/Enabled/") {}
+};
+
INSTANTIATE_TEST_SUITE_P(OnOutputFormatRequests,
VideoAdapterTest,
::testing::Values(true, false));
+INSTANTIATE_TEST_SUITE_P(OnOutputFormatRequests,
+ VideoAdapterTestVariableStartScale,
+ ::testing::Values(true, false));
+
// Do not adapt the frame rate or the resolution. Expect no frame drop, no
// cropping, and no resolution change.
TEST_P(VideoAdapterTest, AdaptNothing) {
@@ -1159,4 +1175,121 @@
EXPECT_EQ(640, out_height);
}
+TEST_P(VideoAdapterTest, AdaptResolutionInSteps) {
+ const int kWidth = 1280;
+ const int kHeight = 720;
+ OnOutputFormatRequest(kWidth, kHeight, absl::nullopt); // 16:9 aspect.
+
+ // Scale factors: 3/4, 2/3, 3/4, 2/3, ...
+ // Scale : 3/4, 1/2, 3/8, 1/4, 3/16, 1/8.
+ const int kExpectedWidths[] = {960, 640, 480, 320, 240, 160};
+ const int kExpectedHeights[] = {540, 360, 270, 180, 135, 90};
+
+ int request_width = kWidth;
+ int request_height = kHeight;
+
+ for (size_t i = 0; i < arraysize(kExpectedWidths); ++i) {
+ // Adapt down one step.
+ adapter_.OnResolutionFramerateRequest(absl::nullopt,
+ request_width * request_height - 1,
+ std::numeric_limits<int>::max());
+ EXPECT_TRUE(adapter_.AdaptFrameResolution(kWidth, kHeight, 0,
+ &cropped_width_, &cropped_height_,
+ &out_width_, &out_height_));
+ EXPECT_EQ(kExpectedWidths[i], out_width_);
+ EXPECT_EQ(kExpectedHeights[i], out_height_);
+ request_width = out_width_;
+ request_height = out_height_;
+ }
+}
+
+// Scale factors are 3/4, 2/3, 3/4, 2/3, ... (see test above).
+// In VideoAdapterTestVariableStartScale, first scale factor depends on
+// resolution. May start with:
+// - 2/3 (if width/height multiple of 3) or
+// - 2/3, 2/3 (if width/height multiple of 9).
+TEST_P(VideoAdapterTestVariableStartScale, AdaptResolutionInStepsFirst3_4) {
+ const int kWidth = 1280;
+ const int kHeight = 720;
+ OnOutputFormatRequest(kWidth, kHeight, absl::nullopt); // 16:9 aspect.
+
+ // Scale factors: 3/4, 2/3, 3/4, 2/3, ...
+ // Scale : 3/4, 1/2, 3/8, 1/4, 3/16, 1/8.
+ const int kExpectedWidths[] = {960, 640, 480, 320, 240, 160};
+ const int kExpectedHeights[] = {540, 360, 270, 180, 135, 90};
+
+ int request_width = kWidth;
+ int request_height = kHeight;
+
+ for (size_t i = 0; i < arraysize(kExpectedWidths); ++i) {
+ // Adapt down one step.
+ adapter_.OnResolutionFramerateRequest(absl::nullopt,
+ request_width * request_height - 1,
+ std::numeric_limits<int>::max());
+ EXPECT_TRUE(adapter_.AdaptFrameResolution(kWidth, kHeight, 0,
+ &cropped_width_, &cropped_height_,
+ &out_width_, &out_height_));
+ EXPECT_EQ(kExpectedWidths[i], out_width_);
+ EXPECT_EQ(kExpectedHeights[i], out_height_);
+ request_width = out_width_;
+ request_height = out_height_;
+ }
+}
+
+TEST_P(VideoAdapterTestVariableStartScale, AdaptResolutionInStepsFirst2_3) {
+ const int kWidth = 1920;
+ const int kHeight = 1080;
+ OnOutputFormatRequest(kWidth, kHeight, absl::nullopt); // 16:9 aspect.
+
+ // Scale factors: 2/3, 3/4, 2/3, 3/4, ...
+ // Scale: 2/3, 1/2, 1/3, 1/4, 1/6, 1/8, 1/12.
+ const int kExpectedWidths[] = {1280, 960, 640, 480, 320, 240, 160};
+ const int kExpectedHeights[] = {720, 540, 360, 270, 180, 135, 90};
+
+ int request_width = kWidth;
+ int request_height = kHeight;
+
+ for (size_t i = 0; i < arraysize(kExpectedWidths); ++i) {
+ // Adapt down one step.
+ adapter_.OnResolutionFramerateRequest(absl::nullopt,
+ request_width * request_height - 1,
+ std::numeric_limits<int>::max());
+ EXPECT_TRUE(adapter_.AdaptFrameResolution(kWidth, kHeight, 0,
+ &cropped_width_, &cropped_height_,
+ &out_width_, &out_height_));
+ EXPECT_EQ(kExpectedWidths[i], out_width_);
+ EXPECT_EQ(kExpectedHeights[i], out_height_);
+ request_width = out_width_;
+ request_height = out_height_;
+ }
+}
+
+TEST_P(VideoAdapterTestVariableStartScale, AdaptResolutionInStepsFirst2x2_3) {
+ const int kWidth = 1440;
+ const int kHeight = 1080;
+ OnOutputFormatRequest(kWidth, kHeight, absl::nullopt); // 4:3 aspect.
+
+ // Scale factors: 2/3, 2/3, 3/4, 2/3, 3/4, ...
+ // Scale : 2/3, 4/9, 1/3, 2/9, 1/6, 1/9, 1/12, 1/18, 1/24, 1/36.
+ const int kExpectedWidths[] = {960, 640, 480, 320, 240, 160, 120, 80, 60, 40};
+ const int kExpectedHeights[] = {720, 480, 360, 240, 180, 120, 90, 60, 45, 30};
+
+ int request_width = kWidth;
+ int request_height = kHeight;
+
+ for (size_t i = 0; i < arraysize(kExpectedWidths); ++i) {
+ // Adapt down one step.
+ adapter_.OnResolutionFramerateRequest(absl::nullopt,
+ request_width * request_height - 1,
+ std::numeric_limits<int>::max());
+ EXPECT_TRUE(adapter_.AdaptFrameResolution(kWidth, kHeight, 0,
+ &cropped_width_, &cropped_height_,
+ &out_width_, &out_height_));
+ EXPECT_EQ(kExpectedWidths[i], out_width_);
+ EXPECT_EQ(kExpectedHeights[i], out_height_);
+ request_width = out_width_;
+ request_height = out_height_;
+ }
+}
+
} // namespace cricket