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
&&
40 !base::StartsWith(group_name
, "Disabled",
41 base::CompareCase::SENSITIVE
);
44 VideoRendererImpl::VideoRendererImpl(
45 const scoped_refptr
<base::SingleThreadTaskRunner
>& task_runner
,
46 VideoRendererSink
* sink
,
47 ScopedVector
<VideoDecoder
> decoders
,
49 const scoped_refptr
<GpuVideoAcceleratorFactories
>& gpu_factories
,
50 const scoped_refptr
<MediaLog
>& media_log
)
51 : task_runner_(task_runner
),
52 use_new_video_renderering_path_(ShouldUseVideoRenderingPath()),
56 new VideoFrameStream(task_runner
, decoders
.Pass(), media_log
)),
57 gpu_memory_buffer_pool_(
58 new GpuMemoryBufferVideoFramePool(task_runner
, gpu_factories
)),
60 received_end_of_stream_(false),
61 rendered_end_of_stream_(false),
62 frame_available_(&lock_
),
63 state_(kUninitialized
),
66 drop_frames_(drop_frames
),
67 buffering_state_(BUFFERING_HAVE_NOTHING
),
70 is_shutting_down_(false),
71 tick_clock_(new base::DefaultTickClock()),
72 was_background_rendering_(false),
73 time_progressing_(false),
74 render_first_frame_and_stop_(false),
75 posted_maybe_stop_after_first_paint_(false),
79 VideoRendererImpl::~VideoRendererImpl() {
80 DCHECK(task_runner_
->BelongsToCurrentThread());
82 if (!use_new_video_renderering_path_
) {
83 base::AutoLock
auto_lock(lock_
);
84 is_shutting_down_
= true;
85 frame_available_
.Signal();
88 if (!thread_
.is_null())
89 base::PlatformThread::Join(thread_
);
91 if (!init_cb_
.is_null())
92 base::ResetAndReturn(&init_cb_
).Run(PIPELINE_ERROR_ABORT
);
94 if (!flush_cb_
.is_null())
95 base::ResetAndReturn(&flush_cb_
).Run();
97 if (use_new_video_renderering_path_
&& sink_started_
)
101 void VideoRendererImpl::Flush(const base::Closure
& callback
) {
102 DVLOG(1) << __FUNCTION__
;
103 DCHECK(task_runner_
->BelongsToCurrentThread());
105 if (use_new_video_renderering_path_
&& sink_started_
)
108 base::AutoLock
auto_lock(lock_
);
109 DCHECK_EQ(state_
, kPlaying
);
110 flush_cb_
= callback
;
113 // This is necessary if the |video_frame_stream_| has already seen an end of
114 // stream and needs to drain it before flushing it.
115 ready_frames_
.clear();
116 if (buffering_state_
!= BUFFERING_HAVE_NOTHING
) {
117 buffering_state_
= BUFFERING_HAVE_NOTHING
;
118 buffering_state_cb_
.Run(BUFFERING_HAVE_NOTHING
);
120 received_end_of_stream_
= false;
121 rendered_end_of_stream_
= false;
123 if (use_new_video_renderering_path_
)
126 video_frame_stream_
->Reset(
127 base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone
,
128 weak_factory_
.GetWeakPtr()));
131 void VideoRendererImpl::StartPlayingFrom(base::TimeDelta timestamp
) {
132 DVLOG(1) << __FUNCTION__
<< "(" << timestamp
.InMicroseconds() << ")";
133 DCHECK(task_runner_
->BelongsToCurrentThread());
134 base::AutoLock
auto_lock(lock_
);
135 DCHECK_EQ(state_
, kFlushed
);
136 DCHECK(!pending_read_
);
137 DCHECK(ready_frames_
.empty());
138 DCHECK_EQ(buffering_state_
, BUFFERING_HAVE_NOTHING
);
141 start_timestamp_
= timestamp
;
142 AttemptRead_Locked();
145 void VideoRendererImpl::Initialize(
146 DemuxerStream
* stream
,
147 const PipelineStatusCB
& init_cb
,
148 const SetDecryptorReadyCB
& set_decryptor_ready_cb
,
149 const StatisticsCB
& statistics_cb
,
150 const BufferingStateCB
& buffering_state_cb
,
151 const base::Closure
& ended_cb
,
152 const PipelineStatusCB
& error_cb
,
153 const TimeSource::WallClockTimeCB
& wall_clock_time_cb
,
154 const base::Closure
& waiting_for_decryption_key_cb
) {
155 DCHECK(task_runner_
->BelongsToCurrentThread());
156 base::AutoLock
auto_lock(lock_
);
158 DCHECK_EQ(stream
->type(), DemuxerStream::VIDEO
);
159 DCHECK(!init_cb
.is_null());
160 DCHECK(!statistics_cb
.is_null());
161 DCHECK(!buffering_state_cb
.is_null());
162 DCHECK(!ended_cb
.is_null());
163 DCHECK(!wall_clock_time_cb
.is_null());
164 DCHECK_EQ(kUninitialized
, state_
);
165 DCHECK(!render_first_frame_and_stop_
);
166 DCHECK(!posted_maybe_stop_after_first_paint_
);
167 DCHECK(!was_background_rendering_
);
168 DCHECK(!time_progressing_
);
170 low_delay_
= (stream
->liveness() == DemuxerStream::LIVENESS_LIVE
);
172 // Always post |init_cb_| because |this| could be destroyed if initialization
174 init_cb_
= BindToCurrentLoop(init_cb
);
176 // Always post |buffering_state_cb_| because it may otherwise invoke reentrant
177 // calls to OnTimeStateChanged() under lock, which can deadlock the compositor
178 // and media threads.
179 buffering_state_cb_
= BindToCurrentLoop(buffering_state_cb
);
181 statistics_cb_
= statistics_cb
;
182 paint_cb_
= base::Bind(&VideoRendererSink::PaintFrameUsingOldRenderingPath
,
183 base::Unretained(sink_
));
184 ended_cb_
= ended_cb
;
185 error_cb_
= error_cb
;
186 wall_clock_time_cb_
= wall_clock_time_cb
;
187 state_
= kInitializing
;
189 video_frame_stream_
->Initialize(
190 stream
, base::Bind(&VideoRendererImpl::OnVideoFrameStreamInitialized
,
191 weak_factory_
.GetWeakPtr()),
192 set_decryptor_ready_cb
, statistics_cb
, waiting_for_decryption_key_cb
);
195 scoped_refptr
<VideoFrame
> VideoRendererImpl::Render(
196 base::TimeTicks deadline_min
,
197 base::TimeTicks deadline_max
,
198 bool background_rendering
) {
199 base::AutoLock
auto_lock(lock_
);
200 DCHECK(use_new_video_renderering_path_
);
201 DCHECK_EQ(state_
, kPlaying
);
203 size_t frames_dropped
= 0;
204 scoped_refptr
<VideoFrame
> result
=
205 algorithm_
->Render(deadline_min
, deadline_max
, &frames_dropped
);
207 // Due to how the |algorithm_| holds frames, this should never be null if
208 // we've had a proper startup sequence.
211 // Declare HAVE_NOTHING if we reach a state where we can't progress playback
212 // any further. We don't want to do this if we've already done so, reached
213 // end of stream, or have frames available. We also don't want to do this in
214 // background rendering mode unless this isn't the first background render
215 // tick and we haven't seen any decoded frames since the last one.
217 // We use the inverse of |render_first_frame_and_stop_| as a proxy for the
218 // value of |time_progressing_| here since we can't access it from the
219 // compositor thread. If we're here (in Render()) the sink must have been
220 // started -- but if it was started only to render the first frame and stop,
221 // then |time_progressing_| is likely false. If we're still in Render() when
222 // |render_first_frame_and_stop_| is false, then |time_progressing_| is true.
223 // If |time_progressing_| is actually true when |render_first_frame_and_stop_|
224 // is also true, then the ended callback will be harmlessly delayed until
225 // MaybeStopSinkAfterFirstPaint() runs and the next Render() call comes in.
226 const size_t effective_frames
=
227 MaybeFireEndedCallback_Locked(!render_first_frame_and_stop_
);
228 if (buffering_state_
== BUFFERING_HAVE_ENOUGH
&& !received_end_of_stream_
&&
229 !effective_frames
&& (!background_rendering
||
230 (!frames_decoded_
&& was_background_rendering_
))) {
231 // Do not set |buffering_state_| here as the lock in FrameReady() may be
232 // held already and it fire the state changes in the wrong order.
233 task_runner_
->PostTask(
234 FROM_HERE
, base::Bind(&VideoRendererImpl::TransitionToHaveNothing
,
235 weak_factory_
.GetWeakPtr()));
238 // We don't count dropped frames in the background to avoid skewing the count
239 // and impacting JavaScript visible metrics used by web developers.
241 // Just after resuming from background rendering, we also don't count the
242 // dropped frames since they are likely just dropped due to being too old.
243 if (!background_rendering
&& !was_background_rendering_
)
244 frames_dropped_
+= frames_dropped
;
245 UpdateStatsAndWait_Locked(base::TimeDelta());
246 was_background_rendering_
= background_rendering
;
248 // After painting the first frame, if playback hasn't started, we post a
249 // delayed task to request that the sink be stopped. The task is delayed to
250 // give videos with autoplay time to start.
252 // OnTimeStateChanged() will clear this flag if time starts before we get here
253 // and MaybeStopSinkAfterFirstPaint() will ignore this request if time starts
254 // before the call executes.
255 if (render_first_frame_and_stop_
&& !posted_maybe_stop_after_first_paint_
) {
256 posted_maybe_stop_after_first_paint_
= true;
257 task_runner_
->PostDelayedTask(
258 FROM_HERE
, base::Bind(&VideoRendererImpl::MaybeStopSinkAfterFirstPaint
,
259 weak_factory_
.GetWeakPtr()),
260 base::TimeDelta::FromMilliseconds(250));
263 // Always post this task, it will acquire new frames if necessary and since it
264 // happens on another thread, even if we don't have room in the queue now, by
265 // the time it runs (may be delayed up to 50ms for complex decodes!) we might.
266 task_runner_
->PostTask(FROM_HERE
, base::Bind(&VideoRendererImpl::AttemptRead
,
267 weak_factory_
.GetWeakPtr()));
272 void VideoRendererImpl::OnFrameDropped() {
273 base::AutoLock
auto_lock(lock_
);
274 DCHECK(use_new_video_renderering_path_
);
275 algorithm_
->OnLastFrameDropped();
278 void VideoRendererImpl::CreateVideoThread() {
279 // This may fail and cause a crash if there are too many threads created in
280 // the current process. See http://crbug.com/443291
281 const base::ThreadPriority priority
=
283 // Bump up our priority so our sleeping is more accurate.
284 // TODO(scherkus): find out if this is necessary, but it seems to help.
285 base::ThreadPriority::DISPLAY
;
287 base::ThreadPriority::NORMAL
;
289 CHECK(base::PlatformThread::CreateWithPriority(0, this, &thread_
, priority
));
292 void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success
) {
293 DCHECK(task_runner_
->BelongsToCurrentThread());
294 base::AutoLock
auto_lock(lock_
);
295 DCHECK_EQ(state_
, kInitializing
);
298 state_
= kUninitialized
;
299 base::ResetAndReturn(&init_cb_
).Run(DECODER_ERROR_NOT_SUPPORTED
);
303 // We're all good! Consider ourselves flushed. (ThreadMain() should never
304 // see us in the kUninitialized state).
305 // Since we had an initial Preroll(), we consider ourself flushed, because we
306 // have not populated any buffers yet.
309 if (use_new_video_renderering_path_
) {
310 algorithm_
.reset(new VideoRendererAlgorithm(wall_clock_time_cb_
));
312 algorithm_
->disable_frame_dropping();
317 base::ResetAndReturn(&init_cb_
).Run(PIPELINE_OK
);
320 // PlatformThread::Delegate implementation.
321 void VideoRendererImpl::ThreadMain() {
322 DCHECK(!use_new_video_renderering_path_
);
323 base::PlatformThread::SetName("CrVideoRenderer");
325 // The number of milliseconds to idle when we do not have anything to do.
326 // Nothing special about the value, other than we're being more OS-friendly
327 // than sleeping for 1 millisecond.
329 // TODO(scherkus): switch to pure event-driven frame timing instead of this
330 // kIdleTimeDelta business http://crbug.com/106874
331 const base::TimeDelta kIdleTimeDelta
=
332 base::TimeDelta::FromMilliseconds(10);
335 base::AutoLock
auto_lock(lock_
);
337 // Thread exit condition.
338 if (is_shutting_down_
)
341 // Remain idle as long as we're not playing.
342 if (state_
!= kPlaying
|| buffering_state_
!= BUFFERING_HAVE_ENOUGH
) {
343 UpdateStatsAndWait_Locked(kIdleTimeDelta
);
347 base::TimeTicks now
= tick_clock_
->NowTicks();
349 // Remain idle until we have the next frame ready for rendering.
350 if (ready_frames_
.empty()) {
351 base::TimeDelta wait_time
= kIdleTimeDelta
;
352 if (received_end_of_stream_
) {
353 if (!rendered_end_of_stream_
) {
354 rendered_end_of_stream_
= true;
355 task_runner_
->PostTask(FROM_HERE
, ended_cb_
);
357 } else if (now
>= latest_possible_paint_time_
) {
358 // Declare HAVE_NOTHING if we don't have another frame by the time we
359 // are ready to paint the next one.
360 buffering_state_
= BUFFERING_HAVE_NOTHING
;
361 task_runner_
->PostTask(
362 FROM_HERE
, base::Bind(buffering_state_cb_
, BUFFERING_HAVE_NOTHING
));
364 wait_time
= std::min(kIdleTimeDelta
, latest_possible_paint_time_
- now
);
367 UpdateStatsAndWait_Locked(wait_time
);
371 base::TimeTicks target_paint_time
=
372 ConvertMediaTimestamp(ready_frames_
.front()->timestamp());
374 // If media time has stopped, don't attempt to paint any more frames.
375 if (target_paint_time
.is_null()) {
376 UpdateStatsAndWait_Locked(kIdleTimeDelta
);
380 // Deadline is defined as the duration between this frame and the next
381 // frame, using the delta between this frame and the previous frame as the
382 // assumption for frame duration.
384 // TODO(scherkus): This can be vastly improved. Use a histogram to measure
385 // the accuracy of our frame timing code. http://crbug.com/149829
386 if (last_media_time_
.is_null()) {
387 latest_possible_paint_time_
= now
;
389 base::TimeDelta duration
= target_paint_time
- last_media_time_
;
390 latest_possible_paint_time_
= target_paint_time
+ duration
;
393 // Remain idle until we've reached our target paint window.
394 if (now
< target_paint_time
) {
395 UpdateStatsAndWait_Locked(
396 std::min(target_paint_time
- now
, kIdleTimeDelta
));
400 if (ready_frames_
.size() > 1 && now
> latest_possible_paint_time_
&&
402 DropNextReadyFrame_Locked();
406 // Congratulations! You've made it past the video frame timing gauntlet.
408 // At this point enough time has passed that the next frame that ready for
410 PaintNextReadyFrame_Locked();
414 void VideoRendererImpl::SetTickClockForTesting(
415 scoped_ptr
<base::TickClock
> tick_clock
) {
416 tick_clock_
.swap(tick_clock
);
419 void VideoRendererImpl::OnTimeStateChanged(bool time_progressing
) {
420 DCHECK(task_runner_
->BelongsToCurrentThread());
421 time_progressing_
= time_progressing
;
423 // WARNING: Do not attempt to use |lock_| here as this may be a reentrant call
424 // in response to callbacks firing above.
426 if (!use_new_video_renderering_path_
|| sink_started_
== time_progressing_
)
429 if (time_progressing_
) {
430 // If only an EOS frame came in after a seek, the renderer may not have
431 // received the ended event yet though we've posted it.
432 if (!rendered_end_of_stream_
)
439 void VideoRendererImpl::PaintNextReadyFrame_Locked() {
440 DCHECK(!use_new_video_renderering_path_
);
441 lock_
.AssertAcquired();
443 scoped_refptr
<VideoFrame
> next_frame
= ready_frames_
.front();
444 ready_frames_
.pop_front();
446 last_media_time_
= ConvertMediaTimestamp(next_frame
->timestamp());
448 paint_cb_
.Run(next_frame
);
450 task_runner_
->PostTask(
452 base::Bind(&VideoRendererImpl::AttemptRead
, weak_factory_
.GetWeakPtr()));
455 void VideoRendererImpl::DropNextReadyFrame_Locked() {
456 DCHECK(!use_new_video_renderering_path_
);
457 TRACE_EVENT0("media", "VideoRendererImpl:frameDropped");
459 lock_
.AssertAcquired();
461 last_media_time_
= ConvertMediaTimestamp(ready_frames_
.front()->timestamp());
463 ready_frames_
.pop_front();
466 task_runner_
->PostTask(
468 base::Bind(&VideoRendererImpl::AttemptRead
, weak_factory_
.GetWeakPtr()));
471 void VideoRendererImpl::FrameReady(VideoFrameStream::Status status
,
472 const scoped_refptr
<VideoFrame
>& frame
) {
473 DCHECK(task_runner_
->BelongsToCurrentThread());
474 bool start_sink
= false;
476 base::AutoLock
auto_lock(lock_
);
477 DCHECK_NE(state_
, kUninitialized
);
478 DCHECK_NE(state_
, kFlushed
);
480 CHECK(pending_read_
);
481 pending_read_
= false;
483 if (status
== VideoFrameStream::DECODE_ERROR
) {
484 DCHECK(!frame
.get());
485 PipelineStatus error
= PIPELINE_ERROR_DECODE
;
486 task_runner_
->PostTask(FROM_HERE
, base::Bind(error_cb_
, error
));
490 // Already-queued VideoFrameStream ReadCB's can fire after various state
491 // transitions have happened; in that case just drop those frames
493 if (state_
== kFlushing
)
496 DCHECK_EQ(state_
, kPlaying
);
498 // Can happen when demuxers are preparing for a new Seek().
500 DCHECK_EQ(status
, VideoFrameStream::DEMUXER_READ_ABORTED
);
504 // In low delay mode, don't accumulate frames that's earlier than the start
505 // time. Otherwise we could declare HAVE_ENOUGH_DATA and start playback
508 !frame
->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM
) &&
509 frame
->timestamp() < start_timestamp_
) {
510 AttemptRead_Locked();
514 if (frame
->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM
)) {
515 DCHECK(!received_end_of_stream_
);
516 received_end_of_stream_
= true;
518 // See if we can fire EOS immediately instead of waiting for Render().
519 if (use_new_video_renderering_path_
)
520 MaybeFireEndedCallback_Locked(time_progressing_
);
522 // Maintain the latest frame decoded so the correct frame is displayed
523 // after prerolling has completed.
524 if (frame
->timestamp() <= start_timestamp_
) {
525 if (use_new_video_renderering_path_
)
527 ready_frames_
.clear();
529 AddReadyFrame_Locked(frame
);
532 // Background rendering updates may not be ticking fast enough by itself to
533 // remove expired frames, so give it a boost here by ensuring we don't exit
534 // the decoding cycle too early.
536 // Similarly, if we've paused for underflow, remove all frames which are
537 // before the current media time.
538 const bool have_nothing
= buffering_state_
!= BUFFERING_HAVE_ENOUGH
;
539 const bool have_nothing_and_paused
= have_nothing
&& !sink_started_
;
540 if (was_background_rendering_
||
541 (use_new_video_renderering_path_
&& have_nothing_and_paused
&&
543 base::TimeTicks expiry_time
;
544 if (have_nothing_and_paused
) {
545 // Use the current media wall clock time plus the frame duration since
546 // RemoveExpiredFrames() is expecting the end point of an interval (it
547 // will subtract from the given value).
548 std::vector
<base::TimeTicks
> current_time
;
549 wall_clock_time_cb_
.Run(std::vector
<base::TimeDelta
>(), ¤t_time
);
550 expiry_time
= current_time
[0] + algorithm_
->average_frame_duration();
552 expiry_time
= tick_clock_
->NowTicks();
555 // Prior to rendering the first frame, |have_nothing_and_paused| will be
556 // true, correspondingly the |expiry_time| will be null; in this case
557 // there's no reason to try and remove any frames.
558 if (!expiry_time
.is_null()) {
559 const size_t removed_frames
=
560 algorithm_
->RemoveExpiredFrames(expiry_time
);
562 // Frames removed during underflow should be counted as dropped.
563 if (have_nothing_and_paused
&& removed_frames
)
564 frames_dropped_
+= removed_frames
;
568 // Signal buffering state if we've met our conditions for having enough
570 if (have_nothing
&& HaveEnoughData_Locked()) {
571 TransitionToHaveEnough_Locked();
572 if (use_new_video_renderering_path_
&& !sink_started_
&&
573 !rendered_end_of_stream_
) {
575 render_first_frame_and_stop_
= true;
576 posted_maybe_stop_after_first_paint_
= false;
580 // Always request more decoded video if we have capacity. This serves two
582 // 1) Prerolling while paused
583 // 2) Keeps decoding going if video rendering thread starts falling behind
584 AttemptRead_Locked();
587 // If time is progressing, the sink has already been started; this may be true
588 // if we have previously underflowed, yet weren't stopped because of audio.
589 if (use_new_video_renderering_path_
&& start_sink
) {
590 DCHECK(!sink_started_
);
595 bool VideoRendererImpl::HaveEnoughData_Locked() {
596 DCHECK_EQ(state_
, kPlaying
);
598 if (received_end_of_stream_
|| !video_frame_stream_
->CanReadWithoutStalling())
601 if (HaveReachedBufferingCap())
604 if (use_new_video_renderering_path_
&& was_background_rendering_
&&
612 return ready_frames_
.size() > 0 ||
613 (use_new_video_renderering_path_
&& algorithm_
->frames_queued() > 0);
616 void VideoRendererImpl::TransitionToHaveEnough_Locked() {
617 DCHECK(task_runner_
->BelongsToCurrentThread());
618 DCHECK_EQ(buffering_state_
, BUFFERING_HAVE_NOTHING
);
620 if (!ready_frames_
.empty()) {
621 DCHECK(!use_new_video_renderering_path_
);
622 // Because the clock might remain paused in for an undetermined amount
623 // of time (e.g., seeking while paused), paint the first frame.
624 PaintNextReadyFrame_Locked();
627 buffering_state_
= BUFFERING_HAVE_ENOUGH
;
628 buffering_state_cb_
.Run(BUFFERING_HAVE_ENOUGH
);
631 void VideoRendererImpl::TransitionToHaveNothing() {
632 DCHECK(task_runner_
->BelongsToCurrentThread());
634 base::AutoLock
auto_lock(lock_
);
635 if (buffering_state_
!= BUFFERING_HAVE_ENOUGH
|| HaveEnoughData_Locked())
638 buffering_state_
= BUFFERING_HAVE_NOTHING
;
639 buffering_state_cb_
.Run(BUFFERING_HAVE_NOTHING
);
642 void VideoRendererImpl::AddReadyFrame_Locked(
643 const scoped_refptr
<VideoFrame
>& frame
) {
644 DCHECK(task_runner_
->BelongsToCurrentThread());
645 lock_
.AssertAcquired();
646 DCHECK(!frame
->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM
));
650 if (use_new_video_renderering_path_
) {
651 algorithm_
->EnqueueFrame(frame
);
655 ready_frames_
.push_back(frame
);
656 DCHECK_LE(ready_frames_
.size(),
657 static_cast<size_t>(limits::kMaxVideoFrames
));
659 // Avoid needlessly waking up |thread_| unless playing.
660 if (state_
== kPlaying
)
661 frame_available_
.Signal();
664 void VideoRendererImpl::AttemptRead() {
665 base::AutoLock
auto_lock(lock_
);
666 AttemptRead_Locked();
669 void VideoRendererImpl::AttemptRead_Locked() {
670 DCHECK(task_runner_
->BelongsToCurrentThread());
671 lock_
.AssertAcquired();
673 if (pending_read_
|| received_end_of_stream_
)
676 if (HaveReachedBufferingCap())
681 pending_read_
= true;
682 video_frame_stream_
->Read(base::Bind(&VideoRendererImpl::FrameReady
,
683 weak_factory_
.GetWeakPtr()));
694 void VideoRendererImpl::OnVideoFrameStreamResetDone() {
695 base::AutoLock
auto_lock(lock_
);
696 DCHECK_EQ(kFlushing
, state_
);
697 DCHECK(!pending_read_
);
698 DCHECK(ready_frames_
.empty());
699 DCHECK(!received_end_of_stream_
);
700 DCHECK(!rendered_end_of_stream_
);
701 DCHECK_EQ(buffering_state_
, BUFFERING_HAVE_NOTHING
);
704 latest_possible_paint_time_
= last_media_time_
= base::TimeTicks();
705 base::ResetAndReturn(&flush_cb_
).Run();
708 void VideoRendererImpl::UpdateStatsAndWait_Locked(
709 base::TimeDelta wait_duration
) {
710 lock_
.AssertAcquired();
711 DCHECK_GE(frames_decoded_
, 0);
712 DCHECK_GE(frames_dropped_
, 0);
714 if (frames_decoded_
|| frames_dropped_
) {
715 PipelineStatistics statistics
;
716 statistics
.video_frames_decoded
= frames_decoded_
;
717 statistics
.video_frames_dropped
= frames_dropped_
;
718 task_runner_
->PostTask(FROM_HERE
, base::Bind(statistics_cb_
, statistics
));
724 if (wait_duration
> base::TimeDelta())
725 frame_available_
.TimedWait(wait_duration
);
728 void VideoRendererImpl::MaybeStopSinkAfterFirstPaint() {
729 DCHECK(task_runner_
->BelongsToCurrentThread());
730 DCHECK(use_new_video_renderering_path_
);
732 if (!time_progressing_
&& sink_started_
)
735 base::AutoLock
auto_lock(lock_
);
736 render_first_frame_and_stop_
= false;
739 bool VideoRendererImpl::HaveReachedBufferingCap() {
740 DCHECK(task_runner_
->BelongsToCurrentThread());
741 const size_t kMaxVideoFrames
= limits::kMaxVideoFrames
;
743 if (use_new_video_renderering_path_
) {
744 // When the display rate is less than the frame rate, the effective frames
745 // queued may be much smaller than the actual number of frames queued. Here
746 // we ensure that frames_queued() doesn't get excessive.
747 return algorithm_
->EffectiveFramesQueued() >= kMaxVideoFrames
||
748 algorithm_
->frames_queued() >= 3 * kMaxVideoFrames
;
751 return ready_frames_
.size() >= kMaxVideoFrames
;
754 void VideoRendererImpl::StartSink() {
755 DCHECK(task_runner_
->BelongsToCurrentThread());
756 DCHECK_GT(algorithm_
->frames_queued(), 0u);
757 sink_started_
= true;
758 was_background_rendering_
= false;
762 void VideoRendererImpl::StopSink() {
763 DCHECK(task_runner_
->BelongsToCurrentThread());
765 algorithm_
->set_time_stopped();
766 sink_started_
= false;
767 was_background_rendering_
= false;
770 size_t VideoRendererImpl::MaybeFireEndedCallback_Locked(bool time_progressing
) {
771 lock_
.AssertAcquired();
773 // If there's only one frame in the video or Render() was never called, the
774 // algorithm will have one frame linger indefinitely. So in cases where the
775 // frame duration is unknown and we've received EOS, fire it once we get down
776 // to a single frame.
777 const size_t effective_frames
= algorithm_
->EffectiveFramesQueued();
779 // Don't fire ended if we haven't received EOS or have already done so.
780 if (!received_end_of_stream_
|| rendered_end_of_stream_
)
781 return effective_frames
;
783 // Don't fire ended if time isn't moving and we have frames.
784 if (!time_progressing
&& algorithm_
->frames_queued())
785 return effective_frames
;
787 // Fire ended if we have no more effective frames or only ever had one frame.
788 if (!effective_frames
||
789 (algorithm_
->frames_queued() == 1u &&
790 algorithm_
->average_frame_duration() == base::TimeDelta())) {
791 rendered_end_of_stream_
= true;
792 task_runner_
->PostTask(FROM_HERE
, ended_cb_
);
795 return effective_frames
;
798 base::TimeTicks
VideoRendererImpl::ConvertMediaTimestamp(
799 base::TimeDelta media_time
) {
800 std::vector
<base::TimeDelta
> media_times(1, media_time
);
801 std::vector
<base::TimeTicks
> wall_clock_times
;
802 if (!wall_clock_time_cb_
.Run(media_times
, &wall_clock_times
))
803 return base::TimeTicks();
804 return wall_clock_times
[0];