1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "cc/metrics/video_playback_roughness_reporter.h"
12 #include "base/callback.h"
13 #include "base/test/bind.h"
14 #include "base/time/time.h"
15 #include "testing/gtest/include/gtest/gtest.h"
17 using VideoFrame = media::VideoFrame;
18 using VideoFrameMetadata = media::VideoFrameMetadata;
22 class VideoPlaybackRoughnessReporterTest : public ::testing::Test {
24 std::unique_ptr<VideoPlaybackRoughnessReporter> reporter_;
25 base::TimeTicks time_;
29 void SetReportingCallabck(T cb) {
30 reporter_ = std::make_unique<VideoPlaybackRoughnessReporter>(
31 base::BindLambdaForTesting(cb));
34 VideoPlaybackRoughnessReporter* reporter() {
36 return reporter_.get();
39 scoped_refptr<VideoFrame> MakeFrame(base::TimeDelta duration,
40 int frame_size = 100) {
41 scoped_refptr<VideoFrame> result = media::VideoFrame::CreateColorFrame(
42 gfx::Size(frame_size, frame_size), 0x80, 0x80, 0x80, base::TimeDelta());
43 result->metadata().wallclock_frame_duration = duration;
47 ::testing::AssertionResult CheckSizes() {
49 2 * size_t{VideoPlaybackRoughnessReporter::kMaxWindowSize};
50 if (reporter()->frames_.size() > max_frames)
51 return ::testing::AssertionFailure();
53 constexpr int max_worst_windows_size =
54 1 + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit *
55 (100 - VideoPlaybackRoughnessReporter::kPercentileToSubmit) /
57 if (reporter()->worst_windows_.size() > max_worst_windows_size)
58 return ::testing::AssertionFailure()
59 << "windows " << reporter()->worst_windows_.size();
60 return ::testing::AssertionSuccess();
63 void NormalRun(double fps,
65 std::vector<int> cadence,
67 int frame_size = 100) {
68 base::TimeDelta vsync = base::Seconds(1 / hz);
69 base::TimeDelta ideal_duration = base::Seconds(1 / fps);
70 for (int idx = 0; idx < frames; idx++) {
71 int frame_cadence = cadence[idx % cadence.size()];
72 base::TimeDelta duration = vsync * frame_cadence;
73 auto frame = MakeFrame(ideal_duration, frame_size);
74 reporter()->FrameSubmitted(token_, *frame, vsync);
75 reporter()->FramePresented(token_++, time_, true);
76 reporter()->ProcessFrameWindow();
81 void BatchPresentationRun(double fps,
83 std::vector<int> cadence,
85 base::TimeDelta vsync = base::Seconds(1 / hz);
86 base::TimeDelta ideal_duration = base::Seconds(1 / fps);
87 constexpr int batch_size = 3;
88 for (int idx = 0; idx < frames; idx++) {
89 auto frame = MakeFrame(ideal_duration);
90 reporter()->FrameSubmitted(idx, *frame, vsync);
91 if (idx % batch_size == batch_size - 1) {
92 for (int i = batch_size - 1; i >= 0; i--) {
93 int presented_idx = idx - i;
94 int frame_cadence = cadence[presented_idx % cadence.size()];
95 base::TimeDelta duration = vsync * frame_cadence;
96 reporter()->FramePresented(presented_idx, time_, true);
101 reporter()->ProcessFrameWindow();
105 void FreezingRun(double fps,
107 std::vector<int> cadence,
109 int frame_size = 100,
110 int freeze_on_frame = 50,
111 int frozen_vsyncs = 10) {
112 base::TimeDelta vsync = base::Seconds(1 / hz);
113 base::TimeDelta ideal_duration = base::Seconds(1 / fps);
114 for (int idx = 0; idx < frames; idx++) {
115 int frame_cadence = cadence[idx % cadence.size()];
116 base::TimeDelta duration = vsync * frame_cadence;
117 auto frame = MakeFrame(ideal_duration, frame_size);
118 reporter()->FrameSubmitted(token_, *frame, vsync);
119 reporter()->FramePresented(token_++, time_, true);
120 reporter()->ProcessFrameWindow();
121 if (idx == freeze_on_frame)
122 time_ += duration * frozen_vsyncs;
129 TEST_F(VideoPlaybackRoughnessReporterTest, BestCase24fps) {
132 SetReportingCallabck(
133 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
134 ASSERT_EQ(measurement.frames, fps);
135 ASSERT_EQ(measurement.refresh_rate_hz, 60);
136 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
137 ASSERT_NEAR(measurement.roughness, 5.9, 0.1);
138 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
142 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 10;
143 NormalRun(fps, 60, {2, 3}, frames_to_run);
144 EXPECT_EQ(call_count, 1);
147 TEST_F(VideoPlaybackRoughnessReporterTest, BestCase24fpsOn120Hz) {
150 SetReportingCallabck(
151 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
152 ASSERT_EQ(measurement.frames, fps);
153 ASSERT_EQ(measurement.refresh_rate_hz, 120);
154 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
155 ASSERT_NEAR(measurement.roughness, 0.0, 0.1);
156 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
160 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 10;
161 NormalRun(fps, 120, {5}, frames_to_run);
162 EXPECT_EQ(call_count, 1);
165 TEST_F(VideoPlaybackRoughnessReporterTest, BestCase30fps) {
168 SetReportingCallabck(
169 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
170 ASSERT_EQ(measurement.frames, fps);
171 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
172 ASSERT_NEAR(measurement.roughness, 0.0, 0.1);
173 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
177 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1;
178 NormalRun(fps, 60, {2}, frames_to_run);
179 EXPECT_EQ(call_count, 1);
182 // This cadence pattern was used in the small user study and was found
183 // to be perceived by participants as not as good as ideal 30fps playback but
184 // better than the pattern from UserStudyBad.
185 // The main characteristic of this test is that cadence breaks by having a frame
186 // shown only once, but the very next frame is being shown 3 times thus
187 // fixing the synchronization.
188 TEST_F(VideoPlaybackRoughnessReporterTest, UserStudyOkay) {
191 SetReportingCallabck(
192 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
193 ASSERT_EQ(measurement.frames, fps);
194 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
195 ASSERT_NEAR(measurement.roughness, 4.3, 0.1);
196 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
200 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1;
201 NormalRun(fps, 60, {2, 2, 2, 2, 2, 2, 1, 3, 2, 2, 2, 2, 2, 2, 2},
203 EXPECT_EQ(call_count, 1);
206 // This cadence pattern was used in the small user study and was found
207 // to be perceived as worst of all options in the study.
208 // The main characteristic of this test is that cadence breaks by having a frame
209 // shown only once, and it takes 2 more frames for a frame that is shown 3 times
210 // thus fixing the synchronization.
211 TEST_F(VideoPlaybackRoughnessReporterTest, UserStudyBad) {
214 SetReportingCallabck(
215 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
216 ASSERT_EQ(measurement.frames, fps);
217 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
218 ASSERT_NEAR(measurement.roughness, 7.46, 0.1);
219 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
223 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1;
224 NormalRun(fps, 60, {2, 2, 2, 2, 2, 1, 2, 2, 3, 2, 2, 2, 2, 2, 2},
226 EXPECT_EQ(call_count, 1);
229 TEST_F(VideoPlaybackRoughnessReporterTest, Glitchy24fps) {
232 SetReportingCallabck(
233 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
234 ASSERT_EQ(measurement.frames, fps);
235 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
236 ASSERT_NEAR(measurement.roughness, 14.8, 0.1);
237 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
241 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1;
242 NormalRun(fps, 60, {2, 3, 1, 3, 2, 4, 2, 3, 2, 3, 3, 3}, frames_to_run);
243 EXPECT_EQ(call_count, 1);
246 TEST_F(VideoPlaybackRoughnessReporterTest, BestCase60fps) {
249 SetReportingCallabck(
250 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
251 ASSERT_EQ(measurement.frames, fps);
252 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
253 ASSERT_NEAR(measurement.roughness, 0.0, 0.1);
254 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
258 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1;
259 NormalRun(fps, 60, {1}, frames_to_run);
260 EXPECT_EQ(call_count, 1);
263 TEST_F(VideoPlaybackRoughnessReporterTest, BestCase50fps) {
266 SetReportingCallabck(
267 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
268 ASSERT_EQ(measurement.frames, fps);
269 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
270 ASSERT_NEAR(measurement.roughness, 8.1, 01);
271 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
275 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1;
276 NormalRun(fps, 60, {1, 1, 1, 1, 2}, frames_to_run);
277 EXPECT_EQ(call_count, 1);
280 // Test that we understand the roughness algorithm by checking that we can
281 // get any result we need.
282 TEST_F(VideoPlaybackRoughnessReporterTest, PredictableRoughnessValue) {
284 int frames_in_window = fps;
286 double intended_roughness = 4.2;
287 base::TimeDelta vsync = base::Seconds(1.0 / fps);
288 // Calculating the error value that needs to be injected into one frame
289 // in order to get desired roughness.
290 base::TimeDelta error = base::Milliseconds(
291 std::sqrt(intended_roughness * intended_roughness * frames_in_window));
294 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
295 ASSERT_EQ(frames_in_window, measurement.frames);
296 ASSERT_NEAR(measurement.roughness, intended_roughness, 0.1);
297 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.0, 0.1);
300 SetReportingCallabck(callback);
303 for (int win_idx = 0; win_idx < win_count; win_idx++) {
304 for (int frame_idx = 0; frame_idx < frames_in_window; frame_idx++) {
305 base::TimeTicks time;
306 time += token * vsync;
307 if (frame_idx == frames_in_window - 1)
310 auto frame = MakeFrame(vsync);
311 reporter()->FrameSubmitted(token, *frame, vsync);
312 reporter()->FramePresented(token++, time, true);
313 reporter()->ProcessFrameWindow();
317 EXPECT_EQ(call_count, 1);
320 // Test that the reporter indeed takes 95% worst window.
321 TEST_F(VideoPlaybackRoughnessReporterTest, TakingPercentile) {
324 int frames_in_window = fps;
327 base::TimeDelta vsync = base::Seconds(1.0 / fps);
328 std::vector<double> targets;
329 targets.reserve(win_count);
330 for (int i = 0; i < win_count; i++)
331 targets.push_back(i * 0.1);
332 double expected_roughness =
333 VideoPlaybackRoughnessReporter::kPercentileToSubmit * 0.1;
335 std::shuffle(targets.begin(), targets.end(), rnd);
338 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
339 ASSERT_EQ(frames_in_window, measurement.frames);
340 ASSERT_NEAR(measurement.roughness, expected_roughness, 0.05);
343 SetReportingCallabck(callback);
345 for (int win_idx = 0; win_idx < win_count; win_idx++) {
346 double roughness = targets[win_idx];
347 // Calculating the error value that needs to be injected into one frame
348 // in order to get desired roughness.
349 base::TimeDelta error =
350 base::Milliseconds(std::sqrt(roughness * roughness * frames_in_window));
352 for (int frame_idx = 0; frame_idx < frames_in_window; frame_idx++) {
353 base::TimeTicks time;
354 time += token * vsync;
355 if (frame_idx == frames_in_window - 1)
358 auto frame = MakeFrame(vsync);
359 reporter()->FrameSubmitted(token, *frame, vsync);
360 reporter()->FramePresented(token++, time, true);
361 reporter()->ProcessFrameWindow();
365 EXPECT_EQ(call_count, 1);
368 // Test that even if no windows can be reported due to unstable presentation
369 // feedback, the reporter still doesn't run out of memory.
370 TEST_F(VideoPlaybackRoughnessReporterTest, LongRunWithoutWindows) {
372 base::TimeDelta vsync = base::Milliseconds(1);
373 SetReportingCallabck(
374 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
377 for (int i = 0; i < 10000; i++) {
378 auto frame = MakeFrame(vsync);
379 reporter()->FrameSubmitted(i, *frame, vsync);
381 reporter()->FramePresented(i, base::TimeTicks() + i * vsync, true);
382 reporter()->ProcessFrameWindow();
383 ASSERT_TRUE(CheckSizes());
385 EXPECT_EQ(call_count, 0);
388 // Test that the reporter is no spooked by FramePresented() on unknown frame
390 TEST_F(VideoPlaybackRoughnessReporterTest, PresentingUnknownFrames) {
392 base::TimeDelta vsync = base::Milliseconds(1);
393 SetReportingCallabck(
394 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
397 for (int i = 0; i < 10000; i++) {
398 auto frame = MakeFrame(vsync);
399 reporter()->FrameSubmitted(i, *frame, vsync);
400 reporter()->FramePresented(i + 100000, base::TimeTicks() + i * vsync, true);
401 reporter()->ProcessFrameWindow();
402 ASSERT_TRUE(CheckSizes());
404 EXPECT_EQ(call_count, 0);
407 // Test that the reporter is ignoring frames with unreliable
408 // presentation timestamp.
409 TEST_F(VideoPlaybackRoughnessReporterTest, IgnoringUnreliableTimings) {
411 base::TimeDelta vsync = base::Milliseconds(1);
412 SetReportingCallabck(
413 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
416 for (int i = 0; i < 10000; i++) {
417 auto frame = MakeFrame(vsync);
418 reporter()->FrameSubmitted(i, *frame, vsync);
419 reporter()->FramePresented(i, base::TimeTicks() + i * vsync, false);
420 reporter()->ProcessFrameWindow();
421 ASSERT_TRUE(CheckSizes());
423 EXPECT_EQ(call_count, 0);
426 // Test that Reset() causes reporting if there is sufficient number of windows
428 TEST_F(VideoPlaybackRoughnessReporterTest, ReportingInReset) {
432 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
435 SetReportingCallabck(callback);
437 // Set number of frames insufficient for reporting in Reset()
439 VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit * fps - 1;
440 NormalRun(fps, 60, {1}, frames_to_run);
441 // No calls since, not enough windows were reported
442 EXPECT_EQ(call_count, 0);
444 // Reset the reporter, still no calls
446 EXPECT_EQ(call_count, 0);
448 // Set number of frames sufficient for reporting in Reset()
450 VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit * fps + 1;
451 NormalRun(fps, 60, {1}, frames_to_run);
453 // No calls since, not enough windows were reported
454 EXPECT_EQ(call_count, 0);
456 // A window should be reported in the Reset()
458 EXPECT_EQ(call_count, 1);
461 // Test that a change of display refresh rate or frame size causes reporting
462 // iff there is sufficient number of windows accumulated.
463 TEST_F(VideoPlaybackRoughnessReporterTest, ReportingAfterParameterChange) {
469 std::vector<Report> reports;
472 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
473 reports.push_back({measurement.refresh_rate_hz,
474 measurement.frame_size.height(),
475 measurement.roughness});
477 SetReportingCallabck(callback);
480 (VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit - 1) * fps + 3;
481 NormalRun(fps, 59, {1}, frames_to_run, 480);
482 ASSERT_TRUE(reports.empty());
485 (VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit + 1) * fps + 3;
486 NormalRun(fps, 60, {1}, frames_to_run, 480);
487 // Check that if parameters change after only a few windows, nothing gets
489 ASSERT_TRUE(reports.empty());
492 (VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit + 1) * fps + 3;
493 NormalRun(fps, 120, {2}, frames_to_run, 481);
495 // Check that if parameters change after sufficient number of windows
496 // roughness is reported. The second report is done normally after max
497 // number of windows is seen.
498 ASSERT_EQ(reports.size(), 2u);
499 EXPECT_EQ(reports[0].hz, 60);
500 EXPECT_EQ(reports[0].height, 480);
501 EXPECT_EQ(reports[0].roughness, 0.0);
502 EXPECT_EQ(reports[1].hz, 120);
503 EXPECT_EQ(reports[1].height, 481);
504 EXPECT_EQ(reports[1].roughness, 0.0);
507 // Test that reporting works even if frame presentation signal come out of
509 TEST_F(VideoPlaybackRoughnessReporterTest, BatchPresentation) {
514 SetReportingCallabck(
515 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
516 ASSERT_EQ(measurement.frames, fps);
517 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
518 ASSERT_NEAR(measurement.roughness, 0.0, 0.1);
522 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 10;
523 BatchPresentationRun(fps, 60, {1}, frames_to_run);
524 EXPECT_EQ(call_count, 1);
527 SetReportingCallabck(
528 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
529 ASSERT_EQ(measurement.frames, fps);
530 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
531 ASSERT_NEAR(measurement.roughness, 5.9, 0.1);
532 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0, 0.01);
537 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 10;
538 BatchPresentationRun(fps, 60, {2, 3}, frames_to_run);
539 EXPECT_EQ(call_count, 2);
542 TEST_F(VideoPlaybackRoughnessReporterTest, Freezing30fps) {
545 SetReportingCallabck(
546 [&](const VideoPlaybackRoughnessReporter::Measurement& measurement) {
547 ASSERT_EQ(measurement.frames, fps);
548 ASSERT_NEAR(measurement.duration.InMillisecondsF(), 1000.0, 1.0);
549 ASSERT_NEAR(measurement.roughness, 0.0, 0.1);
550 ASSERT_NEAR(measurement.freezing.InSecondsF(), 0.25, 0.05);
554 VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1;
555 FreezingRun(fps, 60, {2}, frames_to_run);
556 EXPECT_EQ(call_count, 1);