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}
This commit is contained in:
parent
89e130a2d0
commit
3f7e0ede1e
@ -106,6 +106,7 @@ rtc_library("rtc_media_base") {
|
||||
"../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",
|
||||
|
||||
@ -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 @@ int roundUp(int value_to_round, int multiple, int max_value) {
|
||||
|
||||
// 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 @@ Fraction FindScale(int input_pixels, int target_pixels, int max_pixels) {
|
||||
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 @@ Fraction FindScale(int input_pixels, int target_pixels, int max_pixels) {
|
||||
}
|
||||
}
|
||||
}
|
||||
best_scale.DivideByGcd();
|
||||
|
||||
return best_scale;
|
||||
}
|
||||
@ -104,6 +143,8 @@ VideoAdapter::VideoAdapter(int required_resolution_alignment)
|
||||
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 @@ bool VideoAdapter::AdaptFrameResolution(int in_width,
|
||||
*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.
|
||||
|
||||
@ -105,6 +105,7 @@ class VideoAdapter {
|
||||
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.
|
||||
|
||||
@ -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 @@ const int kDefaultFps = 30;
|
||||
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 @@ class VideoAdapterTest : public ::testing::Test,
|
||||
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 @@ class VideoAdapterTest : public ::testing::Test,
|
||||
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 @@ TEST(VideoAdapterTestMultipleOrientation, TestForcePortrait) {
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user