Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(226)

Side by Side Diff: media/filters/video_renderer_algorithm.cc

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

Powered by Google App Engine
This is Rietveld 408576698