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

Side by Side Diff: content/browser/media/capture/smooth_event_sampler_unittest.cc

Issue 1109603003: Clean-up: Break sampler classes into their own files. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 8 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
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/media/capture/video_capture_oracle.h" 5 #include "content/browser/media/capture/smooth_event_sampler.h"
6 6
7 #include <cstdlib>
8 #include <utility>
9 #include <vector>
10
11 #include "base/logging.h"
12 #include "base/strings/stringprintf.h" 7 #include "base/strings/stringprintf.h"
13 #include "base/time/time.h"
14 #include "testing/gtest/include/gtest/gtest.h" 8 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/gfx/geometry/rect.h"
16 9
17 namespace content { 10 namespace content {
11
18 namespace { 12 namespace {
19 13
20 bool AddEventAndConsiderSampling(SmoothEventSampler* sampler, 14 bool AddEventAndConsiderSampling(SmoothEventSampler* sampler,
21 base::TimeTicks event_time) { 15 base::TimeTicks event_time) {
22 sampler->ConsiderPresentationEvent(event_time); 16 sampler->ConsiderPresentationEvent(event_time);
23 return sampler->ShouldSample(); 17 return sampler->ShouldSample();
24 } 18 }
25 19
26 void SteadyStateSampleAndAdvance(base::TimeDelta vsync, 20 void SteadyStateSampleAndAdvance(base::TimeDelta vsync,
27 SmoothEventSampler* sampler, 21 SmoothEventSampler* sampler,
(...skipping 404 matching lines...) Expand 10 before | Expand all | Expand 10 after
432 { true, 33.441 }, { false, 0 }, { true, 33.44 }, { false, 16.72 }, 426 { true, 33.441 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
433 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 50.161 }, 427 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 50.161 },
434 { false, 0 }, { true, 16.72 }, { true, 33.44 }, { false, 0 }, 428 { false, 0 }, { true, 16.72 }, { true, 33.44 }, { false, 0 },
435 { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, { true, 50.16 } 429 { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, { true, 50.16 }
436 }; 430 };
437 431
438 SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, 3); 432 SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
439 ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler); 433 ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler);
440 } 434 }
441 435
442 class AnimatedContentSamplerTest : public ::testing::Test {
443 public:
444 AnimatedContentSamplerTest() {}
445 ~AnimatedContentSamplerTest() override {}
446
447 void SetUp() override {
448 const base::TimeDelta since_epoch =
449 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch();
450 rand_seed_ = abs(static_cast<int>(since_epoch.InMicroseconds()));
451 sampler_.reset(new AnimatedContentSampler(GetMinCapturePeriod()));
452 }
453
454 protected:
455 // Overridden by subclass for parameterized tests.
456 virtual base::TimeDelta GetMinCapturePeriod() const {
457 return base::TimeDelta::FromSeconds(1) / 30;
458 }
459
460 AnimatedContentSampler* sampler() const {
461 return sampler_.get();
462 }
463
464 int GetRandomInRange(int begin, int end) {
465 const int len = end - begin;
466 const int rand_offset = (len == 0) ? 0 : (NextRandomInt() % (end - begin));
467 return begin + rand_offset;
468 }
469
470 gfx::Rect GetRandomDamageRect() {
471 return gfx::Rect(0, 0, GetRandomInRange(1, 100), GetRandomInRange(1, 100));
472 }
473
474 gfx::Rect GetContentDamageRect() {
475 // This must be distinct from anything GetRandomDamageRect() could return.
476 return gfx::Rect(0, 0, 1280, 720);
477 }
478
479 // Directly inject an observation. Only used to test
480 // ElectMajorityDamageRect().
481 void ObserveDamageRect(const gfx::Rect& damage_rect) {
482 sampler_->observations_.push_back(
483 AnimatedContentSampler::Observation(damage_rect, base::TimeTicks()));
484 }
485
486 gfx::Rect ElectMajorityDamageRect() const {
487 return sampler_->ElectMajorityDamageRect();
488 }
489
490 private:
491 // Note: Not using base::RandInt() because it is horribly slow on debug
492 // builds. The following is a very simple, deterministic LCG:
493 int NextRandomInt() {
494 rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31);
495 return rand_seed_;
496 }
497
498 int rand_seed_;
499 scoped_ptr<AnimatedContentSampler> sampler_;
500 };
501
502 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromZeroDamageRects) {
503 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
504 }
505
506 TEST_F(AnimatedContentSamplerTest, ElectsMajorityFromOneDamageRect) {
507 const gfx::Rect the_one_rect(0, 0, 1, 1);
508 ObserveDamageRect(the_one_rect);
509 EXPECT_EQ(the_one_rect, ElectMajorityDamageRect());
510 }
511
512 TEST_F(AnimatedContentSamplerTest, ElectsNoneFromTwoDamageRectsOfSameArea) {
513 const gfx::Rect one_rect(0, 0, 1, 1);
514 const gfx::Rect another_rect(1, 1, 1, 1);
515 ObserveDamageRect(one_rect);
516 ObserveDamageRect(another_rect);
517 EXPECT_EQ(gfx::Rect(), ElectMajorityDamageRect());
518 }
519
520 TEST_F(AnimatedContentSamplerTest, ElectsLargerOfTwoDamageRects_1) {
521 const gfx::Rect one_rect(0, 0, 1, 1);
522 const gfx::Rect another_rect(0, 0, 2, 2);
523 ObserveDamageRect(one_rect);
524 ObserveDamageRect(another_rect);
525 EXPECT_EQ(another_rect, ElectMajorityDamageRect());
526 }
527
528 TEST_F(AnimatedContentSamplerTest, ElectsLargerOfTwoDamageRects_2) {
529 const gfx::Rect one_rect(0, 0, 2, 2);
530 const gfx::Rect another_rect(0, 0, 1, 1);
531 ObserveDamageRect(one_rect);
532 ObserveDamageRect(another_rect);
533 EXPECT_EQ(one_rect, ElectMajorityDamageRect());
534 }
535
536 TEST_F(AnimatedContentSamplerTest, ElectsSameAsMooreDemonstration) {
537 // A more complex sequence (from Moore's web site): Three different Rects with
538 // the same area, but occurring a different number of times. C should win the
539 // vote.
540 const gfx::Rect rect_a(0, 0, 1, 4);
541 const gfx::Rect rect_b(1, 1, 4, 1);
542 const gfx::Rect rect_c(2, 2, 2, 2);
543 for (int i = 0; i < 3; ++i)
544 ObserveDamageRect(rect_a);
545 for (int i = 0; i < 2; ++i)
546 ObserveDamageRect(rect_c);
547 for (int i = 0; i < 2; ++i)
548 ObserveDamageRect(rect_b);
549 for (int i = 0; i < 3; ++i)
550 ObserveDamageRect(rect_c);
551 ObserveDamageRect(rect_b);
552 for (int i = 0; i < 2; ++i)
553 ObserveDamageRect(rect_c);
554 EXPECT_EQ(rect_c, ElectMajorityDamageRect());
555 }
556
557 TEST_F(AnimatedContentSamplerTest, Elects24FpsVideoInsteadOf48FpsSpinner) {
558 // Scenario: 24 FPS 720x480 Video versus 48 FPS 96x96 "Busy Spinner"
559 const gfx::Rect video_rect(100, 100, 720, 480);
560 const gfx::Rect spinner_rect(360, 0, 96, 96);
561 for (int i = 0; i < 100; ++i) {
562 // |video_rect| occurs once for every two |spinner_rect|. Vary the order
563 // of events between the two:
564 ObserveDamageRect(video_rect);
565 ObserveDamageRect(spinner_rect);
566 ObserveDamageRect(spinner_rect);
567 ObserveDamageRect(video_rect);
568 ObserveDamageRect(spinner_rect);
569 ObserveDamageRect(spinner_rect);
570 ObserveDamageRect(spinner_rect);
571 ObserveDamageRect(video_rect);
572 ObserveDamageRect(spinner_rect);
573 ObserveDamageRect(spinner_rect);
574 ObserveDamageRect(video_rect);
575 ObserveDamageRect(spinner_rect);
576 }
577 EXPECT_EQ(video_rect, ElectMajorityDamageRect());
578 }
579
580 namespace {
581
582 // A test scenario for AnimatedContentSamplerParameterizedTest.
583 struct Scenario {
584 base::TimeDelta vsync_interval; // Reflects compositor's update rate.
585 base::TimeDelta min_capture_period; // Reflects maximum capture rate.
586 base::TimeDelta content_period; // Reflects content animation rate.
587
588 Scenario(base::TimeDelta v, base::TimeDelta m, base::TimeDelta c)
589 : vsync_interval(v), min_capture_period(m), content_period(c) {
590 CHECK(content_period >= vsync_interval)
591 << "Bad test params: Impossible to animate faster than the compositor.";
592 }
593 };
594
595 // Value printer for Scenario.
596 ::std::ostream& operator<<(::std::ostream& os, const Scenario& s) {
597 return os << "{ vsync_interval=" << s.vsync_interval.InMicroseconds()
598 << ", min_capture_period=" << s.min_capture_period.InMicroseconds()
599 << ", content_period=" << s.content_period.InMicroseconds()
600 << " }";
601 }
602
603 base::TimeDelta FpsAsPeriod(int frame_rate) {
604 return base::TimeDelta::FromSeconds(1) / frame_rate;
605 }
606
607 } // namespace
608
609 class AnimatedContentSamplerParameterizedTest
610 : public AnimatedContentSamplerTest,
611 public ::testing::WithParamInterface<Scenario> {
612 public:
613 AnimatedContentSamplerParameterizedTest()
614 : count_dropped_frames_(0), count_sampled_frames_(0) {}
615 virtual ~AnimatedContentSamplerParameterizedTest() {}
616
617 protected:
618 typedef std::pair<gfx::Rect, base::TimeTicks> Event;
619
620 base::TimeDelta GetMinCapturePeriod() const override {
621 return GetParam().min_capture_period;
622 }
623
624 // Generate a sequence of events from the compositor pipeline. The event
625 // times will all be at compositor vsync boundaries.
626 std::vector<Event> GenerateEventSequence(base::TimeTicks begin,
627 base::TimeTicks end,
628 bool include_content_frame_events,
629 bool include_random_events) {
630 DCHECK(GetParam().content_period >= GetParam().vsync_interval);
631 base::TimeTicks next_content_time = begin - GetParam().content_period;
632 std::vector<Event> events;
633 for (base::TimeTicks compositor_time = begin; compositor_time < end;
634 compositor_time += GetParam().vsync_interval) {
635 if (include_content_frame_events && next_content_time < compositor_time) {
636 events.push_back(Event(GetContentDamageRect(), compositor_time));
637 next_content_time += GetParam().content_period;
638 } else if (include_random_events && GetRandomInRange(0, 1) == 0) {
639 events.push_back(Event(GetRandomDamageRect(), compositor_time));
640 }
641 }
642
643 DCHECK(!events.empty());
644 return events;
645 }
646
647 // Feed |events| through the sampler, and detect whether the expected
648 // lock-in/out transition occurs. Also, track and measure the frame drop
649 // ratio and check it against the expected drop rate.
650 void RunEventSequence(const std::vector<Event> events,
651 bool was_detecting_before,
652 bool is_detecting_after,
653 bool simulate_pipeline_back_pressure) {
654 gfx::Rect first_detected_region;
655
656 EXPECT_EQ(was_detecting_before, sampler()->HasProposal());
657 bool has_detection_switched = false;
658 ResetFrameCounters();
659 for (std::vector<Event>::const_iterator i = events.begin();
660 i != events.end(); ++i) {
661 sampler()->ConsiderPresentationEvent(i->first, i->second);
662
663 // Detect when the sampler locks in/out, and that it stays that way for
664 // all further iterations of this loop.
665 if (!has_detection_switched &&
666 was_detecting_before != sampler()->HasProposal()) {
667 has_detection_switched = true;
668 }
669 ASSERT_EQ(
670 has_detection_switched ? is_detecting_after : was_detecting_before,
671 sampler()->HasProposal());
672
673 if (sampler()->HasProposal()) {
674 // Make sure the sampler doesn't flip-flop and keep proposing sampling
675 // based on locking into different regions.
676 if (first_detected_region.IsEmpty()) {
677 first_detected_region = sampler()->detected_region();
678 ASSERT_FALSE(first_detected_region.IsEmpty());
679 } else {
680 EXPECT_EQ(first_detected_region, sampler()->detected_region());
681 }
682
683 if (simulate_pipeline_back_pressure && GetRandomInRange(0, 2) == 0)
684 ClientCannotSampleFrame(*i);
685 else
686 ClientDoesWhatSamplerProposes(*i);
687 } else {
688 EXPECT_FALSE(sampler()->ShouldSample());
689 if (!simulate_pipeline_back_pressure || GetRandomInRange(0, 2) == 1)
690 sampler()->RecordSample(i->second);
691 }
692 }
693 EXPECT_EQ(is_detecting_after, sampler()->HasProposal());
694 ExpectFrameDropRatioIsCorrect();
695 }
696
697 void ResetFrameCounters() {
698 count_dropped_frames_ = 0;
699 count_sampled_frames_ = 0;
700 }
701
702 // Keep track what the sampler is proposing, and call RecordSample() if it
703 // proposes sampling |event|.
704 void ClientDoesWhatSamplerProposes(const Event& event) {
705 if (sampler()->ShouldSample()) {
706 EXPECT_EQ(GetContentDamageRect(), event.first);
707 sampler()->RecordSample(sampler()->frame_timestamp());
708 ++count_sampled_frames_;
709 } else if (event.first == GetContentDamageRect()) {
710 ++count_dropped_frames_;
711 }
712 }
713
714 // RecordSample() is not called, but for testing, keep track of what the
715 // sampler is proposing for |event|.
716 void ClientCannotSampleFrame(const Event& event) {
717 if (sampler()->ShouldSample()) {
718 EXPECT_EQ(GetContentDamageRect(), event.first);
719 ++count_sampled_frames_;
720 } else if (event.first == GetContentDamageRect()) {
721 ++count_dropped_frames_;
722 }
723 }
724
725 // Confirm the AnimatedContentSampler is not dropping more frames than
726 // expected, given current test parameters.
727 void ExpectFrameDropRatioIsCorrect() {
728 if (count_sampled_frames_ == 0) {
729 EXPECT_EQ(0, count_dropped_frames_);
730 return;
731 }
732 const double content_framerate =
733 1000000.0 / GetParam().content_period.InMicroseconds();
734 const double capture_framerate =
735 1000000.0 / GetParam().min_capture_period.InMicroseconds();
736 const double expected_drop_rate = std::max(
737 0.0, (content_framerate - capture_framerate) / capture_framerate);
738 const double actual_drop_rate =
739 static_cast<double>(count_dropped_frames_) / count_sampled_frames_;
740 EXPECT_NEAR(expected_drop_rate, actual_drop_rate, 0.015);
741 }
742
743 private:
744 // These counters only include the frames with the desired content.
745 int count_dropped_frames_;
746 int count_sampled_frames_;
747 };
748
749 // Tests that the implementation locks in/out of frames containing stable
750 // animated content, whether or not random events are also simultaneously
751 // present.
752 TEST_P(AnimatedContentSamplerParameterizedTest, DetectsAnimatedContent) {
753 // |begin| refers to the start of an event sequence in terms of the
754 // Compositor's clock.
755 base::TimeTicks begin = InitialTestTimeTicks();
756
757 // Provide random events and expect no lock-in.
758 base::TimeTicks end = begin + base::TimeDelta::FromSeconds(5);
759 RunEventSequence(GenerateEventSequence(begin, end, false, true),
760 false,
761 false,
762 false);
763 begin = end;
764
765 // Provide content frame events with some random events mixed-in, and expect
766 // the sampler to lock-in.
767 end = begin + base::TimeDelta::FromSeconds(5);
768 RunEventSequence(GenerateEventSequence(begin, end, true, true),
769 false,
770 true,
771 false);
772 begin = end;
773
774 // Continue providing content frame events without the random events mixed-in
775 // and expect the lock-in to hold.
776 end = begin + base::TimeDelta::FromSeconds(5);
777 RunEventSequence(GenerateEventSequence(begin, end, true, false),
778 true,
779 true,
780 false);
781 begin = end;
782
783 // Continue providing just content frame events and expect the lock-in to
784 // hold. Also simulate the capture pipeline experiencing back pressure.
785 end = begin + base::TimeDelta::FromSeconds(20);
786 RunEventSequence(GenerateEventSequence(begin, end, true, false),
787 true,
788 true,
789 true);
790 begin = end;
791
792 // Provide a half-second of random events only, and expect the lock-in to be
793 // broken.
794 end = begin + base::TimeDelta::FromMilliseconds(500);
795 RunEventSequence(GenerateEventSequence(begin, end, false, true),
796 true,
797 false,
798 false);
799 begin = end;
800
801 // Now, go back to providing content frame events, and expect the sampler to
802 // lock-in once again.
803 end = begin + base::TimeDelta::FromSeconds(5);
804 RunEventSequence(GenerateEventSequence(begin, end, true, false),
805 false,
806 true,
807 false);
808 begin = end;
809 }
810
811 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between,
812 // two animations of the same pixel change rate. VideoCaptureOracle should
813 // revert to using the SmoothEventSampler for these kinds of situations, as
814 // there is no "right answer" as to which animation to lock into.
815 TEST_P(AnimatedContentSamplerParameterizedTest,
816 DoesNotLockInToTwoCompetingAnimations) {
817 // Don't test when the event stream cannot indicate two separate content
818 // animations under the current test parameters.
819 if (GetParam().content_period < 2 * GetParam().vsync_interval)
820 return;
821
822 // Start the first animation and run for a bit, and expect the sampler to
823 // lock-in.
824 base::TimeTicks begin = InitialTestTimeTicks();
825 base::TimeTicks end = begin + base::TimeDelta::FromSeconds(5);
826 RunEventSequence(GenerateEventSequence(begin, end, true, false),
827 false,
828 true,
829 false);
830 begin = end;
831
832 // Now, keep the first animation and blend in an second animation of the same
833 // size and frame rate, but at a different position. This will should cause
834 // the sampler to enter an "undetected" state since it's unclear which
835 // animation should be locked into.
836 end = begin + base::TimeDelta::FromSeconds(20);
837 std::vector<Event> first_animation_events =
838 GenerateEventSequence(begin, end, true, false);
839 gfx::Rect second_animation_rect(
840 gfx::Point(0, GetContentDamageRect().height()),
841 GetContentDamageRect().size());
842 std::vector<Event> both_animations_events;
843 base::TimeDelta second_animation_offset = GetParam().vsync_interval;
844 for (std::vector<Event>::const_iterator i = first_animation_events.begin();
845 i != first_animation_events.end(); ++i) {
846 both_animations_events.push_back(*i);
847 both_animations_events.push_back(
848 Event(second_animation_rect, i->second + second_animation_offset));
849 }
850 RunEventSequence(both_animations_events, true, false, false);
851 begin = end;
852
853 // Now, run just the first animation, and expect the sampler to lock-in once
854 // again.
855 end = begin + base::TimeDelta::FromSeconds(5);
856 RunEventSequence(GenerateEventSequence(begin, end, true, false),
857 false,
858 true,
859 false);
860 begin = end;
861
862 // Now, blend in the second animation again, but it has half the frame rate of
863 // the first animation and damage Rects with twice the area. This will should
864 // cause the sampler to enter an "undetected" state again. This tests that
865 // pixel-weighting is being accounted for in the sampler's logic.
866 end = begin + base::TimeDelta::FromSeconds(20);
867 first_animation_events = GenerateEventSequence(begin, end, true, false);
868 second_animation_rect.set_width(second_animation_rect.width() * 2);
869 both_animations_events.clear();
870 bool include_second_animation_frame = true;
871 for (std::vector<Event>::const_iterator i = first_animation_events.begin();
872 i != first_animation_events.end(); ++i) {
873 both_animations_events.push_back(*i);
874 if (include_second_animation_frame) {
875 both_animations_events.push_back(
876 Event(second_animation_rect, i->second + second_animation_offset));
877 }
878 include_second_animation_frame = !include_second_animation_frame;
879 }
880 RunEventSequence(both_animations_events, true, false, false);
881 begin = end;
882 }
883
884 // Tests that the frame timestamps are smooth; meaning, that when run through a
885 // simulated compositor, each frame is held displayed for the right number of
886 // v-sync intervals.
887 TEST_P(AnimatedContentSamplerParameterizedTest, FrameTimestampsAreSmooth) {
888 // Generate 30 seconds of animated content events, run the events through
889 // AnimatedContentSampler, and record all frame timestamps being proposed
890 // once lock-in is continuous.
891 base::TimeTicks begin = InitialTestTimeTicks();
892 std::vector<Event> events = GenerateEventSequence(
893 begin,
894 begin + base::TimeDelta::FromSeconds(20),
895 true,
896 false);
897 typedef std::vector<base::TimeTicks> Timestamps;
898 Timestamps frame_timestamps;
899 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end();
900 ++i) {
901 sampler()->ConsiderPresentationEvent(i->first, i->second);
902 if (sampler()->HasProposal()) {
903 if (sampler()->ShouldSample()) {
904 frame_timestamps.push_back(sampler()->frame_timestamp());
905 sampler()->RecordSample(sampler()->frame_timestamp());
906 }
907 } else {
908 frame_timestamps.clear(); // Reset until continuous lock-in.
909 }
910 }
911 ASSERT_LE(2u, frame_timestamps.size());
912
913 // Iterate through the |frame_timestamps|, building a histogram counting the
914 // number of times each frame was displayed k times. For example, 10 frames
915 // of 30 Hz content on a 60 Hz v-sync interval should result in
916 // display_counts[2] == 10. Quit early if any one frame was obviously
917 // repeated too many times.
918 const int64 max_expected_repeats_per_frame = 1 +
919 std::max(GetParam().min_capture_period, GetParam().content_period) /
920 GetParam().vsync_interval;
921 std::vector<size_t> display_counts(max_expected_repeats_per_frame + 1, 0);
922 base::TimeTicks last_present_time = frame_timestamps.front();
923 for (Timestamps::const_iterator i = frame_timestamps.begin() + 1;
924 i != frame_timestamps.end(); ++i) {
925 const size_t num_vsync_intervals = static_cast<size_t>(
926 (*i - last_present_time) / GetParam().vsync_interval);
927 ASSERT_LT(0u, num_vsync_intervals);
928 ASSERT_GT(display_counts.size(), num_vsync_intervals); // Quit early.
929 ++display_counts[num_vsync_intervals];
930 last_present_time += num_vsync_intervals * GetParam().vsync_interval;
931 }
932
933 // Analyze the histogram for an expected result pattern. If the frame
934 // timestamps are smooth, there should only be one or two buckets with
935 // non-zero counts and they should be next to each other. Because the clock
936 // precision for the event_times provided to the sampler is very granular
937 // (i.e., the vsync_interval), it's okay if other buckets have a tiny "stray"
938 // count in this test.
939 size_t highest_count = 0;
940 size_t second_highest_count = 0;
941 for (size_t repeats = 1; repeats < display_counts.size(); ++repeats) {
942 DVLOG(1) << "display_counts[" << repeats << "] is "
943 << display_counts[repeats];
944 if (display_counts[repeats] >= highest_count) {
945 second_highest_count = highest_count;
946 highest_count = display_counts[repeats];
947 } else if (display_counts[repeats] > second_highest_count) {
948 second_highest_count = display_counts[repeats];
949 }
950 }
951 size_t stray_count_remaining =
952 (frame_timestamps.size() - 1) - (highest_count + second_highest_count);
953 // Expect no more than 0.75% of frames fall outside the two main buckets.
954 EXPECT_GT(frame_timestamps.size() * 75 / 10000, stray_count_remaining);
955 for (size_t repeats = 1; repeats < display_counts.size() - 1; ++repeats) {
956 if (display_counts[repeats] == highest_count) {
957 EXPECT_EQ(second_highest_count, display_counts[repeats + 1]);
958 ++repeats;
959 } else if (display_counts[repeats] == second_highest_count) {
960 EXPECT_EQ(highest_count, display_counts[repeats + 1]);
961 ++repeats;
962 } else {
963 EXPECT_GE(stray_count_remaining, display_counts[repeats]);
964 stray_count_remaining -= display_counts[repeats];
965 }
966 }
967 }
968
969 // Tests that frame timestamps are "lightly pushed" back towards the original
970 // presentation event times, which tells us the AnimatedContentSampler can
971 // account for sources of timestamp drift and correct the drift.
972 TEST_P(AnimatedContentSamplerParameterizedTest,
973 FrameTimestampsConvergeTowardsEventTimes) {
974 const int max_drift_increment_millis = 3;
975
976 // Generate a full minute of events.
977 const base::TimeTicks begin = InitialTestTimeTicks();
978 const base::TimeTicks end = begin + base::TimeDelta::FromMinutes(1);
979 std::vector<Event> events = GenerateEventSequence(begin, end, true, false);
980
981 // Modify the event sequence so that 1-3 ms of additional drift is suddenly
982 // present every 100 events. This is meant to simulate that, external to
983 // AnimatedContentSampler, the video hardware vsync timebase is being
984 // refreshed and is showing severe drift from the system clock.
985 base::TimeDelta accumulated_drift;
986 for (size_t i = 1; i < events.size(); ++i) {
987 if (i % 100 == 0) {
988 accumulated_drift += base::TimeDelta::FromMilliseconds(
989 GetRandomInRange(1, max_drift_increment_millis + 1));
990 }
991 events[i].second += accumulated_drift;
992 }
993
994 // Run all the events through the sampler and track the last rewritten frame
995 // timestamp.
996 base::TimeTicks last_frame_timestamp;
997 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end();
998 ++i) {
999 sampler()->ConsiderPresentationEvent(i->first, i->second);
1000 if (sampler()->ShouldSample())
1001 last_frame_timestamp = sampler()->frame_timestamp();
1002 }
1003
1004 // If drift was accounted for, the |last_frame_timestamp| should be close to
1005 // the last event's timestamp.
1006 const base::TimeDelta total_error =
1007 events.back().second - last_frame_timestamp;
1008 const base::TimeDelta max_acceptable_error = GetParam().min_capture_period +
1009 base::TimeDelta::FromMilliseconds(max_drift_increment_millis);
1010 EXPECT_NEAR(0.0,
1011 total_error.InMicroseconds(),
1012 max_acceptable_error.InMicroseconds());
1013 }
1014
1015 INSTANTIATE_TEST_CASE_P(
1016 ,
1017 AnimatedContentSamplerParameterizedTest,
1018 ::testing::Values(
1019 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30
1020 // Hz, and content video animates at 30, 25, or 24 Hz.
1021 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(30)),
1022 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(25)),
1023 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(24)),
1024
1025 // High frame rate content that leverages the Compositor's
1026 // capabilities, but capture is still at 30 Hz.
1027 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(60)),
1028 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(50)),
1029 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(48)),
1030
1031 // High frame rate content that leverages the Compositor's
1032 // capabilities, and capture is also a buttery 60 Hz.
1033 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(60)),
1034 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(50)),
1035 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(48)),
1036
1037 // On some platforms, the Compositor runs at 50 Hz.
1038 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(30)),
1039 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(25)),
1040 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(24)),
1041 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(50)),
1042 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(48)),
1043
1044 // Stable, but non-standard content frame rates.
1045 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(16)),
1046 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(20)),
1047 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(23)),
1048 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(26)),
1049 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(27)),
1050 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(28)),
1051 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(29)),
1052 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(31)),
1053 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(32)),
1054 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(33))));
1055
1056 // Tests that VideoCaptureOracle filters out events whose timestamps are
1057 // decreasing.
1058 TEST(VideoCaptureOracleTest, EnforcesEventTimeMonotonicity) {
1059 const base::TimeDelta min_capture_period =
1060 base::TimeDelta::FromSeconds(1) / 30;
1061 const gfx::Rect damage_rect(0, 0, 1280, 720);
1062 const base::TimeDelta event_increment = min_capture_period * 2;
1063
1064 VideoCaptureOracle oracle(min_capture_period);
1065
1066 base::TimeTicks t = InitialTestTimeTicks();
1067 for (int i = 0; i < 10; ++i) {
1068 t += event_increment;
1069 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
1070 VideoCaptureOracle::kCompositorUpdate,
1071 damage_rect, t));
1072 }
1073
1074 base::TimeTicks furthest_event_time = t;
1075 for (int i = 0; i < 10; ++i) {
1076 t -= event_increment;
1077 ASSERT_FALSE(oracle.ObserveEventAndDecideCapture(
1078 VideoCaptureOracle::kCompositorUpdate,
1079 damage_rect, t));
1080 }
1081
1082 t = furthest_event_time;
1083 for (int i = 0; i < 10; ++i) {
1084 t += event_increment;
1085 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
1086 VideoCaptureOracle::kCompositorUpdate,
1087 damage_rect, t));
1088 }
1089 }
1090
1091 // Tests that VideoCaptureOracle is enforcing the requirement that captured
1092 // frames are delivered in order. Otherwise, downstream consumers could be
1093 // tripped-up by out-of-order frames or frame timestamps.
1094 TEST(VideoCaptureOracleTest, EnforcesFramesDeliveredInOrder) {
1095 const base::TimeDelta min_capture_period =
1096 base::TimeDelta::FromSeconds(1) / 30;
1097 const gfx::Rect damage_rect(0, 0, 1280, 720);
1098 const base::TimeDelta event_increment = min_capture_period * 2;
1099
1100 VideoCaptureOracle oracle(min_capture_period);
1101
1102 // Most basic scenario: Frames delivered one at a time, with no additional
1103 // captures in-between deliveries.
1104 base::TimeTicks t = InitialTestTimeTicks();
1105 int last_frame_number;
1106 base::TimeTicks ignored;
1107 for (int i = 0; i < 10; ++i) {
1108 t += event_increment;
1109 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
1110 VideoCaptureOracle::kCompositorUpdate,
1111 damage_rect, t));
1112 last_frame_number = oracle.RecordCapture();
1113 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, &ignored));
1114 }
1115
1116 // Basic pipelined scenario: More than one frame in-flight at delivery points.
1117 for (int i = 0; i < 50; ++i) {
1118 const int num_in_flight = 1 + i % 3;
1119 for (int j = 0; j < num_in_flight; ++j) {
1120 t += event_increment;
1121 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
1122 VideoCaptureOracle::kCompositorUpdate,
1123 damage_rect, t));
1124 last_frame_number = oracle.RecordCapture();
1125 }
1126 for (int j = num_in_flight - 1; j >= 0; --j) {
1127 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number - j, &ignored));
1128 }
1129 }
1130
1131 // Pipelined scenario with out-of-order delivery attempts rejected.
1132 for (int i = 0; i < 50; ++i) {
1133 const int num_in_flight = 1 + i % 3;
1134 for (int j = 0; j < num_in_flight; ++j) {
1135 t += event_increment;
1136 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
1137 VideoCaptureOracle::kCompositorUpdate,
1138 damage_rect, t));
1139 last_frame_number = oracle.RecordCapture();
1140 }
1141 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, &ignored));
1142 for (int j = 1; j < num_in_flight; ++j) {
1143 ASSERT_FALSE(oracle.CompleteCapture(last_frame_number - j, &ignored));
1144 }
1145 }
1146 }
1147
1148 // Tests that VideoCaptureOracle transitions between using its two samplers in a
1149 // way that does not introduce severe jank, pauses, etc.
1150 TEST(VideoCaptureOracleTest, TransitionsSmoothlyBetweenSamplers) {
1151 const base::TimeDelta min_capture_period =
1152 base::TimeDelta::FromSeconds(1) / 30;
1153 const gfx::Rect animation_damage_rect(0, 0, 1280, 720);
1154 const base::TimeDelta event_increment = min_capture_period * 2;
1155
1156 VideoCaptureOracle oracle(min_capture_period);
1157
1158 // Run sequences of animation events and non-animation events through the
1159 // oracle. As the oracle transitions between each sampler, make sure the
1160 // frame timestamps won't trip-up downstream consumers.
1161 base::TimeTicks t = InitialTestTimeTicks();
1162 base::TimeTicks last_frame_timestamp;
1163 for (int i = 0; i < 1000; ++i) {
1164 t += event_increment;
1165
1166 // For every 100 events, provide 50 that will cause the
1167 // AnimatedContentSampler to lock-in, followed by 50 that will cause it to
1168 // lock-out (i.e., the oracle will use the SmoothEventSampler instead).
1169 const bool provide_animated_content_event =
1170 (i % 100) >= 25 && (i % 100) < 75;
1171
1172 // Only the few events that trigger the lock-out transition should be
1173 // dropped, because the AnimatedContentSampler doesn't yet realize the
1174 // animation ended. Otherwise, the oracle should always decide to sample
1175 // because one of its samplers says to.
1176 const bool require_oracle_says_sample = (i % 100) < 75 || (i % 100) >= 78;
1177 const bool oracle_says_sample = oracle.ObserveEventAndDecideCapture(
1178 VideoCaptureOracle::kCompositorUpdate,
1179 provide_animated_content_event ? animation_damage_rect : gfx::Rect(),
1180 t);
1181 if (require_oracle_says_sample)
1182 ASSERT_TRUE(oracle_says_sample);
1183 if (!oracle_says_sample)
1184 continue;
1185
1186 const int frame_number = oracle.RecordCapture();
1187
1188 base::TimeTicks frame_timestamp;
1189 ASSERT_TRUE(oracle.CompleteCapture(frame_number, &frame_timestamp));
1190 ASSERT_FALSE(frame_timestamp.is_null());
1191 if (!last_frame_timestamp.is_null()) {
1192 const base::TimeDelta delta = frame_timestamp - last_frame_timestamp;
1193 EXPECT_LE(event_increment.InMicroseconds(), delta.InMicroseconds());
1194 // Right after the AnimatedContentSampler lock-out transition, there were
1195 // a few frames dropped, so allow a gap in the timestamps. Otherwise, the
1196 // delta between frame timestamps should never be more than 2X the
1197 // |event_increment|.
1198 const base::TimeDelta max_acceptable_delta = (i % 100) == 78 ?
1199 event_increment * 5 : event_increment * 2;
1200 EXPECT_GE(max_acceptable_delta.InMicroseconds(), delta.InMicroseconds());
1201 }
1202 last_frame_timestamp = frame_timestamp;
1203 }
1204 }
1205
1206 } // namespace content 436 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/media/capture/smooth_event_sampler.cc ('k') | content/browser/media/capture/video_capture_oracle.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698