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