/* * 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 "modules/video_coding/utility/quality_scaler.h" #include #include #include "rtc_base/checks.h" #include "rtc_base/event.h" #include "rtc_base/task_queue.h" #include "test/field_trial.h" #include "test/gtest.h" namespace webrtc { namespace { static const int kFramerate = 30; static const int kLowQp = 15; static const int kHighQp = 40; static const int kMinFramesNeededToScale = 60; // From quality_scaler.cc. static const size_t kDefaultTimeoutMs = 150; } // namespace #define DO_SYNC(q, block) \ do { \ rtc::Event event; \ q->PostTask([this, &event] { \ block; \ event.Set(); \ }); \ RTC_CHECK(event.Wait(1000)); \ } while (0) class MockAdaptationObserver : public AdaptationObserverInterface { public: virtual ~MockAdaptationObserver() {} void AdaptUp(AdaptReason r) override { adapt_up_events_++; event.Set(); } void AdaptDown(AdaptReason r) override { adapt_down_events_++; event.Set(); } rtc::Event event; int adapt_up_events_ = 0; int adapt_down_events_ = 0; }; // Pass a lower sampling period to speed up the tests. class QualityScalerUnderTest : public QualityScaler { public: explicit QualityScalerUnderTest(rtc::TaskQueue* task_queue, AdaptationObserverInterface* observer, VideoEncoder::QpThresholds thresholds) : QualityScaler(task_queue, observer, thresholds, 5) {} }; class QualityScalerTest : public ::testing::Test, public ::testing::WithParamInterface { protected: enum ScaleDirection { kKeepScaleAboveLowQp, kKeepScaleAtHighQp, kScaleDown, kScaleDownAboveHighQp, kScaleUp }; QualityScalerTest() : scoped_field_trial_(GetParam()), q_(new rtc::TaskQueue("QualityScalerTestQueue")), observer_(new MockAdaptationObserver()) { DO_SYNC(q_, { qs_ = std::unique_ptr(new QualityScalerUnderTest( q_.get(), observer_.get(), VideoEncoder::QpThresholds(kLowQp, kHighQp))); }); } ~QualityScalerTest() { DO_SYNC(q_, { qs_.reset(nullptr); }); } void TriggerScale(ScaleDirection scale_direction) { for (int i = 0; i < kFramerate * 5; ++i) { switch (scale_direction) { case kKeepScaleAboveLowQp: qs_->ReportQp(kLowQp + 1, 0); break; case kScaleUp: qs_->ReportQp(kLowQp, 0); break; case kScaleDown: qs_->ReportDroppedFrameByMediaOpt(); break; case kKeepScaleAtHighQp: qs_->ReportQp(kHighQp, 0); break; case kScaleDownAboveHighQp: qs_->ReportQp(kHighQp + 1, 0); break; } } } test::ScopedFieldTrials scoped_field_trial_; std::unique_ptr q_; std::unique_ptr qs_; std::unique_ptr observer_; }; INSTANTIATE_TEST_SUITE_P( FieldTrials, QualityScalerTest, ::testing::Values( "WebRTC-Video-QualityScaling/Enabled-1,2,3,4,5,6,7,8,0.9,0.99,1/", "")); TEST_P(QualityScalerTest, DownscalesAfterContinuousFramedrop) { DO_SYNC(q_, { TriggerScale(kScaleDown); }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(1, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, KeepsScaleAtHighQp) { DO_SYNC(q_, { TriggerScale(kKeepScaleAtHighQp); }); EXPECT_FALSE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(0, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, DownscalesAboveHighQp) { DO_SYNC(q_, { TriggerScale(kScaleDownAboveHighQp); }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(1, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) { DO_SYNC(q_, { for (int i = 0; i < kFramerate * 5; ++i) { qs_->ReportDroppedFrameByMediaOpt(); qs_->ReportDroppedFrameByMediaOpt(); qs_->ReportQp(kHighQp, 0); } }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(1, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) { DO_SYNC(q_, { for (int i = 0; i < kFramerate * 5; ++i) { qs_->ReportDroppedFrameByMediaOpt(); qs_->ReportQp(kHighQp, 0); } }); EXPECT_FALSE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(0, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, DownscalesAfterTwoThirdsIfFieldTrialEnabled) { const bool kDownScaleExpected = !GetParam().empty(); DO_SYNC(q_, { for (int i = 0; i < kFramerate * 5; ++i) { qs_->ReportDroppedFrameByMediaOpt(); qs_->ReportDroppedFrameByEncoder(); qs_->ReportQp(kHighQp, 0); } }); EXPECT_EQ(kDownScaleExpected, observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(kDownScaleExpected ? 1 : 0, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, KeepsScaleOnNormalQp) { DO_SYNC(q_, { TriggerScale(kKeepScaleAboveLowQp); }); EXPECT_FALSE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(0, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, UpscalesAfterLowQp) { DO_SYNC(q_, { TriggerScale(kScaleUp); }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(0, observer_->adapt_down_events_); EXPECT_EQ(1, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, ScalesDownAndBackUp) { DO_SYNC(q_, { TriggerScale(kScaleDown); }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(1, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); DO_SYNC(q_, { TriggerScale(kScaleUp); }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(1, observer_->adapt_down_events_); EXPECT_EQ(1, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, DoesNotScaleUntilEnoughFramesObserved) { DO_SYNC(q_, { // Not enough frames to make a decision. for (int i = 0; i < kMinFramesNeededToScale - 1; ++i) { qs_->ReportQp(kLowQp, 0); } }); EXPECT_FALSE(observer_->event.Wait(kDefaultTimeoutMs)); DO_SYNC(q_, { // Send 1 more. Enough frames observed, should result in an adapt request. qs_->ReportQp(kLowQp, 0); }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(0, observer_->adapt_down_events_); EXPECT_EQ(1, observer_->adapt_up_events_); // Samples should be cleared after an adapt request. DO_SYNC(q_, { // Not enough frames to make a decision. qs_->ReportQp(kLowQp, 0); }); EXPECT_FALSE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(0, observer_->adapt_down_events_); EXPECT_EQ(1, observer_->adapt_up_events_); } TEST_P(QualityScalerTest, ScalesDownAndBackUpWithMinFramesNeeded) { DO_SYNC(q_, { for (int i = 0; i < kMinFramesNeededToScale; ++i) { qs_->ReportQp(kHighQp + 1, 0); } }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(1, observer_->adapt_down_events_); EXPECT_EQ(0, observer_->adapt_up_events_); // Samples cleared. DO_SYNC(q_, { for (int i = 0; i < kMinFramesNeededToScale; ++i) { qs_->ReportQp(kLowQp, 0); } }); EXPECT_TRUE(observer_->event.Wait(kDefaultTimeoutMs)); EXPECT_EQ(1, observer_->adapt_down_events_); EXPECT_EQ(1, observer_->adapt_up_events_); } } // namespace webrtc #undef DO_SYNC