OLD | NEW |
(Empty) | |
| 1 // Copyright 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/filters/video_renderer_algorithm.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <limits> |
| 9 |
| 10 namespace media { |
| 11 |
| 12 enum FrameSelector { NONE, CADENCE, COVERAGE, DRIFT }; |
| 13 |
| 14 VideoRendererAlgorithm::ReadyFrame::ReadyFrame( |
| 15 const scoped_refptr<VideoFrame>& ready_frame) |
| 16 : frame(ready_frame), |
| 17 media_timestamp(ready_frame->timestamp()), |
| 18 ideal_render_count(0), |
| 19 render_count(0) { |
| 20 } |
| 21 |
| 22 VideoRendererAlgorithm::ReadyFrame::~ReadyFrame() { |
| 23 } |
| 24 |
| 25 bool VideoRendererAlgorithm::ReadyFrame::operator<( |
| 26 const ReadyFrame& other) const { |
| 27 return media_timestamp < other.media_timestamp; |
| 28 } |
| 29 |
| 30 VideoRendererAlgorithm::VideoRendererAlgorithm( |
| 31 const TimeConverterCB& time_converter_cb) |
| 32 : time_converter_cb_(time_converter_cb) { |
| 33 Reset(); |
| 34 } |
| 35 |
| 36 VideoRendererAlgorithm::~VideoRendererAlgorithm() { |
| 37 } |
| 38 |
| 39 scoped_refptr<VideoFrame> VideoRendererAlgorithm::Render( |
| 40 base::TimeTicks deadline_min, |
| 41 base::TimeTicks deadline_max, |
| 42 int* frames_dropped) { |
| 43 DCHECK(deadline_min < deadline_max); |
| 44 |
| 45 if (frame_queue_.empty()) |
| 46 return nullptr; |
| 47 |
| 48 // Once Render() is called |last_frame_index_| has meaning and should thus be |
| 49 // preserved even if better frames come in before it due to out of order |
| 50 // timestamps. |
| 51 have_rendered_frames_ = true; |
| 52 |
| 53 // Step 1: Update the current render interval for subroutines. |
| 54 render_interval_ = deadline_max - deadline_min; |
| 55 |
| 56 // Step 2: Figure out if any intervals have been skipped since the last call |
| 57 // to Render(). If so, we assume the last frame provided was rendered during |
| 58 // those intervals and adjust its render count appropriately. |
| 59 AccountForMissedIntervals(deadline_min, deadline_max); |
| 60 |
| 61 // Step 3: Update the wall clock timestamps and frame duration estimates for |
| 62 // all frames currently in the |frame_queue_|. |
| 63 if (!UpdateFrameStatistics()) { |
| 64 DVLOG(2) << "Failed to update frame statistics."; |
| 65 DCHECK(frame_queue_[last_frame_index_].frame); |
| 66 return frame_queue_[last_frame_index_].frame; |
| 67 } |
| 68 |
| 69 FrameSelector frame_selector = NONE; |
| 70 base::TimeDelta selected_frame_drift; |
| 71 |
| 72 // Step 4: Attempt to find the best frame by cadence. |
| 73 int frame_to_render = -1; |
| 74 if (ideal_cadence_) { |
| 75 frame_selector = CADENCE; |
| 76 frame_to_render = FindBestFrameByCadence(); |
| 77 |
| 78 if (frame_to_render >= 0) { |
| 79 selected_frame_drift = |
| 80 CalculateDriftForFrame(deadline_min, frame_to_render); |
| 81 } |
| 82 } |
| 83 |
| 84 // Step 5: If no frame could be found by cadence or the selected frame exceeds |
| 85 // acceptable drift, try to find the best frame by coverage of the deadline. |
| 86 if (frame_to_render < 0 || selected_frame_drift > max_acceptable_drift_) { |
| 87 frame_selector = COVERAGE; |
| 88 int second_best_by_coverage = -1; |
| 89 const int best_by_coverage = FindBestFrameByCoverage( |
| 90 deadline_min, deadline_max, &second_best_by_coverage); |
| 91 |
| 92 // If the frame was previously selected based on cadence, we're only here |
| 93 // because the drift is too large, so even if the cadence frame has the best |
| 94 // coverage, fallback to the second best by coverage if it has better drift. |
| 95 if (frame_to_render == best_by_coverage && second_best_by_coverage >= 0 && |
| 96 CalculateDriftForFrame(deadline_min, second_best_by_coverage) <= |
| 97 selected_frame_drift) { |
| 98 frame_to_render = second_best_by_coverage; |
| 99 } else { |
| 100 frame_to_render = best_by_coverage; |
| 101 } |
| 102 |
| 103 if (frame_to_render >= 0) { |
| 104 selected_frame_drift = |
| 105 CalculateDriftForFrame(deadline_min, frame_to_render); |
| 106 } |
| 107 } |
| 108 |
| 109 // Step 6: If _still_ no frame could be found by coverage, try to choose the |
| 110 // least crappy option based on the drift from the deadline. If we're here the |
| 111 // selection is going to be bad because it means no suitable frame has any |
| 112 // coverage of the deadline interval. |
| 113 if (frame_to_render < 0 || selected_frame_drift > max_acceptable_drift_) { |
| 114 frame_selector = DRIFT; |
| 115 frame_to_render = FindBestFrameByDrift(deadline_min); |
| 116 selected_frame_drift = |
| 117 CalculateDriftForFrame(deadline_min, frame_to_render); |
| 118 } |
| 119 |
| 120 last_render_had_glitch_ = selected_frame_drift > max_acceptable_drift_; |
| 121 if (last_render_had_glitch_) { |
| 122 DVLOG(2) << "Frame drift is too far: " |
| 123 << selected_frame_drift.InMillisecondsF() << "ms"; |
| 124 } |
| 125 |
| 126 DCHECK_GE(frame_to_render, 0); |
| 127 |
| 128 // Drop some debugging information if a frame had poor cadence. |
| 129 if (ideal_cadence_) { |
| 130 const ReadyFrame& last_frame_info = frame_queue_[last_frame_index_]; |
| 131 if (frame_to_render != last_frame_index_ && |
| 132 last_frame_info.render_count < last_frame_info.ideal_render_count) { |
| 133 last_render_had_glitch_ = true; |
| 134 DVLOG(2) << "Underdisplayed frame " << last_frame_info.media_timestamp |
| 135 << "; only " << last_frame_info.render_count |
| 136 << " times instead of " << last_frame_info.ideal_render_count; |
| 137 } else if (frame_to_render == last_frame_index_ && |
| 138 last_frame_info.render_count >= |
| 139 last_frame_info.ideal_render_count) { |
| 140 DVLOG(2) << "Overdisplayed frame " << last_frame_info.media_timestamp |
| 141 << "; displayed " << last_frame_info.render_count + 1 |
| 142 << " times instead of " << last_frame_info.ideal_render_count; |
| 143 last_render_had_glitch_ = true; |
| 144 } |
| 145 } |
| 146 |
| 147 // Step 7: Drop frames which occur prior to the frame to be rendered. If any |
| 148 // frame has a zero render count it should be reported as dropped. |
| 149 if (frame_to_render > 0) { |
| 150 if (frames_dropped) { |
| 151 for (int i = 0; i < frame_to_render; ++i) { |
| 152 if (!frame_queue_[i].render_count) { |
| 153 DVLOG(2) << "Dropping undisplayed frame " |
| 154 << frame_queue_[i].media_timestamp; |
| 155 ++(*frames_dropped); |
| 156 if (!fractional_cadence_) |
| 157 last_render_had_glitch_ = true; |
| 158 } |
| 159 } |
| 160 } |
| 161 |
| 162 frame_queue_.erase(frame_queue_.begin(), |
| 163 frame_queue_.begin() + frame_to_render); |
| 164 } |
| 165 |
| 166 // Step 8: Congratulations, the frame selection guantlet has been passed! |
| 167 last_frame_index_ = 0; |
| 168 ++frame_queue_.front().render_count; |
| 169 DCHECK(frame_queue_.front().frame); |
| 170 return frame_queue_.front().frame; |
| 171 } |
| 172 |
| 173 int VideoRendererAlgorithm::RemoveExpiredFrames(base::TimeTicks deadline_min) { |
| 174 if (!UpdateFrameStatistics() || frame_duration_ == base::TimeDelta()) |
| 175 return 0; |
| 176 |
| 177 DCHECK_GE(frame_queue_.size(), 2u); |
| 178 |
| 179 // Finds and removes all frames which are too old to be used; I.e., the end of |
| 180 // their display interval is further than |max_acceptable_drift_| from the |
| 181 // given |deadline_min|. |
| 182 int frames_to_expire = 0; |
| 183 const base::TimeTicks mininum_frame_time = |
| 184 deadline_min - max_acceptable_drift_ - frame_duration_; |
| 185 for (; static_cast<size_t>(frames_to_expire) < frame_queue_.size() - 1; |
| 186 ++frames_to_expire) { |
| 187 if (frame_queue_[frames_to_expire].wall_clock_time >= mininum_frame_time) |
| 188 break; |
| 189 } |
| 190 |
| 191 if (!frames_to_expire) |
| 192 return 0; |
| 193 |
| 194 frame_queue_.erase(frame_queue_.begin(), |
| 195 frame_queue_.begin() + frames_to_expire); |
| 196 |
| 197 last_frame_index_ = std::max(0, last_frame_index_ - frames_to_expire); |
| 198 return frames_to_expire; |
| 199 } |
| 200 |
| 201 void VideoRendererAlgorithm::OnLastFrameDropped() { |
| 202 DCHECK(!frame_queue_.empty()); |
| 203 |
| 204 // We only care if the frame was never rendered at all; otherwise we assume |
| 205 // that the frame was redisplayed even if the renderer was told otherwise. |
| 206 if (frame_queue_[last_frame_index_].render_count == 1) |
| 207 frame_queue_[last_frame_index_].render_count = 0; |
| 208 } |
| 209 |
| 210 void VideoRendererAlgorithm::Reset() { |
| 211 last_frame_index_ = ideal_cadence_ = fractional_cadence_ = 0; |
| 212 last_detected_cadence_ = render_intervals_cadence_held_ = 0; |
| 213 have_rendered_frames_ = last_render_had_glitch_ = false; |
| 214 cadence_hysteresis_enabled_ = true; |
| 215 last_deadline_max_ = base::TimeTicks(); |
| 216 frame_duration_ = render_interval_ = base::TimeDelta(); |
| 217 frame_queue_.clear(); |
| 218 |
| 219 // Default to ATSC IS/191 recommendations for maximum acceptable drift before |
| 220 // we have enough frames to base the the maximum on frame duration. |
| 221 max_acceptable_drift_ = base::TimeDelta::FromMilliseconds(15); |
| 222 } |
| 223 |
| 224 size_t VideoRendererAlgorithm::EffectiveFramesQueued() const { |
| 225 if (!have_rendered_frames_ || !ideal_cadence_) |
| 226 return frame_queue_.size(); |
| 227 |
| 228 size_t renderable_frame_count = 0; |
| 229 for (size_t i = last_frame_index_; i < frame_queue_.size(); ++i) { |
| 230 if (frame_queue_[i].ideal_render_count > 0) |
| 231 ++renderable_frame_count; |
| 232 } |
| 233 |
| 234 return renderable_frame_count; |
| 235 } |
| 236 |
| 237 void VideoRendererAlgorithm::EnqueueFrame( |
| 238 const scoped_refptr<VideoFrame>& frame) { |
| 239 DCHECK(frame); |
| 240 DCHECK(!frame->end_of_stream()); |
| 241 |
| 242 ReadyFrame ready_frame(frame); |
| 243 auto it = frame_queue_.empty() ? frame_queue_.end() |
| 244 : std::lower_bound(frame_queue_.begin(), |
| 245 frame_queue_.end(), frame); |
| 246 |
| 247 // If a frame was inserted before the first frame, update the index. On the |
| 248 // next call to Render() it will be dropped. |
| 249 if (it - frame_queue_.begin() <= last_frame_index_ && have_rendered_frames_) |
| 250 ++last_frame_index_; |
| 251 |
| 252 // The vast majority of cases should always append to the back, but in rare |
| 253 // circumstance we get out of order timestamps, http://crbug.com/386551. |
| 254 it = frame_queue_.insert(it, ready_frame); |
| 255 |
| 256 // Project the current cadence calculations to include the new frame. These |
| 257 // may not be accurate until the next Render() call. These updates are done |
| 258 // to ensure EffectiveFramesQueued() returns a semi-reliable result. |
| 259 if (ideal_cadence_ && !fractional_cadence_) |
| 260 it->ideal_render_count = ideal_cadence_; |
| 261 else if (fractional_cadence_) |
| 262 UpdateFractionalCadenceForFrames(fractional_cadence_); |
| 263 |
| 264 // Verify sorted order in debug mode. |
| 265 for (size_t i = 0; i < frame_queue_.size() - 1; ++i) { |
| 266 DCHECK(frame_queue_[i].frame->timestamp() <= |
| 267 frame_queue_[i + 1].frame->timestamp()); |
| 268 } |
| 269 } |
| 270 |
| 271 void VideoRendererAlgorithm::AccountForMissedIntervals( |
| 272 base::TimeTicks deadline_min, |
| 273 base::TimeTicks deadline_max) { |
| 274 const base::TimeTicks previous_deadline_max = last_deadline_max_; |
| 275 last_deadline_max_ = deadline_max; |
| 276 |
| 277 if (previous_deadline_max.is_null()) |
| 278 return; |
| 279 |
| 280 const int64 render_cycle_count = |
| 281 std::abs((deadline_min - previous_deadline_max) / render_interval_); |
| 282 |
| 283 // In the ideal case this value will be zero. |
| 284 if (!render_cycle_count) |
| 285 return; |
| 286 |
| 287 DVLOG(2) << "Missed " << render_cycle_count << " Render() intervals."; |
| 288 |
| 289 // Only update display count if the frame was displayed at all; it may not |
| 290 // have been if OnFrameDropped() was called. |
| 291 if (frame_queue_[last_frame_index_].render_count) |
| 292 frame_queue_[last_frame_index_].render_count += render_cycle_count; |
| 293 } |
| 294 |
| 295 base::TimeDelta VideoRendererAlgorithm::CalculateTimeUntilGlitch( |
| 296 double perfect_cadence, |
| 297 double clamped_cadence, |
| 298 bool fractional) { |
| 299 if (clamped_cadence == 0.0) |
| 300 return base::TimeDelta(); |
| 301 |
| 302 // Calculate the drift in microseconds for each frame we render at cadence |
| 303 // instead of for its real duration. |
| 304 const double rendered_frame_duration = |
| 305 fractional ? render_interval_.InMicroseconds() |
| 306 : clamped_cadence * render_interval_.InMicroseconds(); |
| 307 |
| 308 // When computing a fractional drift, we render the first of |clamped_cadence| |
| 309 // frames and drop |clamped_cadence| - 1 frames. To make the calculations |
| 310 // below work we need to project out the timestamp of the frame which would be |
| 311 // displayed after accounting for those |clamped_cadence| frames. |
| 312 const double actual_frame_duration = |
| 313 fractional ? clamped_cadence * frame_duration_.InMicroseconds() |
| 314 : frame_duration_.InMicroseconds(); |
| 315 |
| 316 const double rendered_vs_actual_duration_delta = |
| 317 std::abs(rendered_frame_duration - actual_frame_duration); |
| 318 if (rendered_vs_actual_duration_delta < |
| 319 std::numeric_limits<double>::epsilon()) { |
| 320 return kInfiniteDuration(); |
| 321 } |
| 322 |
| 323 const double frames_rendered_before_drift_exhausted = |
| 324 std::ceil(max_acceptable_drift_.InMicroseconds() / |
| 325 rendered_vs_actual_duration_delta); |
| 326 |
| 327 const base::TimeDelta time_until_glitch = base::TimeDelta::FromMicroseconds( |
| 328 rendered_frame_duration * frames_rendered_before_drift_exhausted); |
| 329 |
| 330 return time_until_glitch; |
| 331 } |
| 332 |
| 333 bool VideoRendererAlgorithm::UpdateFrameStatistics() { |
| 334 // Figure out all current ready frame times at once so we minimize the drift |
| 335 // relative to real time as the code below executes. |
| 336 for (auto& frame_info : frame_queue_) { |
| 337 frame_info.wall_clock_time = |
| 338 time_converter_cb_.Run(frame_info.media_timestamp); |
| 339 |
| 340 // If time stops or never started, exit immediately. |
| 341 if (frame_info.wall_clock_time.is_null()) |
| 342 return false; |
| 343 } |
| 344 |
| 345 // Do we have enough frames to compute statistics? |
| 346 const bool have_frame_duration = frame_duration_ != base::TimeDelta(); |
| 347 if (frame_queue_.size() < 2 && !have_frame_duration) |
| 348 return true; |
| 349 |
| 350 // Update |frame_duration_| estimate weighted towards the existing duration. |
| 351 const base::TimeDelta wall_clock_delta = frame_queue_.back().wall_clock_time - |
| 352 frame_queue_.front().wall_clock_time; |
| 353 if (!have_frame_duration) { |
| 354 frame_duration_ = wall_clock_delta / (frame_queue_.size() - 1); |
| 355 } else { |
| 356 frame_duration_ = |
| 357 (frame_duration_ + wall_clock_delta) / frame_queue_.size(); |
| 358 } |
| 359 |
| 360 // ITU-R BR.265 recommends a maximum acceptable drift of +/- half of the frame |
| 361 // duration; there are other asymmetric, more lenient measures, that we're |
| 362 // forgoing in favor of simplicity. |
| 363 // |
| 364 // We'll always allow at least 8.33ms of drift since literature suggests it's |
| 365 // well below the floor of detection. |
| 366 max_acceptable_drift_ = |
| 367 std::max(frame_duration_ / 2, base::TimeDelta::FromSecondsD(1.0 / 120)); |
| 368 |
| 369 // The perfect cadence is the number of render intervals per frame, while the |
| 370 // clamped cadence is the nearest matching integer cadence. |
| 371 const double perfect_cadence = |
| 372 frame_duration_.InSecondsF() / render_interval_.InSecondsF(); |
| 373 const int clamped_cadence = perfect_cadence + 0.5; |
| 374 |
| 375 // Inverse cadence is checked to see if we have a fractional cadence which |
| 376 // would look best if we consistently drop the same frames. A fractional |
| 377 // cadence is something like 120fps content on a 60Hz display. |
| 378 const double inverse_perfect_cadence = 1.0 / perfect_cadence; |
| 379 const int clamped_inverse_cadence = inverse_perfect_cadence + 0.5; |
| 380 |
| 381 const base::TimeDelta minimum_glitch_time = |
| 382 base::TimeDelta::FromSeconds(kMinimumAcceptableTimeBetweenGlitchesSecs); |
| 383 |
| 384 // See if the clamped cadence fits acceptable thresholds for exhausting drift. |
| 385 int new_cadence = 0, new_fractional_cadence = 0; |
| 386 if (CalculateTimeUntilGlitch(perfect_cadence, clamped_cadence, false) >= |
| 387 minimum_glitch_time) { |
| 388 new_cadence = clamped_cadence; |
| 389 } else if (CalculateTimeUntilGlitch(inverse_perfect_cadence, |
| 390 clamped_inverse_cadence, |
| 391 true) >= minimum_glitch_time) { |
| 392 new_cadence = 1; |
| 393 new_fractional_cadence = clamped_inverse_cadence; |
| 394 } |
| 395 |
| 396 // If hysteresis is enabled, require cadence to hold for some time before |
| 397 // switching in or out of cadence based rendering mode. |
| 398 if (cadence_hysteresis_enabled_) { |
| 399 if (new_fractional_cadence) { |
| 400 if (last_detected_cadence_ == new_fractional_cadence) { |
| 401 ++render_intervals_cadence_held_; |
| 402 } else { |
| 403 last_detected_cadence_ = new_fractional_cadence; |
| 404 render_intervals_cadence_held_ = 0; |
| 405 } |
| 406 } else { |
| 407 if (last_detected_cadence_ == new_cadence) { |
| 408 ++render_intervals_cadence_held_; |
| 409 } else { |
| 410 last_detected_cadence_ = new_cadence; |
| 411 render_intervals_cadence_held_ = 0; |
| 412 } |
| 413 } |
| 414 |
| 415 // To prevent oscillation of cadence selection, ensure cadence selections |
| 416 // are held for some time before applying them. Value chosen arbitrarily. |
| 417 const base::TimeDelta cadence_hysteresis = |
| 418 base::TimeDelta::FromMilliseconds(100); |
| 419 if (render_intervals_cadence_held_ * render_interval_ < cadence_hysteresis) |
| 420 return true; |
| 421 } |
| 422 |
| 423 // No need to update cadence if there's been no change; cadence will be set |
| 424 // as frames are added to the queue. |
| 425 if (ideal_cadence_ == new_cadence && |
| 426 new_fractional_cadence == fractional_cadence_) { |
| 427 return true; |
| 428 } |
| 429 |
| 430 if (new_fractional_cadence) { |
| 431 UpdateFractionalCadenceForFrames(new_fractional_cadence); |
| 432 } else { |
| 433 // |new_cadence| may be zero at this point, which clears previous cadences. |
| 434 for (auto& frame_info : frame_queue_) |
| 435 frame_info.ideal_render_count = new_cadence; |
| 436 } |
| 437 |
| 438 // Thus far there appears to be no need for special 3:2 considerations, the |
| 439 // smoothness scores seem to naturally fit that pattern based on maximizing |
| 440 // frame coverage. |
| 441 |
| 442 if (ideal_cadence_ != new_cadence) { |
| 443 DVLOG(1) << "Cadence switch from " << ideal_cadence_ << " to " |
| 444 << new_cadence << "; perfect_cadence: " << perfect_cadence; |
| 445 } |
| 446 if (fractional_cadence_ != new_fractional_cadence) { |
| 447 DVLOG(1) << "Fractional cadence switch from " << fractional_cadence_ |
| 448 << " to " << new_fractional_cadence |
| 449 << "; inverse_perfect_cadence: " << inverse_perfect_cadence; |
| 450 } |
| 451 |
| 452 ideal_cadence_ = new_cadence; |
| 453 fractional_cadence_ = new_fractional_cadence; |
| 454 return true; |
| 455 } |
| 456 |
| 457 void VideoRendererAlgorithm::UpdateFractionalCadenceForFrames( |
| 458 int fractional_cadence) { |
| 459 // Cadence is 1 for the first of every |fractional_cadence| frames and zero |
| 460 // elsewhere. |
| 461 for (size_t i = last_frame_index_; i < frame_queue_.size(); ++i) { |
| 462 frame_queue_[i].ideal_render_count = |
| 463 static_cast<int>((i - last_frame_index_) % fractional_cadence == 0); |
| 464 } |
| 465 } |
| 466 |
| 467 int VideoRendererAlgorithm::FindBestFrameByCadence() { |
| 468 DCHECK(!frame_queue_.empty()); |
| 469 if (!ideal_cadence_) |
| 470 return -1; |
| 471 |
| 472 const ReadyFrame& current_frame = frame_queue_[last_frame_index_]; |
| 473 |
| 474 // If the current frame is below cadence, we should prefer it. |
| 475 if (current_frame.render_count < current_frame.ideal_render_count) |
| 476 return last_frame_index_; |
| 477 |
| 478 // If the current frame is on cadence, find the next displayable frame. |
| 479 if (current_frame.render_count == current_frame.ideal_render_count) { |
| 480 for (size_t i = last_frame_index_ + 1; i < frame_queue_.size(); ++i) { |
| 481 if (frame_queue_[i].ideal_render_count > 0) |
| 482 return i; |
| 483 } |
| 484 } |
| 485 |
| 486 // The frame is overdisplayed or we don't have enough frames to find a better |
| 487 // once by cadence, so return nothing. |
| 488 return -1; |
| 489 } |
| 490 |
| 491 int VideoRendererAlgorithm::FindBestFrameByCoverage( |
| 492 base::TimeTicks deadline_min, |
| 493 base::TimeTicks deadline_max, |
| 494 int* second_best) { |
| 495 DCHECK(!frame_queue_.empty()); |
| 496 |
| 497 // Find the frame which covers the most of the interval [deadline_min, |
| 498 // deadline_max]. Frames outside of the interval are considered to have 0% |
| 499 // coverage, while those which completely overlap the interval have 100%. |
| 500 double best_coverage = 0.0; |
| 501 int best_frame_by_coverage = -1; |
| 502 std::vector<double> coverage(frame_queue_.size(), 0.0); |
| 503 for (size_t i = last_frame_index_; i < frame_queue_.size(); ++i) { |
| 504 // Frames which start after the deadline interval have zero coverage. |
| 505 if (frame_queue_[i].wall_clock_time > deadline_max) |
| 506 continue; |
| 507 |
| 508 // Clamp frame times to a maximum of |deadline_max|. |
| 509 const base::TimeTicks next_frame_time = std::min( |
| 510 deadline_max, i + 1 < frame_queue_.size() |
| 511 ? frame_queue_[i + 1].wall_clock_time |
| 512 : frame_queue_[i].wall_clock_time + frame_duration_); |
| 513 |
| 514 // Frames entirely before the deadline interval have zero coverage. |
| 515 if (next_frame_time < deadline_min) |
| 516 continue; |
| 517 |
| 518 // If we're here, the current frame overlaps the deadline in some way; so |
| 519 // compute the duration of the interval which is covered. |
| 520 const base::TimeDelta duration = |
| 521 next_frame_time - |
| 522 std::max(deadline_min, frame_queue_[i].wall_clock_time); |
| 523 |
| 524 coverage[i] = duration.InSecondsF() / render_interval_.InSecondsF(); |
| 525 if (coverage[i] > best_coverage) { |
| 526 best_frame_by_coverage = i; |
| 527 best_coverage = coverage[i]; |
| 528 } |
| 529 } |
| 530 |
| 531 // Find the second best frame by coverage; done by zeroing the coverage for |
| 532 // the previous best and recomputing the maximum. |
| 533 *second_best = -1; |
| 534 if (best_frame_by_coverage >= 0) { |
| 535 coverage[best_frame_by_coverage] = 0.0; |
| 536 auto it = std::max_element(coverage.begin(), coverage.end()); |
| 537 if (*it > 0) |
| 538 *second_best = it - coverage.begin(); |
| 539 } |
| 540 |
| 541 // If two frames have coverage within half a millisecond, prefer the earliest |
| 542 // frame as having the best coverage. Value chosen via experimentation to |
| 543 // ensure proper coverage calculation for 24fps in 60Hz where +/- 100us of |
| 544 // jitter is present within the |render_interval_|. At 60Hz this works out to |
| 545 // an allowed jitter of 3%. |
| 546 const double kAllowableJitter = 500.0 / render_interval_.InMicroseconds(); |
| 547 if (*second_best >= 0 && best_frame_by_coverage > *second_best && |
| 548 std::abs(best_coverage - coverage[*second_best]) <= kAllowableJitter) { |
| 549 std::swap(best_frame_by_coverage, *second_best); |
| 550 } |
| 551 |
| 552 // TODO(dalecurtis): We may want to make a better decision about what to do |
| 553 // when multiple frames have equivalent coverage over an interval. Jitter in |
| 554 // the render interval may result in irregular frame selection which may be |
| 555 // visible to a viewer. |
| 556 // |
| 557 // 23.974 fps and 24 fps in 60Hz are the most common susceptible rates, so |
| 558 // extensive tests have been added to ensure these cases work properly. |
| 559 |
| 560 return best_frame_by_coverage; |
| 561 } |
| 562 |
| 563 int VideoRendererAlgorithm::FindBestFrameByDrift(base::TimeTicks deadline_min) { |
| 564 DCHECK(!frame_queue_.empty()); |
| 565 |
| 566 int best_frame_by_drift = -1; |
| 567 base::TimeDelta min_drift = base::TimeDelta::Max(); |
| 568 |
| 569 for (size_t i = last_frame_index_; i < frame_queue_.size(); ++i) { |
| 570 const base::TimeDelta drift = CalculateDriftForFrame(deadline_min, i); |
| 571 if (drift < min_drift) { |
| 572 min_drift = drift; |
| 573 best_frame_by_drift = i; |
| 574 } |
| 575 } |
| 576 |
| 577 return best_frame_by_drift; |
| 578 } |
| 579 |
| 580 base::TimeDelta VideoRendererAlgorithm::CalculateDriftForFrame( |
| 581 base::TimeTicks deadline_min, |
| 582 int frame_index) { |
| 583 const ReadyFrame& frame = frame_queue_[frame_index]; |
| 584 |
| 585 // If the frame lies before the deadline, compute the delta against the end |
| 586 // of the frame's correct display duration. |
| 587 if (frame.wall_clock_time < deadline_min && |
| 588 frame.wall_clock_time + frame_duration_ < deadline_min) { |
| 589 return deadline_min - (frame.wall_clock_time + frame_duration_); |
| 590 } |
| 591 |
| 592 // If the frame lies after the deadline, compute the delta against the start |
| 593 // of the frame's correct display time. |
| 594 if (frame.wall_clock_time > deadline_min) |
| 595 return frame.wall_clock_time - deadline_min; |
| 596 |
| 597 // Drift is zero for frames which overlap the deadline interval. |
| 598 DCHECK_GE(deadline_min, frame.wall_clock_time); |
| 599 DCHECK_GE(frame.wall_clock_time + frame_duration_, deadline_min); |
| 600 return base::TimeDelta(); |
| 601 } |
| 602 |
| 603 } // namespace media |
OLD | NEW |