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

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: 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 sampling_period_ = ComputeSamplingPeriod(detected_period_,
hubbe 2015/05/06 19:53:18 Do we need to do this here? Can we do it after the
miu 2015/05/09 20:57:38 Added a comment explaining this. It could be move
72 target_sampling_period_,
73 min_capture_period_);
74
75 // If this is the first event causing animating content to be detected,
76 // transition to the START_SAMPLING state.
77 if (sampling_state_ == NOT_SAMPLING)
78 sampling_state_ = START_SAMPLING;
79
80 // If the current event does not represent a frame that is part of the
81 // animation, do not sample.
82 if (damage_rect != detected_region_) {
83 if (sampling_state_ == SHOULD_SAMPLE)
84 sampling_state_ = SHOULD_NOT_SAMPLE;
85 return;
86 }
87
88 // When starting sampling, determine where to sync-up for sampling and frame
89 // timestamp rewriting. Otherwise, just add one animation period's worth of
90 // tokens to the token bucket.
91 if (sampling_state_ == START_SAMPLING) {
92 if (event_time - frame_timestamp_ > sampling_period_) {
93 // The frame timestamp sequence should start with the current event
94 // time.
95 frame_timestamp_ = event_time - sampling_period_;
96 token_bucket_ = sampling_period_;
97 } else {
98 // The frame timestamp sequence will continue from the last recorded
99 // frame timestamp.
100 token_bucket_ = event_time - frame_timestamp_;
101 }
102 } else {
103 token_bucket_ += detected_period_;
104 }
105
106 // If the token bucket is full enough, take tokens from it and propose
107 // sampling. Otherwise, do not sample.
108 DCHECK_LE(detected_period_, sampling_period_);
109 if (token_bucket_ >= sampling_period_) {
hubbe 2015/05/06 19:53:18 Correct me if I'm wrong, but it seems like this to
miu 2015/05/09 20:57:38 It doesn't. Both real-world testing and the unit
hubbe 2015/05/12 19:29:03 I think you're right. I was thinking that the prob
miu 2015/05/13 00:01:01 Ah, yes. I little extra shouldn't hurt and should
110 token_bucket_ -= sampling_period_;
111 frame_timestamp_ = ComputeNextFrameTimestamp(event_time);
112 sampling_state_ = SHOULD_SAMPLE;
113 } else {
114 sampling_state_ = SHOULD_NOT_SAMPLE;
64 } 115 }
65 } 116 }
66 117
67 bool AnimatedContentSampler::HasProposal() const { 118 bool AnimatedContentSampler::HasProposal() const {
68 return detected_period_ > base::TimeDelta(); 119 return sampling_state_ != NOT_SAMPLING;
69 } 120 }
70 121
71 bool AnimatedContentSampler::ShouldSample() const { 122 bool AnimatedContentSampler::ShouldSample() const {
72 return !frame_timestamp_.is_null(); 123 return sampling_state_ == SHOULD_SAMPLE;
73 } 124 }
74 125
75 void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) { 126 void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) {
76 recorded_frame_timestamp_ = 127 if (sampling_state_ == NOT_SAMPLING)
77 HasProposal() ? frame_timestamp : base::TimeTicks(); 128 frame_timestamp_ = frame_timestamp;
78 sequence_offset_ = base::TimeDelta(); 129 else if (sampling_state_ == SHOULD_SAMPLE)
130 sampling_state_ = SHOULD_NOT_SAMPLE;
79 } 131 }
80 132
81 void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect, 133 void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect,
82 base::TimeTicks event_time) { 134 base::TimeTicks event_time) {
83 if (damage_rect.IsEmpty()) 135 if (damage_rect.IsEmpty())
84 return; // Useless observation. 136 return; // Useless observation.
85 137
86 // Add the observation to the FIFO queue. 138 // Add the observation to the FIFO queue.
87 if (!observations_.empty() && observations_.back().event_time > event_time) 139 if (!observations_.empty() && observations_.back().event_time > event_time)
88 return; // The implementation assumes chronological order. 140 return; // The implementation assumes chronological order.
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
168 } 220 }
169 if (num_pixels_damaged_in_chosen <= (num_pixels_damaged_in_all * 2 / 3)) 221 if (num_pixels_damaged_in_chosen <= (num_pixels_damaged_in_all * 2 / 3))
170 return false; // Animation is not damaging a supermajority of pixels. 222 return false; // Animation is not damaging a supermajority of pixels.
171 223
172 *rect = elected_rect; 224 *rect = elected_rect;
173 DCHECK_GT(count_frame_durations, 0u); 225 DCHECK_GT(count_frame_durations, 0u);
174 *period = sum_frame_durations / count_frame_durations; 226 *period = sum_frame_durations / count_frame_durations;
175 return true; 227 return true;
176 } 228 }
177 229
178 void AnimatedContentSampler::UpdateFrameTimestamp(base::TimeTicks event_time) { 230 base::TimeTicks AnimatedContentSampler::ComputeNextFrameTimestamp(
179 // This is how much time to advance from the last frame timestamp. Never 231 base::TimeTicks event_time) const {
180 // advance by less than |min_capture_period_| because the downstream consumer 232 // 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 233 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 234
186 // Compute the |timebase| upon which to determine the |frame_timestamp_|. 235 // 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 236 // 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 237 // 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 238 // based on averaging over recent events.
190 // many frame timestamps. 239 const base::TimeDelta drift = ideal_timestamp - event_time;
191 // 240 const int64 correct_over_num_frames =
192 // This accounts for two main sources of drift: 1) The clock drift of the 241 base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) /
193 // system clock relative to the video hardware, which affects the event times; 242 sampling_period_;
194 // and 2) The small error introduced by this frame timestamp rewriting, as it 243 DCHECK_GT(correct_over_num_frames, 0);
195 // is based on averaging over recent events. 244
196 base::TimeTicks timebase = event_time - sequence_offset_ - advancement; 245 return ideal_timestamp - drift / correct_over_num_frames;
hubbe 2015/05/06 19:53:18 Wonder if it would make sense to break out ClockSm
miu 2015/05/09 20:57:38 As discussed face-to-face, I'll consider this for
hubbe 2015/05/12 19:29:03 Optional: Add a TODO?
miu 2015/05/13 00:01:01 Done.
197 if (!recorded_frame_timestamp_.is_null()) { 246 }
198 const base::TimeDelta drift = recorded_frame_timestamp_ - timebase; 247
199 const int64 correct_over_num_frames = 248 // static
200 base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) / 249 base::TimeDelta AnimatedContentSampler::ComputeSamplingPeriod(
201 detected_period_; 250 base::TimeDelta animation_period,
202 DCHECK_GT(correct_over_num_frames, 0); 251 base::TimeDelta target_sampling_period,
203 timebase = recorded_frame_timestamp_ - (drift / correct_over_num_frames); 252 base::TimeDelta min_capture_period) {
253 // If the animation rate is unknown, return the ideal sampling period.
254 if (animation_period == base::TimeDelta()) {
255 return std::max(target_sampling_period, min_capture_period);
204 } 256 }
205 257
206 // Compute |frame_timestamp_|. Whenever |detected_period_| is less than 258 // Determine whether subsampling is needed to achieve the target sampling
207 // |min_capture_period_|, some extra time is "borrowed" to be able to advance 259 // period. If so, compute the sampling period such that the sampling rate is
208 // by the full |min_capture_period_|. Then, whenever the total amount of 260 // the closest integer multiple of the animation frame rate.
209 // borrowed time reaches a full |min_capture_period_|, drop a frame. Note 261 base::TimeDelta sampling_period;
210 // that when |detected_period_| is greater or equal to |min_capture_period_|, 262 if (animation_period < target_sampling_period) {
211 // this logic is effectively disabled. 263 const double target_fps = 1.0 / target_sampling_period.InSecondsF();
212 borrowed_time_ += advancement - detected_period_; 264 const double animation_fps = 1.0 / animation_period.InSecondsF();
213 if (borrowed_time_ >= min_capture_period_) { 265 const int64 ratio = target_sampling_period / animation_period;
214 borrowed_time_ -= min_capture_period_; 266 if (std::abs(animation_fps / ratio - target_fps) <
hubbe 2015/05/06 19:53:18 I don't think you need this if statement. I think
miu 2015/05/09 20:57:38 |ratio| is an int64, so there's an implicit floor'
hubbe 2015/05/12 19:29:03 Ok, I'm officially an idiot, now I've read up on f
miu 2015/05/13 00:01:01 As discussed, our mad algebra and spreadsheet skil
215 frame_timestamp_ = base::TimeTicks(); 267 std::abs(animation_fps / (ratio + 1) - target_fps)) {
268 sampling_period = ratio * animation_period;
269 } else {
270 sampling_period = (ratio + 1) * animation_period;
271 }
216 } else { 272 } else {
217 sequence_offset_ += advancement; 273 sampling_period = animation_period;
218 frame_timestamp_ = timebase + sequence_offset_;
219 } 274 }
275 return std::max(sampling_period, min_capture_period);
220 } 276 }
221 277
222 } // namespace content 278 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698