/* * 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 kMaxQp = 56; } // namespace class QualityScalerTest : public ::testing::Test { protected: enum ScaleDirection { kScaleDown, kScaleUp }; QualityScalerTest() { input_frame_.CreateEmptyFrame( kWidth, kHeight, kWidth, kHalfWidth, kHalfWidth); qs_.Init(kMaxQp / kDefaultLowQpDenominator); qs_.ReportFramerate(kFramerate); } void TriggerScale(ScaleDirection scale_direction) { int initial_width = qs_.GetScaledResolution(input_frame_).width; for (int i = 0; i < kFramerate * kNumSeconds; ++i) { switch (scale_direction) { case kScaleUp: qs_.ReportQP(kLowQp); break; case kScaleDown: qs_.ReportDroppedFrame(); break; } if (qs_.GetScaledResolution(input_frame_).width != initial_width) return; } FAIL() << "No downscale within " << kNumSeconds << " seconds."; } void ExpectOriginalFrame() { EXPECT_EQ(&input_frame_, &qs_.GetScaledFrame(input_frame_)) << "Using scaled frame instead of original input."; } void ExpectScaleUsingReportedResolution() { QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); const I420VideoFrame& 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); QualityScaler qs_; I420VideoFrame input_frame_; }; TEST_F(QualityScalerTest, UsesOriginalFrameInitially) { ExpectOriginalFrame(); } TEST_F(QualityScalerTest, ReportsOriginalResolutionInitially) { QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); EXPECT_EQ(input_frame_.width(), res.width); EXPECT_EQ(input_frame_.height(), res.height); } TEST_F(QualityScalerTest, DownscalesAfterContinuousFramedrop) { TriggerScale(kScaleDown); QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); 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(); if (qs_.GetScaledResolution(input_frame_).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); ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width) << "Unexpected scale on half framedrop."; } } TEST_F(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) { for (int i = 0; i < kFramerate * kNumSeconds / 2; ++i) { qs_.ReportQP(kNormalQp); ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width) << "Unexpected scale on half framedrop."; qs_.ReportDroppedFrame(); ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).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 > 16) { TriggerScale(kScaleDown); QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); 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) { TriggerScale(kScaleUp); QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_); 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(); ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).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); } } // namespace webrtc