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

Side by Side Diff: media/capture/content/animated_content_sampler.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 <algorithm>
11
12 namespace media {
13
14 namespace {
15
16 // These specify the minimum/maximum amount of recent event history to examine
17 // to detect animated content. If the values are too low, there is a greater
18 // risk of false-positive detections and low accuracy. If they are too high,
19 // the the implementation will be slow to lock-in/out, and also will not react
20 // well to mildly-variable frame rate content (e.g., 25 +/- 1 FPS).
21 //
22 // These values were established by experimenting with a wide variety of
23 // scenarios, including 24/25/30 FPS videos, 60 FPS WebGL demos, and the
24 // transitions between static and animated content.
25 const int kMinObservationWindowMillis = 1000;
26 const int kMaxObservationWindowMillis = 2000;
27
28 // The maximum amount of time that can elapse before declaring two subsequent
29 // events as "not animating." This is the same value found in
30 // cc::FrameRateCounter.
31 const int kNonAnimatingThresholdMillis = 250; // 4 FPS
32
33 // The slowest that content can be animating in order for AnimatedContentSampler
34 // to lock-in. This is the threshold at which the "smoothness" problem is no
35 // longer relevant.
36 const int kMaxLockInPeriodMicros = 83333; // 12 FPS
37
38 // The amount of time over which to fully correct the drift of the rewritten
39 // frame timestamps from the presentation event timestamps. The lower the
40 // value, the higher the variance in frame timestamps.
41 const int kDriftCorrectionMillis = 2000;
42
43 } // anonymous namespace
44
45 AnimatedContentSampler::AnimatedContentSampler(
46 base::TimeDelta min_capture_period)
47 : min_capture_period_(min_capture_period), sampling_state_(NOT_SAMPLING) {
48 DCHECK_GT(min_capture_period_, base::TimeDelta());
49 }
50
51 AnimatedContentSampler::~AnimatedContentSampler() {
52 }
53
54 void AnimatedContentSampler::SetTargetSamplingPeriod(base::TimeDelta period) {
55 target_sampling_period_ = period;
56 }
57
58 void AnimatedContentSampler::ConsiderPresentationEvent(
59 const gfx::Rect& damage_rect,
60 base::TimeTicks event_time) {
61 // Analyze the current event and recent history to determine whether animating
62 // content is detected.
63 AddObservation(damage_rect, event_time);
64 if (!AnalyzeObservations(event_time, &detected_region_, &detected_period_) ||
65 detected_period_ <= base::TimeDelta() ||
66 detected_period_ >
67 base::TimeDelta::FromMicroseconds(kMaxLockInPeriodMicros)) {
68 // Animated content not detected.
69 detected_region_ = gfx::Rect();
70 detected_period_ = base::TimeDelta();
71 sampling_state_ = NOT_SAMPLING;
72 return;
73 }
74
75 // At this point, animation is being detected. Update the sampling period
76 // since the client may call the accessor method even if the heuristics below
77 // decide not to sample the current event.
78 sampling_period_ = ComputeSamplingPeriod(
79 detected_period_, target_sampling_period_, min_capture_period_);
80
81 // If this is the first event causing animating content to be detected,
82 // transition to the START_SAMPLING state.
83 if (sampling_state_ == NOT_SAMPLING)
84 sampling_state_ = START_SAMPLING;
85
86 // If the current event does not represent a frame that is part of the
87 // animation, do not sample.
88 if (damage_rect != detected_region_) {
89 if (sampling_state_ == SHOULD_SAMPLE)
90 sampling_state_ = SHOULD_NOT_SAMPLE;
91 return;
92 }
93
94 // When starting sampling, determine where to sync-up for sampling and frame
95 // timestamp rewriting. Otherwise, just add one animation period's worth of
96 // tokens to the token bucket.
97 if (sampling_state_ == START_SAMPLING) {
98 if (event_time - frame_timestamp_ > sampling_period_) {
99 // The frame timestamp sequence should start with the current event
100 // time.
101 frame_timestamp_ = event_time - sampling_period_;
102 token_bucket_ = sampling_period_;
103 } else {
104 // The frame timestamp sequence will continue from the last recorded
105 // frame timestamp.
106 token_bucket_ = event_time - frame_timestamp_;
107 }
108
109 // Provide a little extra in the initial token bucket so that minor error in
110 // the detected period won't prevent a reasonably-timed event from being
111 // sampled.
112 token_bucket_ += detected_period_ / 2;
113 } else {
114 token_bucket_ += detected_period_;
115 }
116
117 // If the token bucket is full enough, take tokens from it and propose
118 // sampling. Otherwise, do not sample.
119 DCHECK_LE(detected_period_, sampling_period_);
120 if (token_bucket_ >= sampling_period_) {
121 token_bucket_ -= sampling_period_;
122 frame_timestamp_ = ComputeNextFrameTimestamp(event_time);
123 sampling_state_ = SHOULD_SAMPLE;
124 } else {
125 sampling_state_ = SHOULD_NOT_SAMPLE;
126 }
127 }
128
129 bool AnimatedContentSampler::HasProposal() const {
130 return sampling_state_ != NOT_SAMPLING;
131 }
132
133 bool AnimatedContentSampler::ShouldSample() const {
134 return sampling_state_ == SHOULD_SAMPLE;
135 }
136
137 void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) {
138 if (sampling_state_ == NOT_SAMPLING)
139 frame_timestamp_ = frame_timestamp;
140 else if (sampling_state_ == SHOULD_SAMPLE)
141 sampling_state_ = SHOULD_NOT_SAMPLE;
142 }
143
144 void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect,
145 base::TimeTicks event_time) {
146 if (damage_rect.IsEmpty())
147 return; // Useless observation.
148
149 // Add the observation to the FIFO queue.
150 if (!observations_.empty() && observations_.back().event_time > event_time)
151 return; // The implementation assumes chronological order.
152 observations_.push_back(Observation(damage_rect, event_time));
153
154 // Prune-out old observations.
155 const base::TimeDelta threshold =
156 base::TimeDelta::FromMilliseconds(kMaxObservationWindowMillis);
157 while ((event_time - observations_.front().event_time) > threshold)
158 observations_.pop_front();
159 }
160
161 gfx::Rect AnimatedContentSampler::ElectMajorityDamageRect() const {
162 // This is an derivative of the Boyer-Moore Majority Vote Algorithm where each
163 // pixel in a candidate gets one vote, as opposed to each candidate getting
164 // one vote.
165 const gfx::Rect* candidate = NULL;
166 int64_t votes = 0;
167 for (ObservationFifo::const_iterator i = observations_.begin();
168 i != observations_.end(); ++i) {
169 DCHECK_GT(i->damage_rect.size().GetArea(), 0);
170 if (votes == 0) {
171 candidate = &(i->damage_rect);
172 votes = candidate->size().GetArea();
173 } else if (i->damage_rect == *candidate) {
174 votes += i->damage_rect.size().GetArea();
175 } else {
176 votes -= i->damage_rect.size().GetArea();
177 if (votes < 0) {
178 candidate = &(i->damage_rect);
179 votes = -votes;
180 }
181 }
182 }
183 return (votes > 0) ? *candidate : gfx::Rect();
184 }
185
186 bool AnimatedContentSampler::AnalyzeObservations(
187 base::TimeTicks event_time,
188 gfx::Rect* rect,
189 base::TimeDelta* period) const {
190 const gfx::Rect elected_rect = ElectMajorityDamageRect();
191 if (elected_rect.IsEmpty())
192 return false; // There is no regular animation present.
193
194 // Scan |observations_|, gathering metrics about the ones having a damage Rect
195 // equivalent to the |elected_rect|. Along the way, break early whenever the
196 // event times reveal a non-animating period.
197 int64_t num_pixels_damaged_in_all = 0;
198 int64_t num_pixels_damaged_in_chosen = 0;
199 base::TimeDelta sum_frame_durations;
200 size_t count_frame_durations = 0;
201 base::TimeTicks first_event_time;
202 base::TimeTicks last_event_time;
203 for (ObservationFifo::const_reverse_iterator i = observations_.rbegin();
204 i != observations_.rend(); ++i) {
205 const int area = i->damage_rect.size().GetArea();
206 num_pixels_damaged_in_all += area;
207 if (i->damage_rect != elected_rect)
208 continue;
209 num_pixels_damaged_in_chosen += area;
210 if (last_event_time.is_null()) {
211 last_event_time = i->event_time;
212 if ((event_time - last_event_time) >=
213 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis)) {
214 return false; // Content animation has recently ended.
215 }
216 } else {
217 const base::TimeDelta frame_duration = first_event_time - i->event_time;
218 if (frame_duration >=
219 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis)) {
220 break; // Content not animating before this point.
221 }
222 sum_frame_durations += frame_duration;
223 ++count_frame_durations;
224 }
225 first_event_time = i->event_time;
226 }
227
228 if ((last_event_time - first_event_time) <
229 base::TimeDelta::FromMilliseconds(kMinObservationWindowMillis)) {
230 return false; // Content has not animated for long enough for accuracy.
231 }
232 if (num_pixels_damaged_in_chosen <= (num_pixels_damaged_in_all * 2 / 3))
233 return false; // Animation is not damaging a supermajority of pixels.
234
235 *rect = elected_rect;
236 DCHECK_GT(count_frame_durations, 0u);
237 *period = sum_frame_durations / count_frame_durations;
238 return true;
239 }
240
241 base::TimeTicks AnimatedContentSampler::ComputeNextFrameTimestamp(
242 base::TimeTicks event_time) const {
243 // The ideal next frame timestamp one sampling period since the last one.
244 const base::TimeTicks ideal_timestamp = frame_timestamp_ + sampling_period_;
245
246 // Account for two main sources of drift: 1) The clock drift of the system
247 // clock relative to the video hardware, which affects the event times; and
248 // 2) The small error introduced by this frame timestamp rewriting, as it is
249 // based on averaging over recent events.
250 //
251 // TODO(miu): This is similar to the ClockSmoother in
252 // media/base/audio_shifter.cc. Consider refactor-and-reuse here.
253 const base::TimeDelta drift = ideal_timestamp - event_time;
254 const int64_t correct_over_num_frames =
255 base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) /
256 sampling_period_;
257 DCHECK_GT(correct_over_num_frames, 0);
258
259 return ideal_timestamp - drift / correct_over_num_frames;
260 }
261
262 // static
263 base::TimeDelta AnimatedContentSampler::ComputeSamplingPeriod(
264 base::TimeDelta animation_period,
265 base::TimeDelta target_sampling_period,
266 base::TimeDelta min_capture_period) {
267 // If the animation rate is unknown, return the ideal sampling period.
268 if (animation_period.is_zero()) {
269 return std::max(target_sampling_period, min_capture_period);
270 }
271
272 // Determine whether subsampling is needed. If so, compute the sampling
273 // period corresponding to the sampling rate is the closest integer division
274 // of the animation frame rate to the target sampling rate.
275 //
276 // For example, consider a target sampling rate of 30 FPS and an animation
277 // rate of 42 FPS. Possible sampling rates would be 42/1 = 42, 42/2 = 21,
278 // 42/3 = 14, and so on. Of these candidates, 21 FPS is closest to 30.
279 base::TimeDelta sampling_period;
280 if (animation_period < target_sampling_period) {
281 const int64_t ratio = target_sampling_period / animation_period;
282 const double target_fps = 1.0 / target_sampling_period.InSecondsF();
283 const double animation_fps = 1.0 / animation_period.InSecondsF();
284 if (std::abs(animation_fps / ratio - target_fps) <
285 std::abs(animation_fps / (ratio + 1) - target_fps)) {
286 sampling_period = ratio * animation_period;
287 } else {
288 sampling_period = (ratio + 1) * animation_period;
289 }
290 } else {
291 sampling_period = animation_period;
292 }
293 return std::max(sampling_period, min_capture_period);
294 }
295
296 } // namespace media
OLDNEW
« no previous file with comments | « media/capture/content/animated_content_sampler.h ('k') | media/capture/content/animated_content_sampler_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698