1 // Copyright 2013 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.
5 #include "media/renderers/video_renderer_impl.h"
8 #include "base/callback.h"
9 #include "base/callback_helpers.h"
10 #include "base/command_line.h"
11 #include "base/location.h"
12 #include "base/metrics/field_trial.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/strings/string_util.h"
15 #include "base/time/default_tick_clock.h"
16 #include "base/trace_event/trace_event.h"
17 #include "media/base/bind_to_current_loop.h"
18 #include "media/base/buffers.h"
19 #include "media/base/limits.h"
20 #include "media/base/media_switches.h"
21 #include "media/base/pipeline.h"
22 #include "media/base/video_frame.h"
23 #include "media/renderers/gpu_video_accelerator_factories.h"
24 #include "media/video/gpu_memory_buffer_video_frame_pool.h"
28 // TODO(dalecurtis): This experiment is temporary and should be removed once we
29 // have enough data to support the primacy of the new video rendering path; see
30 // http://crbug.com/485699 for details.
31 static bool ShouldUseVideoRenderingPath() {
32 // Note: It's important to query the field trial state first, to ensure that
33 // UMA reports the correct group.
34 const std::string group_name
=
35 base::FieldTrialList::FindFullName("NewVideoRendererTrial");
36 const bool disabled_via_cli
=
37 base::CommandLine::ForCurrentProcess()->HasSwitch(
38 switches::kDisableNewVideoRenderer
);
39 return !disabled_via_cli
&& !StartsWithASCII(group_name
, "Disabled", true);
42 VideoRendererImpl::VideoRendererImpl(
43 const scoped_refptr
<base::SingleThreadTaskRunner
>& task_runner
,
44 VideoRendererSink
* sink
,
45 ScopedVector
<VideoDecoder
> decoders
,
47 const scoped_refptr
<GpuVideoAcceleratorFactories
>& gpu_factories
,
48 const scoped_refptr
<MediaLog
>& media_log
)
49 : task_runner_(task_runner
),
50 use_new_video_renderering_path_(ShouldUseVideoRenderingPath()),
54 new VideoFrameStream(task_runner
, decoders
.Pass(), media_log
)),
55 gpu_memory_buffer_pool_(
56 new GpuMemoryBufferVideoFramePool(task_runner
, gpu_factories
)),
58 received_end_of_stream_(false),
59 rendered_end_of_stream_(false),
60 frame_available_(&lock_
),
61 state_(kUninitialized
),
64 drop_frames_(drop_frames
),
65 buffering_state_(BUFFERING_HAVE_NOTHING
),
68 is_shutting_down_(false),
69 tick_clock_(new base::DefaultTickClock()),
70 was_background_rendering_(false),
71 time_progressing_(false),
72 render_first_frame_and_stop_(false),
73 posted_maybe_stop_after_first_paint_(false),
77 VideoRendererImpl::~VideoRendererImpl() {
78 DCHECK(task_runner_
->BelongsToCurrentThread());
80 if (!use_new_video_renderering_path_
) {
81 base::AutoLock
auto_lock(lock_
);
82 is_shutting_down_
= true;
83 frame_available_
.Signal();
86 if (!thread_
.is_null())
87 base::PlatformThread::Join(thread_
);
89 if (!init_cb_
.is_null())
90 base::ResetAndReturn(&init_cb_
).Run(PIPELINE_ERROR_ABORT
);
92 if (!flush_cb_
.is_null())
93 base::ResetAndReturn(&flush_cb_
).Run();
95 if (use_new_video_renderering_path_
&& sink_started_
)
99 void VideoRendererImpl::Flush(const base::Closure
& callback
) {
100 DVLOG(1) << __FUNCTION__
;
101 DCHECK(task_runner_
->BelongsToCurrentThread());
103 if (use_new_video_renderering_path_
&& sink_started_
)
106 base::AutoLock
auto_lock(lock_
);
107 DCHECK_EQ(state_
, kPlaying
);
108 flush_cb_
= callback
;
111 // This is necessary if the |video_frame_stream_| has already seen an end of
112 // stream and needs to drain it before flushing it.
113 ready_frames_
.clear();
114 if (buffering_state_
!= BUFFERING_HAVE_NOTHING
) {
115 buffering_state_
= BUFFERING_HAVE_NOTHING
;
116 buffering_state_cb_
.Run(BUFFERING_HAVE_NOTHING
);
118 received_end_of_stream_
= false;
119 rendered_end_of_stream_
= false;
121 if (use_new_video_renderering_path_
)
124 video_frame_stream_
->Reset(
125 base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone
,
126 weak_factory_
.GetWeakPtr()));
129 void VideoRendererImpl::StartPlayingFrom(base::TimeDelta timestamp
) {
130 DVLOG(1) << __FUNCTION__
<< "(" << timestamp
.InMicroseconds() << ")";
131 DCHECK(task_runner_
->BelongsToCurrentThread());
132 base::AutoLock
auto_lock(lock_
);
133 DCHECK_EQ(state_
, kFlushed
);
134 DCHECK(!pending_read_
);
135 DCHECK(ready_frames_
.empty());
136 DCHECK_EQ(buffering_state_
, BUFFERING_HAVE_NOTHING
);
139 start_timestamp_
= timestamp
;
140 AttemptRead_Locked();
143 void VideoRendererImpl::Initialize(
144 DemuxerStream
* stream
,
145 const PipelineStatusCB
& init_cb
,
146 const SetDecryptorReadyCB
& set_decryptor_ready_cb
,
147 const StatisticsCB
& statistics_cb
,
148 const BufferingStateCB
& buffering_state_cb
,
149 const base::Closure
& ended_cb
,
150 const PipelineStatusCB
& error_cb
,
151 const TimeSource::WallClockTimeCB
& wall_clock_time_cb
,
152 const base::Closure
& waiting_for_decryption_key_cb
) {
153 DCHECK(task_runner_
->BelongsToCurrentThread());
154 base::AutoLock
auto_lock(lock_
);
156 DCHECK_EQ(stream
->type(), DemuxerStream::VIDEO
);
157 DCHECK(!init_cb
.is_null());
158 DCHECK(!statistics_cb
.is_null());
159 DCHECK(!buffering_state_cb
.is_null());
160 DCHECK(!ended_cb
.is_null());
161 DCHECK(!wall_clock_time_cb
.is_null());
162 DCHECK_EQ(kUninitialized
, state_
);
163 DCHECK(!render_first_frame_and_stop_
);
164 DCHECK(!posted_maybe_stop_after_first_paint_
);
165 DCHECK(!was_background_rendering_
);
166 DCHECK(!time_progressing_
);
168 low_delay_
= (stream
->liveness() == DemuxerStream::LIVENESS_LIVE
);
170 // Always post |init_cb_| because |this| could be destroyed if initialization
172 init_cb_
= BindToCurrentLoop(init_cb
);
174 statistics_cb_
= statistics_cb
;
175 buffering_state_cb_
= buffering_state_cb
;
176 paint_cb_
= base::Bind(&VideoRendererSink::PaintFrameUsingOldRenderingPath
,
177 base::Unretained(sink_
));
178 ended_cb_
= ended_cb
;
179 error_cb_
= error_cb
;
180 wall_clock_time_cb_
= wall_clock_time_cb
;
181 state_
= kInitializing
;
183 video_frame_stream_
->Initialize(
184 stream
, base::Bind(&VideoRendererImpl::OnVideoFrameStreamInitialized
,
185 weak_factory_
.GetWeakPtr()),
186 set_decryptor_ready_cb
, statistics_cb
, waiting_for_decryption_key_cb
);
189 scoped_refptr
<VideoFrame
> VideoRendererImpl::Render(
190 base::TimeTicks deadline_min
,
191 base::TimeTicks deadline_max
,
192 bool background_rendering
) {
193 base::AutoLock
auto_lock(lock_
);
194 DCHECK(use_new_video_renderering_path_
);
195 DCHECK_EQ(state_
, kPlaying
);
197 size_t frames_dropped
= 0;
198 scoped_refptr
<VideoFrame
> result
=
199 algorithm_
->Render(deadline_min
, deadline_max
, &frames_dropped
);
201 // Due to how the |algorithm_| holds frames, this should never be null if
202 // we've had a proper startup sequence.
205 // Declare HAVE_NOTHING if we reach a state where we can't progress playback
206 // any further. We don't want to do this if we've already done so, reached
207 // end of stream, or have frames available. We also don't want to do this in
208 // background rendering mode unless this isn't the first background render
209 // tick and we haven't seen any decoded frames since the last one.
210 const size_t effective_frames
= MaybeFireEndedCallback();
211 if (buffering_state_
== BUFFERING_HAVE_ENOUGH
&& !received_end_of_stream_
&&
212 !effective_frames
&& (!background_rendering
||
213 (!frames_decoded_
&& was_background_rendering_
))) {
214 // Do not set |buffering_state_| here as the lock in FrameReady() may be
215 // held already and it fire the state changes in the wrong order.
216 task_runner_
->PostTask(
217 FROM_HERE
, base::Bind(&VideoRendererImpl::TransitionToHaveNothing
,
218 weak_factory_
.GetWeakPtr()));
221 // We don't count dropped frames in the background to avoid skewing the count
222 // and impacting JavaScript visible metrics used by web developers.
224 // Just after resuming from background rendering, we also don't count the
225 // dropped frames since they are likely just dropped due to being too old.
226 if (!background_rendering
&& !was_background_rendering_
)
227 frames_dropped_
+= frames_dropped
;
228 UpdateStatsAndWait_Locked(base::TimeDelta());
229 was_background_rendering_
= background_rendering
;
231 // After painting the first frame, if playback hasn't started, we post a
232 // delayed task to request that the sink be stopped. The task is delayed to
233 // give videos with autoplay time to start.
235 // OnTimeStateChanged() will clear this flag if time starts before we get here
236 // and MaybeStopSinkAfterFirstPaint() will ignore this request if time starts
237 // before the call executes.
238 if (render_first_frame_and_stop_
&& !posted_maybe_stop_after_first_paint_
) {
239 posted_maybe_stop_after_first_paint_
= true;
240 task_runner_
->PostDelayedTask(
241 FROM_HERE
, base::Bind(&VideoRendererImpl::MaybeStopSinkAfterFirstPaint
,
242 weak_factory_
.GetWeakPtr()),
243 base::TimeDelta::FromMilliseconds(250));
246 // Always post this task, it will acquire new frames if necessary and since it
247 // happens on another thread, even if we don't have room in the queue now, by
248 // the time it runs (may be delayed up to 50ms for complex decodes!) we might.
249 task_runner_
->PostTask(FROM_HERE
, base::Bind(&VideoRendererImpl::AttemptRead
,
250 weak_factory_
.GetWeakPtr()));
255 void VideoRendererImpl::OnFrameDropped() {
256 base::AutoLock
auto_lock(lock_
);
257 DCHECK(use_new_video_renderering_path_
);
258 algorithm_
->OnLastFrameDropped();
261 void VideoRendererImpl::CreateVideoThread() {
262 // This may fail and cause a crash if there are too many threads created in
263 // the current process. See http://crbug.com/443291
264 CHECK(base::PlatformThread::Create(0, this, &thread_
));
267 // Bump up our priority so our sleeping is more accurate.
268 // TODO(scherkus): find out if this is necessary, but it seems to help.
269 ::SetThreadPriority(thread_
.platform_handle(), THREAD_PRIORITY_ABOVE_NORMAL
);
270 #endif // defined(OS_WIN)
273 void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success
) {
274 DCHECK(task_runner_
->BelongsToCurrentThread());
275 base::AutoLock
auto_lock(lock_
);
276 DCHECK_EQ(state_
, kInitializing
);
279 state_
= kUninitialized
;
280 base::ResetAndReturn(&init_cb_
).Run(DECODER_ERROR_NOT_SUPPORTED
);
284 // We're all good! Consider ourselves flushed. (ThreadMain() should never
285 // see us in the kUninitialized state).
286 // Since we had an initial Preroll(), we consider ourself flushed, because we
287 // have not populated any buffers yet.
290 if (use_new_video_renderering_path_
) {
291 algorithm_
.reset(new VideoRendererAlgorithm(wall_clock_time_cb_
));
293 algorithm_
->disable_frame_dropping();
298 base::ResetAndReturn(&init_cb_
).Run(PIPELINE_OK
);
301 // PlatformThread::Delegate implementation.
302 void VideoRendererImpl::ThreadMain() {
303 DCHECK(!use_new_video_renderering_path_
);
304 base::PlatformThread::SetName("CrVideoRenderer");
306 // The number of milliseconds to idle when we do not have anything to do.
307 // Nothing special about the value, other than we're being more OS-friendly
308 // than sleeping for 1 millisecond.
310 // TODO(scherkus): switch to pure event-driven frame timing instead of this
311 // kIdleTimeDelta business http://crbug.com/106874
312 const base::TimeDelta kIdleTimeDelta
=
313 base::TimeDelta::FromMilliseconds(10);
316 base::AutoLock
auto_lock(lock_
);
318 // Thread exit condition.
319 if (is_shutting_down_
)
322 // Remain idle as long as we're not playing.
323 if (state_
!= kPlaying
|| buffering_state_
!= BUFFERING_HAVE_ENOUGH
) {
324 UpdateStatsAndWait_Locked(kIdleTimeDelta
);
328 base::TimeTicks now
= tick_clock_
->NowTicks();
330 // Remain idle until we have the next frame ready for rendering.
331 if (ready_frames_
.empty()) {
332 base::TimeDelta wait_time
= kIdleTimeDelta
;
333 if (received_end_of_stream_
) {
334 if (!rendered_end_of_stream_
) {
335 rendered_end_of_stream_
= true;
336 task_runner_
->PostTask(FROM_HERE
, ended_cb_
);
338 } else if (now
>= latest_possible_paint_time_
) {
339 // Declare HAVE_NOTHING if we don't have another frame by the time we
340 // are ready to paint the next one.
341 buffering_state_
= BUFFERING_HAVE_NOTHING
;
342 task_runner_
->PostTask(
343 FROM_HERE
, base::Bind(buffering_state_cb_
, BUFFERING_HAVE_NOTHING
));
345 wait_time
= std::min(kIdleTimeDelta
, latest_possible_paint_time_
- now
);
348 UpdateStatsAndWait_Locked(wait_time
);
352 base::TimeTicks target_paint_time
=
353 ConvertMediaTimestamp(ready_frames_
.front()->timestamp());
355 // If media time has stopped, don't attempt to paint any more frames.
356 if (target_paint_time
.is_null()) {
357 UpdateStatsAndWait_Locked(kIdleTimeDelta
);
361 // Deadline is defined as the duration between this frame and the next
362 // frame, using the delta between this frame and the previous frame as the
363 // assumption for frame duration.
365 // TODO(scherkus): This can be vastly improved. Use a histogram to measure
366 // the accuracy of our frame timing code. http://crbug.com/149829
367 if (last_media_time_
.is_null()) {
368 latest_possible_paint_time_
= now
;
370 base::TimeDelta duration
= target_paint_time
- last_media_time_
;
371 latest_possible_paint_time_
= target_paint_time
+ duration
;
374 // Remain idle until we've reached our target paint window.
375 if (now
< target_paint_time
) {
376 UpdateStatsAndWait_Locked(
377 std::min(target_paint_time
- now
, kIdleTimeDelta
));
381 if (ready_frames_
.size() > 1 && now
> latest_possible_paint_time_
&&
383 DropNextReadyFrame_Locked();
387 // Congratulations! You've made it past the video frame timing gauntlet.
389 // At this point enough time has passed that the next frame that ready for
391 PaintNextReadyFrame_Locked();
395 void VideoRendererImpl::SetTickClockForTesting(
396 scoped_ptr
<base::TickClock
> tick_clock
) {
397 tick_clock_
.swap(tick_clock
);
400 void VideoRendererImpl::OnTimeStateChanged(bool time_progressing
) {
401 DCHECK(task_runner_
->BelongsToCurrentThread());
402 time_progressing_
= time_progressing
;
404 // WARNING: Do not attempt to use |lock_| here as this may be a reentrant call
405 // in response to callbacks firing above.
407 if (!use_new_video_renderering_path_
|| sink_started_
== time_progressing_
)
410 if (time_progressing_
) {
411 // If only an EOS frame came in after a seek, the renderer may not have
412 // received the ended event yet though we've posted it.
413 if (!rendered_end_of_stream_
)
420 void VideoRendererImpl::PaintNextReadyFrame_Locked() {
421 DCHECK(!use_new_video_renderering_path_
);
422 lock_
.AssertAcquired();
424 scoped_refptr
<VideoFrame
> next_frame
= ready_frames_
.front();
425 ready_frames_
.pop_front();
427 last_media_time_
= ConvertMediaTimestamp(next_frame
->timestamp());
429 paint_cb_
.Run(next_frame
);
431 task_runner_
->PostTask(
433 base::Bind(&VideoRendererImpl::AttemptRead
, weak_factory_
.GetWeakPtr()));
436 void VideoRendererImpl::DropNextReadyFrame_Locked() {
437 DCHECK(!use_new_video_renderering_path_
);
438 TRACE_EVENT0("media", "VideoRendererImpl:frameDropped");
440 lock_
.AssertAcquired();
442 last_media_time_
= ConvertMediaTimestamp(ready_frames_
.front()->timestamp());
444 ready_frames_
.pop_front();
447 task_runner_
->PostTask(
449 base::Bind(&VideoRendererImpl::AttemptRead
, weak_factory_
.GetWeakPtr()));
452 void VideoRendererImpl::FrameReady(VideoFrameStream::Status status
,
453 const scoped_refptr
<VideoFrame
>& frame
) {
454 DCHECK(task_runner_
->BelongsToCurrentThread());
455 bool start_sink
= false;
457 base::AutoLock
auto_lock(lock_
);
458 DCHECK_NE(state_
, kUninitialized
);
459 DCHECK_NE(state_
, kFlushed
);
461 CHECK(pending_read_
);
462 pending_read_
= false;
464 if (status
== VideoFrameStream::DECODE_ERROR
||
465 status
== VideoFrameStream::DECRYPT_ERROR
) {
466 DCHECK(!frame
.get());
467 PipelineStatus error
= PIPELINE_ERROR_DECODE
;
468 if (status
== VideoFrameStream::DECRYPT_ERROR
)
469 error
= PIPELINE_ERROR_DECRYPT
;
470 task_runner_
->PostTask(FROM_HERE
, base::Bind(error_cb_
, error
));
474 // Already-queued VideoFrameStream ReadCB's can fire after various state
475 // transitions have happened; in that case just drop those frames
477 if (state_
== kFlushing
)
480 DCHECK_EQ(state_
, kPlaying
);
482 // Can happen when demuxers are preparing for a new Seek().
484 DCHECK_EQ(status
, VideoFrameStream::DEMUXER_READ_ABORTED
);
488 if (frame
->end_of_stream()) {
489 DCHECK(!received_end_of_stream_
);
490 received_end_of_stream_
= true;
492 // See if we can fire EOS immediately instead of waiting for Render().
493 if (use_new_video_renderering_path_
)
494 MaybeFireEndedCallback();
496 // Maintain the latest frame decoded so the correct frame is displayed
497 // after prerolling has completed.
498 if (frame
->timestamp() <= start_timestamp_
) {
499 if (use_new_video_renderering_path_
)
501 ready_frames_
.clear();
503 AddReadyFrame_Locked(frame
);
506 // Signal buffering state if we've met our conditions for having enough
508 if (buffering_state_
!= BUFFERING_HAVE_ENOUGH
&& HaveEnoughData_Locked()) {
509 TransitionToHaveEnough_Locked();
510 if (use_new_video_renderering_path_
&& !sink_started_
&&
511 !rendered_end_of_stream_
) {
513 render_first_frame_and_stop_
= true;
514 posted_maybe_stop_after_first_paint_
= false;
518 // Background rendering updates may not be ticking fast enough by itself to
519 // remove expired frames, so give it a boost here by ensuring we don't exit
520 // the decoding cycle too early.
521 if (was_background_rendering_
) {
522 DCHECK(use_new_video_renderering_path_
);
523 algorithm_
->RemoveExpiredFrames(tick_clock_
->NowTicks());
526 // Always request more decoded video if we have capacity. This serves two
528 // 1) Prerolling while paused
529 // 2) Keeps decoding going if video rendering thread starts falling behind
530 AttemptRead_Locked();
533 // If time is progressing, the sink has already been started; this may be true
534 // if we have previously underflowed, yet weren't stopped because of audio.
535 if (use_new_video_renderering_path_
&& start_sink
) {
536 DCHECK(!sink_started_
);
541 bool VideoRendererImpl::HaveEnoughData_Locked() {
542 DCHECK_EQ(state_
, kPlaying
);
544 if (received_end_of_stream_
|| !video_frame_stream_
->CanReadWithoutStalling())
547 if (HaveReachedBufferingCap())
550 if (use_new_video_renderering_path_
&& was_background_rendering_
&&
558 return ready_frames_
.size() > 0 ||
559 (use_new_video_renderering_path_
&& algorithm_
->frames_queued() > 0);
562 void VideoRendererImpl::TransitionToHaveEnough_Locked() {
563 DCHECK(task_runner_
->BelongsToCurrentThread());
564 DCHECK_EQ(buffering_state_
, BUFFERING_HAVE_NOTHING
);
566 if (!ready_frames_
.empty()) {
567 DCHECK(!use_new_video_renderering_path_
);
568 // Because the clock might remain paused in for an undetermined amount
569 // of time (e.g., seeking while paused), paint the first frame.
570 PaintNextReadyFrame_Locked();
573 buffering_state_
= BUFFERING_HAVE_ENOUGH
;
574 buffering_state_cb_
.Run(BUFFERING_HAVE_ENOUGH
);
577 void VideoRendererImpl::TransitionToHaveNothing() {
578 DCHECK(task_runner_
->BelongsToCurrentThread());
580 base::AutoLock
auto_lock(lock_
);
581 if (buffering_state_
!= BUFFERING_HAVE_ENOUGH
|| HaveEnoughData_Locked())
584 buffering_state_
= BUFFERING_HAVE_NOTHING
;
585 buffering_state_cb_
.Run(BUFFERING_HAVE_NOTHING
);
588 void VideoRendererImpl::AddReadyFrame_Locked(
589 const scoped_refptr
<VideoFrame
>& frame
) {
590 DCHECK(task_runner_
->BelongsToCurrentThread());
591 lock_
.AssertAcquired();
592 DCHECK(!frame
->end_of_stream());
596 if (use_new_video_renderering_path_
) {
597 algorithm_
->EnqueueFrame(frame
);
601 ready_frames_
.push_back(frame
);
602 DCHECK_LE(ready_frames_
.size(),
603 static_cast<size_t>(limits::kMaxVideoFrames
));
605 // Avoid needlessly waking up |thread_| unless playing.
606 if (state_
== kPlaying
)
607 frame_available_
.Signal();
610 void VideoRendererImpl::AttemptRead() {
611 base::AutoLock
auto_lock(lock_
);
612 AttemptRead_Locked();
615 void VideoRendererImpl::AttemptRead_Locked() {
616 DCHECK(task_runner_
->BelongsToCurrentThread());
617 lock_
.AssertAcquired();
619 if (pending_read_
|| received_end_of_stream_
)
622 if (HaveReachedBufferingCap())
627 pending_read_
= true;
628 video_frame_stream_
->Read(base::Bind(&VideoRendererImpl::FrameReady
,
629 weak_factory_
.GetWeakPtr()));
640 void VideoRendererImpl::OnVideoFrameStreamResetDone() {
641 base::AutoLock
auto_lock(lock_
);
642 DCHECK_EQ(kFlushing
, state_
);
643 DCHECK(!pending_read_
);
644 DCHECK(ready_frames_
.empty());
645 DCHECK(!received_end_of_stream_
);
646 DCHECK(!rendered_end_of_stream_
);
647 DCHECK_EQ(buffering_state_
, BUFFERING_HAVE_NOTHING
);
650 latest_possible_paint_time_
= last_media_time_
= base::TimeTicks();
651 base::ResetAndReturn(&flush_cb_
).Run();
654 void VideoRendererImpl::UpdateStatsAndWait_Locked(
655 base::TimeDelta wait_duration
) {
656 lock_
.AssertAcquired();
657 DCHECK_GE(frames_decoded_
, 0);
658 DCHECK_GE(frames_dropped_
, 0);
660 if (frames_decoded_
|| frames_dropped_
) {
661 PipelineStatistics statistics
;
662 statistics
.video_frames_decoded
= frames_decoded_
;
663 statistics
.video_frames_dropped
= frames_dropped_
;
664 task_runner_
->PostTask(FROM_HERE
, base::Bind(statistics_cb_
, statistics
));
670 if (wait_duration
> base::TimeDelta())
671 frame_available_
.TimedWait(wait_duration
);
674 void VideoRendererImpl::MaybeStopSinkAfterFirstPaint() {
675 DCHECK(task_runner_
->BelongsToCurrentThread());
676 DCHECK(use_new_video_renderering_path_
);
679 base::AutoLock
auto_lock(lock_
);
680 render_first_frame_and_stop_
= false;
683 if (!time_progressing_
&& sink_started_
)
687 bool VideoRendererImpl::HaveReachedBufferingCap() {
688 DCHECK(task_runner_
->BelongsToCurrentThread());
689 const size_t kMaxVideoFrames
= limits::kMaxVideoFrames
;
691 if (use_new_video_renderering_path_
) {
692 // When the display rate is less than the frame rate, the effective frames
693 // queued may be much smaller than the actual number of frames queued. Here
694 // we ensure that frames_queued() doesn't get excessive.
695 return algorithm_
->EffectiveFramesQueued() >= kMaxVideoFrames
||
696 algorithm_
->frames_queued() >= 3 * kMaxVideoFrames
;
699 return ready_frames_
.size() >= kMaxVideoFrames
;
702 void VideoRendererImpl::StartSink() {
703 DCHECK(task_runner_
->BelongsToCurrentThread());
704 DCHECK_GT(algorithm_
->frames_queued(), 0u);
706 sink_started_
= true;
707 was_background_rendering_
= false;
710 void VideoRendererImpl::StopSink() {
711 DCHECK(task_runner_
->BelongsToCurrentThread());
713 sink_started_
= false;
714 was_background_rendering_
= false;
717 size_t VideoRendererImpl::MaybeFireEndedCallback() {
718 // If there's only one frame in the video or Render() was never called, the
719 // algorithm will have one frame linger indefinitely. So in cases where the
720 // frame duration is unknown and we've received EOS, fire it once we get down
721 // to a single frame.
722 const size_t effective_frames
= algorithm_
->EffectiveFramesQueued();
724 // Don't fire ended if we haven't received EOS or have already done so.
725 if (!received_end_of_stream_
|| rendered_end_of_stream_
)
726 return effective_frames
;
728 // Don't fire ended if time isn't moving and we have frames.
729 if (!time_progressing_
&& algorithm_
->frames_queued())
730 return effective_frames
;
732 // Fire ended if we have no more effective frames or only ever had one frame.
733 if (!effective_frames
||
734 (algorithm_
->frames_queued() == 1u &&
735 algorithm_
->average_frame_duration() == base::TimeDelta())) {
736 rendered_end_of_stream_
= true;
737 task_runner_
->PostTask(FROM_HERE
, ended_cb_
);
740 return effective_frames
;
743 base::TimeTicks
VideoRendererImpl::ConvertMediaTimestamp(
744 base::TimeDelta media_time
) {
745 std::vector
<base::TimeDelta
> media_times(1, media_time
);
746 std::vector
<base::TimeTicks
> wall_clock_times
;
747 if (!wall_clock_time_cb_
.Run(media_times
, &wall_clock_times
))
748 return base::TimeTicks();
749 return wall_clock_times
[0];