Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015 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 "media/capture/video_capture_oracle.h" | 5 #include "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/numerics/safe_conversions.h" | |
| 10 #include "base/strings/stringprintf.h" | 11 #include "base/strings/stringprintf.h" |
| 11 | 12 |
| 12 namespace media { | 13 namespace media { |
| 13 | 14 |
| 14 namespace { | 15 namespace { |
| 15 | 16 |
| 16 // 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 |
| 17 // content is static. Redundantly capturing the same frame allows iterative | 18 // content is static. Redundantly capturing the same frame allows iterative |
| 18 // 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". |
| 19 // | 20 // |
| 20 // 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 |
| 21 // 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 |
| 22 // 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 |
| 23 // 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 |
| 24 // further into the WebRTC encoding stack. | 25 // further into the WebRTC encoding stack. |
| 25 const int kNumRedundantCapturesOfStaticContent = 200; | 26 const int kNumRedundantCapturesOfStaticContent = 200; |
| 26 | 27 |
| 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; | |
| 33 | |
| 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; | |
| 38 | |
| 39 // The half-life of data points provided to the long-term accumulators, which | |
| 40 // are used to evaluate whether to increase the capture size. | |
| 41 const int kPessimisticEvaluationMicros = 30000000; | |
| 42 | |
| 43 // The minimum amount of time that must pass between changes to the capture | |
| 44 // size. This throttles the rate of size changes, to avoid stressing consumers | |
| 45 // and to allow the end-to-end system sufficient time to stabilize before | |
| 46 // re-evaluating the capture size. | |
| 47 const int kMinSizeChangePeriodMicros = 3000000; | |
| 48 | |
| 49 // The maximum amount of time that may elapse without a feedback update. Any | |
| 50 // longer, and currently-accumulated feedback is not considered recent enough to | |
| 51 // base decisions off of. This prevents changes to the capture size when there | |
| 52 // is an unexpected pause in events. | |
| 53 const int kMaxTimeSinceLastFeedbackUpdateMicros = 1000000; | |
| 54 | |
| 55 // The amount of additional time, since content animation was last detected, to | |
| 56 // continue being pessimistic 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 kPessimisticWatchPeriodMicros = 3000000; | |
| 60 | |
| 27 // Given the amount of time between frames, compare to the expected amount of | 61 // Given the amount of time between frames, compare to the expected amount of |
| 28 // time between frames at |frame_rate| and return the fractional difference. | 62 // time between frames at |frame_rate| and return the fractional difference. |
| 29 double FractionFromExpectedFrameRate(base::TimeDelta delta, int frame_rate) { | 63 double FractionFromExpectedFrameRate(base::TimeDelta delta, int frame_rate) { |
| 30 DCHECK_GT(frame_rate, 0); | 64 DCHECK_GT(frame_rate, 0); |
| 31 const base::TimeDelta expected_delta = | 65 const base::TimeDelta expected_delta = |
| 32 base::TimeDelta::FromSeconds(1) / frame_rate; | 66 base::TimeDelta::FromSeconds(1) / frame_rate; |
| 33 return (delta - expected_delta).InMillisecondsF() / | 67 return (delta - expected_delta).InMillisecondsF() / |
| 34 expected_delta.InMillisecondsF(); | 68 expected_delta.InMillisecondsF(); |
| 35 } | 69 } |
| 36 | 70 |
| 71 // Returns the next-higher TimeTicks value. | |
| 72 // TODO(miu): Patch FeedbackSignalAccumulator reset behavior and remove this | |
| 73 // hack. | |
| 74 base::TimeTicks JustAfter(base::TimeTicks t) { | |
| 75 return t + base::TimeDelta::FromMicroseconds(1); | |
| 76 } | |
| 77 | |
| 78 // Returns true if updates have been accumulated by |accumulator| for a | |
| 79 // sufficient amount of time and the latest update was fairly recent, relative | |
| 80 // to |analyze_time|. | |
|
hubbe
2015/06/23 20:04:31
analyze_time is roughly *now*, isn't it?
(Perhaps
miu
2015/06/25 20:48:59
Done. Makes sense here, to aid in readability. H
| |
| 81 bool HasSufficientRecentFeedback(const FeedbackSignalAccumulator& accumulator, | |
| 82 base::TimeTicks analyze_time) { | |
| 83 const base::TimeDelta amount_of_history = | |
| 84 accumulator.update_time() - accumulator.reset_time(); | |
| 85 return (amount_of_history.InMicroseconds() >= kMinSizeChangePeriodMicros) && | |
| 86 ((analyze_time - accumulator.update_time()).InMicroseconds() <= | |
| 87 kMaxTimeSinceLastFeedbackUpdateMicros); | |
| 88 } | |
| 89 | |
| 37 } // anonymous namespace | 90 } // anonymous namespace |
| 38 | 91 |
| 39 VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta min_capture_period) | 92 VideoCaptureOracle::VideoCaptureOracle( |
| 93 base::TimeDelta min_capture_period, | |
| 94 const gfx::Size& max_frame_size, | |
| 95 media::ResolutionChangePolicy resolution_change_policy) | |
| 40 : next_frame_number_(0), | 96 : next_frame_number_(0), |
| 41 last_successfully_delivered_frame_number_(-1), | 97 last_successfully_delivered_frame_number_(-1), |
| 42 num_frames_pending_(0), | 98 num_frames_pending_(0), |
| 43 smoothing_sampler_(min_capture_period, | 99 smoothing_sampler_(min_capture_period, |
| 44 kNumRedundantCapturesOfStaticContent), | 100 kNumRedundantCapturesOfStaticContent), |
| 45 content_sampler_(min_capture_period) { | 101 content_sampler_(min_capture_period), |
| 46 } | 102 resolution_chooser_(max_frame_size, resolution_change_policy), |
| 103 buffer_pool_utilization_(base::TimeDelta::FromMicroseconds( | |
| 104 kBufferUtilizationEvaluationMicros)), | |
| 105 estimated_capable_area_(base::TimeDelta::FromMicroseconds( | |
| 106 kConsumerCapabilityEvaluationMicros)), | |
| 107 pessimistic_pool_utilization_(base::TimeDelta::FromMicroseconds( | |
| 108 kPessimisticEvaluationMicros)), | |
| 109 pessimistic_capable_area_(base::TimeDelta::FromMicroseconds( | |
| 110 kPessimisticEvaluationMicros)) {} | |
| 47 | 111 |
| 48 VideoCaptureOracle::~VideoCaptureOracle() {} | 112 VideoCaptureOracle::~VideoCaptureOracle() {} |
| 49 | 113 |
| 114 void VideoCaptureOracle::SetSourceSize(const gfx::Size& source_size) { | |
| 115 resolution_chooser_.SetSourceSize(source_size); | |
| 116 // If the |resolution_chooser_| computed a new capture size, that will become | |
| 117 // visible via a future call to ObserveEventAndDecideCapture(). | |
| 118 } | |
| 119 | |
| 50 bool VideoCaptureOracle::ObserveEventAndDecideCapture( | 120 bool VideoCaptureOracle::ObserveEventAndDecideCapture( |
| 51 Event event, | 121 Event event, |
| 52 const gfx::Rect& damage_rect, | 122 const gfx::Rect& damage_rect, |
| 53 base::TimeTicks event_time) { | 123 base::TimeTicks event_time) { |
| 54 DCHECK_GE(event, 0); | 124 DCHECK_GE(event, 0); |
| 55 DCHECK_LT(event, kNumEvents); | 125 DCHECK_LT(event, kNumEvents); |
| 56 if (event_time < last_event_time_[event]) { | 126 if (event_time < last_event_time_[event]) { |
| 57 LOG(WARNING) << "Event time is not monotonically non-decreasing. " | 127 LOG(WARNING) << "Event time is not monotonically non-decreasing. " |
| 58 << "Deciding not to capture this frame."; | 128 << "Deciding not to capture this frame."; |
| 59 return false; | 129 return false; |
| 60 } | 130 } |
| 61 last_event_time_[event] = event_time; | 131 last_event_time_[event] = event_time; |
| 62 | 132 |
| 63 bool should_sample = false; | 133 bool should_sample = false; |
| 64 duration_of_next_frame_ = base::TimeDelta(); | 134 duration_of_next_frame_ = base::TimeDelta(); |
| 65 switch (event) { | 135 switch (event) { |
| 66 case kCompositorUpdate: | 136 case kCompositorUpdate: { |
| 67 smoothing_sampler_.ConsiderPresentationEvent(event_time); | 137 smoothing_sampler_.ConsiderPresentationEvent(event_time); |
| 138 const bool had_proposal = content_sampler_.HasProposal(); | |
| 68 content_sampler_.ConsiderPresentationEvent(damage_rect, event_time); | 139 content_sampler_.ConsiderPresentationEvent(damage_rect, event_time); |
| 69 if (content_sampler_.HasProposal()) { | 140 if (content_sampler_.HasProposal()) { |
| 141 VLOG_IF(1, !had_proposal) << "Content sampler now detects animation."; | |
| 70 should_sample = content_sampler_.ShouldSample(); | 142 should_sample = content_sampler_.ShouldSample(); |
| 71 if (should_sample) { | 143 if (should_sample) { |
| 72 event_time = content_sampler_.frame_timestamp(); | 144 event_time = content_sampler_.frame_timestamp(); |
| 73 duration_of_next_frame_ = content_sampler_.sampling_period(); | 145 duration_of_next_frame_ = content_sampler_.sampling_period(); |
| 74 } | 146 } |
| 147 last_time_animation_was_detected_ = event_time; | |
| 75 } else { | 148 } else { |
| 149 VLOG_IF(1, had_proposal) << "Content sampler detects animation ended."; | |
| 76 should_sample = smoothing_sampler_.ShouldSample(); | 150 should_sample = smoothing_sampler_.ShouldSample(); |
| 77 if (should_sample) | 151 if (should_sample) |
| 78 duration_of_next_frame_ = smoothing_sampler_.min_capture_period(); | 152 duration_of_next_frame_ = smoothing_sampler_.min_capture_period(); |
| 79 } | 153 } |
| 80 break; | 154 break; |
| 155 } | |
| 156 | |
| 81 case kTimerPoll: | 157 case kTimerPoll: |
| 82 // While the timer is firing, only allow a sampling if there are none | 158 // While the timer is firing, only allow a sampling if there are none |
| 83 // currently in-progress. | 159 // currently in-progress. |
| 84 if (num_frames_pending_ == 0) { | 160 if (num_frames_pending_ == 0) { |
| 85 should_sample = smoothing_sampler_.IsOverdueForSamplingAt(event_time); | 161 should_sample = smoothing_sampler_.IsOverdueForSamplingAt(event_time); |
| 86 if (should_sample) | 162 if (should_sample) |
| 87 duration_of_next_frame_ = smoothing_sampler_.min_capture_period(); | 163 duration_of_next_frame_ = smoothing_sampler_.min_capture_period(); |
| 88 } | 164 } |
| 89 break; | 165 break; |
| 166 | |
| 90 case kNumEvents: | 167 case kNumEvents: |
| 91 NOTREACHED(); | 168 NOTREACHED(); |
| 92 break; | 169 break; |
| 93 } | 170 } |
| 94 | 171 |
| 172 if (!should_sample) | |
| 173 return false; | |
| 174 | |
| 175 // Update |capture_size_| and reset all feedback signal accumulators if | |
| 176 // either: 1) this is the first frame; or 2) |resolution_chooser_| has an | |
| 177 // updated capture size and sufficient time has passed since the last size | |
| 178 // change. | |
| 179 if (next_frame_number_ == 0) { | |
| 180 CommitCaptureSizeAndReset(event_time - duration_of_next_frame_); | |
| 181 } else if (capture_size_ != resolution_chooser_.capture_size()) { | |
| 182 const base::TimeDelta time_since_last_change = | |
| 183 event_time - buffer_pool_utilization_.reset_time(); | |
| 184 if (time_since_last_change.InMicroseconds() >= kMinSizeChangePeriodMicros) | |
| 185 CommitCaptureSizeAndReset(GetFrameTimestamp(next_frame_number_ - 1)); | |
| 186 } | |
| 187 | |
| 95 SetFrameTimestamp(next_frame_number_, event_time); | 188 SetFrameTimestamp(next_frame_number_, event_time); |
| 96 return should_sample; | 189 return true; |
| 97 } | 190 } |
| 98 | 191 |
| 99 int VideoCaptureOracle::RecordCapture() { | 192 int VideoCaptureOracle::RecordCapture(double pool_utilization) { |
| 100 smoothing_sampler_.RecordSample(); | 193 smoothing_sampler_.RecordSample(); |
| 101 content_sampler_.RecordSample(GetFrameTimestamp(next_frame_number_)); | 194 const base::TimeTicks timestamp = GetFrameTimestamp(next_frame_number_); |
| 195 content_sampler_.RecordSample(timestamp); | |
| 196 | |
| 197 if (std::isfinite(pool_utilization) && pool_utilization >= 0.0) { | |
|
hubbe
2015/06/23 20:04:31
This seems like it could hide bugs.
Can we DCHECK(
miu
2015/06/25 20:48:59
Done.
| |
| 198 buffer_pool_utilization_.Update(pool_utilization, timestamp); | |
| 199 pessimistic_pool_utilization_.Update(pool_utilization, timestamp); | |
| 200 } | |
| 201 AnalyzeAndAdjust(timestamp); | |
| 202 | |
| 102 num_frames_pending_++; | 203 num_frames_pending_++; |
| 103 return next_frame_number_++; | 204 return next_frame_number_++; |
| 104 } | 205 } |
| 105 | 206 |
| 207 void VideoCaptureOracle::RecordWillNotCapture(double pool_utilization) { | |
| 208 VLOG(1) << "Client rejects proposal to capture frame (at #" | |
| 209 << next_frame_number_ << ")."; | |
| 210 | |
| 211 const base::TimeTicks timestamp = GetFrameTimestamp(next_frame_number_); | |
| 212 if (std::isfinite(pool_utilization) && pool_utilization >= 0.0) | |
| 213 buffer_pool_utilization_.Update(pool_utilization, timestamp); | |
| 214 ResetPessimisticAccumulators(timestamp); | |
| 215 AnalyzeAndAdjust(timestamp); | |
| 216 | |
| 217 // Note: Do not advance |next_frame_number_| since it will be re-used for the | |
| 218 // next capture proposal. | |
| 219 } | |
| 220 | |
| 106 bool VideoCaptureOracle::CompleteCapture(int frame_number, | 221 bool VideoCaptureOracle::CompleteCapture(int frame_number, |
| 107 bool capture_was_successful, | 222 bool capture_was_successful, |
| 108 base::TimeTicks* frame_timestamp) { | 223 base::TimeTicks* frame_timestamp) { |
| 109 num_frames_pending_--; | 224 num_frames_pending_--; |
| 225 DCHECK_GE(num_frames_pending_, 0); | |
| 110 | 226 |
| 111 // Drop frame if previously delivered frame number is higher. | 227 // Drop frame if previously delivered frame number is higher. |
| 112 if (last_successfully_delivered_frame_number_ > frame_number) { | 228 if (last_successfully_delivered_frame_number_ > frame_number) { |
| 113 LOG_IF(WARNING, capture_was_successful) | 229 LOG_IF(WARNING, capture_was_successful) |
| 114 << "Out of order frame delivery detected (have #" << frame_number | 230 << "Out of order frame delivery detected (have #" << frame_number |
| 115 << ", last was #" << last_successfully_delivered_frame_number_ | 231 << ", last was #" << last_successfully_delivered_frame_number_ |
| 116 << "). Dropping frame."; | 232 << "). Dropping frame."; |
| 117 return false; | 233 return false; |
| 118 } | 234 } |
| 119 | 235 |
| 236 if (!IsFrameInRecentHistory(frame_number)) { | |
| 237 LOG(WARNING) << "Very old capture being ignored: frame #" << frame_number; | |
| 238 return false; | |
| 239 } | |
| 240 | |
| 120 if (!capture_was_successful) { | 241 if (!capture_was_successful) { |
| 121 VLOG(2) << "Capture of frame #" << frame_number << " was not successful."; | 242 VLOG(2) << "Capture of frame #" << frame_number << " was not successful."; |
| 122 return false; | 243 return false; |
| 123 } | 244 } |
| 124 | 245 |
| 125 DCHECK_NE(last_successfully_delivered_frame_number_, frame_number); | 246 DCHECK_NE(last_successfully_delivered_frame_number_, frame_number); |
| 126 last_successfully_delivered_frame_number_ = frame_number; | 247 last_successfully_delivered_frame_number_ = frame_number; |
| 127 | 248 |
| 128 *frame_timestamp = GetFrameTimestamp(frame_number); | 249 *frame_timestamp = GetFrameTimestamp(frame_number); |
| 129 | 250 |
| 130 // If enabled, log a measurement of how this frame timestamp has incremented | 251 // If enabled, log a measurement of how this frame timestamp has incremented |
| 131 // in relation to an ideal increment. | 252 // in relation to an ideal increment. |
| 132 if (VLOG_IS_ON(2) && frame_number > 0) { | 253 if (VLOG_IS_ON(3) && frame_number > 0) { |
| 133 const base::TimeDelta delta = | 254 const base::TimeDelta delta = |
| 134 *frame_timestamp - GetFrameTimestamp(frame_number - 1); | 255 *frame_timestamp - GetFrameTimestamp(frame_number - 1); |
| 135 if (content_sampler_.HasProposal()) { | 256 if (content_sampler_.HasProposal()) { |
| 136 const double estimated_frame_rate = | 257 const double estimated_frame_rate = |
| 137 1000000.0 / content_sampler_.detected_period().InMicroseconds(); | 258 1000000.0 / content_sampler_.detected_period().InMicroseconds(); |
| 138 const int rounded_frame_rate = | 259 const int rounded_frame_rate = |
| 139 static_cast<int>(estimated_frame_rate + 0.5); | 260 static_cast<int>(estimated_frame_rate + 0.5); |
| 140 VLOG(2) << base::StringPrintf( | 261 VLOG_STREAM(3) << base::StringPrintf( |
| 141 "Captured #%d: delta=%" PRId64 " usec" | 262 "Captured #%d: delta=%" PRId64 " usec" |
| 142 ", now locked into {%s}, %+0.1f%% slower than %d FPS", | 263 ", now locked into {%s}, %+0.1f%% slower than %d FPS", |
| 143 frame_number, | 264 frame_number, |
| 144 delta.InMicroseconds(), | 265 delta.InMicroseconds(), |
| 145 content_sampler_.detected_region().ToString().c_str(), | 266 content_sampler_.detected_region().ToString().c_str(), |
| 146 100.0 * FractionFromExpectedFrameRate(delta, rounded_frame_rate), | 267 100.0 * FractionFromExpectedFrameRate(delta, rounded_frame_rate), |
| 147 rounded_frame_rate); | 268 rounded_frame_rate); |
| 148 } else { | 269 } else { |
| 149 VLOG(2) << base::StringPrintf( | 270 VLOG_STREAM(3) << base::StringPrintf( |
| 150 "Captured #%d: delta=%" PRId64 " usec" | 271 "Captured #%d: delta=%" PRId64 " usec" |
| 151 ", d/30fps=%+0.1f%%, d/25fps=%+0.1f%%, d/24fps=%+0.1f%%", | 272 ", d/30fps=%+0.1f%%, d/25fps=%+0.1f%%, d/24fps=%+0.1f%%", |
| 152 frame_number, | 273 frame_number, |
| 153 delta.InMicroseconds(), | 274 delta.InMicroseconds(), |
| 154 100.0 * FractionFromExpectedFrameRate(delta, 30), | 275 100.0 * FractionFromExpectedFrameRate(delta, 30), |
| 155 100.0 * FractionFromExpectedFrameRate(delta, 25), | 276 100.0 * FractionFromExpectedFrameRate(delta, 25), |
| 156 100.0 * FractionFromExpectedFrameRate(delta, 24)); | 277 100.0 * FractionFromExpectedFrameRate(delta, 24)); |
| 157 } | 278 } |
| 158 } | 279 } |
| 159 | 280 |
| 160 return !frame_timestamp->is_null(); | 281 return true; |
| 282 } | |
| 283 | |
| 284 void VideoCaptureOracle::RecordConsumerFeedback(int frame_number, | |
| 285 double resource_utilization) { | |
| 286 if (!std::isfinite(resource_utilization) || resource_utilization <= 0.0) | |
|
hubbe
2015/06/23 20:04:31
Bug hiding!
However, these values come from the no
miu
2015/06/25 20:48:59
Done.
| |
| 287 return; // Invalid data point. | |
| 288 | |
| 289 if (!IsFrameInRecentHistory(frame_number)) { | |
| 290 VLOG(1) << "Very old frame feedback being ignored: frame #" << frame_number; | |
| 291 return; | |
| 292 } | |
| 293 const base::TimeTicks timestamp = GetFrameTimestamp(frame_number); | |
| 294 | |
| 295 // Translate the utilization metric to be in terms of the capable frame area | |
| 296 // and update the feedback accumulators. Research suggests utilization is at | |
| 297 // most linearly proportional to area, and typically is sublinear. Either | |
| 298 // way, the end-to-end system should converge to the right place using the | |
| 299 // more-conservative assumption (linear). | |
| 300 const int area_at_full_utilization = | |
| 301 base::saturated_cast<int>(capture_size_.GetArea() / resource_utilization); | |
| 302 estimated_capable_area_.Update(area_at_full_utilization, timestamp); | |
| 303 pessimistic_capable_area_.Update(area_at_full_utilization, timestamp); | |
| 161 } | 304 } |
| 162 | 305 |
| 163 base::TimeTicks VideoCaptureOracle::GetFrameTimestamp(int frame_number) const { | 306 base::TimeTicks VideoCaptureOracle::GetFrameTimestamp(int frame_number) const { |
| 164 DCHECK_LE(frame_number, next_frame_number_); | 307 DCHECK(IsFrameInRecentHistory(frame_number)); |
| 165 DCHECK_LT(next_frame_number_ - frame_number, kMaxFrameTimestamps); | |
| 166 return frame_timestamps_[frame_number % kMaxFrameTimestamps]; | 308 return frame_timestamps_[frame_number % kMaxFrameTimestamps]; |
| 167 } | 309 } |
| 168 | 310 |
| 169 void VideoCaptureOracle::SetFrameTimestamp(int frame_number, | 311 void VideoCaptureOracle::SetFrameTimestamp(int frame_number, |
| 170 base::TimeTicks timestamp) { | 312 base::TimeTicks timestamp) { |
| 313 DCHECK(IsFrameInRecentHistory(frame_number)); | |
| 171 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp; | 314 frame_timestamps_[frame_number % kMaxFrameTimestamps] = timestamp; |
| 172 } | 315 } |
| 173 | 316 |
| 317 bool VideoCaptureOracle::IsFrameInRecentHistory(int frame_number) const { | |
| 318 return ((next_frame_number_ - frame_number) < kMaxFrameTimestamps && | |
| 319 frame_number <= next_frame_number_ && | |
| 320 frame_number >= 0); | |
| 321 } | |
| 322 | |
| 323 void VideoCaptureOracle::CommitCaptureSizeAndReset( | |
| 324 base::TimeTicks last_frame_time) { | |
| 325 capture_size_ = resolution_chooser_.capture_size(); | |
| 326 VLOG(2) << "Now proposing a capture size of " << capture_size_.ToString(); | |
| 327 | |
| 328 // Reset each short-term feedback accumulator with a stable-state starting | |
| 329 // value. | |
| 330 const base::TimeTicks ignore_before_time = JustAfter(last_frame_time); | |
| 331 buffer_pool_utilization_.Reset(1.0, ignore_before_time); | |
| 332 estimated_capable_area_.Reset(capture_size_.GetArea(), ignore_before_time); | |
| 333 | |
| 334 ResetPessimisticAccumulators(last_frame_time); | |
| 335 } | |
| 336 | |
| 337 void VideoCaptureOracle::ResetPessimisticAccumulators( | |
| 338 base::TimeTicks event_time) { | |
| 339 // Reset each pessimistic feedback accumulator with "overloaded" values so | |
| 340 // that a long period of time must pass before they produce a result that | |
| 341 // would encourage increasing the capture size. | |
| 342 const base::TimeTicks ignore_before_time = JustAfter(event_time); | |
| 343 pessimistic_pool_utilization_.Reset(1.5, ignore_before_time); | |
|
hubbe
2015/06/23 20:04:31
1.5?
constant?
miu
2015/06/25 20:48:59
Not applicable, since I got rid of these extra acc
| |
| 344 pessimistic_capable_area_.Reset(0.0, ignore_before_time); | |
| 345 } | |
| 346 | |
| 347 void VideoCaptureOracle::AnalyzeAndAdjust(const base::TimeTicks analyze_time) { | |
| 348 const int decreased_area = AnalyzeForDecreasedArea(analyze_time); | |
| 349 if (decreased_area > 0) { | |
| 350 ResetPessimisticAccumulators(analyze_time); | |
| 351 resolution_chooser_.SetTargetFrameArea(decreased_area); | |
| 352 return; | |
| 353 } | |
| 354 | |
| 355 const int increased_area = AnalyzeForIncreasedArea(analyze_time); | |
| 356 if (increased_area > 0) { | |
| 357 resolution_chooser_.SetTargetFrameArea(increased_area); | |
| 358 return; | |
| 359 } | |
| 360 | |
| 361 // Explicitly set the target frame area to the current capture area. This | |
| 362 // cancels-out the results of a previous call to this method, where the | |
| 363 // |resolution_chooser_| may have been instructed to increase or decrease the | |
| 364 // capture size. Conditions may have changed since then which indicate no | |
| 365 // change should be committed (via CommitCaptureSizeAndReset()). | |
| 366 resolution_chooser_.SetTargetFrameArea(capture_size_.GetArea()); | |
| 367 } | |
| 368 | |
| 369 int VideoCaptureOracle::AnalyzeForDecreasedArea(base::TimeTicks analyze_time) { | |
| 370 const int current_area = capture_size_.GetArea(); | |
| 371 DCHECK_GT(current_area, 0); | |
| 372 | |
| 373 // Translate the recent-average buffer pool utilization to be in terms of | |
| 374 // "capable number of pixels per frame," for an apples-to-apples comparison | |
| 375 // below. | |
| 376 int buffer_capable_area; | |
| 377 if (HasSufficientRecentFeedback(buffer_pool_utilization_, analyze_time) && | |
| 378 buffer_pool_utilization_.current() > 1.0) { | |
| 379 // This calculation is hand-wavy, but seems to work well in a variety of | |
| 380 // situations. | |
| 381 buffer_capable_area = static_cast<int>( | |
| 382 current_area / buffer_pool_utilization_.current()); | |
| 383 } else { | |
| 384 buffer_capable_area = current_area; | |
| 385 } | |
| 386 | |
| 387 int consumer_capable_area; | |
| 388 if (HasSufficientRecentFeedback(estimated_capable_area_, analyze_time)) { | |
| 389 consumer_capable_area = | |
| 390 base::saturated_cast<int>(estimated_capable_area_.current()); | |
| 391 } else { | |
| 392 consumer_capable_area = current_area; | |
| 393 } | |
| 394 | |
| 395 // If either of the "capable areas" is less than the current capture area, | |
| 396 // decrease the capture area by AT LEAST one step. | |
| 397 int decreased_area = -1; | |
| 398 const int capable_area = std::min(buffer_capable_area, consumer_capable_area); | |
| 399 if (capable_area < current_area) { | |
| 400 decreased_area = std::min( | |
| 401 capable_area, | |
| 402 resolution_chooser_.FindSnappedFrameSize(current_area, -1).GetArea()); | |
| 403 VLOG(2) << "Proposing a " | |
| 404 << (100.0 * (current_area - decreased_area) / current_area) | |
| 405 << "% decrease in capture area. :-("; | |
| 406 } | |
| 407 | |
| 408 // Always log the capability interpretations at verbose logging level 3. At | |
| 409 // level 2, only log when when proposing a decreased area. | |
| 410 VLOG(decreased_area == -1 ? 3 : 2) | |
| 411 << "Capability of pool=" << (100.0 * buffer_capable_area / current_area) | |
| 412 << "%, consumer=" << (100.0 * consumer_capable_area / current_area) | |
| 413 << '%'; | |
| 414 | |
| 415 return decreased_area; | |
| 416 } | |
| 417 | |
| 418 int VideoCaptureOracle::AnalyzeForIncreasedArea(base::TimeTicks analyze_time) { | |
| 419 // While content is animating, be pessimistic about increasing the capture | |
| 420 // area since the end-to-end system should prove, over a longer period of | |
| 421 // time, that it will be able to handle increased load without dropping a | |
| 422 // frame. Otherwise, be aggressive about increasing the capture area to | |
| 423 // improve the quality of non-animating content (where frame drops are not as | |
| 424 // important). | |
| 425 const bool use_pessimistic_outlook = | |
| 426 (analyze_time - last_time_animation_was_detected_).InMicroseconds() < | |
| 427 kPessimisticWatchPeriodMicros; | |
| 428 DCHECK(!content_sampler_.HasProposal() || use_pessimistic_outlook); | |
| 429 | |
| 430 // If buffer pool utilization is already at maximum, do not propose an | |
| 431 // increase in area. | |
| 432 FeedbackSignalAccumulator* const pool_utilization = use_pessimistic_outlook ? | |
| 433 &pessimistic_pool_utilization_ : &buffer_pool_utilization_; | |
| 434 if (!HasSufficientRecentFeedback(*pool_utilization, analyze_time) || | |
| 435 pool_utilization->current() >= 1.0) { | |
| 436 return -1; | |
| 437 } | |
| 438 | |
| 439 // Compute what one step up in capture size/area would be. If the current | |
| 440 // area is already at the maximum, no further analysis is necessary. | |
| 441 const int current_area = capture_size_.GetArea(); | |
| 442 const int increased_area = | |
| 443 resolution_chooser_.FindSnappedFrameSize(current_area, +1).GetArea(); | |
| 444 if (increased_area <= current_area) | |
| 445 return -1; | |
| 446 | |
| 447 // Determine whether the consumer could handle an increase in area. | |
| 448 FeedbackSignalAccumulator* const capable_area = use_pessimistic_outlook ? | |
| 449 &pessimistic_capable_area_ : &estimated_capable_area_; | |
| 450 if (HasSufficientRecentFeedback(*capable_area, analyze_time)) { | |
| 451 if (capable_area->current() < increased_area) | |
| 452 return -1; // Not capable. | |
| 453 } else if (estimated_capable_area_.update_time() == | |
| 454 estimated_capable_area_.reset_time()) { | |
|
hubbe
2015/06/23 20:04:31
odd indentation
miu
2015/06/25 20:48:59
Done.
| |
| 455 // The consumer does not provide any feedback. In this case, the consumer's | |
| 456 // capability isn't a consideration. | |
| 457 } else { | |
| 458 // Consumer is providing feedback, but hasn't reported it recently. Just in | |
| 459 // case it's stalled, don't make things worse by increasing the capture | |
| 460 // area. | |
| 461 return -1; | |
| 462 } | |
| 463 | |
| 464 VLOG(2) << "Proposing a " | |
| 465 << (100.0 * (increased_area - current_area) / current_area) | |
| 466 << "% increase in capture area based on " | |
| 467 << (use_pessimistic_outlook ? "pessimistic" : "short-term") | |
| 468 << " outlook. :-)"; | |
| 469 | |
| 470 return increased_area; | |
| 471 } | |
| 472 | |
| 174 } // namespace media | 473 } // namespace media |
| OLD | NEW |