From 6a688f5265040c64e49dd16244ca5bac03327453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Bostr=C3=B6m?= Date: Mon, 22 Jun 2015 08:02:58 +0200 Subject: [PATCH] Add default downscale threshold to QualityScaler. Prevents downscaling below 160x90 or 90x160 to gain more quality. BUG=4625 R=mflodman@webrtc.org Review URL: https://codereview.webrtc.org/1160403004. Cr-Commit-Position: refs/heads/master@{#9480} --- .../video_coding/codecs/vp8/vp8_impl.cc | 2 +- .../utility/include/quality_scaler.h | 3 +- .../video_coding/utility/quality_scaler.cc | 21 +++-- .../utility/quality_scaler_unittest.cc | 92 +++++++++++++++++-- 4 files changed, 99 insertions(+), 19 deletions(-) diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc index e5a776e06..437042447 100644 --- a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc +++ b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc @@ -583,7 +583,7 @@ int VP8EncoderImpl::InitEncode(const VideoCodec* inst, } rps_.Init(); - quality_scaler_.Init(codec_.qpMax / kDefaultLowQpDenominator); + quality_scaler_.Init(codec_.qpMax / QualityScaler::kDefaultLowQpDenominator); quality_scaler_.ReportFramerate(codec_.maxFramerate); return InitAndSetControlSettings(); diff --git a/webrtc/modules/video_coding/utility/include/quality_scaler.h b/webrtc/modules/video_coding/utility/include/quality_scaler.h index 325343544..1d6c917ec 100644 --- a/webrtc/modules/video_coding/utility/include/quality_scaler.h +++ b/webrtc/modules/video_coding/utility/include/quality_scaler.h @@ -15,9 +15,10 @@ #include "webrtc/modules/video_coding/utility/include/moving_average.h" namespace webrtc { -const int kDefaultLowQpDenominator = 3; class QualityScaler { public: + static const int kDefaultLowQpDenominator; + static const int kDefaultMinDownscaleDimension; struct Resolution { int width; int height; diff --git a/webrtc/modules/video_coding/utility/quality_scaler.cc b/webrtc/modules/video_coding/utility/quality_scaler.cc index 3c9c6c4d5..7a2a9c026 100644 --- a/webrtc/modules/video_coding/utility/quality_scaler.cc +++ b/webrtc/modules/video_coding/utility/quality_scaler.cc @@ -15,9 +15,17 @@ static const int kMinFps = 10; static const int kMeasureSeconds = 5; static const int kFramedropPercentThreshold = 60; +const int QualityScaler::kDefaultLowQpDenominator = 3; +// Note that this is the same for width and height to permit 120x90 in both +// portrait and landscape mode. +const int QualityScaler::kDefaultMinDownscaleDimension = 90; + QualityScaler::QualityScaler() - : num_samples_(0), low_qp_threshold_(-1), downscale_shift_(0), - min_width_(0), min_height_(0) { + : num_samples_(0), + low_qp_threshold_(-1), + downscale_shift_(0), + min_width_(kDefaultMinDownscaleDimension), + min_height_(kDefaultMinDownscaleDimension) { } void QualityScaler::Init(int low_qp_threshold) { @@ -68,18 +76,13 @@ QualityScaler::Resolution QualityScaler::GetScaledResolution( assert(downscale_shift_ >= 0); for (int shift = downscale_shift_; - shift > 0 && res.width > 1 && res.height > 1; + shift > 0 && (res.width >> 1 >= min_width_) && + (res.height >> 1 >= min_height_); --shift) { res.width >>= 1; res.height >>= 1; } - // Set this limitation for VP8 HW encoder to avoid crash. - if (min_width_ > 0 && res.width * res.height < min_width_ * min_height_) { - res.width = min_width_; - res.height = min_height_; - } - return res; } diff --git a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc index b93fd96a3..c09dffb82 100644 --- a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc +++ b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc @@ -31,11 +31,11 @@ class QualityScalerTest : public ::testing::Test { QualityScalerTest() { input_frame_.CreateEmptyFrame( kWidth, kHeight, kWidth, kHalfWidth, kHalfWidth); - qs_.Init(kMaxQp / kDefaultLowQpDenominator); + qs_.Init(kMaxQp / QualityScaler::kDefaultLowQpDenominator); qs_.ReportFramerate(kFramerate); } - void TriggerScale(ScaleDirection scale_direction) { + bool TriggerScale(ScaleDirection scale_direction) { int initial_width = qs_.GetScaledResolution(input_frame_).width; for (int i = 0; i < kFramerate * kNumSeconds; ++i) { switch (scale_direction) { @@ -48,10 +48,10 @@ class QualityScalerTest : public ::testing::Test { } if (qs_.GetScaledResolution(input_frame_).width != initial_width) - return; + return true; } - FAIL() << "No downscale within " << kNumSeconds << " seconds."; + return false; } void ExpectOriginalFrame() { @@ -70,6 +70,11 @@ class QualityScalerTest : public ::testing::Test { void DoesNotDownscaleFrameDimensions(int width, int height); + void DownscaleEndsAt(int input_width, + int input_height, + int end_width, + int end_height); + QualityScaler qs_; VideoFrame input_frame_; }; @@ -85,7 +90,8 @@ TEST_F(QualityScalerTest, ReportsOriginalResolutionInitially) { } TEST_F(QualityScalerTest, DownscalesAfterContinuousFramedrop) { - TriggerScale(kScaleDown); + EXPECT_TRUE(TriggerScale(kScaleDown)) << "No downscale within " << kNumSeconds + << " seconds."; QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); EXPECT_LT(res.width, input_frame_.width()); EXPECT_LT(res.height, input_frame_.height()); @@ -130,8 +136,9 @@ void QualityScalerTest::ContinuouslyDownscalesByHalfDimensionsAndBackUp() { int min_dimension = initial_min_dimension; int current_shift = 0; // Drop all frames to force-trigger downscaling. - while (min_dimension > 16) { - TriggerScale(kScaleDown); + while (min_dimension >= 2 * QualityScaler::kDefaultMinDownscaleDimension) { + EXPECT_TRUE(TriggerScale(kScaleDown)) << "No downscale within " + << kNumSeconds << " seconds."; QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); min_dimension = res.width < res.height ? res.width : res.height; ++current_shift; @@ -142,7 +149,8 @@ void QualityScalerTest::ContinuouslyDownscalesByHalfDimensionsAndBackUp() { // Make sure we can scale back with good-quality frames. while (min_dimension < initial_min_dimension) { - TriggerScale(kScaleUp); + EXPECT_TRUE(TriggerScale(kScaleUp)) << "No upscale within " << kNumSeconds + << " seconds."; QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); min_dimension = res.width < res.height ? res.width : res.height; --current_shift; @@ -195,4 +203,72 @@ TEST_F(QualityScalerTest, DoesNotDownscaleFrom1Px) { DoesNotDownscaleFrameDimensions(1, 1); } +TEST_F(QualityScalerTest, DoesNotDownscaleBelow2xDefaultMinDimensionsWidth) { + DoesNotDownscaleFrameDimensions( + 2 * QualityScaler::kDefaultMinDownscaleDimension - 1, 1000); +} + +TEST_F(QualityScalerTest, DoesNotDownscaleBelow2xDefaultMinDimensionsHeight) { + DoesNotDownscaleFrameDimensions( + 1000, 2 * QualityScaler::kDefaultMinDownscaleDimension - 1); +} + +void QualityScalerTest::DownscaleEndsAt(int input_width, + int input_height, + int end_width, + int end_height) { + // Create a frame with 2x expected end width/height to verify that we can + // scale down to expected end width/height. + input_frame_.CreateEmptyFrame(input_width, input_height, input_width, + (input_width + 1) / 2, (input_width + 1) / 2); + + int last_width = input_width; + int last_height = input_height; + // Drop all frames to force-trigger downscaling. + while (true) { + TriggerScale(kScaleDown); + QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); + if (last_width == res.width) { + EXPECT_EQ(last_height, res.height); + EXPECT_EQ(end_width, res.width); + EXPECT_EQ(end_height, res.height); + break; + } + last_width = res.width; + last_height = res.height; + } +} + +TEST_F(QualityScalerTest, DefaultDownscalesTo160x90) { + DownscaleEndsAt(320, 180, 160, 90); +} + +TEST_F(QualityScalerTest, DefaultDownscalesTo90x160) { + DownscaleEndsAt(180, 320, 90, 160); +} + +TEST_F(QualityScalerTest, DefaultDownscalesFrom1280x720To160x90) { + DownscaleEndsAt(1280, 720, 160, 90); +} + +TEST_F(QualityScalerTest, DefaultDoesntDownscaleBelow160x90) { + DownscaleEndsAt(320 - 1, 180 - 1, 320 - 1, 180 - 1); +} + +TEST_F(QualityScalerTest, DefaultDoesntDownscaleBelow90x160) { + DownscaleEndsAt(180 - 1, 320 - 1, 180 - 1, 320 - 1); +} + +TEST_F(QualityScalerTest, RespectsMinResolutionWidth) { + // Should end at 200x100, as width can't go lower. + qs_.SetMinResolution(200, 10); + DownscaleEndsAt(1600, 800, 200, 100); +} + +TEST_F(QualityScalerTest, RespectsMinResolutionHeight) { + // Should end at 100x200, as height can't go lower. + qs_.SetMinResolution(10, 200); + DownscaleEndsAt(800, 1600, 100, 200); +} + } // namespace webrtc