| 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 |