ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / content / browser / media / capture / video_capture_oracle.cc
blobdad77c72e965da7e79bca59a1417f0b29a09a816
1 // Copyright (c) 2013 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.
5 #include "content/browser/media/capture/video_capture_oracle.h"
7 #include <algorithm>
9 #include "base/format_macros.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/trace_event/trace_event.h"
13 namespace content {
15 namespace {
17 // This value controls how many redundant, timer-base captures occur when the
18 // content is static. Redundantly capturing the same frame allows iterative
19 // quality enhancement, and also allows the buffer to fill in "buffered mode".
21 // TODO(nick): Controlling this here is a hack and a layering violation, since
22 // it's a strategy specific to the WebRTC consumer, and probably just papers
23 // over some frame dropping and quality bugs. It should either be controlled at
24 // a higher level, or else redundant frame generation should be pushed down
25 // further into the WebRTC encoding stack.
26 const int kNumRedundantCapturesOfStaticContent = 200;
28 // These specify the minimum/maximum amount of recent event history to examine
29 // to detect animated content. If the values are too low, there is a greater
30 // risk of false-positive detections and low accuracy. If they are too high,
31 // the the implementation will be slow to lock-in/out, and also will not react
32 // well to mildly-variable frame rate content (e.g., 25 +/- 1 FPS).
34 // These values were established by experimenting with a wide variety of
35 // scenarios, including 24/25/30 FPS videos, 60 FPS WebGL demos, and the
36 // transitions between static and animated content.
37 const int kMinObservationWindowMillis = 1000;
38 const int kMaxObservationWindowMillis = 2000;
40 // The maximum amount of time that can elapse before declaring two subsequent
41 // events as "not animating." This is the same value found in
42 // cc::FrameRateCounter.
43 const int kNonAnimatingThresholdMillis = 250; // 4 FPS
45 // The slowest that content can be animating in order for AnimatedContentSampler
46 // to lock-in. This is the threshold at which the "smoothness" problem is no
47 // longer relevant.
48 const int kMaxLockInPeriodMicros = 83333; // 12 FPS
50 // The amount of time over which to fully correct the drift of the rewritten
51 // frame timestamps from the presentation event timestamps. The lower the
52 // value, the higher the variance in frame timestamps.
53 const int kDriftCorrectionMillis = 2000;
55 // Given the amount of time between frames, compare to the expected amount of
56 // time between frames at |frame_rate| and return the fractional difference.
57 double FractionFromExpectedFrameRate(base::TimeDelta delta, int frame_rate) {
58 DCHECK_GT(frame_rate, 0);
59 const base::TimeDelta expected_delta =
60 base::TimeDelta::FromSeconds(1) / frame_rate;
61 return (delta - expected_delta).InMillisecondsF() /
62 expected_delta.InMillisecondsF();
65 } // anonymous namespace
67 VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta min_capture_period)
68 : frame_number_(0),
69 last_delivered_frame_number_(-1),
70 smoothing_sampler_(min_capture_period,
71 kNumRedundantCapturesOfStaticContent),
72 content_sampler_(min_capture_period) {
75 VideoCaptureOracle::~VideoCaptureOracle() {}
77 bool VideoCaptureOracle::ObserveEventAndDecideCapture(
78 Event event,
79 const gfx::Rect& damage_rect,
80 base::TimeTicks event_time) {
81 DCHECK_GE(event, 0);
82 DCHECK_LT(event, kNumEvents);
83 if (event_time < last_event_time_[event]) {
84 LOG(WARNING) << "Event time is not monotonically non-decreasing. "
85 << "Deciding not to capture this frame.";
86 return false;
88 last_event_time_[event] = event_time;
90 bool should_sample;
91 switch (event) {
92 case kCompositorUpdate:
93 case kSoftwarePaint:
94 smoothing_sampler_.ConsiderPresentationEvent(event_time);
95 content_sampler_.ConsiderPresentationEvent(damage_rect, event_time);
96 if (content_sampler_.HasProposal()) {
97 should_sample = content_sampler_.ShouldSample();
98 if (should_sample)
99 event_time = content_sampler_.frame_timestamp();
100 } else {
101 should_sample = smoothing_sampler_.ShouldSample();
103 break;
104 default:
105 should_sample = smoothing_sampler_.IsOverdueForSamplingAt(event_time);
106 break;
109 SetFrameTimestamp(frame_number_, event_time);
110 return should_sample;
113 int VideoCaptureOracle::RecordCapture() {
114 smoothing_sampler_.RecordSample();
115 content_sampler_.RecordSample(GetFrameTimestamp(frame_number_));
116 return frame_number_++;
119 bool VideoCaptureOracle::CompleteCapture(int frame_number,
120 base::TimeTicks* frame_timestamp) {
121 // Drop frame if previous frame number is higher.
122 if (last_delivered_frame_number_ > frame_number) {
123 LOG(WARNING) << "Out of order frame delivery detected. Dropping frame.";
124 return false;
126 last_delivered_frame_number_ = frame_number;
128 *frame_timestamp = GetFrameTimestamp(frame_number);
130 // If enabled, log a measurement of how this frame timestamp has incremented
131 // in relation to an ideal increment.
132 if (VLOG_IS_ON(2) && frame_number > 0) {
133 const base::TimeDelta delta =
134 *frame_timestamp - GetFrameTimestamp(frame_number - 1);
135 if (content_sampler_.HasProposal()) {
136 const double estimated_frame_rate =
137 1000000.0 / content_sampler_.detected_period().InMicroseconds();
138 const int rounded_frame_rate =
139 static_cast<int>(estimated_frame_rate + 0.5);
140 VLOG(2) << base::StringPrintf(
141 "Captured #%d: delta=%" PRId64 " usec"
142 ", now locked into {%s}, %+0.1f%% slower than %d FPS",
143 frame_number,
144 delta.InMicroseconds(),
145 content_sampler_.detected_region().ToString().c_str(),
146 100.0 * FractionFromExpectedFrameRate(delta, rounded_frame_rate),
147 rounded_frame_rate);
148 } else {
149 VLOG(2) << base::StringPrintf(
150 "Captured #%d: delta=%" PRId64 " usec"
151 ", d/30fps=%+0.1f%%, d/25fps=%+0.1f%%, d/24fps=%+0.1f%%",
152 frame_number,
153 delta.InMicroseconds(),
154 100.0 * FractionFromExpectedFrameRate(delta, 30),
155 100.0 * FractionFromExpectedFrameRate(delta, 25),
156 100.0 * FractionFromExpectedFrameRate(delta, 24));
160 return !frame_timestamp->is_null();
163 base::TimeTicks VideoCaptureOracle::GetFrameTimestamp(int frame_number) const {
164 DCHECK_LE(frame_number, frame_number_);
165 DCHECK_LT(frame_number_ - frame_number, kMaxFrameTimestamps);
166 return frame_timestamps_[frame_number % kMaxFrameTimestamps];
169 void VideoCaptureOracle::SetFrameTimestamp(int frame_number,
170 base::TimeTicks timestamp) {
171 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp;
174 SmoothEventSampler::SmoothEventSampler(base::TimeDelta min_capture_period,
175 int redundant_capture_goal)
176 : min_capture_period_(min_capture_period),
177 redundant_capture_goal_(redundant_capture_goal),
178 token_bucket_capacity_(min_capture_period + min_capture_period / 2),
179 overdue_sample_count_(0),
180 token_bucket_(token_bucket_capacity_) {
181 DCHECK_GT(min_capture_period_.InMicroseconds(), 0);
184 void SmoothEventSampler::ConsiderPresentationEvent(base::TimeTicks event_time) {
185 DCHECK(!event_time.is_null());
187 // Add tokens to the bucket based on advancement in time. Then, re-bound the
188 // number of tokens in the bucket. Overflow occurs when there is too much
189 // time between events (a common case), or when RecordSample() is not being
190 // called often enough (a bug). On the other hand, if RecordSample() is being
191 // called too often (e.g., as a reaction to IsOverdueForSamplingAt()), the
192 // bucket will underflow.
193 if (!current_event_.is_null()) {
194 if (current_event_ < event_time) {
195 token_bucket_ += event_time - current_event_;
196 if (token_bucket_ > token_bucket_capacity_)
197 token_bucket_ = token_bucket_capacity_;
199 TRACE_COUNTER1("mirroring",
200 "MirroringTokenBucketUsec",
201 std::max<int64>(0, token_bucket_.InMicroseconds()));
203 current_event_ = event_time;
206 bool SmoothEventSampler::ShouldSample() const {
207 return token_bucket_ >= min_capture_period_;
210 void SmoothEventSampler::RecordSample() {
211 token_bucket_ -= min_capture_period_;
212 if (token_bucket_ < base::TimeDelta())
213 token_bucket_ = base::TimeDelta();
214 TRACE_COUNTER1("mirroring",
215 "MirroringTokenBucketUsec",
216 std::max<int64>(0, token_bucket_.InMicroseconds()));
218 if (HasUnrecordedEvent()) {
219 last_sample_ = current_event_;
220 overdue_sample_count_ = 0;
221 } else {
222 ++overdue_sample_count_;
226 bool SmoothEventSampler::IsOverdueForSamplingAt(base::TimeTicks event_time)
227 const {
228 DCHECK(!event_time.is_null());
230 if (!HasUnrecordedEvent() && overdue_sample_count_ >= redundant_capture_goal_)
231 return false; // Not dirty.
233 if (last_sample_.is_null())
234 return true;
236 // If we're dirty but not yet old, then we've recently gotten updates, so we
237 // won't request a sample just yet.
238 base::TimeDelta dirty_interval = event_time - last_sample_;
239 return dirty_interval >=
240 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis);
243 bool SmoothEventSampler::HasUnrecordedEvent() const {
244 return !current_event_.is_null() && current_event_ != last_sample_;
247 AnimatedContentSampler::AnimatedContentSampler(
248 base::TimeDelta min_capture_period)
249 : min_capture_period_(min_capture_period) {}
251 AnimatedContentSampler::~AnimatedContentSampler() {}
253 void AnimatedContentSampler::ConsiderPresentationEvent(
254 const gfx::Rect& damage_rect, base::TimeTicks event_time) {
255 AddObservation(damage_rect, event_time);
257 if (AnalyzeObservations(event_time, &detected_region_, &detected_period_) &&
258 detected_period_ > base::TimeDelta() &&
259 detected_period_ <=
260 base::TimeDelta::FromMicroseconds(kMaxLockInPeriodMicros)) {
261 if (damage_rect == detected_region_)
262 UpdateFrameTimestamp(event_time);
263 else
264 frame_timestamp_ = base::TimeTicks();
265 } else {
266 detected_region_ = gfx::Rect();
267 detected_period_ = base::TimeDelta();
268 frame_timestamp_ = base::TimeTicks();
272 bool AnimatedContentSampler::HasProposal() const {
273 return detected_period_ > base::TimeDelta();
276 bool AnimatedContentSampler::ShouldSample() const {
277 return !frame_timestamp_.is_null();
280 void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) {
281 recorded_frame_timestamp_ =
282 HasProposal() ? frame_timestamp : base::TimeTicks();
283 sequence_offset_ = base::TimeDelta();
286 void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect,
287 base::TimeTicks event_time) {
288 if (damage_rect.IsEmpty())
289 return; // Useless observation.
291 // Add the observation to the FIFO queue.
292 if (!observations_.empty() && observations_.back().event_time > event_time)
293 return; // The implementation assumes chronological order.
294 observations_.push_back(Observation(damage_rect, event_time));
296 // Prune-out old observations.
297 const base::TimeDelta threshold =
298 base::TimeDelta::FromMilliseconds(kMaxObservationWindowMillis);
299 while ((event_time - observations_.front().event_time) > threshold)
300 observations_.pop_front();
303 gfx::Rect AnimatedContentSampler::ElectMajorityDamageRect() const {
304 // This is an derivative of the Boyer-Moore Majority Vote Algorithm where each
305 // pixel in a candidate gets one vote, as opposed to each candidate getting
306 // one vote.
307 const gfx::Rect* candidate = NULL;
308 int64 votes = 0;
309 for (ObservationFifo::const_iterator i = observations_.begin();
310 i != observations_.end(); ++i) {
311 DCHECK_GT(i->damage_rect.size().GetArea(), 0);
312 if (votes == 0) {
313 candidate = &(i->damage_rect);
314 votes = candidate->size().GetArea();
315 } else if (i->damage_rect == *candidate) {
316 votes += i->damage_rect.size().GetArea();
317 } else {
318 votes -= i->damage_rect.size().GetArea();
319 if (votes < 0) {
320 candidate = &(i->damage_rect);
321 votes = -votes;
325 return (votes > 0) ? *candidate : gfx::Rect();
328 bool AnimatedContentSampler::AnalyzeObservations(
329 base::TimeTicks event_time,
330 gfx::Rect* rect,
331 base::TimeDelta* period) const {
332 const gfx::Rect elected_rect = ElectMajorityDamageRect();
333 if (elected_rect.IsEmpty())
334 return false; // There is no regular animation present.
336 // Scan |observations_|, gathering metrics about the ones having a damage Rect
337 // equivalent to the |elected_rect|. Along the way, break early whenever the
338 // event times reveal a non-animating period.
339 int64 num_pixels_damaged_in_all = 0;
340 int64 num_pixels_damaged_in_chosen = 0;
341 base::TimeDelta sum_frame_durations;
342 size_t count_frame_durations = 0;
343 base::TimeTicks first_event_time;
344 base::TimeTicks last_event_time;
345 for (ObservationFifo::const_reverse_iterator i = observations_.rbegin();
346 i != observations_.rend(); ++i) {
347 const int area = i->damage_rect.size().GetArea();
348 num_pixels_damaged_in_all += area;
349 if (i->damage_rect != elected_rect)
350 continue;
351 num_pixels_damaged_in_chosen += area;
352 if (last_event_time.is_null()) {
353 last_event_time = i->event_time;
354 if ((event_time - last_event_time) >=
355 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis)) {
356 return false; // Content animation has recently ended.
358 } else {
359 const base::TimeDelta frame_duration = first_event_time - i->event_time;
360 if (frame_duration >=
361 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis)) {
362 break; // Content not animating before this point.
364 sum_frame_durations += frame_duration;
365 ++count_frame_durations;
367 first_event_time = i->event_time;
370 if ((last_event_time - first_event_time) <
371 base::TimeDelta::FromMilliseconds(kMinObservationWindowMillis)) {
372 return false; // Content has not animated for long enough for accuracy.
374 if (num_pixels_damaged_in_chosen <= (num_pixels_damaged_in_all * 2 / 3))
375 return false; // Animation is not damaging a supermajority of pixels.
377 *rect = elected_rect;
378 DCHECK_GT(count_frame_durations, 0u);
379 *period = sum_frame_durations / count_frame_durations;
380 return true;
383 void AnimatedContentSampler::UpdateFrameTimestamp(base::TimeTicks event_time) {
384 // This is how much time to advance from the last frame timestamp. Never
385 // advance by less than |min_capture_period_| because the downstream consumer
386 // cannot handle the higher frame rate. If |detected_period_| is less than
387 // |min_capture_period_|, excess frames should be dropped.
388 const base::TimeDelta advancement =
389 std::max(detected_period_, min_capture_period_);
391 // Compute the |timebase| upon which to determine the |frame_timestamp_|.
392 // Ideally, this would always equal the timestamp of the last recorded frame
393 // sampling. Determine how much drift from the ideal is present, then adjust
394 // the timebase by a small amount to spread out the entire correction over
395 // many frame timestamps.
397 // This accounts for two main sources of drift: 1) The clock drift of the
398 // system clock relative to the video hardware, which affects the event times;
399 // and 2) The small error introduced by this frame timestamp rewriting, as it
400 // is based on averaging over recent events.
401 base::TimeTicks timebase = event_time - sequence_offset_ - advancement;
402 if (!recorded_frame_timestamp_.is_null()) {
403 const base::TimeDelta drift = recorded_frame_timestamp_ - timebase;
404 const int64 correct_over_num_frames =
405 base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) /
406 detected_period_;
407 DCHECK_GT(correct_over_num_frames, 0);
408 timebase = recorded_frame_timestamp_ - (drift / correct_over_num_frames);
411 // Compute |frame_timestamp_|. Whenever |detected_period_| is less than
412 // |min_capture_period_|, some extra time is "borrowed" to be able to advance
413 // by the full |min_capture_period_|. Then, whenever the total amount of
414 // borrowed time reaches a full |min_capture_period_|, drop a frame. Note
415 // that when |detected_period_| is greater or equal to |min_capture_period_|,
416 // this logic is effectively disabled.
417 borrowed_time_ += advancement - detected_period_;
418 if (borrowed_time_ >= min_capture_period_) {
419 borrowed_time_ -= min_capture_period_;
420 frame_timestamp_ = base::TimeTicks();
421 } else {
422 sequence_offset_ += advancement;
423 frame_timestamp_ = timebase + sequence_offset_;
427 } // namespace content