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

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

Issue 1123623005: Tab Capture: AnimatedContentSampler subsampling and phase fixes in tests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed comments and expanded coverage of TargetsSamplingPeriod test. Created 5 years, 7 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) 2015 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/animated_content_sampler.h" 5 #include "content/browser/media/capture/animated_content_sampler.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 namespace content { 9 namespace content {
10 10
(...skipping 23 matching lines...) Expand all
34 34
35 // The amount of time over which to fully correct the drift of the rewritten 35 // The amount of time over which to fully correct the drift of the rewritten
36 // frame timestamps from the presentation event timestamps. The lower the 36 // frame timestamps from the presentation event timestamps. The lower the
37 // value, the higher the variance in frame timestamps. 37 // value, the higher the variance in frame timestamps.
38 const int kDriftCorrectionMillis = 2000; 38 const int kDriftCorrectionMillis = 2000;
39 39
40 } // anonymous namespace 40 } // anonymous namespace
41 41
42 AnimatedContentSampler::AnimatedContentSampler( 42 AnimatedContentSampler::AnimatedContentSampler(
43 base::TimeDelta min_capture_period) 43 base::TimeDelta min_capture_period)
44 : min_capture_period_(min_capture_period) {} 44 : min_capture_period_(min_capture_period),
45 sampling_state_(NOT_SAMPLING) {
46 DCHECK_GT(min_capture_period_, base::TimeDelta());
47 }
45 48
46 AnimatedContentSampler::~AnimatedContentSampler() {} 49 AnimatedContentSampler::~AnimatedContentSampler() {}
47 50
51 void AnimatedContentSampler::SetTargetSamplingPeriod(base::TimeDelta period) {
52 target_sampling_period_ = period;
53 }
54
48 void AnimatedContentSampler::ConsiderPresentationEvent( 55 void AnimatedContentSampler::ConsiderPresentationEvent(
49 const gfx::Rect& damage_rect, base::TimeTicks event_time) { 56 const gfx::Rect& damage_rect, base::TimeTicks event_time) {
57 // Analyze the current event and recent history to determine whether animating
58 // content is detected.
50 AddObservation(damage_rect, event_time); 59 AddObservation(damage_rect, event_time);
51 60 if (!AnalyzeObservations(event_time, &detected_region_, &detected_period_) ||
52 if (AnalyzeObservations(event_time, &detected_region_, &detected_period_) && 61 detected_period_ <= base::TimeDelta() ||
53 detected_period_ > base::TimeDelta() && 62 detected_period_ >
54 detected_period_ <=
55 base::TimeDelta::FromMicroseconds(kMaxLockInPeriodMicros)) { 63 base::TimeDelta::FromMicroseconds(kMaxLockInPeriodMicros)) {
56 if (damage_rect == detected_region_) 64 // Animated content not detected.
57 UpdateFrameTimestamp(event_time);
58 else
59 frame_timestamp_ = base::TimeTicks();
60 } else {
61 detected_region_ = gfx::Rect(); 65 detected_region_ = gfx::Rect();
62 detected_period_ = base::TimeDelta(); 66 detected_period_ = base::TimeDelta();
63 frame_timestamp_ = base::TimeTicks(); 67 sampling_state_ = NOT_SAMPLING;
68 return;
69 }
70
71 // At this point, animation is being detected. Update the sampling period
72 // since the client may call the accessor method even if the heuristics below
73 // decide not to sample the current event.
74 sampling_period_ = ComputeSamplingPeriod(detected_period_,
75 target_sampling_period_,
76 min_capture_period_);
77
78 // If this is the first event causing animating content to be detected,
79 // transition to the START_SAMPLING state.
80 if (sampling_state_ == NOT_SAMPLING)
81 sampling_state_ = START_SAMPLING;
82
83 // If the current event does not represent a frame that is part of the
84 // animation, do not sample.
85 if (damage_rect != detected_region_) {
86 if (sampling_state_ == SHOULD_SAMPLE)
87 sampling_state_ = SHOULD_NOT_SAMPLE;
88 return;
89 }
90
91 // When starting sampling, determine where to sync-up for sampling and frame
92 // timestamp rewriting. Otherwise, just add one animation period's worth of
93 // tokens to the token bucket.
94 if (sampling_state_ == START_SAMPLING) {
95 if (event_time - frame_timestamp_ > sampling_period_) {
96 // The frame timestamp sequence should start with the current event
97 // time.
98 frame_timestamp_ = event_time - sampling_period_;
99 token_bucket_ = sampling_period_;
100 } else {
101 // The frame timestamp sequence will continue from the last recorded
102 // frame timestamp.
103 token_bucket_ = event_time - frame_timestamp_;
104 }
105
106 // Provide a little extra in the initial token bucket so that minor error in
107 // the detected period won't prevent a reasonably-timed event from being
108 // sampled.
109 token_bucket_ += detected_period_ / 2;
110 } else {
111 token_bucket_ += detected_period_;
112 }
113
114 // If the token bucket is full enough, take tokens from it and propose
115 // sampling. Otherwise, do not sample.
116 DCHECK_LE(detected_period_, sampling_period_);
117 if (token_bucket_ >= sampling_period_) {
118 token_bucket_ -= sampling_period_;
119 frame_timestamp_ = ComputeNextFrameTimestamp(event_time);
120 sampling_state_ = SHOULD_SAMPLE;
121 } else {
122 sampling_state_ = SHOULD_NOT_SAMPLE;
64 } 123 }
65 } 124 }
66 125
67 bool AnimatedContentSampler::HasProposal() const { 126 bool AnimatedContentSampler::HasProposal() const {
68 return detected_period_ > base::TimeDelta(); 127 return sampling_state_ != NOT_SAMPLING;
69 } 128 }
70 129
71 bool AnimatedContentSampler::ShouldSample() const { 130 bool AnimatedContentSampler::ShouldSample() const {
72 return !frame_timestamp_.is_null(); 131 return sampling_state_ == SHOULD_SAMPLE;
73 } 132 }
74 133
75 void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) { 134 void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) {
76 recorded_frame_timestamp_ = 135 if (sampling_state_ == NOT_SAMPLING)
77 HasProposal() ? frame_timestamp : base::TimeTicks(); 136 frame_timestamp_ = frame_timestamp;
78 sequence_offset_ = base::TimeDelta(); 137 else if (sampling_state_ == SHOULD_SAMPLE)
138 sampling_state_ = SHOULD_NOT_SAMPLE;
79 } 139 }
80 140
81 void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect, 141 void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect,
82 base::TimeTicks event_time) { 142 base::TimeTicks event_time) {
83 if (damage_rect.IsEmpty()) 143 if (damage_rect.IsEmpty())
84 return; // Useless observation. 144 return; // Useless observation.
85 145
86 // Add the observation to the FIFO queue. 146 // Add the observation to the FIFO queue.
87 if (!observations_.empty() && observations_.back().event_time > event_time) 147 if (!observations_.empty() && observations_.back().event_time > event_time)
88 return; // The implementation assumes chronological order. 148 return; // The implementation assumes chronological order.
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
168 } 228 }
169 if (num_pixels_damaged_in_chosen <= (num_pixels_damaged_in_all * 2 / 3)) 229 if (num_pixels_damaged_in_chosen <= (num_pixels_damaged_in_all * 2 / 3))
170 return false; // Animation is not damaging a supermajority of pixels. 230 return false; // Animation is not damaging a supermajority of pixels.
171 231
172 *rect = elected_rect; 232 *rect = elected_rect;
173 DCHECK_GT(count_frame_durations, 0u); 233 DCHECK_GT(count_frame_durations, 0u);
174 *period = sum_frame_durations / count_frame_durations; 234 *period = sum_frame_durations / count_frame_durations;
175 return true; 235 return true;
176 } 236 }
177 237
178 void AnimatedContentSampler::UpdateFrameTimestamp(base::TimeTicks event_time) { 238 base::TimeTicks AnimatedContentSampler::ComputeNextFrameTimestamp(
179 // This is how much time to advance from the last frame timestamp. Never 239 base::TimeTicks event_time) const {
180 // advance by less than |min_capture_period_| because the downstream consumer 240 // The ideal next frame timestamp one sampling period since the last one.
181 // cannot handle the higher frame rate. If |detected_period_| is less than 241 const base::TimeTicks ideal_timestamp = frame_timestamp_ + sampling_period_;
182 // |min_capture_period_|, excess frames should be dropped.
183 const base::TimeDelta advancement =
184 std::max(detected_period_, min_capture_period_);
185 242
186 // Compute the |timebase| upon which to determine the |frame_timestamp_|. 243 // Account for two main sources of drift: 1) The clock drift of the system
187 // Ideally, this would always equal the timestamp of the last recorded frame 244 // clock relative to the video hardware, which affects the event times; and
188 // sampling. Determine how much drift from the ideal is present, then adjust 245 // 2) The small error introduced by this frame timestamp rewriting, as it is
189 // the timebase by a small amount to spread out the entire correction over 246 // based on averaging over recent events.
190 // many frame timestamps.
191 // 247 //
192 // This accounts for two main sources of drift: 1) The clock drift of the 248 // TODO(miu): This is similar to the ClockSmoother in
193 // system clock relative to the video hardware, which affects the event times; 249 // media/base/audio_shifter.cc. Consider refactor-and-reuse here.
194 // and 2) The small error introduced by this frame timestamp rewriting, as it 250 const base::TimeDelta drift = ideal_timestamp - event_time;
195 // is based on averaging over recent events. 251 const int64 correct_over_num_frames =
196 base::TimeTicks timebase = event_time - sequence_offset_ - advancement; 252 base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) /
197 if (!recorded_frame_timestamp_.is_null()) { 253 sampling_period_;
198 const base::TimeDelta drift = recorded_frame_timestamp_ - timebase; 254 DCHECK_GT(correct_over_num_frames, 0);
199 const int64 correct_over_num_frames = 255
200 base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) / 256 return ideal_timestamp - drift / correct_over_num_frames;
201 detected_period_; 257 }
202 DCHECK_GT(correct_over_num_frames, 0); 258
203 timebase = recorded_frame_timestamp_ - (drift / correct_over_num_frames); 259 // static
260 base::TimeDelta AnimatedContentSampler::ComputeSamplingPeriod(
261 base::TimeDelta animation_period,
262 base::TimeDelta target_sampling_period,
263 base::TimeDelta min_capture_period) {
264 // If the animation rate is unknown, return the ideal sampling period.
265 if (animation_period == base::TimeDelta()) {
266 return std::max(target_sampling_period, min_capture_period);
204 } 267 }
205 268
206 // Compute |frame_timestamp_|. Whenever |detected_period_| is less than 269 // Determine whether subsampling is needed. If so, compute the sampling
207 // |min_capture_period_|, some extra time is "borrowed" to be able to advance 270 // period corresponding to the sampling rate is the closest integer division
208 // by the full |min_capture_period_|. Then, whenever the total amount of 271 // of the animation frame rate to the target sampling rate.
209 // borrowed time reaches a full |min_capture_period_|, drop a frame. Note 272 //
210 // that when |detected_period_| is greater or equal to |min_capture_period_|, 273 // For example, consider a target sampling rate of 30 FPS and an animation
211 // this logic is effectively disabled. 274 // rate of 42 FPS. Possible sampling rates would be 42/1 = 42, 42/2 = 21,
212 borrowed_time_ += advancement - detected_period_; 275 // 42/3 = 14, and so on. Of these candidates, 21 FPS is closest to 30.
213 if (borrowed_time_ >= min_capture_period_) { 276 base::TimeDelta sampling_period;
214 borrowed_time_ -= min_capture_period_; 277 if (animation_period < target_sampling_period) {
215 frame_timestamp_ = base::TimeTicks(); 278 const int64 ratio = target_sampling_period / animation_period;
279 const double target_fps = 1.0 / target_sampling_period.InSecondsF();
280 const double animation_fps = 1.0 / animation_period.InSecondsF();
281 if (std::abs(animation_fps / ratio - target_fps) <
282 std::abs(animation_fps / (ratio + 1) - target_fps)) {
283 sampling_period = ratio * animation_period;
284 } else {
285 sampling_period = (ratio + 1) * animation_period;
286 }
216 } else { 287 } else {
217 sequence_offset_ += advancement; 288 sampling_period = animation_period;
218 frame_timestamp_ = timebase + sequence_offset_;
219 } 289 }
290 return std::max(sampling_period, min_capture_period);
220 } 291 }
221 292
222 } // namespace content 293 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698