OLD | NEW |
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> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/format_macros.h" | 9 #include "base/format_macros.h" |
10 #include "base/strings/stringprintf.h" | 10 #include "base/strings/stringprintf.h" |
11 #include "base/trace_event/trace_event.h" | |
12 | 11 |
13 namespace content { | 12 namespace content { |
14 | 13 |
15 namespace { | 14 namespace { |
16 | 15 |
17 // This value controls how many redundant, timer-base captures occur when the | 16 // This value controls how many redundant, timer-base captures occur when the |
18 // content is static. Redundantly capturing the same frame allows iterative | 17 // content is static. Redundantly capturing the same frame allows iterative |
19 // quality enhancement, and also allows the buffer to fill in "buffered mode". | 18 // quality enhancement, and also allows the buffer to fill in "buffered mode". |
20 // | 19 // |
21 // TODO(nick): Controlling this here is a hack and a layering violation, since | 20 // 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 | 21 // 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 | 22 // 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 | 23 // a higher level, or else redundant frame generation should be pushed down |
25 // further into the WebRTC encoding stack. | 24 // further into the WebRTC encoding stack. |
26 const int kNumRedundantCapturesOfStaticContent = 200; | 25 const int kNumRedundantCapturesOfStaticContent = 200; |
27 | 26 |
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 | 27 // 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. | 28 // time between frames at |frame_rate| and return the fractional difference. |
57 double FractionFromExpectedFrameRate(base::TimeDelta delta, int frame_rate) { | 29 double FractionFromExpectedFrameRate(base::TimeDelta delta, int frame_rate) { |
58 DCHECK_GT(frame_rate, 0); | 30 DCHECK_GT(frame_rate, 0); |
59 const base::TimeDelta expected_delta = | 31 const base::TimeDelta expected_delta = |
60 base::TimeDelta::FromSeconds(1) / frame_rate; | 32 base::TimeDelta::FromSeconds(1) / frame_rate; |
61 return (delta - expected_delta).InMillisecondsF() / | 33 return (delta - expected_delta).InMillisecondsF() / |
62 expected_delta.InMillisecondsF(); | 34 expected_delta.InMillisecondsF(); |
63 } | 35 } |
64 | 36 |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
165 DCHECK_LE(frame_number, frame_number_); | 137 DCHECK_LE(frame_number, frame_number_); |
166 DCHECK_LT(frame_number_ - frame_number, kMaxFrameTimestamps); | 138 DCHECK_LT(frame_number_ - frame_number, kMaxFrameTimestamps); |
167 return frame_timestamps_[frame_number % kMaxFrameTimestamps]; | 139 return frame_timestamps_[frame_number % kMaxFrameTimestamps]; |
168 } | 140 } |
169 | 141 |
170 void VideoCaptureOracle::SetFrameTimestamp(int frame_number, | 142 void VideoCaptureOracle::SetFrameTimestamp(int frame_number, |
171 base::TimeTicks timestamp) { | 143 base::TimeTicks timestamp) { |
172 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp; | 144 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp; |
173 } | 145 } |
174 | 146 |
175 SmoothEventSampler::SmoothEventSampler(base::TimeDelta min_capture_period, | |
176 int redundant_capture_goal) | |
177 : min_capture_period_(min_capture_period), | |
178 redundant_capture_goal_(redundant_capture_goal), | |
179 token_bucket_capacity_(min_capture_period + min_capture_period / 2), | |
180 overdue_sample_count_(0), | |
181 token_bucket_(token_bucket_capacity_) { | |
182 DCHECK_GT(min_capture_period_.InMicroseconds(), 0); | |
183 } | |
184 | |
185 void SmoothEventSampler::ConsiderPresentationEvent(base::TimeTicks event_time) { | |
186 DCHECK(!event_time.is_null()); | |
187 | |
188 // Add tokens to the bucket based on advancement in time. Then, re-bound the | |
189 // number of tokens in the bucket. Overflow occurs when there is too much | |
190 // time between events (a common case), or when RecordSample() is not being | |
191 // called often enough (a bug). On the other hand, if RecordSample() is being | |
192 // called too often (e.g., as a reaction to IsOverdueForSamplingAt()), the | |
193 // bucket will underflow. | |
194 if (!current_event_.is_null()) { | |
195 if (current_event_ < event_time) { | |
196 token_bucket_ += event_time - current_event_; | |
197 if (token_bucket_ > token_bucket_capacity_) | |
198 token_bucket_ = token_bucket_capacity_; | |
199 } | |
200 TRACE_COUNTER1("gpu.capture", | |
201 "MirroringTokenBucketUsec", | |
202 std::max<int64>(0, token_bucket_.InMicroseconds())); | |
203 } | |
204 current_event_ = event_time; | |
205 } | |
206 | |
207 bool SmoothEventSampler::ShouldSample() const { | |
208 return token_bucket_ >= min_capture_period_; | |
209 } | |
210 | |
211 void SmoothEventSampler::RecordSample() { | |
212 token_bucket_ -= min_capture_period_; | |
213 if (token_bucket_ < base::TimeDelta()) | |
214 token_bucket_ = base::TimeDelta(); | |
215 TRACE_COUNTER1("gpu.capture", | |
216 "MirroringTokenBucketUsec", | |
217 std::max<int64>(0, token_bucket_.InMicroseconds())); | |
218 | |
219 if (HasUnrecordedEvent()) { | |
220 last_sample_ = current_event_; | |
221 overdue_sample_count_ = 0; | |
222 } else { | |
223 ++overdue_sample_count_; | |
224 } | |
225 } | |
226 | |
227 bool SmoothEventSampler::IsOverdueForSamplingAt(base::TimeTicks event_time) | |
228 const { | |
229 DCHECK(!event_time.is_null()); | |
230 | |
231 if (!HasUnrecordedEvent() && overdue_sample_count_ >= redundant_capture_goal_) | |
232 return false; // Not dirty. | |
233 | |
234 if (last_sample_.is_null()) | |
235 return true; | |
236 | |
237 // If we're dirty but not yet old, then we've recently gotten updates, so we | |
238 // won't request a sample just yet. | |
239 base::TimeDelta dirty_interval = event_time - last_sample_; | |
240 return dirty_interval >= | |
241 base::TimeDelta::FromMilliseconds(kNonAnimatingThresholdMillis); | |
242 } | |
243 | |
244 bool SmoothEventSampler::HasUnrecordedEvent() const { | |
245 return !current_event_.is_null() && current_event_ != last_sample_; | |
246 } | |
247 | |
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 | 147 } // namespace content |
OLD | NEW |