OLD | NEW |
1 // Copyright (c) 2013 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/video_capture_oracle.h" | 5 #include "content/browser/media/capture/smooth_event_sampler.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/format_macros.h" | |
10 #include "base/strings/stringprintf.h" | |
11 #include "base/trace_event/trace_event.h" | 9 #include "base/trace_event/trace_event.h" |
12 | 10 |
13 namespace content { | 11 namespace content { |
14 | 12 |
15 namespace { | 13 namespace { |
16 | 14 |
17 // This value controls how many redundant, timer-base captures occur when the | 15 // The maximum amount of time that can elapse before considering unchanged |
18 // content is static. Redundantly capturing the same frame allows iterative | 16 // content as dirty for the purposes of timer-based overdue sampling. This is |
19 // quality enhancement, and also allows the buffer to fill in "buffered mode". | 17 // the same value found in cc::FrameRateCounter. |
20 // | 18 const int kOverdueDirtyThresholdMillis = 250; // 4 FPS |
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; | |
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 kMaxLockInPeriodMicros = 83333; // 12 FPS | |
49 | |
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; | |
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 | 19 |
65 } // anonymous namespace | 20 } // anonymous namespace |
66 | 21 |
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) { | |
73 } | |
74 | |
75 VideoCaptureOracle::~VideoCaptureOracle() {} | |
76 | |
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; | |
87 } | |
88 last_event_time_[event] = event_time; | |
89 | |
90 bool should_sample; | |
91 switch (event) { | |
92 case kCompositorUpdate: | |
93 smoothing_sampler_.ConsiderPresentationEvent(event_time); | |
94 content_sampler_.ConsiderPresentationEvent(damage_rect, event_time); | |
95 if (content_sampler_.HasProposal()) { | |
96 should_sample = content_sampler_.ShouldSample(); | |
97 if (should_sample) | |
98 event_time = content_sampler_.frame_timestamp(); | |
99 } else { | |
100 should_sample = smoothing_sampler_.ShouldSample(); | |
101 } | |
102 break; | |
103 default: | |
104 should_sample = smoothing_sampler_.IsOverdueForSamplingAt(event_time); | |
105 break; | |
106 } | |
107 | |
108 SetFrameTimestamp(frame_number_, event_time); | |
109 return should_sample; | |
110 } | |
111 | |
112 int VideoCaptureOracle::RecordCapture() { | |
113 smoothing_sampler_.RecordSample(); | |
114 content_sampler_.RecordSample(GetFrameTimestamp(frame_number_)); | |
115 return frame_number_++; | |
116 } | |
117 | |
118 bool VideoCaptureOracle::CompleteCapture(int frame_number, | |
119 base::TimeTicks* frame_timestamp) { | |
120 // Drop frame if previous frame number is higher. | |
121 if (last_delivered_frame_number_ > frame_number) { | |
122 LOG(WARNING) << "Out of order frame delivery detected (have #" | |
123 << frame_number << ", last was #" | |
124 << last_delivered_frame_number_ << "). Dropping frame."; | |
125 return false; | |
126 } | |
127 last_delivered_frame_number_ = frame_number; | |
128 | |
129 *frame_timestamp = GetFrameTimestamp(frame_number); | |
130 | |
131 // If enabled, log a measurement of how this frame timestamp has incremented | |
132 // in relation to an ideal increment. | |
133 if (VLOG_IS_ON(2) && frame_number > 0) { | |
134 const base::TimeDelta delta = | |
135 *frame_timestamp - GetFrameTimestamp(frame_number - 1); | |
136 if (content_sampler_.HasProposal()) { | |
137 const double estimated_frame_rate = | |
138 1000000.0 / content_sampler_.detected_period().InMicroseconds(); | |
139 const int rounded_frame_rate = | |
140 static_cast<int>(estimated_frame_rate + 0.5); | |
141 VLOG(2) << base::StringPrintf( | |
142 "Captured #%d: delta=%" PRId64 " usec" | |
143 ", now locked into {%s}, %+0.1f%% slower than %d FPS", | |
144 frame_number, | |
145 delta.InMicroseconds(), | |
146 content_sampler_.detected_region().ToString().c_str(), | |
147 100.0 * FractionFromExpectedFrameRate(delta, rounded_frame_rate), | |
148 rounded_frame_rate); | |
149 } else { | |
150 VLOG(2) << base::StringPrintf( | |
151 "Captured #%d: delta=%" PRId64 " usec" | |
152 ", d/30fps=%+0.1f%%, d/25fps=%+0.1f%%, d/24fps=%+0.1f%%", | |
153 frame_number, | |
154 delta.InMicroseconds(), | |
155 100.0 * FractionFromExpectedFrameRate(delta, 30), | |
156 100.0 * FractionFromExpectedFrameRate(delta, 25), | |
157 100.0 * FractionFromExpectedFrameRate(delta, 24)); | |
158 } | |
159 } | |
160 | |
161 return !frame_timestamp->is_null(); | |
162 } | |
163 | |
164 base::TimeTicks VideoCaptureOracle::GetFrameTimestamp(int frame_number) const { | |
165 DCHECK_LE(frame_number, frame_number_); | |
166 DCHECK_LT(frame_number_ - frame_number, kMaxFrameTimestamps); | |
167 return frame_timestamps_[frame_number % kMaxFrameTimestamps]; | |
168 } | |
169 | |
170 void VideoCaptureOracle::SetFrameTimestamp(int frame_number, | |
171 base::TimeTicks timestamp) { | |
172 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp; | |
173 } | |
174 | |
175 SmoothEventSampler::SmoothEventSampler(base::TimeDelta min_capture_period, | 22 SmoothEventSampler::SmoothEventSampler(base::TimeDelta min_capture_period, |
176 int redundant_capture_goal) | 23 int redundant_capture_goal) |
177 : min_capture_period_(min_capture_period), | 24 : min_capture_period_(min_capture_period), |
178 redundant_capture_goal_(redundant_capture_goal), | 25 redundant_capture_goal_(redundant_capture_goal), |
179 token_bucket_capacity_(min_capture_period + min_capture_period / 2), | 26 token_bucket_capacity_(min_capture_period + min_capture_period / 2), |
180 overdue_sample_count_(0), | 27 overdue_sample_count_(0), |
181 token_bucket_(token_bucket_capacity_) { | 28 token_bucket_(token_bucket_capacity_) { |
182 DCHECK_GT(min_capture_period_.InMicroseconds(), 0); | 29 DCHECK_GT(min_capture_period_.InMicroseconds(), 0); |
183 } | 30 } |
184 | 31 |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
231 if (!HasUnrecordedEvent() && overdue_sample_count_ >= redundant_capture_goal_) | 78 if (!HasUnrecordedEvent() && overdue_sample_count_ >= redundant_capture_goal_) |
232 return false; // Not dirty. | 79 return false; // Not dirty. |
233 | 80 |
234 if (last_sample_.is_null()) | 81 if (last_sample_.is_null()) |
235 return true; | 82 return true; |
236 | 83 |
237 // If we're dirty but not yet old, then we've recently gotten updates, so we | 84 // If we're dirty but not yet old, then we've recently gotten updates, so we |
238 // won't request a sample just yet. | 85 // won't request a sample just yet. |
239 base::TimeDelta dirty_interval = event_time - last_sample_; | 86 base::TimeDelta dirty_interval = event_time - last_sample_; |
240 return dirty_interval >= | 87 return dirty_interval >= |
241 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis); | 88 base::TimeDelta::FromMilliseconds(kOverdueDirtyThresholdMillis); |
242 } | 89 } |
243 | 90 |
244 bool SmoothEventSampler::HasUnrecordedEvent() const { | 91 bool SmoothEventSampler::HasUnrecordedEvent() const { |
245 return !current_event_.is_null() && current_event_ != last_sample_; | 92 return !current_event_.is_null() && current_event_ != last_sample_; |
246 } | 93 } |
247 | 94 |
248 AnimatedContentSampler::AnimatedContentSampler( | |
249 base::TimeDelta min_capture_period) | |
250 : min_capture_period_(min_capture_period) {} | |
251 | |
252 AnimatedContentSampler::~AnimatedContentSampler() {} | |
253 | |
254 void AnimatedContentSampler::ConsiderPresentationEvent( | |
255 const gfx::Rect& damage_rect, base::TimeTicks event_time) { | |
256 AddObservation(damage_rect, event_time); | |
257 | |
258 if (AnalyzeObservations(event_time, &detected_region_, &detected_period_) && | |
259 detected_period_ > base::TimeDelta() && | |
260 detected_period_ <= | |
261 base::TimeDelta::FromMicroseconds(kMaxLockInPeriodMicros)) { | |
262 if (damage_rect == detected_region_) | |
263 UpdateFrameTimestamp(event_time); | |
264 else | |
265 frame_timestamp_ = base::TimeTicks(); | |
266 } else { | |
267 detected_region_ = gfx::Rect(); | |
268 detected_period_ = base::TimeDelta(); | |
269 frame_timestamp_ = base::TimeTicks(); | |
270 } | |
271 } | |
272 | |
273 bool AnimatedContentSampler::HasProposal() const { | |
274 return detected_period_ > base::TimeDelta(); | |
275 } | |
276 | |
277 bool AnimatedContentSampler::ShouldSample() const { | |
278 return !frame_timestamp_.is_null(); | |
279 } | |
280 | |
281 void AnimatedContentSampler::RecordSample(base::TimeTicks frame_timestamp) { | |
282 recorded_frame_timestamp_ = | |
283 HasProposal() ? frame_timestamp : base::TimeTicks(); | |
284 sequence_offset_ = base::TimeDelta(); | |
285 } | |
286 | |
287 void AnimatedContentSampler::AddObservation(const gfx::Rect& damage_rect, | |
288 base::TimeTicks event_time) { | |
289 if (damage_rect.IsEmpty()) | |
290 return; // Useless observation. | |
291 | |
292 // Add the observation to the FIFO queue. | |
293 if (!observations_.empty() && observations_.back().event_time > event_time) | |
294 return; // The implementation assumes chronological order. | |
295 observations_.push_back(Observation(damage_rect, event_time)); | |
296 | |
297 // Prune-out old observations. | |
298 const base::TimeDelta threshold = | |
299 base::TimeDelta::FromMilliseconds(kMaxObservationWindowMillis); | |
300 while ((event_time - observations_.front().event_time) > threshold) | |
301 observations_.pop_front(); | |
302 } | |
303 | |
304 gfx::Rect AnimatedContentSampler::ElectMajorityDamageRect() const { | |
305 // This is an derivative of the Boyer-Moore Majority Vote Algorithm where each | |
306 // pixel in a candidate gets one vote, as opposed to each candidate getting | |
307 // one vote. | |
308 const gfx::Rect* candidate = NULL; | |
309 int64 votes = 0; | |
310 for (ObservationFifo::const_iterator i = observations_.begin(); | |
311 i != observations_.end(); ++i) { | |
312 DCHECK_GT(i->damage_rect.size().GetArea(), 0); | |
313 if (votes == 0) { | |
314 candidate = &(i->damage_rect); | |
315 votes = candidate->size().GetArea(); | |
316 } else if (i->damage_rect == *candidate) { | |
317 votes += i->damage_rect.size().GetArea(); | |
318 } else { | |
319 votes -= i->damage_rect.size().GetArea(); | |
320 if (votes < 0) { | |
321 candidate = &(i->damage_rect); | |
322 votes = -votes; | |
323 } | |
324 } | |
325 } | |
326 return (votes > 0) ? *candidate : gfx::Rect(); | |
327 } | |
328 | |
329 bool AnimatedContentSampler::AnalyzeObservations( | |
330 base::TimeTicks event_time, | |
331 gfx::Rect* rect, | |
332 base::TimeDelta* period) const { | |
333 const gfx::Rect elected_rect = ElectMajorityDamageRect(); | |
334 if (elected_rect.IsEmpty()) | |
335 return false; // There is no regular animation present. | |
336 | |
337 // Scan |observations_|, gathering metrics about the ones having a damage Rect | |
338 // equivalent to the |elected_rect|. Along the way, break early whenever the | |
339 // event times reveal a non-animating period. | |
340 int64 num_pixels_damaged_in_all = 0; | |
341 int64 num_pixels_damaged_in_chosen = 0; | |
342 base::TimeDelta sum_frame_durations; | |
343 size_t count_frame_durations = 0; | |
344 base::TimeTicks first_event_time; | |
345 base::TimeTicks last_event_time; | |
346 for (ObservationFifo::const_reverse_iterator i = observations_.rbegin(); | |
347 i != observations_.rend(); ++i) { | |
348 const int area = i->damage_rect.size().GetArea(); | |
349 num_pixels_damaged_in_all += area; | |
350 if (i->damage_rect != elected_rect) | |
351 continue; | |
352 num_pixels_damaged_in_chosen += area; | |
353 if (last_event_time.is_null()) { | |
354 last_event_time = i->event_time; | |
355 if ((event_time - last_event_time) >= | |
356 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis)) { | |
357 return false; // Content animation has recently ended. | |
358 } | |
359 } else { | |
360 const base::TimeDelta frame_duration = first_event_time - i->event_time; | |
361 if (frame_duration >= | |
362 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis)) { | |
363 break; // Content not animating before this point. | |
364 } | |
365 sum_frame_durations += frame_duration; | |
366 ++count_frame_durations; | |
367 } | |
368 first_event_time = i->event_time; | |
369 } | |
370 | |
371 if ((last_event_time - first_event_time) < | |
372 base::TimeDelta::FromMilliseconds(kMinObservationWindowMillis)) { | |
373 return false; // Content has not animated for long enough for accuracy. | |
374 } | |
375 if (num_pixels_damaged_in_chosen <= (num_pixels_damaged_in_all * 2 / 3)) | |
376 return false; // Animation is not damaging a supermajority of pixels. | |
377 | |
378 *rect = elected_rect; | |
379 DCHECK_GT(count_frame_durations, 0u); | |
380 *period = sum_frame_durations / count_frame_durations; | |
381 return true; | |
382 } | |
383 | |
384 void AnimatedContentSampler::UpdateFrameTimestamp(base::TimeTicks event_time) { | |
385 // This is how much time to advance from the last frame timestamp. Never | |
386 // advance by less than |min_capture_period_| because the downstream consumer | |
387 // cannot handle the higher frame rate. If |detected_period_| is less than | |
388 // |min_capture_period_|, excess frames should be dropped. | |
389 const base::TimeDelta advancement = | |
390 std::max(detected_period_, min_capture_period_); | |
391 | |
392 // Compute the |timebase| upon which to determine the |frame_timestamp_|. | |
393 // Ideally, this would always equal the timestamp of the last recorded frame | |
394 // sampling. Determine how much drift from the ideal is present, then adjust | |
395 // the timebase by a small amount to spread out the entire correction over | |
396 // many frame timestamps. | |
397 // | |
398 // This accounts for two main sources of drift: 1) The clock drift of the | |
399 // system clock relative to the video hardware, which affects the event times; | |
400 // and 2) The small error introduced by this frame timestamp rewriting, as it | |
401 // is based on averaging over recent events. | |
402 base::TimeTicks timebase = event_time - sequence_offset_ - advancement; | |
403 if (!recorded_frame_timestamp_.is_null()) { | |
404 const base::TimeDelta drift = recorded_frame_timestamp_ - timebase; | |
405 const int64 correct_over_num_frames = | |
406 base::TimeDelta::FromMilliseconds(kDriftCorrectionMillis) / | |
407 detected_period_; | |
408 DCHECK_GT(correct_over_num_frames, 0); | |
409 timebase = recorded_frame_timestamp_ - (drift / correct_over_num_frames); | |
410 } | |
411 | |
412 // Compute |frame_timestamp_|. Whenever |detected_period_| is less than | |
413 // |min_capture_period_|, some extra time is "borrowed" to be able to advance | |
414 // by the full |min_capture_period_|. Then, whenever the total amount of | |
415 // borrowed time reaches a full |min_capture_period_|, drop a frame. Note | |
416 // that when |detected_period_| is greater or equal to |min_capture_period_|, | |
417 // this logic is effectively disabled. | |
418 borrowed_time_ += advancement - detected_period_; | |
419 if (borrowed_time_ >= min_capture_period_) { | |
420 borrowed_time_ -= min_capture_period_; | |
421 frame_timestamp_ = base::TimeTicks(); | |
422 } else { | |
423 sequence_offset_ += advancement; | |
424 frame_timestamp_ = timebase + sequence_offset_; | |
425 } | |
426 } | |
427 | |
428 } // namespace content | 95 } // namespace content |
OLD | NEW |