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

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

Issue 418283003: "Buttery Smooth" Tab Capture. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 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 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/video_capture_oracle.h"
6 6
7 #include <algorithm>
8
7 #include "base/debug/trace_event.h" 9 #include "base/debug/trace_event.h"
10 #include "base/format_macros.h"
11 #include "base/strings/stringprintf.h"
8 12
9 namespace content { 13 namespace content {
10 14
11 namespace { 15 namespace {
12 16
13 // This value controls how many redundant, timer-base captures occur when the 17 // This value controls how many redundant, timer-base captures occur when the
14 // content is static. Redundantly capturing the same frame allows iterative 18 // content is static. Redundantly capturing the same frame allows iterative
15 // quality enhancement, and also allows the buffer to fill in "buffered mode". 19 // quality enhancement, and also allows the buffer to fill in "buffered mode".
16 // 20 //
17 // TODO(nick): Controlling this here is a hack and a layering violation, since 21 // TODO(nick): Controlling this here is a hack and a layering violation, since
18 // it's a strategy specific to the WebRTC consumer, and probably just papers 22 // it's a strategy specific to the WebRTC consumer, and probably just papers
19 // over some frame dropping and quality bugs. It should either be controlled at 23 // over some frame dropping and quality bugs. It should either be controlled at
20 // a higher level, or else redundant frame generation should be pushed down 24 // a higher level, or else redundant frame generation should be pushed down
21 // further into the WebRTC encoding stack. 25 // further into the WebRTC encoding stack.
22 const int kNumRedundantCapturesOfStaticContent = 200; 26 const int kNumRedundantCapturesOfStaticContent = 200;
23 27
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).
33 //
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;
39
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
44
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 kMaxLockInPeriodMillis = 83333; // 12 FPS
49
50 // The amount of time over which to fully correct clock drift, when computing
51 // the timestamp of each successive frame. The lower the value, the higher the
52 // variance in frame timestamps.
53 const int kDriftCorrectionMillis = 6000;
54
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();
63 }
64
24 } // anonymous namespace 65 } // anonymous namespace
25 66
26 VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta capture_period, 67 VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta min_capture_period,
27 bool events_are_reliable) 68 bool events_are_reliable)
28 : capture_period_(capture_period), 69 : min_capture_period_(min_capture_period),
29 frame_number_(0), 70 frame_number_(0),
30 last_delivered_frame_number_(0), 71 last_delivered_frame_number_(-1),
31 sampler_(capture_period_, 72 smoothing_sampler_(min_capture_period_,
32 events_are_reliable, 73 events_are_reliable,
33 kNumRedundantCapturesOfStaticContent) {} 74 kNumRedundantCapturesOfStaticContent),
75 content_sampler_(min_capture_period_) {
76 }
77
78 VideoCaptureOracle::~VideoCaptureOracle() {}
34 79
35 bool VideoCaptureOracle::ObserveEventAndDecideCapture( 80 bool VideoCaptureOracle::ObserveEventAndDecideCapture(
36 Event event, 81 Event event,
82 const gfx::Rect& damage_rect,
37 base::TimeTicks event_time) { 83 base::TimeTicks event_time) {
38 // Record |event| and decide whether it's a good time to capture. 84 DCHECK_GE(event, 0);
39 const bool content_is_dirty = (event == kCompositorUpdate || 85 DCHECK_LT(event, kNumEvents);
40 event == kSoftwarePaint); 86 if (event_time < last_event_time_[event]) {
87 LOG(WARNING) << "Event time is not monotonically non-decreasing. "
88 << "Deciding not to capture this frame.";
89 return false;
90 }
91 last_event_time_[event] = event_time;
92
41 bool should_sample; 93 bool should_sample;
42 if (content_is_dirty) { 94 switch (event) {
43 frame_number_++; 95 case kCompositorUpdate:
44 should_sample = sampler_.AddEventAndConsiderSampling(event_time); 96 case kSoftwarePaint:
45 } else { 97 should_sample =
46 should_sample = sampler_.IsOverdueForSamplingAt(event_time); 98 smoothing_sampler_.AddEventAndConsiderSampling(event_time);
99 if (content_sampler_.ConsiderPresentationEvent(damage_rect, event_time)) {
100 event_time = content_sampler_.next_frame_timestamp();
101 should_sample = !event_time.is_null();
102 }
103 break;
104 default:
105 should_sample = smoothing_sampler_.IsOverdueForSamplingAt(event_time);
106 break;
47 } 107 }
108
109 SetFrameTimestamp(frame_number_, event_time);
48 return should_sample; 110 return should_sample;
49 } 111 }
50 112
51 int VideoCaptureOracle::RecordCapture() { 113 int VideoCaptureOracle::RecordCapture() {
52 sampler_.RecordSample(); 114 smoothing_sampler_.RecordSample();
53 return frame_number_; 115 content_sampler_.RecordSample(GetFrameTimestamp(frame_number_));
116 return frame_number_++;
54 } 117 }
55 118
56 bool VideoCaptureOracle::CompleteCapture(int frame_number, 119 bool VideoCaptureOracle::CompleteCapture(int frame_number,
57 base::TimeTicks timestamp) { 120 base::TimeTicks* frame_timestamp) {
58 // Drop frame if previous frame number is higher or we're trying to deliver 121 // Drop frame if previous frame number is higher.
59 // a frame with the same timestamp. 122 if (last_delivered_frame_number_ > frame_number) {
60 if (last_delivered_frame_number_ > frame_number || 123 LOG(WARNING) << "Out of order frame delivery detected. Dropping frame ";
61 last_delivered_frame_timestamp_ == timestamp) {
62 LOG(ERROR) << "Frame with same timestamp or out of order delivery. "
63 << "Dropping frame.";
64 return false; 124 return false;
65 } 125 }
126 last_delivered_frame_number_ = frame_number;
66 127
67 if (last_delivered_frame_timestamp_ > timestamp) { 128 *frame_timestamp = GetFrameTimestamp(frame_number);
68 // We should not get here unless time was adjusted backwards. 129
69 LOG(ERROR) << "Frame with past timestamp (" << timestamp.ToInternalValue() 130 // If enabled, log a measurement of how this frame timestamp has incremented
70 << ") was delivered"; 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_.detected_period() > base::TimeDelta()) {
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));
157 }
71 } 158 }
72 159
73 last_delivered_frame_number_ = frame_number; 160 return !frame_timestamp->is_null();
74 last_delivered_frame_timestamp_ = timestamp; 161 }
75 162
76 return true; 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];
167 }
168
169 void VideoCaptureOracle::SetFrameTimestamp(int frame_number,
170 base::TimeTicks timestamp) {
171 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp;
77 } 172 }
78 173
79 SmoothEventSampler::SmoothEventSampler(base::TimeDelta capture_period, 174 SmoothEventSampler::SmoothEventSampler(base::TimeDelta capture_period,
80 bool events_are_reliable, 175 bool events_are_reliable,
81 int redundant_capture_goal) 176 int redundant_capture_goal)
82 : events_are_reliable_(events_are_reliable), 177 : events_are_reliable_(events_are_reliable),
83 capture_period_(capture_period), 178 capture_period_(capture_period),
84 redundant_capture_goal_(redundant_capture_goal), 179 redundant_capture_goal_(redundant_capture_goal),
85 token_bucket_capacity_(capture_period + capture_period / 2), 180 token_bucket_capacity_(capture_period + capture_period / 2),
86 overdue_sample_count_(0), 181 overdue_sample_count_(0),
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
152 return false; // Not dirty. 247 return false; // Not dirty.
153 } 248 }
154 } 249 }
155 250
156 if (last_sample_.is_null()) 251 if (last_sample_.is_null())
157 return true; 252 return true;
158 253
159 // If we're dirty but not yet old, then we've recently gotten updates, so we 254 // If we're dirty but not yet old, then we've recently gotten updates, so we
160 // won't request a sample just yet. 255 // won't request a sample just yet.
161 base::TimeDelta dirty_interval = event_time - last_sample_; 256 base::TimeDelta dirty_interval = event_time - last_sample_;
162 if (dirty_interval < capture_period_ * 4) 257 return dirty_interval >=
163 return false; 258 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis);
164 else
165 return true;
166 } 259 }
167 260
168 bool SmoothEventSampler::HasUnrecordedEvent() const { 261 bool SmoothEventSampler::HasUnrecordedEvent() const {
169 return !current_event_.is_null() && current_event_ != last_sample_; 262 return !current_event_.is_null() && current_event_ != last_sample_;
170 } 263 }
171 264
265 AnimatedContentSampler::AnimatedContentSampler(
266 base::TimeDelta min_capture_period)
267 : min_capture_period_(min_capture_period) {}
268
269 AnimatedContentSampler::~AnimatedContentSampler() {}
270
271 bool AnimatedContentSampler::ConsiderPresentationEvent(
272 const gfx::Rect& damage_rect, base::TimeTicks event_time) {
273 AddObservation(damage_rect, event_time);
274
275 if (AnalyzeObservations(event_time, &detected_region_, &detected_period_) &&
276 detected_period_ > base::TimeDelta() &&
277 detected_period_ <=
278 base::TimeDelta::FromMilliseconds(kMaxLockInPeriodMillis)) {
279 if (damage_rect == detected_region_)
280 UpdateNextFrameTimestamp(event_time);
281 else
282 next_frame_timestamp_ = base::TimeTicks();
283
284 return true;
285 }
286
287 detected_region_ = gfx::Rect();
288 detected_period_ = base::TimeDelta();
289 next_frame_timestamp_ = base::TimeTicks();
290 return false;
291 }
292
293 void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) {
294 recorded_frame_timestamp_ = frame_timestamp;
295 sequence_offset_ = base::TimeDelta();
296 }
297
298 void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect,
299 base::TimeTicks event_time) {
300 if (damage_rect.IsEmpty())
301 return; // Useless observation.
302
303 // Add the observation to the FIFO queue.
304 if (!observations_.empty() && observations_.back().second > event_time)
305 return; // The implementation assumes chronological order.
306 observations_.push_back(Observation(damage_rect, event_time));
307
308 // Prune-out old observations.
309 const base::TimeDelta threshold =
310 base::TimeDelta::FromMilliseconds(kMaxObservationWindowMillis);
311 while ((event_time - observations_.front().second) > threshold)
312 observations_.pop_front();
313 }
314
315 bool AnimatedContentSampler::AnalyzeObservations(
316 base::TimeTicks event_time,
317 gfx::Rect* rect,
318 base::TimeDelta* period) const {
319 // There must be at least three observations, or else it's possible to divide
320 // by zero at the end of this method, where |*period| is assigned the result.
321 if (observations_.size() < 3)
322 return false;
323
324 // Find the candidate damage Rect that *would* be the majority value, if a
325 // majority value exists. This is an implementation of the Boyer-Moore
326 // Majority Vote Algorithm.
327 const gfx::Rect* candidate = NULL;
328 size_t count = 0;
329 for (ObservationFifo::const_iterator i = observations_.begin();
330 i != observations_.end(); ++i) {
331 if (count == 0) {
332 candidate = &(i->first);
333 count = 1;
334 } else if (i->first == *candidate) {
335 ++count;
336 } else {
337 --count;
338 }
339 }
340
341 // Accomplish two goals by making a second pass over |observations_|. First,
342 // confirm that |candidate| in fact points to the majority damage Rect, and
343 // didn't just win the "voting" phase. Second, sum up the durations between
344 // the frames having the candidate damage Rect, and track the event time of
345 // the first and last of those frames.
346 count = 0;
347 base::TimeDelta sum_frame_durations;
348 base::TimeTicks first_event_time;
349 base::TimeTicks last_event_time;
350 for (ObservationFifo::const_iterator i = observations_.begin();
351 i != observations_.end(); ++i) {
352 if (i->first != *candidate)
353 continue;
354 ++count;
355
356 if (first_event_time.is_null()) {
357 first_event_time = i->second;
358 } else {
359 const base::TimeDelta frame_duration = i->second - last_event_time;
360 if (frame_duration >=
361 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis)) {
362 return false; // Content has not animated continuously.
363 }
364 sum_frame_durations += frame_duration;
365 }
366 last_event_time = i->second;
367 }
368
369 if (count <= observations_.size() / 2)
370 return false; // |candidate| was not a majority value.
371 if ((last_event_time - first_event_time) <
372 base::TimeDelta::FromMilliseconds(kMinObservationWindowMillis)) {
373 return false; // Content has not animated for long enough.
374 }
375 if ((event_time - last_event_time) >=
376 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis)) {
377 return false; // Content animation has recently ended.
378 }
379
380 *rect = *candidate;
381 *period = sum_frame_durations / (count - 1);
382 return true;
383 }
384
385 void AnimatedContentSampler::UpdateNextFrameTimestamp(
386 base::TimeTicks event_time) {
387 // This is how much time to advance from the last frame timestamp. Never
388 // advance by less than |min_capture_period_| because the downstream consumer
389 // cannot handle the higher frame rate. If |detected_period_| is less than
390 // |min_capture_period_|, excess frames should be dropped.
391 const base::TimeDelta advancement =
392 std::max(detected_period_, min_capture_period_);
393
394 // Compute the |timebase| upon which to determine the |next_frame_timestamp_|.
395 // Ideally, this would always equal the timestamp of the last recorded frame
396 // sampling, but no clock is perfect. Determine how much drift from the ideal
397 // is present, then adjust the timebase by a small amount to spread out the
398 // entire correction over many frame timestamps.
399 base::TimeTicks timebase = event_time - sequence_offset_ - advancement;
400 if (!recorded_frame_timestamp_.is_null()) {
401 const base::TimeDelta drift = recorded_frame_timestamp_ - timebase;
402 const int64 correct_over_num_frames =
403 base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) /
404 detected_period_;
405 DCHECK_GT(correct_over_num_frames, 0);
406 timebase = recorded_frame_timestamp_ - (drift / correct_over_num_frames);
407 }
408
409 // Compute the |next_frame_timestamp_|. Whenever |detected_period_| is less
410 // than |min_capture_period_|, some extra time is "borrowed" to be able to
411 // advance by the full |min_capture_period_|. Then, whenever the total amount
412 // of borrowed time reaches a full |min_capture_period_|, drop a frame. Note
413 // that when |detected_period_| is greater or equal to |min_capture_period_|,
414 // this logic is effectively disabled.
415 borrowed_time_ += advancement - detected_period_;
416 if (borrowed_time_ >= min_capture_period_) {
417 borrowed_time_ -= min_capture_period_;
418 next_frame_timestamp_ = base::TimeTicks();
419 } else {
420 sequence_offset_ += advancement;
421 next_frame_timestamp_ = timebase + sequence_offset_;
422 }
423 }
424
172 } // namespace content 425 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698