1 /*
2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/video_coding/utility/quality_scaler.h"
12
13 #include <memory>
14 #include <string>
15
16 #include "rtc_base/checks.h"
17 #include "rtc_base/event.h"
18 #include "rtc_base/task_queue_for_test.h"
19 #include "test/field_trial.h"
20 #include "test/gtest.h"
21
22 namespace webrtc {
23 namespace {
24 static const int kFramerate = 30;
25 static const int kLowQp = 15;
26 static const int kHighQp = 40;
27 static const int kMinFramesNeededToScale = 60; // From quality_scaler.cc.
28 static const size_t kDefaultTimeoutMs = 150;
29 } // namespace
30
31 class MockQpUsageHandler : public QualityScalerQpUsageHandlerInterface {
32 public:
~MockQpUsageHandler()33 virtual ~MockQpUsageHandler() {}
34
35 // QualityScalerQpUsageHandlerInterface implementation.
OnReportQpUsageHigh(rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback)36 void OnReportQpUsageHigh(
37 rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback)
38 override {
39 callback_ = callback;
40 adapt_down_events_++;
41 event.Set();
42 if (synchronously_invoke_callback)
43 callback_->OnQpUsageHandled(true);
44 }
45
OnReportQpUsageLow(rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback)46 void OnReportQpUsageLow(
47 rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback)
48 override {
49 callback_ = callback;
50 adapt_up_events_++;
51 event.Set();
52 if (synchronously_invoke_callback)
53 callback_->OnQpUsageHandled(true);
54 }
55
56 rtc::Event event;
57 int adapt_up_events_ = 0;
58 int adapt_down_events_ = 0;
59 bool synchronously_invoke_callback = true;
60 rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface> callback_ =
61 nullptr;
62 };
63
64 // Pass a lower sampling period to speed up the tests.
65 class QualityScalerUnderTest : public QualityScaler {
66 public:
QualityScalerUnderTest(QualityScalerQpUsageHandlerInterface * handler,VideoEncoder::QpThresholds thresholds)67 explicit QualityScalerUnderTest(QualityScalerQpUsageHandlerInterface* handler,
68 VideoEncoder::QpThresholds thresholds)
69 : QualityScaler(handler, thresholds, 5) {}
70 };
71
72 class QualityScalerTest : public ::testing::Test,
73 public ::testing::WithParamInterface<std::string> {
74 protected:
75 enum ScaleDirection {
76 kKeepScaleAboveLowQp,
77 kKeepScaleAtHighQp,
78 kScaleDown,
79 kScaleDownAboveHighQp,
80 kScaleUp
81 };
82
QualityScalerTest()83 QualityScalerTest()
84 : scoped_field_trial_(GetParam()),
85 task_queue_("QualityScalerTestQueue"),
86 handler_(new MockQpUsageHandler()) {
87 task_queue_.SendTask(
88 [this] {
89 qs_ = std::unique_ptr<QualityScaler>(new QualityScalerUnderTest(
90 handler_.get(), VideoEncoder::QpThresholds(kLowQp, kHighQp)));
91 },
92 RTC_FROM_HERE);
93 }
94
~QualityScalerTest()95 ~QualityScalerTest() {
96 task_queue_.SendTask([this] { qs_ = nullptr; }, RTC_FROM_HERE);
97 }
98
TriggerScale(ScaleDirection scale_direction)99 void TriggerScale(ScaleDirection scale_direction) {
100 for (int i = 0; i < kFramerate * 5; ++i) {
101 switch (scale_direction) {
102 case kKeepScaleAboveLowQp:
103 qs_->ReportQp(kLowQp + 1, 0);
104 break;
105 case kScaleUp:
106 qs_->ReportQp(kLowQp, 0);
107 break;
108 case kScaleDown:
109 qs_->ReportDroppedFrameByMediaOpt();
110 break;
111 case kKeepScaleAtHighQp:
112 qs_->ReportQp(kHighQp, 0);
113 break;
114 case kScaleDownAboveHighQp:
115 qs_->ReportQp(kHighQp + 1, 0);
116 break;
117 }
118 }
119 }
120
121 test::ScopedFieldTrials scoped_field_trial_;
122 TaskQueueForTest task_queue_;
123 std::unique_ptr<QualityScaler> qs_;
124 std::unique_ptr<MockQpUsageHandler> handler_;
125 };
126
127 INSTANTIATE_TEST_SUITE_P(
128 FieldTrials,
129 QualityScalerTest,
130 ::testing::Values(
131 "WebRTC-Video-QualityScaling/Enabled-1,2,3,4,5,6,7,8,0.9,0.99,1/",
132 ""));
133
TEST_P(QualityScalerTest,DownscalesAfterContinuousFramedrop)134 TEST_P(QualityScalerTest, DownscalesAfterContinuousFramedrop) {
135 task_queue_.SendTask([this] { TriggerScale(kScaleDown); }, RTC_FROM_HERE);
136 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
137 EXPECT_EQ(1, handler_->adapt_down_events_);
138 EXPECT_EQ(0, handler_->adapt_up_events_);
139 }
140
TEST_P(QualityScalerTest,KeepsScaleAtHighQp)141 TEST_P(QualityScalerTest, KeepsScaleAtHighQp) {
142 task_queue_.SendTask([this] { TriggerScale(kKeepScaleAtHighQp); },
143 RTC_FROM_HERE);
144 EXPECT_FALSE(handler_->event.Wait(kDefaultTimeoutMs));
145 EXPECT_EQ(0, handler_->adapt_down_events_);
146 EXPECT_EQ(0, handler_->adapt_up_events_);
147 }
148
TEST_P(QualityScalerTest,DownscalesAboveHighQp)149 TEST_P(QualityScalerTest, DownscalesAboveHighQp) {
150 task_queue_.SendTask([this] { TriggerScale(kScaleDownAboveHighQp); },
151 RTC_FROM_HERE);
152 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
153 EXPECT_EQ(1, handler_->adapt_down_events_);
154 EXPECT_EQ(0, handler_->adapt_up_events_);
155 }
156
TEST_P(QualityScalerTest,DownscalesAfterTwoThirdsFramedrop)157 TEST_P(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) {
158 task_queue_.SendTask(
159 [this] {
160 for (int i = 0; i < kFramerate * 5; ++i) {
161 qs_->ReportDroppedFrameByMediaOpt();
162 qs_->ReportDroppedFrameByMediaOpt();
163 qs_->ReportQp(kHighQp, 0);
164 }
165 },
166 RTC_FROM_HERE);
167 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
168 EXPECT_EQ(1, handler_->adapt_down_events_);
169 EXPECT_EQ(0, handler_->adapt_up_events_);
170 }
171
TEST_P(QualityScalerTest,DoesNotDownscaleAfterHalfFramedrop)172 TEST_P(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) {
173 task_queue_.SendTask(
174 [this] {
175 for (int i = 0; i < kFramerate * 5; ++i) {
176 qs_->ReportDroppedFrameByMediaOpt();
177 qs_->ReportQp(kHighQp, 0);
178 }
179 },
180 RTC_FROM_HERE);
181 EXPECT_FALSE(handler_->event.Wait(kDefaultTimeoutMs));
182 EXPECT_EQ(0, handler_->adapt_down_events_);
183 EXPECT_EQ(0, handler_->adapt_up_events_);
184 }
185
TEST_P(QualityScalerTest,DownscalesAfterTwoThirdsIfFieldTrialEnabled)186 TEST_P(QualityScalerTest, DownscalesAfterTwoThirdsIfFieldTrialEnabled) {
187 const bool kDownScaleExpected = !GetParam().empty();
188 task_queue_.SendTask(
189 [this] {
190 for (int i = 0; i < kFramerate * 5; ++i) {
191 qs_->ReportDroppedFrameByMediaOpt();
192 qs_->ReportDroppedFrameByEncoder();
193 qs_->ReportQp(kHighQp, 0);
194 }
195 },
196 RTC_FROM_HERE);
197 EXPECT_EQ(kDownScaleExpected, handler_->event.Wait(kDefaultTimeoutMs));
198 EXPECT_EQ(kDownScaleExpected ? 1 : 0, handler_->adapt_down_events_);
199 EXPECT_EQ(0, handler_->adapt_up_events_);
200 }
201
TEST_P(QualityScalerTest,KeepsScaleOnNormalQp)202 TEST_P(QualityScalerTest, KeepsScaleOnNormalQp) {
203 task_queue_.SendTask([this] { TriggerScale(kKeepScaleAboveLowQp); },
204 RTC_FROM_HERE);
205 EXPECT_FALSE(handler_->event.Wait(kDefaultTimeoutMs));
206 EXPECT_EQ(0, handler_->adapt_down_events_);
207 EXPECT_EQ(0, handler_->adapt_up_events_);
208 }
209
TEST_P(QualityScalerTest,UpscalesAfterLowQp)210 TEST_P(QualityScalerTest, UpscalesAfterLowQp) {
211 task_queue_.SendTask([this] { TriggerScale(kScaleUp); }, RTC_FROM_HERE);
212 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
213 EXPECT_EQ(0, handler_->adapt_down_events_);
214 EXPECT_EQ(1, handler_->adapt_up_events_);
215 }
216
TEST_P(QualityScalerTest,ScalesDownAndBackUp)217 TEST_P(QualityScalerTest, ScalesDownAndBackUp) {
218 task_queue_.SendTask([this] { TriggerScale(kScaleDown); }, RTC_FROM_HERE);
219 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
220 EXPECT_EQ(1, handler_->adapt_down_events_);
221 EXPECT_EQ(0, handler_->adapt_up_events_);
222 task_queue_.SendTask([this] { TriggerScale(kScaleUp); }, RTC_FROM_HERE);
223 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
224 EXPECT_EQ(1, handler_->adapt_down_events_);
225 EXPECT_EQ(1, handler_->adapt_up_events_);
226 }
227
TEST_P(QualityScalerTest,DoesNotScaleUntilEnoughFramesObserved)228 TEST_P(QualityScalerTest, DoesNotScaleUntilEnoughFramesObserved) {
229 task_queue_.SendTask(
230 [this] {
231 // Not enough frames to make a decision.
232 for (int i = 0; i < kMinFramesNeededToScale - 1; ++i) {
233 qs_->ReportQp(kLowQp, 0);
234 }
235 },
236 RTC_FROM_HERE);
237 EXPECT_FALSE(handler_->event.Wait(kDefaultTimeoutMs));
238 task_queue_.SendTask(
239 [this] {
240 // Send 1 more. Enough frames observed, should result in an adapt
241 // request.
242 qs_->ReportQp(kLowQp, 0);
243 },
244 RTC_FROM_HERE);
245 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
246 EXPECT_EQ(0, handler_->adapt_down_events_);
247 EXPECT_EQ(1, handler_->adapt_up_events_);
248
249 // Samples should be cleared after an adapt request.
250 task_queue_.SendTask(
251 [this] {
252 // Not enough frames to make a decision.
253 qs_->ReportQp(kLowQp, 0);
254 },
255 RTC_FROM_HERE);
256 EXPECT_FALSE(handler_->event.Wait(kDefaultTimeoutMs));
257 EXPECT_EQ(0, handler_->adapt_down_events_);
258 EXPECT_EQ(1, handler_->adapt_up_events_);
259 }
260
TEST_P(QualityScalerTest,ScalesDownAndBackUpWithMinFramesNeeded)261 TEST_P(QualityScalerTest, ScalesDownAndBackUpWithMinFramesNeeded) {
262 task_queue_.SendTask(
263 [this] {
264 for (int i = 0; i < kMinFramesNeededToScale; ++i) {
265 qs_->ReportQp(kHighQp + 1, 0);
266 }
267 },
268 RTC_FROM_HERE);
269 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
270 EXPECT_EQ(1, handler_->adapt_down_events_);
271 EXPECT_EQ(0, handler_->adapt_up_events_);
272 // Samples cleared.
273 task_queue_.SendTask(
274 [this] {
275 for (int i = 0; i < kMinFramesNeededToScale; ++i) {
276 qs_->ReportQp(kLowQp, 0);
277 }
278 },
279 RTC_FROM_HERE);
280 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
281 EXPECT_EQ(1, handler_->adapt_down_events_);
282 EXPECT_EQ(1, handler_->adapt_up_events_);
283 }
284
TEST_P(QualityScalerTest,CheckingQpAgainRequiresResolvingCallback)285 TEST_P(QualityScalerTest, CheckingQpAgainRequiresResolvingCallback) {
286 handler_->synchronously_invoke_callback = false;
287 task_queue_.SendTask([this] { TriggerScale(kScaleDown); }, RTC_FROM_HERE);
288 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
289 EXPECT_EQ(1, handler_->adapt_down_events_);
290 // Without invoking the callback, another downscale should not happen.
291 handler_->event.Reset();
292 rtc::Event event;
293 task_queue_.SendTask(
294 [this, &event] {
295 TriggerScale(kScaleDown);
296 event.Set();
297 },
298 RTC_FROM_HERE);
299 EXPECT_TRUE(event.Wait(kDefaultTimeoutMs));
300 EXPECT_FALSE(handler_->event.Wait(0));
301 EXPECT_EQ(1, handler_->adapt_down_events_);
302 // Resume checking for QP again by invoking the callback.
303 task_queue_.SendTask(
304 [this] {
305 handler_->callback_->OnQpUsageHandled(true);
306 TriggerScale(kScaleDown);
307 },
308 RTC_FROM_HERE);
309 EXPECT_TRUE(handler_->event.Wait(kDefaultTimeoutMs));
310 EXPECT_EQ(2, handler_->adapt_down_events_);
311 task_queue_.SendTask([this] { handler_->callback_->OnQpUsageHandled(true); },
312 RTC_FROM_HERE);
313 }
314
315 } // namespace webrtc
316