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