Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(268)

Side by Side Diff: media/capture/content/animated_content_sampler_unittest.cc

Issue 2143903003: [WIP] Move media/capture to device/capture (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/capture/content/animated_content_sampler.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <cmath>
11 #include <memory>
12 #include <utility>
13 #include <vector>
14
15 #include "base/logging.h"
16 #include "base/time/time.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "ui/gfx/geometry/rect.h"
19
20 namespace media {
21
22 namespace {
23
24 base::TimeTicks InitialTestTimeTicks() {
25 return base::TimeTicks() + base::TimeDelta::FromSeconds(1);
26 }
27
28 base::TimeDelta FpsAsPeriod(int frame_rate) {
29 return base::TimeDelta::FromSeconds(1) / frame_rate;
30 }
31
32 } // namespace
33
34 class AnimatedContentSamplerTest : public ::testing::Test {
35 public:
36 AnimatedContentSamplerTest() {}
37 ~AnimatedContentSamplerTest() override {}
38
39 void SetUp() override {
40 rand_seed_ = static_cast<int>(
41 (InitialTestTimeTicks() - base::TimeTicks()).InMicroseconds());
42 sampler_.reset(new AnimatedContentSampler(GetMinCapturePeriod()));
43 }
44
45 protected:
46 // Overridden by subclass for parameterized tests.
47 virtual base::TimeDelta GetMinCapturePeriod() const {
48 return base::TimeDelta::FromSeconds(1) / 30;
49 }
50
51 AnimatedContentSampler* sampler() const { return sampler_.get(); }
52
53 int GetRandomInRange(int begin, int end) {
54 const int len = end - begin;
55 const int rand_offset = (len == 0) ? 0 : (NextRandomInt() % (end - begin));
56 return begin + rand_offset;
57 }
58
59 gfx::Rect GetRandomDamageRect() {
60 return gfx::Rect(0, 0, GetRandomInRange(1, 100), GetRandomInRange(1, 100));
61 }
62
63 gfx::Rect GetContentDamageRect() {
64 // This must be distinct from anything GetRandomDamageRect() could return.
65 return gfx::Rect(0, 0, 1280, 720);
66 }
67
68 // Directly inject an observation. Only used to test
69 // ElectMajorityDamageRect().
70 void ObserveDamageRect(const gfx::Rect& damage_rect) {
71 sampler_->observations_.push_back(
72 AnimatedContentSampler::Observation(damage_rect, base::TimeTicks()));
73 }
74
75 gfx::Rect ElectMajorityDamageRect() const {
76 return sampler_->ElectMajorityDamageRect();
77 }
78
79 static base::TimeDelta ComputeSamplingPeriod(
80 base::TimeDelta detected_period,
81 base::TimeDelta target_sampling_period,
82 base::TimeDelta min_capture_period) {
83 return AnimatedContentSampler::ComputeSamplingPeriod(
84 detected_period, target_sampling_period, min_capture_period);
85 }
86
87 private:
88 // Note: Not using base::RandInt() because it is horribly slow on debug
89 // builds. The following is a very simple, deterministic LCG:
90 int NextRandomInt() {
91 rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31);
92 return rand_seed_;
93 }
94
95 int rand_seed_;
96 std::unique_ptr<AnimatedContentSampler> sampler_;
97 };
98
99 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromZeroDamageRects) {
100 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
101 }
102
103 TEST_F(AnimatedContentSamplerTest, ElectsMajorityFromOneDamageRect) {
104 const gfx::Rect the_one_rect(0, 0, 1, 1);
105 ObserveDamageRect(the_one_rect);
106 EXPECT_EQ(the_one_rect, ElectMajorityDamageRect());
107 }
108
109 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromTwoDamageRectsOfSameArea) {
110 const gfx::Rect one_rect(0, 0, 1, 1);
111 const gfx::Rect another_rect(1, 1, 1, 1);
112 ObserveDamageRect(one_rect);
113 ObserveDamageRect(another_rect);
114 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
115 }
116
117 TEST_F(AnimatedContentSamplerTest, ElectsLargerOfTwoDamageRects_1) {
118 const gfx::Rect one_rect(0, 0, 1, 1);
119 const gfx::Rect another_rect(0, 0, 2, 2);
120 ObserveDamageRect(one_rect);
121 ObserveDamageRect(another_rect);
122 EXPECT_EQ(another_rect, ElectMajorityDamageRect());
123 }
124
125 TEST_F(AnimatedContentSamplerTest, ElectsLargerOfTwoDamageRects_2) {
126 const gfx::Rect one_rect(0, 0, 2, 2);
127 const gfx::Rect another_rect(0, 0, 1, 1);
128 ObserveDamageRect(one_rect);
129 ObserveDamageRect(another_rect);
130 EXPECT_EQ(one_rect, ElectMajorityDamageRect());
131 }
132
133 TEST_F(AnimatedContentSamplerTest, ElectsSameAsMooreDemonstration) {
134 // A more complex sequence (from Moore's web site): Three different Rects with
135 // the same area, but occurring a different number of times. C should win the
136 // vote.
137 const gfx::Rect rect_a(0, 0, 1, 4);
138 const gfx::Rect rect_b(1, 1, 4, 1);
139 const gfx::Rect rect_c(2, 2, 2, 2);
140 for (int i = 0; i < 3; ++i)
141 ObserveDamageRect(rect_a);
142 for (int i = 0; i < 2; ++i)
143 ObserveDamageRect(rect_c);
144 for (int i = 0; i < 2; ++i)
145 ObserveDamageRect(rect_b);
146 for (int i = 0; i < 3; ++i)
147 ObserveDamageRect(rect_c);
148 ObserveDamageRect(rect_b);
149 for (int i = 0; i < 2; ++i)
150 ObserveDamageRect(rect_c);
151 EXPECT_EQ(rect_c, ElectMajorityDamageRect());
152 }
153
154 TEST_F(AnimatedContentSamplerTest, Elects24FpsVideoInsteadOf48FpsSpinner) {
155 // Scenario: 24 FPS 720x480 Video versus 48 FPS 96x96 "Busy Spinner"
156 const gfx::Rect video_rect(100, 100, 720, 480);
157 const gfx::Rect spinner_rect(360, 0, 96, 96);
158 for (int i = 0; i < 100; ++i) {
159 // |video_rect| occurs once for every two |spinner_rect|. Vary the order
160 // of events between the two:
161 ObserveDamageRect(video_rect);
162 ObserveDamageRect(spinner_rect);
163 ObserveDamageRect(spinner_rect);
164 ObserveDamageRect(video_rect);
165 ObserveDamageRect(spinner_rect);
166 ObserveDamageRect(spinner_rect);
167 ObserveDamageRect(spinner_rect);
168 ObserveDamageRect(video_rect);
169 ObserveDamageRect(spinner_rect);
170 ObserveDamageRect(spinner_rect);
171 ObserveDamageRect(video_rect);
172 ObserveDamageRect(spinner_rect);
173 }
174 EXPECT_EQ(video_rect, ElectMajorityDamageRect());
175 }
176
177 TEST_F(AnimatedContentSamplerTest, TargetsSamplingPeriod) {
178 struct Helper {
179 static void RunTargetSamplingPeriodTest(int target_fps) {
180 const base::TimeDelta min_capture_period = FpsAsPeriod(60);
181 const base::TimeDelta target_sampling_period = FpsAsPeriod(target_fps);
182
183 for (int content_fps = 1; content_fps <= 60; ++content_fps) {
184 const base::TimeDelta content_period = FpsAsPeriod(content_fps);
185 const base::TimeDelta sampling_period = ComputeSamplingPeriod(
186 content_period, target_sampling_period, min_capture_period);
187 if (content_period >= target_sampling_period) {
188 ASSERT_EQ(content_period, sampling_period);
189 } else {
190 ASSERT_LE(min_capture_period, sampling_period);
191
192 // Check that the sampling rate is as close (or closer) to the target
193 // sampling rate than any integer-subsampling of the content frame
194 // rate.
195 const double absolute_diff =
196 std::abs(1.0 / sampling_period.InSecondsF() - target_fps);
197 const double fudge_for_acceptable_rounding_error = 0.005;
198 for (double divisor = 1; divisor < 4; ++divisor) {
199 SCOPED_TRACE(::testing::Message() << "target_fps=" << target_fps
200 << ", content_fps=" << content_fps
201 << ", divisor=" << divisor);
202 ASSERT_GE(std::abs(content_fps / divisor - target_fps),
203 absolute_diff - fudge_for_acceptable_rounding_error);
204 }
205 }
206 }
207 }
208 };
209
210 for (int target_fps = 1; target_fps <= 60; ++target_fps)
211 Helper::RunTargetSamplingPeriodTest(target_fps);
212 }
213
214 namespace {
215
216 // A test scenario for AnimatedContentSamplerParameterizedTest.
217 struct Scenario {
218 base::TimeDelta vsync_interval; // Reflects compositor's update rate.
219 base::TimeDelta min_capture_period; // Reflects maximum capture rate.
220 base::TimeDelta content_period; // Reflects content animation rate.
221 base::TimeDelta target_sampling_period;
222
223 Scenario(int compositor_frequency, int max_frame_rate, int content_frame_rate)
224 : vsync_interval(FpsAsPeriod(compositor_frequency)),
225 min_capture_period(FpsAsPeriod(max_frame_rate)),
226 content_period(FpsAsPeriod(content_frame_rate)) {
227 CHECK(content_period >= vsync_interval)
228 << "Bad test params: Impossible to animate faster than the compositor.";
229 }
230
231 Scenario(int compositor_frequency,
232 int max_frame_rate,
233 int content_frame_rate,
234 int target_sampling_rate)
235 : vsync_interval(FpsAsPeriod(compositor_frequency)),
236 min_capture_period(FpsAsPeriod(max_frame_rate)),
237 content_period(FpsAsPeriod(content_frame_rate)),
238 target_sampling_period(FpsAsPeriod(target_sampling_rate)) {
239 CHECK(content_period >= vsync_interval)
240 << "Bad test params: Impossible to animate faster than the compositor.";
241 }
242 };
243
244 // Value printer for Scenario.
245 ::std::ostream& operator<<(::std::ostream& os, const Scenario& s) {
246 return os << "{ vsync_interval=" << s.vsync_interval.InMicroseconds()
247 << ", min_capture_period=" << s.min_capture_period.InMicroseconds()
248 << ", content_period=" << s.content_period.InMicroseconds() << " }";
249 }
250
251 } // namespace
252
253 class AnimatedContentSamplerParameterizedTest
254 : public AnimatedContentSamplerTest,
255 public ::testing::WithParamInterface<Scenario> {
256 public:
257 AnimatedContentSamplerParameterizedTest()
258 : count_dropped_frames_(0), count_sampled_frames_(0) {}
259 virtual ~AnimatedContentSamplerParameterizedTest() {}
260
261 void SetUp() override {
262 AnimatedContentSamplerTest::SetUp();
263 sampler()->SetTargetSamplingPeriod(GetParam().target_sampling_period);
264 }
265
266 protected:
267 typedef std::pair<gfx::Rect, base::TimeTicks> Event;
268
269 base::TimeDelta GetMinCapturePeriod() const override {
270 return GetParam().min_capture_period;
271 }
272
273 base::TimeDelta ComputeExpectedSamplingPeriod() const {
274 return AnimatedContentSamplerTest::ComputeSamplingPeriod(
275 GetParam().content_period, GetParam().target_sampling_period,
276 GetParam().min_capture_period);
277 }
278
279 // Generate a sequence of events from the compositor pipeline. The event
280 // times will all be at compositor vsync boundaries.
281 std::vector<Event> GenerateEventSequence(base::TimeTicks begin,
282 base::TimeTicks end,
283 bool include_content_frame_events,
284 bool include_random_events,
285 base::TimeTicks* next_begin_time) {
286 DCHECK(GetParam().content_period >= GetParam().vsync_interval);
287 base::TimeTicks next_content_time = begin;
288 std::vector<Event> events;
289 base::TimeTicks compositor_time;
290 for (compositor_time = begin; compositor_time < end;
291 compositor_time += GetParam().vsync_interval) {
292 if (next_content_time <= compositor_time) {
293 next_content_time += GetParam().content_period;
294 if (include_content_frame_events) {
295 events.push_back(Event(GetContentDamageRect(), compositor_time));
296 continue;
297 }
298 }
299 if (include_random_events && GetRandomInRange(0, 1) == 0) {
300 events.push_back(Event(GetRandomDamageRect(), compositor_time));
301 }
302 }
303
304 if (next_begin_time) {
305 while (compositor_time < next_content_time)
306 compositor_time += GetParam().vsync_interval;
307 *next_begin_time = compositor_time;
308 }
309
310 DCHECK(!events.empty());
311 return events;
312 }
313
314 // Feed |events| through the sampler, and detect whether the expected
315 // lock-in/out transition occurs. Also, track and measure the frame drop
316 // ratio and check it against the expected drop rate.
317 void RunEventSequence(const std::vector<Event> events,
318 bool was_detecting_before,
319 bool is_detecting_after,
320 bool simulate_pipeline_back_pressure,
321 const char* description) {
322 SCOPED_TRACE(::testing::Message() << "Description: " << description);
323
324 gfx::Rect first_detected_region;
325
326 EXPECT_EQ(was_detecting_before, sampler()->HasProposal());
327 bool has_detection_switched = false;
328 bool has_detection_flip_flopped_once = false;
329 ResetFrameCounters();
330 for (std::vector<Event>::const_iterator i = events.begin();
331 i != events.end(); ++i) {
332 sampler()->ConsiderPresentationEvent(i->first, i->second);
333
334 // Detect when the sampler locks in/out, and that it stays that way for
335 // all further iterations of this loop. It is permissible for the lock-in
336 // to flip-flop once, but no more than that.
337 if (!has_detection_switched &&
338 was_detecting_before != sampler()->HasProposal()) {
339 has_detection_switched = true;
340 } else if (has_detection_switched &&
341 is_detecting_after != sampler()->HasProposal()) {
342 ASSERT_FALSE(has_detection_flip_flopped_once);
343 has_detection_flip_flopped_once = true;
344 has_detection_switched = false;
345 }
346 ASSERT_EQ(
347 has_detection_switched ? is_detecting_after : was_detecting_before,
348 sampler()->HasProposal());
349
350 if (sampler()->HasProposal()) {
351 // Make sure the sampler doesn't flip-flop and keep proposing sampling
352 // based on locking into different regions.
353 if (first_detected_region.IsEmpty()) {
354 first_detected_region = sampler()->detected_region();
355 ASSERT_FALSE(first_detected_region.IsEmpty());
356 } else {
357 EXPECT_EQ(first_detected_region, sampler()->detected_region());
358 }
359
360 if (simulate_pipeline_back_pressure && GetRandomInRange(0, 2) == 0)
361 ClientCannotSampleFrame(*i);
362 else
363 ClientDoesWhatSamplerProposes(*i);
364 } else {
365 EXPECT_FALSE(sampler()->ShouldSample());
366 if (!simulate_pipeline_back_pressure || GetRandomInRange(0, 2) == 1)
367 sampler()->RecordSample(i->second);
368 }
369 }
370 EXPECT_EQ(is_detecting_after, sampler()->HasProposal());
371 ExpectFrameDropRatioIsCorrect();
372 }
373
374 void ResetFrameCounters() {
375 count_dropped_frames_ = 0;
376 count_sampled_frames_ = 0;
377 }
378
379 // Keep track what the sampler is proposing, and call RecordSample() if it
380 // proposes sampling |event|.
381 void ClientDoesWhatSamplerProposes(const Event& event) {
382 if (sampler()->ShouldSample()) {
383 EXPECT_EQ(GetContentDamageRect(), event.first);
384 sampler()->RecordSample(sampler()->frame_timestamp());
385 ++count_sampled_frames_;
386 } else if (event.first == GetContentDamageRect()) {
387 ++count_dropped_frames_;
388 }
389 }
390
391 // RecordSample() is not called, but for testing, keep track of what the
392 // sampler is proposing for |event|.
393 void ClientCannotSampleFrame(const Event& event) {
394 if (sampler()->ShouldSample()) {
395 EXPECT_EQ(GetContentDamageRect(), event.first);
396 ++count_sampled_frames_;
397 } else if (event.first == GetContentDamageRect()) {
398 ++count_dropped_frames_;
399 }
400 }
401
402 // Confirm the AnimatedContentSampler is not dropping more frames than
403 // expected, given current test parameters.
404 void ExpectFrameDropRatioIsCorrect() {
405 if (count_sampled_frames_ == 0) {
406 EXPECT_EQ(0, count_dropped_frames_);
407 return;
408 }
409 const double expected_sampling_ratio =
410 GetParam().content_period.InSecondsF() /
411 ComputeExpectedSamplingPeriod().InSecondsF();
412 const int total_frames = count_dropped_frames_ + count_sampled_frames_;
413 EXPECT_NEAR(total_frames * expected_sampling_ratio, count_sampled_frames_,
414 1.5);
415 EXPECT_NEAR(total_frames * (1.0 - expected_sampling_ratio),
416 count_dropped_frames_, 1.5);
417 }
418
419 private:
420 // These counters only include the frames with the desired content.
421 int count_dropped_frames_;
422 int count_sampled_frames_;
423 };
424
425 // Tests that the implementation locks in/out of frames containing stable
426 // animated content, whether or not random events are also simultaneously
427 // present.
428 TEST_P(AnimatedContentSamplerParameterizedTest, DetectsAnimatedContent) {
429 // |begin| refers to the start of an event sequence in terms of the
430 // Compositor's clock.
431 base::TimeTicks begin = InitialTestTimeTicks();
432
433 // Provide random events and expect no lock-in.
434 RunEventSequence(
435 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
436 false, true, &begin),
437 false, false, false, "Provide random events and expect no lock-in.");
438 if (HasFailure())
439 return;
440
441 // Provide content frame events with some random events mixed-in, and expect
442 // the sampler to lock-in.
443 RunEventSequence(
444 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
445 true, true, &begin),
446 false, true, false,
447 "Provide content frame events with some random events mixed-in, and "
448 "expect the sampler to lock-in.");
449 if (HasFailure())
450 return;
451
452 // Continue providing content frame events without the random events mixed-in
453 // and expect the lock-in to hold.
454 RunEventSequence(
455 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
456 true, false, &begin),
457 true, true, false,
458 "Continue providing content frame events without the random events "
459 "mixed-in and expect the lock-in to hold.");
460 if (HasFailure())
461 return;
462
463 // Continue providing just content frame events and expect the lock-in to
464 // hold. Also simulate the capture pipeline experiencing back pressure.
465 RunEventSequence(
466 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(20),
467 true, false, &begin),
468 true, true, true,
469 "Continue providing just content frame events and expect the lock-in to "
470 "hold. Also simulate the capture pipeline experiencing back pressure.");
471 if (HasFailure())
472 return;
473
474 // Provide a half-second of random events only, and expect the lock-in to be
475 // broken.
476 RunEventSequence(
477 GenerateEventSequence(begin,
478 begin + base::TimeDelta::FromMilliseconds(500),
479 false, true, &begin),
480 true, false, false,
481 "Provide a half-second of random events only, and expect the lock-in to "
482 "be broken.");
483 if (HasFailure())
484 return;
485
486 // Now, go back to providing content frame events, and expect the sampler to
487 // lock-in once again.
488 RunEventSequence(
489 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
490 true, false, &begin),
491 false, true, false,
492 "Now, go back to providing content frame events, and expect the sampler "
493 "to lock-in once again.");
494 }
495
496 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between,
497 // two animations of the same pixel change rate. VideoCaptureOracle should
498 // revert to using the SmoothEventSampler for these kinds of situations, as
499 // there is no "right answer" as to which animation to lock into.
500 TEST_P(AnimatedContentSamplerParameterizedTest,
501 DoesNotLockInToTwoCompetingAnimations) {
502 // Don't test when the event stream cannot indicate two separate content
503 // animations under the current test parameters.
504 if (GetParam().content_period < 2 * GetParam().vsync_interval)
505 return;
506
507 // Start the first animation and run for a bit, and expect the sampler to
508 // lock-in.
509 base::TimeTicks begin = InitialTestTimeTicks();
510 RunEventSequence(
511 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
512 true, false, &begin),
513 false, true, false,
514 "Start the first animation and run for a bit, and expect the sampler to "
515 "lock-in.");
516 if (HasFailure())
517 return;
518
519 // Now, keep the first animation and blend in a second animation of the same
520 // size and frame rate, but at a different position. This will should cause
521 // the sampler to enter an "undetected" state since it's unclear which
522 // animation should be locked into.
523 std::vector<Event> first_animation_events = GenerateEventSequence(
524 begin, begin + base::TimeDelta::FromSeconds(20), true, false, &begin);
525 gfx::Rect second_animation_rect(
526 gfx::Point(0, GetContentDamageRect().height()),
527 GetContentDamageRect().size());
528 std::vector<Event> both_animations_events;
529 base::TimeDelta second_animation_offset = GetParam().vsync_interval;
530 for (std::vector<Event>::const_iterator i = first_animation_events.begin();
531 i != first_animation_events.end(); ++i) {
532 both_animations_events.push_back(*i);
533 both_animations_events.push_back(
534 Event(second_animation_rect, i->second + second_animation_offset));
535 }
536 RunEventSequence(
537 both_animations_events, true, false, false,
538 "Now, blend-in a second animation of the same size and frame rate, but "
539 "at a different position.");
540 if (HasFailure())
541 return;
542
543 // Now, run just the first animation, and expect the sampler to lock-in once
544 // again.
545 RunEventSequence(
546 GenerateEventSequence(begin, begin + base::TimeDelta::FromSeconds(5),
547 true, false, &begin),
548 false, true, false,
549 "Now, run just the first animation, and expect the sampler to lock-in "
550 "once again.");
551 if (HasFailure())
552 return;
553
554 // Now, blend in the second animation again, but it has half the frame rate of
555 // the first animation and damage Rects with twice the area. This will should
556 // cause the sampler to enter an "undetected" state again. This tests that
557 // pixel-weighting is being accounted for in the sampler's logic.
558 first_animation_events = GenerateEventSequence(
559 begin, begin + base::TimeDelta::FromSeconds(20), true, false, &begin);
560 second_animation_rect.set_width(second_animation_rect.width() * 2);
561 both_animations_events.clear();
562 bool include_second_animation_frame = true;
563 for (std::vector<Event>::const_iterator i = first_animation_events.begin();
564 i != first_animation_events.end(); ++i) {
565 both_animations_events.push_back(*i);
566 if (include_second_animation_frame) {
567 both_animations_events.push_back(
568 Event(second_animation_rect, i->second + second_animation_offset));
569 }
570 include_second_animation_frame = !include_second_animation_frame;
571 }
572 RunEventSequence(
573 both_animations_events, true, false, false,
574 "Now, blend in the second animation again, but it has half the frame "
575 "rate of the first animation and damage Rects with twice the area.");
576 }
577
578 // Tests that the frame timestamps are smooth; meaning, that when run through a
579 // simulated compositor, each frame is held displayed for the right number of
580 // v-sync intervals.
581 TEST_P(AnimatedContentSamplerParameterizedTest, FrameTimestampsAreSmooth) {
582 // Generate 30 seconds of animated content events, run the events through
583 // AnimatedContentSampler, and record all frame timestamps being proposed
584 // once lock-in is continuous.
585 const base::TimeTicks begin = InitialTestTimeTicks();
586 std::vector<Event> events = GenerateEventSequence(
587 begin, begin + base::TimeDelta::FromSeconds(20), true, false, nullptr);
588 typedef std::vector<base::TimeTicks> Timestamps;
589 Timestamps frame_timestamps;
590 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end();
591 ++i) {
592 sampler()->ConsiderPresentationEvent(i->first, i->second);
593 if (sampler()->HasProposal()) {
594 if (sampler()->ShouldSample()) {
595 frame_timestamps.push_back(sampler()->frame_timestamp());
596 sampler()->RecordSample(sampler()->frame_timestamp());
597 }
598 } else {
599 frame_timestamps.clear(); // Reset until continuous lock-in.
600 }
601 }
602 ASSERT_LE(2u, frame_timestamps.size());
603
604 // Iterate through the |frame_timestamps|, building a histogram counting the
605 // number of times each frame was displayed k times. For example, 10 frames
606 // of 30 Hz content on a 60 Hz v-sync interval should result in
607 // display_counts[2] == 10. Quit early if any one frame was obviously
608 // repeated too many times.
609 const int64_t max_expected_repeats_per_frame =
610 1 + ComputeExpectedSamplingPeriod() / GetParam().vsync_interval;
611 std::vector<size_t> display_counts(max_expected_repeats_per_frame + 1, 0);
612 base::TimeTicks last_present_time = frame_timestamps.front();
613 for (Timestamps::const_iterator i = frame_timestamps.begin() + 1;
614 i != frame_timestamps.end(); ++i) {
615 const size_t num_vsync_intervals = static_cast<size_t>(
616 (*i - last_present_time) / GetParam().vsync_interval);
617 ASSERT_LT(0u, num_vsync_intervals);
618 ASSERT_GT(display_counts.size(), num_vsync_intervals); // Quit early.
619 ++display_counts[num_vsync_intervals];
620 last_present_time += num_vsync_intervals * GetParam().vsync_interval;
621 }
622
623 // Analyze the histogram for an expected result pattern. If the frame
624 // timestamps are smooth, there should only be one or two buckets with
625 // non-zero counts and they should be next to each other. Because the clock
626 // precision for the event_times provided to the sampler is very granular
627 // (i.e., the vsync_interval), it's okay if other buckets have a tiny "stray"
628 // count in this test.
629 size_t highest_count = 0;
630 size_t second_highest_count = 0;
631 for (size_t repeats = 1; repeats < display_counts.size(); ++repeats) {
632 DVLOG(1) << "display_counts[" << repeats << "] is "
633 << display_counts[repeats];
634 if (display_counts[repeats] >= highest_count) {
635 second_highest_count = highest_count;
636 highest_count = display_counts[repeats];
637 } else if (display_counts[repeats] > second_highest_count) {
638 second_highest_count = display_counts[repeats];
639 }
640 }
641 size_t stray_count_remaining =
642 (frame_timestamps.size() - 1) - (highest_count + second_highest_count);
643 // Expect no more than 0.75% of frames fall outside the two main buckets.
644 EXPECT_GT(frame_timestamps.size() * 75 / 10000, stray_count_remaining);
645 for (size_t repeats = 1; repeats < display_counts.size() - 1; ++repeats) {
646 if (display_counts[repeats] == highest_count) {
647 EXPECT_EQ(second_highest_count, display_counts[repeats + 1]);
648 ++repeats;
649 } else if (second_highest_count > 0 &&
650 display_counts[repeats] == second_highest_count) {
651 EXPECT_EQ(highest_count, display_counts[repeats + 1]);
652 ++repeats;
653 } else {
654 EXPECT_GE(stray_count_remaining, display_counts[repeats]);
655 stray_count_remaining -= display_counts[repeats];
656 }
657 }
658 }
659
660 // Tests that frame timestamps are "lightly pushed" back towards the original
661 // presentation event times, which tells us the AnimatedContentSampler can
662 // account for sources of timestamp drift and correct the drift.
663 TEST_P(AnimatedContentSamplerParameterizedTest,
664 FrameTimestampsConvergeTowardsEventTimes) {
665 const int max_drift_increment_millis = 3;
666
667 // Generate a full minute of events.
668 const base::TimeTicks begin = InitialTestTimeTicks();
669 std::vector<Event> events = GenerateEventSequence(
670 begin, begin + base::TimeDelta::FromMinutes(1), true, false, nullptr);
671
672 // Modify the event sequence so that 1-3 ms of additional drift is suddenly
673 // present every 100 events. This is meant to simulate that, external to
674 // AnimatedContentSampler, the video hardware vsync timebase is being
675 // refreshed and is showing severe drift from the system clock.
676 base::TimeDelta accumulated_drift;
677 for (size_t i = 1; i < events.size(); ++i) {
678 if (i % 100 == 0) {
679 accumulated_drift += base::TimeDelta::FromMilliseconds(
680 GetRandomInRange(1, max_drift_increment_millis + 1));
681 }
682 events[i].second += accumulated_drift;
683 }
684
685 // Run all the events through the sampler and track the last rewritten frame
686 // timestamp.
687 base::TimeTicks last_frame_timestamp;
688 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end();
689 ++i) {
690 sampler()->ConsiderPresentationEvent(i->first, i->second);
691 if (sampler()->ShouldSample())
692 last_frame_timestamp = sampler()->frame_timestamp();
693 }
694
695 // If drift was accounted for, the |last_frame_timestamp| should be close to
696 // the last event's timestamp.
697 const base::TimeDelta total_error =
698 events.back().second - last_frame_timestamp;
699 const base::TimeDelta max_acceptable_error =
700 GetParam().min_capture_period +
701 base::TimeDelta::FromMilliseconds(max_drift_increment_millis);
702 EXPECT_NEAR(0.0, total_error.InMicroseconds(),
703 max_acceptable_error.InMicroseconds());
704 }
705
706 INSTANTIATE_TEST_CASE_P(
707 ,
708 AnimatedContentSamplerParameterizedTest,
709 ::testing::Values(
710 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30
711 // Hz, and content video animates at 30, 25, or 24 Hz.
712 Scenario(60, 30, 30),
713 Scenario(60, 30, 25),
714 Scenario(60, 30, 24),
715
716 // High frame rate content that leverages the Compositor's
717 // capabilities, but capture is still at 30 Hz.
718 Scenario(60, 30, 60),
719 Scenario(60, 30, 50),
720 Scenario(60, 30, 48),
721
722 // High frame rate content that leverages the Compositor's
723 // capabilities, and capture is also a buttery 60 Hz.
724 Scenario(60, 60, 60),
725 Scenario(60, 60, 50),
726 Scenario(60, 60, 48),
727
728 // High frame rate content that leverages the Compositor's
729 // capabilities, but the client has disabled HFR sampling.
730 Scenario(60, 60, 60, 30),
731 Scenario(60, 60, 50, 30),
732 Scenario(60, 60, 48, 30),
733
734 // On some platforms, the Compositor runs at 50 Hz.
735 Scenario(50, 30, 30),
736 Scenario(50, 30, 25),
737 Scenario(50, 30, 24),
738 Scenario(50, 30, 50),
739 Scenario(50, 30, 48),
740
741 // Stable, but non-standard content frame rates.
742 Scenario(60, 30, 16),
743 Scenario(60, 30, 20),
744 Scenario(60, 30, 23),
745 Scenario(60, 30, 26),
746 Scenario(60, 30, 27),
747 Scenario(60, 30, 28),
748 Scenario(60, 30, 29),
749 Scenario(60, 30, 31),
750 Scenario(60, 30, 32),
751 Scenario(60, 30, 33)));
752
753 } // namespace media
OLDNEW
« no previous file with comments | « media/capture/content/animated_content_sampler.cc ('k') | media/capture/content/capture_resolution_chooser.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698