Updating XTBs based on .GRDs from branch master
[chromium-blink-merge.git] / media / capture / content / video_capture_oracle.cc
blob6127996fbb70e1c058682794f407bd84c88b9bfd
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.
5 #include "media/capture/content/video_capture_oracle.h"
7 #include <algorithm>
9 #include "base/format_macros.h"
10 #include "base/numerics/safe_conversions.h"
11 #include "base/strings/stringprintf.h"
13 namespace media {
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 // The half-life of data points provided to the accumulator used when evaluating
29 // the recent utilization of the buffer pool. This value is based on a
30 // simulation, and reacts quickly to change to avoid depleting the buffer pool
31 // (which would cause hard frame drops).
32 const int kBufferUtilizationEvaluationMicros = 200000; // 0.2 seconds
34 // The half-life of data points provided to the accumulator used when evaluating
35 // the recent resource utilization of the consumer. The trade-off made here is
36 // reaction time versus over-reacting to outlier data points.
37 const int kConsumerCapabilityEvaluationMicros = 1000000; // 1 second
39 // The minimum amount of time that must pass between changes to the capture
40 // size. This throttles the rate of size changes, to avoid stressing consumers
41 // and to allow the end-to-end system sufficient time to stabilize before
42 // re-evaluating the capture size.
43 const int kMinSizeChangePeriodMicros = 3000000; // 3 seconds
45 // The maximum amount of time that may elapse without a feedback update. Any
46 // longer, and currently-accumulated feedback is not considered recent enough to
47 // base decisions off of. This prevents changes to the capture size when there
48 // is an unexpected pause in events.
49 const int kMaxTimeSinceLastFeedbackUpdateMicros = 1000000; // 1 second
51 // The amount of time, since the source size last changed, to allow frequent
52 // increases in capture area. This allows the system a period of time to
53 // quickly explore up and down to find an ideal point before being more careful
54 // about capture size increases.
55 const int kExplorationPeriodAfterSourceSizeChangeMicros =
56 3 * kMinSizeChangePeriodMicros;
58 // The amount of additional time, since content animation was last detected, to
59 // continue being extra-careful about increasing the capture size. This is used
60 // to prevent breif periods of non-animating content from throwing off the
61 // heuristics that decide whether to increase the capture size.
62 const int kDebouncingPeriodForAnimatedContentMicros = 3000000; // 3 seconds
64 // When content is animating, this is the length of time the system must be
65 // contiguously under-utilized before increasing the capture size.
66 const int kProvingPeriodForAnimatedContentMicros = 30000000; // 30 seconds
68 // Given the amount of time between frames, compare to the expected amount of
69 // time between frames at |frame_rate| and return the fractional difference.
70 double FractionFromExpectedFrameRate(base::TimeDelta delta, int frame_rate) {
71 DCHECK_GT(frame_rate, 0);
72 const base::TimeDelta expected_delta =
73 base::TimeDelta::FromSeconds(1) / frame_rate;
74 return (delta - expected_delta).InMillisecondsF() /
75 expected_delta.InMillisecondsF();
78 // Returns the next-higher TimeTicks value.
79 // TODO(miu): Patch FeedbackSignalAccumulator reset behavior and remove this
80 // hack.
81 base::TimeTicks JustAfter(base::TimeTicks t) {
82 return t + base::TimeDelta::FromMicroseconds(1);
85 // Returns true if updates have been accumulated by |accumulator| for a
86 // sufficient amount of time and the latest update was fairly recent, relative
87 // to |now|.
88 bool HasSufficientRecentFeedback(const FeedbackSignalAccumulator& accumulator,
89 base::TimeTicks now) {
90 const base::TimeDelta amount_of_history =
91 accumulator.update_time() - accumulator.reset_time();
92 return (amount_of_history.InMicroseconds() >= kMinSizeChangePeriodMicros) &&
93 ((now - accumulator.update_time()).InMicroseconds() <=
94 kMaxTimeSinceLastFeedbackUpdateMicros);
97 } // anonymous namespace
99 VideoCaptureOracle::VideoCaptureOracle(
100 base::TimeDelta min_capture_period,
101 const gfx::Size& max_frame_size,
102 media::ResolutionChangePolicy resolution_change_policy,
103 bool enable_auto_throttling)
104 : auto_throttling_enabled_(enable_auto_throttling),
105 next_frame_number_(0),
106 last_successfully_delivered_frame_number_(-1),
107 num_frames_pending_(0),
108 smoothing_sampler_(min_capture_period,
109 kNumRedundantCapturesOfStaticContent),
110 content_sampler_(min_capture_period),
111 resolution_chooser_(max_frame_size, resolution_change_policy),
112 buffer_pool_utilization_(base::TimeDelta::FromMicroseconds(
113 kBufferUtilizationEvaluationMicros)),
114 estimated_capable_area_(base::TimeDelta::FromMicroseconds(
115 kConsumerCapabilityEvaluationMicros)) {
116 VLOG(1) << "Auto-throttling is "
117 << (auto_throttling_enabled_ ? "enabled." : "disabled.");
120 VideoCaptureOracle::~VideoCaptureOracle() {
123 void VideoCaptureOracle::SetSourceSize(const gfx::Size& source_size) {
124 resolution_chooser_.SetSourceSize(source_size);
125 // If the |resolution_chooser_| computed a new capture size, that will become
126 // visible via a future call to ObserveEventAndDecideCapture().
127 source_size_change_time_ = (next_frame_number_ == 0) ?
128 base::TimeTicks() : GetFrameTimestamp(next_frame_number_ - 1);
131 bool VideoCaptureOracle::ObserveEventAndDecideCapture(
132 Event event,
133 const gfx::Rect& damage_rect,
134 base::TimeTicks event_time) {
135 DCHECK_GE(event, 0);
136 DCHECK_LT(event, kNumEvents);
137 if (event_time < last_event_time_[event]) {
138 LOG(WARNING) << "Event time is not monotonically non-decreasing. "
139 << "Deciding not to capture this frame.";
140 return false;
142 last_event_time_[event] = event_time;
144 bool should_sample = false;
145 duration_of_next_frame_ = base::TimeDelta();
146 switch (event) {
147 case kCompositorUpdate: {
148 smoothing_sampler_.ConsiderPresentationEvent(event_time);
149 const bool had_proposal = content_sampler_.HasProposal();
150 content_sampler_.ConsiderPresentationEvent(damage_rect, event_time);
151 if (content_sampler_.HasProposal()) {
152 VLOG_IF(1, !had_proposal) << "Content sampler now detects animation.";
153 should_sample = content_sampler_.ShouldSample();
154 if (should_sample) {
155 event_time = content_sampler_.frame_timestamp();
156 duration_of_next_frame_ = content_sampler_.sampling_period();
158 last_time_animation_was_detected_ = event_time;
159 } else {
160 VLOG_IF(1, had_proposal) << "Content sampler detects animation ended.";
161 should_sample = smoothing_sampler_.ShouldSample();
162 if (should_sample)
163 duration_of_next_frame_ = smoothing_sampler_.min_capture_period();
165 break;
168 case kTimerPoll:
169 // While the timer is firing, only allow a sampling if there are none
170 // currently in-progress.
171 if (num_frames_pending_ == 0) {
172 should_sample = smoothing_sampler_.IsOverdueForSamplingAt(event_time);
173 if (should_sample)
174 duration_of_next_frame_ = smoothing_sampler_.min_capture_period();
176 break;
178 case kNumEvents:
179 NOTREACHED();
180 break;
183 if (!should_sample)
184 return false;
186 // Update |capture_size_| and reset all feedback signal accumulators if
187 // either: 1) this is the first frame; or 2) |resolution_chooser_| has an
188 // updated capture size and sufficient time has passed since the last size
189 // change.
190 if (next_frame_number_ == 0) {
191 CommitCaptureSizeAndReset(event_time - duration_of_next_frame_);
192 } else if (capture_size_ != resolution_chooser_.capture_size()) {
193 const base::TimeDelta time_since_last_change =
194 event_time - buffer_pool_utilization_.reset_time();
195 if (time_since_last_change.InMicroseconds() >= kMinSizeChangePeriodMicros)
196 CommitCaptureSizeAndReset(GetFrameTimestamp(next_frame_number_ - 1));
199 SetFrameTimestamp(next_frame_number_, event_time);
200 return true;
203 int VideoCaptureOracle::RecordCapture(double pool_utilization) {
204 DCHECK(std::isfinite(pool_utilization) && pool_utilization >= 0.0);
206 smoothing_sampler_.RecordSample();
207 const base::TimeTicks timestamp = GetFrameTimestamp(next_frame_number_);
208 content_sampler_.RecordSample(timestamp);
210 if (auto_throttling_enabled_) {
211 buffer_pool_utilization_.Update(pool_utilization, timestamp);
212 AnalyzeAndAdjust(timestamp);
215 num_frames_pending_++;
216 return next_frame_number_++;
219 void VideoCaptureOracle::RecordWillNotCapture(double pool_utilization) {
220 VLOG(1) << "Client rejects proposal to capture frame (at #"
221 << next_frame_number_ << ").";
223 if (auto_throttling_enabled_) {
224 DCHECK(std::isfinite(pool_utilization) && pool_utilization >= 0.0);
225 const base::TimeTicks timestamp = GetFrameTimestamp(next_frame_number_);
226 buffer_pool_utilization_.Update(pool_utilization, timestamp);
227 AnalyzeAndAdjust(timestamp);
230 // Note: Do not advance |next_frame_number_| since it will be re-used for the
231 // next capture proposal.
234 bool VideoCaptureOracle::CompleteCapture(int frame_number,
235 bool capture_was_successful,
236 base::TimeTicks* frame_timestamp) {
237 num_frames_pending_--;
238 DCHECK_GE(num_frames_pending_, 0);
240 // Drop frame if previously delivered frame number is higher.
241 if (last_successfully_delivered_frame_number_ > frame_number) {
242 LOG_IF(WARNING, capture_was_successful)
243 << "Out of order frame delivery detected (have #" << frame_number
244 << ", last was #" << last_successfully_delivered_frame_number_
245 << "). Dropping frame.";
246 return false;
249 if (!IsFrameInRecentHistory(frame_number)) {
250 LOG(WARNING) << "Very old capture being ignored: frame #" << frame_number;
251 return false;
254 if (!capture_was_successful) {
255 VLOG(2) << "Capture of frame #" << frame_number << " was not successful.";
256 return false;
259 DCHECK_NE(last_successfully_delivered_frame_number_, frame_number);
260 last_successfully_delivered_frame_number_ = frame_number;
262 *frame_timestamp = GetFrameTimestamp(frame_number);
264 // If enabled, log a measurement of how this frame timestamp has incremented
265 // in relation to an ideal increment.
266 if (VLOG_IS_ON(3) && frame_number > 0) {
267 const base::TimeDelta delta =
268 *frame_timestamp - GetFrameTimestamp(frame_number - 1);
269 if (content_sampler_.HasProposal()) {
270 const double estimated_frame_rate =
271 1000000.0 / content_sampler_.detected_period().InMicroseconds();
272 const int rounded_frame_rate =
273 static_cast<int>(estimated_frame_rate + 0.5);
274 VLOG_STREAM(3) << base::StringPrintf(
275 "Captured #%d: delta=%" PRId64
276 " usec"
277 ", now locked into {%s}, %+0.1f%% slower than %d FPS",
278 frame_number, delta.InMicroseconds(),
279 content_sampler_.detected_region().ToString().c_str(),
280 100.0 * FractionFromExpectedFrameRate(delta, rounded_frame_rate),
281 rounded_frame_rate);
282 } else {
283 VLOG_STREAM(3) << base::StringPrintf(
284 "Captured #%d: delta=%" PRId64
285 " usec"
286 ", d/30fps=%+0.1f%%, d/25fps=%+0.1f%%, d/24fps=%+0.1f%%",
287 frame_number, delta.InMicroseconds(),
288 100.0 * FractionFromExpectedFrameRate(delta, 30),
289 100.0 * FractionFromExpectedFrameRate(delta, 25),
290 100.0 * FractionFromExpectedFrameRate(delta, 24));
294 return true;
297 void VideoCaptureOracle::RecordConsumerFeedback(int frame_number,
298 double resource_utilization) {
299 if (!auto_throttling_enabled_)
300 return;
302 if (!std::isfinite(resource_utilization)) {
303 LOG(DFATAL) << "Non-finite utilization provided by consumer for frame #"
304 << frame_number << ": " << resource_utilization;
305 return;
307 if (resource_utilization <= 0.0)
308 return; // Non-positive values are normal, meaning N/A.
310 if (!IsFrameInRecentHistory(frame_number)) {
311 VLOG(1) << "Very old frame feedback being ignored: frame #" << frame_number;
312 return;
314 const base::TimeTicks timestamp = GetFrameTimestamp(frame_number);
316 // Translate the utilization metric to be in terms of the capable frame area
317 // and update the feedback accumulators. Research suggests utilization is at
318 // most linearly proportional to area, and typically is sublinear. Either
319 // way, the end-to-end system should converge to the right place using the
320 // more-conservative assumption (linear).
321 const int area_at_full_utilization =
322 base::saturated_cast<int>(capture_size_.GetArea() / resource_utilization);
323 estimated_capable_area_.Update(area_at_full_utilization, timestamp);
326 base::TimeTicks VideoCaptureOracle::GetFrameTimestamp(int frame_number) const {
327 DCHECK(IsFrameInRecentHistory(frame_number));
328 return frame_timestamps_[frame_number % kMaxFrameTimestamps];
331 void VideoCaptureOracle::SetFrameTimestamp(int frame_number,
332 base::TimeTicks timestamp) {
333 DCHECK(IsFrameInRecentHistory(frame_number));
334 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp;
337 bool VideoCaptureOracle::IsFrameInRecentHistory(int frame_number) const {
338 // Adding (next_frame_number_ >= 0) helps the compiler deduce that there
339 // is no possibility of overflow here.
340 return (frame_number >= 0 && next_frame_number_ >= 0 &&
341 frame_number <= next_frame_number_ &&
342 (next_frame_number_ - frame_number) < kMaxFrameTimestamps);
345 void VideoCaptureOracle::CommitCaptureSizeAndReset(
346 base::TimeTicks last_frame_time) {
347 capture_size_ = resolution_chooser_.capture_size();
348 VLOG(2) << "Now proposing a capture size of " << capture_size_.ToString();
350 // Reset each short-term feedback accumulator with a stable-state starting
351 // value.
352 const base::TimeTicks ignore_before_time = JustAfter(last_frame_time);
353 buffer_pool_utilization_.Reset(1.0, ignore_before_time);
354 estimated_capable_area_.Reset(capture_size_.GetArea(), ignore_before_time);
357 void VideoCaptureOracle::AnalyzeAndAdjust(const base::TimeTicks analyze_time) {
358 DCHECK(auto_throttling_enabled_);
360 const int decreased_area = AnalyzeForDecreasedArea(analyze_time);
361 if (decreased_area > 0) {
362 resolution_chooser_.SetTargetFrameArea(decreased_area);
363 return;
366 const int increased_area = AnalyzeForIncreasedArea(analyze_time);
367 if (increased_area > 0) {
368 resolution_chooser_.SetTargetFrameArea(increased_area);
369 return;
372 // Explicitly set the target frame area to the current capture area. This
373 // cancels-out the results of a previous call to this method, where the
374 // |resolution_chooser_| may have been instructed to increase or decrease the
375 // capture size. Conditions may have changed since then which indicate no
376 // change should be committed (via CommitCaptureSizeAndReset()).
377 resolution_chooser_.SetTargetFrameArea(capture_size_.GetArea());
380 int VideoCaptureOracle::AnalyzeForDecreasedArea(base::TimeTicks analyze_time) {
381 const int current_area = capture_size_.GetArea();
382 DCHECK_GT(current_area, 0);
384 // Translate the recent-average buffer pool utilization to be in terms of
385 // "capable number of pixels per frame," for an apples-to-apples comparison
386 // below.
387 int buffer_capable_area;
388 if (HasSufficientRecentFeedback(buffer_pool_utilization_, analyze_time) &&
389 buffer_pool_utilization_.current() > 1.0) {
390 // This calculation is hand-wavy, but seems to work well in a variety of
391 // situations.
392 buffer_capable_area =
393 static_cast<int>(current_area / buffer_pool_utilization_.current());
394 } else {
395 buffer_capable_area = current_area;
398 int consumer_capable_area;
399 if (HasSufficientRecentFeedback(estimated_capable_area_, analyze_time)) {
400 consumer_capable_area =
401 base::saturated_cast<int>(estimated_capable_area_.current());
402 } else {
403 consumer_capable_area = current_area;
406 // If either of the "capable areas" is less than the current capture area,
407 // decrease the capture area by AT LEAST one step.
408 int decreased_area = -1;
409 const int capable_area = std::min(buffer_capable_area, consumer_capable_area);
410 if (capable_area < current_area) {
411 decreased_area = std::min(
412 capable_area,
413 resolution_chooser_.FindSmallerFrameSize(current_area, 1).GetArea());
414 VLOG_IF(2, !start_time_of_underutilization_.is_null())
415 << "Contiguous period of under-utilization ends: "
416 "System is suddenly over-utilized.";
417 start_time_of_underutilization_ = base::TimeTicks();
418 VLOG(2) << "Proposing a "
419 << (100.0 * (current_area - decreased_area) / current_area)
420 << "% decrease in capture area. :-(";
423 // Always log the capability interpretations at verbose logging level 3. At
424 // level 2, only log when when proposing a decreased area.
425 VLOG(decreased_area == -1 ? 3 : 2)
426 << "Capability of pool=" << (100.0 * buffer_capable_area / current_area)
427 << "%, consumer=" << (100.0 * consumer_capable_area / current_area)
428 << '%';
430 return decreased_area;
433 int VideoCaptureOracle::AnalyzeForIncreasedArea(base::TimeTicks analyze_time) {
434 // Compute what one step up in capture size/area would be. If the current
435 // area is already at the maximum, no further analysis is necessary.
436 const int current_area = capture_size_.GetArea();
437 const int increased_area =
438 resolution_chooser_.FindLargerFrameSize(current_area, 1).GetArea();
439 if (increased_area <= current_area)
440 return -1;
442 // Determine whether the buffer pool could handle an increase in area.
443 if (!HasSufficientRecentFeedback(buffer_pool_utilization_, analyze_time))
444 return -1;
445 if (buffer_pool_utilization_.current() > 0.0) {
446 const int buffer_capable_area = base::saturated_cast<int>(
447 current_area / buffer_pool_utilization_.current());
448 if (buffer_capable_area < increased_area) {
449 VLOG_IF(2, !start_time_of_underutilization_.is_null())
450 << "Contiguous period of under-utilization ends: "
451 "Buffer pool is no longer under-utilized.";
452 start_time_of_underutilization_ = base::TimeTicks();
453 return -1; // Buffer pool is not under-utilized.
457 // Determine whether the consumer could handle an increase in area.
458 if (HasSufficientRecentFeedback(estimated_capable_area_, analyze_time)) {
459 if (estimated_capable_area_.current() < increased_area) {
460 VLOG_IF(2, !start_time_of_underutilization_.is_null())
461 << "Contiguous period of under-utilization ends: "
462 "Consumer is no longer under-utilized.";
463 start_time_of_underutilization_ = base::TimeTicks();
464 return -1; // Consumer is not under-utilized.
466 } else if (estimated_capable_area_.update_time() ==
467 estimated_capable_area_.reset_time()) {
468 // The consumer does not provide any feedback. In this case, the consumer's
469 // capability isn't a consideration.
470 } else {
471 // Consumer is providing feedback, but hasn't reported it recently. Just in
472 // case it's stalled, don't make things worse by increasing the capture
473 // area.
474 return -1;
477 // At this point, the system is currently under-utilized. Reset the start
478 // time if the system was not under-utilized when the last analysis was made.
479 if (start_time_of_underutilization_.is_null())
480 start_time_of_underutilization_ = analyze_time;
482 // If the under-utilization started soon after the last source size change,
483 // permit an immediate increase in the capture area. This allows the system
484 // to quickly step-up to an ideal point.
485 if ((start_time_of_underutilization_ -
486 source_size_change_time_).InMicroseconds() <=
487 kExplorationPeriodAfterSourceSizeChangeMicros) {
488 VLOG(2) << "Proposing a "
489 << (100.0 * (increased_area - current_area) / current_area)
490 << "% increase in capture area after source size change. :-)";
491 return increased_area;
494 // While content is animating, require a "proving period" of contiguous
495 // under-utilization before increasing the capture area. This will mitigate
496 // the risk of frames getting dropped when the data volume increases.
497 if ((analyze_time - last_time_animation_was_detected_).InMicroseconds() <
498 kDebouncingPeriodForAnimatedContentMicros) {
499 if ((analyze_time - start_time_of_underutilization_).InMicroseconds() <
500 kProvingPeriodForAnimatedContentMicros) {
501 // Content is animating but the system needs to be under-utilized for a
502 // longer period of time.
503 return -1;
504 } else {
505 // Content is animating and the system has been contiguously
506 // under-utilized for a good long time.
507 VLOG(2) << "Proposing a *cautious* "
508 << (100.0 * (increased_area - current_area) / current_area)
509 << "% increase in capture area while content is animating. :-)";
510 // Reset the "proving period."
511 start_time_of_underutilization_ = base::TimeTicks();
512 return increased_area;
516 // Content is not animating, so permit an immediate increase in the capture
517 // area. This allows the system to quickly improve the quality of
518 // non-animating content (frame drops are not much of a concern).
519 VLOG(2) << "Proposing a "
520 << (100.0 * (increased_area - current_area) / current_area)
521 << "% increase in capture area for non-animating content. :-)";
522 return increased_area;
525 } // namespace media