aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/modules/video_coding/utility/quality_scaler_unittest.cc')
-rw-r--r--webrtc/modules/video_coding/utility/quality_scaler_unittest.cc419
1 files changed, 419 insertions, 0 deletions
diff --git a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
new file mode 100644
index 0000000000..2ce1107472
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2014 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 "webrtc/modules/video_coding/utility/include/quality_scaler.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+namespace {
+static const int kNumSeconds = 10;
+static const int kWidth = 1920;
+static const int kHalfWidth = kWidth / 2;
+static const int kHeight = 1080;
+static const int kFramerate = 30;
+static const int kLowQp = 15;
+static const int kNormalQp = 30;
+static const int kHighQp = 40;
+static const int kMaxQp = 56;
+} // namespace
+
+class QualityScalerTest : public ::testing::Test {
+ public:
+ // Temporal and spatial resolution.
+ struct Resolution {
+ int framerate;
+ int width;
+ int height;
+ };
+ protected:
+ enum ScaleDirection {
+ kKeepScaleAtHighQp,
+ kScaleDown,
+ kScaleDownAboveHighQp,
+ kScaleUp
+ };
+ enum BadQualityMetric { kDropFrame, kReportLowQP };
+
+ QualityScalerTest() {
+ input_frame_.CreateEmptyFrame(
+ kWidth, kHeight, kWidth, kHalfWidth, kHalfWidth);
+ qs_.Init(kMaxQp / QualityScaler::kDefaultLowQpDenominator, kHighQp, false);
+ qs_.ReportFramerate(kFramerate);
+ qs_.OnEncodeFrame(input_frame_);
+ }
+
+ bool TriggerScale(ScaleDirection scale_direction) {
+ qs_.OnEncodeFrame(input_frame_);
+ int initial_width = qs_.GetScaledResolution().width;
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ switch (scale_direction) {
+ case kScaleUp:
+ qs_.ReportQP(kLowQp);
+ break;
+ case kScaleDown:
+ qs_.ReportDroppedFrame();
+ break;
+ case kKeepScaleAtHighQp:
+ qs_.ReportQP(kHighQp);
+ break;
+ case kScaleDownAboveHighQp:
+ qs_.ReportQP(kHighQp + 1);
+ break;
+ }
+ qs_.OnEncodeFrame(input_frame_);
+ if (qs_.GetScaledResolution().width != initial_width)
+ return true;
+ }
+
+ return false;
+ }
+
+ void ExpectOriginalFrame() {
+ EXPECT_EQ(&input_frame_, &qs_.GetScaledFrame(input_frame_))
+ << "Using scaled frame instead of original input.";
+ }
+
+ void ExpectScaleUsingReportedResolution() {
+ qs_.OnEncodeFrame(input_frame_);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ const VideoFrame& scaled_frame = qs_.GetScaledFrame(input_frame_);
+ EXPECT_EQ(res.width, scaled_frame.width());
+ EXPECT_EQ(res.height, scaled_frame.height());
+ }
+
+ void ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+
+ void DoesNotDownscaleFrameDimensions(int width, int height);
+
+ Resolution TriggerResolutionChange(BadQualityMetric dropframe_lowqp,
+ int num_second,
+ int initial_framerate);
+
+ void VerifyQualityAdaptation(int initial_framerate, int seconds,
+ bool expect_spatial_resize,
+ bool expect_framerate_reduction);
+
+ void DownscaleEndsAt(int input_width,
+ int input_height,
+ int end_width,
+ int end_height);
+
+ QualityScaler qs_;
+ VideoFrame input_frame_;
+};
+
+TEST_F(QualityScalerTest, UsesOriginalFrameInitially) {
+ ExpectOriginalFrame();
+}
+
+TEST_F(QualityScalerTest, ReportsOriginalResolutionInitially) {
+ qs_.OnEncodeFrame(input_frame_);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ EXPECT_EQ(input_frame_.width(), res.width);
+ EXPECT_EQ(input_frame_.height(), res.height);
+}
+
+TEST_F(QualityScalerTest, DownscalesAfterContinuousFramedrop) {
+ EXPECT_TRUE(TriggerScale(kScaleDown)) << "No downscale within " << kNumSeconds
+ << " seconds.";
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ EXPECT_LT(res.width, input_frame_.width());
+ EXPECT_LT(res.height, input_frame_.height());
+}
+
+TEST_F(QualityScalerTest, KeepsScaleAtHighQp) {
+ EXPECT_FALSE(TriggerScale(kKeepScaleAtHighQp))
+ << "Downscale at high threshold which should keep scale.";
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ EXPECT_EQ(res.width, input_frame_.width());
+ EXPECT_EQ(res.height, input_frame_.height());
+}
+
+TEST_F(QualityScalerTest, DownscalesAboveHighQp) {
+ EXPECT_TRUE(TriggerScale(kScaleDownAboveHighQp))
+ << "No downscale within " << kNumSeconds << " seconds.";
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ EXPECT_LT(res.width, input_frame_.width());
+ EXPECT_LT(res.height, input_frame_.height());
+}
+
+TEST_F(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) {
+ for (int i = 0; i < kFramerate * kNumSeconds / 3; ++i) {
+ qs_.ReportQP(kNormalQp);
+ qs_.ReportDroppedFrame();
+ qs_.ReportDroppedFrame();
+ qs_.OnEncodeFrame(input_frame_);
+ if (qs_.GetScaledResolution().width < input_frame_.width())
+ return;
+ }
+
+ FAIL() << "No downscale within " << kNumSeconds << " seconds.";
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleOnNormalQp) {
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportQP(kNormalQp);
+ qs_.OnEncodeFrame(input_frame_);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution().width)
+ << "Unexpected scale on half framedrop.";
+ }
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) {
+ for (int i = 0; i < kFramerate * kNumSeconds / 2; ++i) {
+ qs_.ReportQP(kNormalQp);
+ qs_.OnEncodeFrame(input_frame_);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution().width)
+ << "Unexpected scale on half framedrop.";
+
+ qs_.ReportDroppedFrame();
+ qs_.OnEncodeFrame(input_frame_);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution().width)
+ << "Unexpected scale on half framedrop.";
+ }
+}
+
+void QualityScalerTest::ContinuouslyDownscalesByHalfDimensionsAndBackUp() {
+ const int initial_min_dimension = input_frame_.width() < input_frame_.height()
+ ? input_frame_.width()
+ : input_frame_.height();
+ int min_dimension = initial_min_dimension;
+ int current_shift = 0;
+ // Drop all frames to force-trigger downscaling.
+ while (min_dimension >= 2 * QualityScaler::kDefaultMinDownscaleDimension) {
+ EXPECT_TRUE(TriggerScale(kScaleDown)) << "No downscale within "
+ << kNumSeconds << " seconds.";
+ qs_.OnEncodeFrame(input_frame_);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ min_dimension = res.width < res.height ? res.width : res.height;
+ ++current_shift;
+ ASSERT_EQ(input_frame_.width() >> current_shift, res.width);
+ ASSERT_EQ(input_frame_.height() >> current_shift, res.height);
+ ExpectScaleUsingReportedResolution();
+ }
+
+ // Make sure we can scale back with good-quality frames.
+ while (min_dimension < initial_min_dimension) {
+ EXPECT_TRUE(TriggerScale(kScaleUp)) << "No upscale within " << kNumSeconds
+ << " seconds.";
+ qs_.OnEncodeFrame(input_frame_);
+ QualityScaler::Resolution res = qs_.GetScaledResolution();
+ min_dimension = res.width < res.height ? res.width : res.height;
+ --current_shift;
+ ASSERT_EQ(input_frame_.width() >> current_shift, res.width);
+ ASSERT_EQ(input_frame_.height() >> current_shift, res.height);
+ ExpectScaleUsingReportedResolution();
+ }
+
+ // Verify we don't start upscaling after further low use.
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportQP(kLowQp);
+ ExpectOriginalFrame();
+ }
+}
+
+TEST_F(QualityScalerTest, ContinuouslyDownscalesByHalfDimensionsAndBackUp) {
+ ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+}
+
+TEST_F(QualityScalerTest,
+ ContinuouslyDownscalesOddResolutionsByHalfDimensionsAndBackUp) {
+ const int kOddWidth = 517;
+ const int kHalfOddWidth = (kOddWidth + 1) / 2;
+ const int kOddHeight = 1239;
+ input_frame_.CreateEmptyFrame(
+ kOddWidth, kOddHeight, kOddWidth, kHalfOddWidth, kHalfOddWidth);
+ ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+}
+
+void QualityScalerTest::DoesNotDownscaleFrameDimensions(int width, int height) {
+ input_frame_.CreateEmptyFrame(
+ width, height, width, (width + 1) / 2, (width + 1) / 2);
+
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportDroppedFrame();
+ qs_.OnEncodeFrame(input_frame_);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution().width)
+ << "Unexpected scale of minimal-size frame.";
+ }
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1PxWidth) {
+ DoesNotDownscaleFrameDimensions(1, kHeight);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1PxHeight) {
+ DoesNotDownscaleFrameDimensions(kWidth, 1);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1Px) {
+ DoesNotDownscaleFrameDimensions(1, 1);
+}
+
+QualityScalerTest::Resolution QualityScalerTest::TriggerResolutionChange(
+ BadQualityMetric dropframe_lowqp, int num_second, int initial_framerate) {
+ QualityScalerTest::Resolution res;
+ res.framerate = initial_framerate;
+ qs_.OnEncodeFrame(input_frame_);
+ res.width = qs_.GetScaledResolution().width;
+ res.height = qs_.GetScaledResolution().height;
+ for (int i = 0; i < kFramerate * num_second; ++i) {
+ switch (dropframe_lowqp) {
+ case kReportLowQP:
+ qs_.ReportQP(kLowQp);
+ break;
+ case kDropFrame:
+ qs_.ReportDroppedFrame();
+ break;
+ }
+ qs_.OnEncodeFrame(input_frame_);
+ // Simulate the case when SetRates is called right after reducing
+ // framerate.
+ qs_.ReportFramerate(initial_framerate);
+ res.framerate = qs_.GetTargetFramerate();
+ if (res.framerate != -1)
+ qs_.ReportFramerate(res.framerate);
+ res.width = qs_.GetScaledResolution().width;
+ res.height = qs_.GetScaledResolution().height;
+ }
+ return res;
+}
+
+void QualityScalerTest::VerifyQualityAdaptation(
+ int initial_framerate, int seconds, bool expect_spatial_resize,
+ bool expect_framerate_reduction) {
+ const int kDisabledBadQpThreshold = kMaxQp + 1;
+ qs_.Init(kMaxQp / QualityScaler::kDefaultLowQpDenominator,
+ kDisabledBadQpThreshold, true);
+ qs_.OnEncodeFrame(input_frame_);
+ int init_width = qs_.GetScaledResolution().width;
+ int init_height = qs_.GetScaledResolution().height;
+
+ // Test reducing framerate by dropping frame continuously.
+ QualityScalerTest::Resolution res = TriggerResolutionChange(
+ kDropFrame, seconds, initial_framerate);
+
+ if (expect_framerate_reduction) {
+ EXPECT_LT(res.framerate, initial_framerate);
+ } else {
+ // No framerate reduction, video decimator should be disabled.
+ EXPECT_EQ(-1, res.framerate);
+ }
+
+ if (expect_spatial_resize) {
+ EXPECT_LT(res.width, init_width);
+ EXPECT_LT(res.height, init_height);
+ } else {
+ EXPECT_EQ(init_width, res.width);
+ EXPECT_EQ(init_height, res.height);
+ }
+
+ // The "seconds * 1.5" is to ensure spatial resolution to recover.
+ // For example, in 10 seconds test, framerate reduction happens in the first
+ // 5 seconds from 30fps to 15fps and causes the buffer size to be half of the
+ // original one. Then it will take only 75 samples to downscale (twice in 150
+ // samples). So to recover the resolution changes, we need more than 10
+ // seconds (i.e, seconds * 1.5). This is because the framerate increases
+ // before spatial size recovers, so it will take 150 samples to recover
+ // spatial size (300 for twice).
+ res = TriggerResolutionChange(kReportLowQP, seconds * 1.5, initial_framerate);
+ EXPECT_EQ(-1, res.framerate);
+ EXPECT_EQ(init_width, res.width);
+ EXPECT_EQ(init_height, res.height);
+}
+
+// In 5 seconds test, only framerate adjusting should happen.
+TEST_F(QualityScalerTest, ChangeFramerateOnly) {
+ VerifyQualityAdaptation(kFramerate, 5, false, true);
+}
+
+// In 10 seconds test, framerate adjusting and scaling are both
+// triggered, it shows that scaling would happen after framerate
+// adjusting.
+TEST_F(QualityScalerTest, ChangeFramerateAndSpatialSize) {
+ VerifyQualityAdaptation(kFramerate, 10, true, true);
+}
+
+// When starting from a low framerate, only spatial size will be changed.
+TEST_F(QualityScalerTest, ChangeSpatialSizeOnly) {
+ qs_.ReportFramerate(kFramerate >> 1);
+ VerifyQualityAdaptation(kFramerate >> 1, 10, true, false);
+}
+
+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();
+ 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