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