blob: 80d1f3d53643cb1c9b11cccf8ea6ff2062c747fa [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/filters/video_renderer_base.h"
#include <algorithm>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/message_loop.h"
#include "base/threading/platform_thread.h"
#include "media/base/buffers.h"
#include "media/base/limits.h"
#include "media/base/pipeline.h"
#include "media/base/video_frame.h"
#include "media/filters/decrypting_demuxer_stream.h"
#include "media/filters/video_decoder_selector.h"
#if defined(__LB_SHELL__) || defined(COBALT)
#include "base/debug/trace_event.h"
#include "base/stringprintf.h"
#include "media/base/shell_media_platform.h"
#include "media/base/shell_media_statistics.h"
#endif
namespace media {
#if !defined(__LB_SHELL__FOR_RELEASE__)
int VideoRendererBase::videos_played_;
#endif // !defined(__LB_SHELL__FOR_RELEASE__)
base::TimeDelta VideoRendererBase::kMaxLastFrameDuration() {
return base::TimeDelta::FromMilliseconds(250);
}
VideoRendererBase::VideoRendererBase(
const scoped_refptr<base::MessageLoopProxy>& message_loop,
const SetDecryptorReadyCB& set_decryptor_ready_cb,
const base::Closure& paint_cb,
const SetOpaqueCB& set_opaque_cb,
bool drop_frames)
: message_loop_(message_loop),
set_decryptor_ready_cb_(set_decryptor_ready_cb),
frame_available_(&lock_),
state_(kUninitialized),
thread_(base::kNullThreadHandle),
pending_read_(false),
pending_paint_(false),
pending_paint_with_last_available_(false),
drop_frames_(drop_frames),
playback_rate_(0),
paint_cb_(paint_cb),
set_opaque_cb_(set_opaque_cb),
maximum_frames_cached_(0) {
DCHECK(!paint_cb_.is_null());
#if defined(__LB_SHELL__) || defined(COBALT)
frame_provider_ = ShellMediaPlatform::Instance()->GetVideoFrameProvider();
#if !defined(__LB_SHELL__FOR_RELEASE__)
late_frames_ = 0;
DLOG(INFO) << "Start playing back video " << videos_played_;
#endif // !defined(__LB_SHELL__FOR_RELEASE__)
#endif // defined(__LB_SHELL__) || defined(COBALT)
}
void VideoRendererBase::Play(const base::Closure& callback) {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase::Play()");
#endif
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
DCHECK_EQ(kPrerolled, state_);
state_ = kPlaying;
callback.Run();
}
void VideoRendererBase::Pause(const base::Closure& callback) {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase::Pause()");
#endif
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
DCHECK(state_ != kUninitialized || state_ == kError);
state_ = kPaused;
callback.Run();
}
void VideoRendererBase::Flush(const base::Closure& callback) {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase::Flush()");
#endif
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
DCHECK_EQ(state_, kPaused);
flush_cb_ = callback;
state_ = kFlushingDecoder;
if (decrypting_demuxer_stream_) {
decrypting_demuxer_stream_->Reset(base::Bind(
&VideoRendererBase::ResetDecoder, this));
return;
}
decoder_->Reset(base::Bind(&VideoRendererBase::OnDecoderResetDone, this));
}
void VideoRendererBase::ResetDecoder() {
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
decoder_->Reset(base::Bind(&VideoRendererBase::OnDecoderResetDone, this));
}
void VideoRendererBase::Stop(const base::Closure& callback) {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase::Stop()");
#endif
DCHECK(message_loop_->BelongsToCurrentThread());
if (state_ == kUninitialized || state_ == kStopped) {
callback.Run();
return;
}
base::PlatformThreadHandle thread_to_join = base::kNullThreadHandle;
{
base::AutoLock auto_lock(lock_);
state_ = kStopped;
statistics_cb_.Reset();
max_time_cb_.Reset();
if (!pending_paint_ && !pending_paint_with_last_available_)
DoStopOrError_Locked();
// Clean up our thread if present.
if (thread_ != base::kNullThreadHandle) {
// Signal the thread since it's possible to get stopped with the video
// thread waiting for a read to complete.
frame_available_.Signal();
thread_to_join = thread_;
thread_ = base::kNullThreadHandle;
}
}
if (thread_to_join != base::kNullThreadHandle)
base::PlatformThread::Join(thread_to_join);
#if defined(__LB_SHELL__) || defined(COBALT)
if (frame_provider_) { frame_provider_->Stop(); }
#endif // defined(__LB_SHELL__) || defined(COBALT)
if (decrypting_demuxer_stream_) {
decrypting_demuxer_stream_->Reset(base::Bind(
&VideoRendererBase::StopDecoder, this, callback));
return;
}
decoder_->Stop(callback);
}
void VideoRendererBase::StopDecoder(const base::Closure& callback) {
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
decoder_->Stop(callback);
}
void VideoRendererBase::SetPlaybackRate(float playback_rate) {
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
playback_rate_ = playback_rate;
}
void VideoRendererBase::Preroll(base::TimeDelta time,
const PipelineStatusCB& cb) {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT1("media_stack", "VideoRendererBase::Preroll()",
"timestamp", time.InMicroseconds());
#endif
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
DCHECK_EQ(state_, kFlushed) << "Must flush prior to prerolling.";
DCHECK(!cb.is_null());
DCHECK(preroll_cb_.is_null());
state_ = kPrerolling;
preroll_cb_ = cb;
preroll_timestamp_ = time;
prerolling_delayed_frame_ = NULL;
AttemptRead_Locked();
}
void VideoRendererBase::Initialize(const scoped_refptr<DemuxerStream>& stream,
const VideoDecoderList& decoders,
const PipelineStatusCB& init_cb,
const StatisticsCB& statistics_cb,
const TimeCB& max_time_cb,
const NaturalSizeChangedCB& size_changed_cb,
const base::Closure& ended_cb,
const PipelineStatusCB& error_cb,
const TimeDeltaCB& get_time_cb,
const TimeDeltaCB& get_duration_cb) {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase::Initialize()");
#endif
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
DCHECK(stream);
DCHECK(!decoders.empty());
DCHECK_EQ(stream->type(), DemuxerStream::VIDEO);
DCHECK(!init_cb.is_null());
DCHECK(!statistics_cb.is_null());
DCHECK(!max_time_cb.is_null());
DCHECK(!size_changed_cb.is_null());
DCHECK(!ended_cb.is_null());
DCHECK(!get_time_cb.is_null());
DCHECK(!get_duration_cb.is_null());
DCHECK_EQ(kUninitialized, state_);
init_cb_ = init_cb;
statistics_cb_ = statistics_cb;
max_time_cb_ = max_time_cb;
size_changed_cb_ = size_changed_cb;
ended_cb_ = ended_cb;
error_cb_ = error_cb;
get_time_cb_ = get_time_cb;
get_duration_cb_ = get_duration_cb;
scoped_ptr<VideoDecoderSelector> decoder_selector(
new VideoDecoderSelector(base::MessageLoopProxy::current(),
decoders,
set_decryptor_ready_cb_));
// To avoid calling |decoder_selector| methods and passing ownership of
// |decoder_selector| in the same line.
VideoDecoderSelector* decoder_selector_ptr = decoder_selector.get();
decoder_selector_ptr->SelectVideoDecoder(
stream,
statistics_cb,
base::Bind(&VideoRendererBase::OnDecoderSelected, this,
base::Passed(&decoder_selector)));
}
void VideoRendererBase::OnDecoderSelected(
scoped_ptr<VideoDecoderSelector> decoder_selector,
const scoped_refptr<VideoDecoder>& selected_decoder,
const scoped_refptr<DecryptingDemuxerStream>& decrypting_demuxer_stream) {
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
if (state_ == kStopped)
return;
if (!selected_decoder) {
state_ = kUninitialized;
base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
return;
}
decoder_ = selected_decoder;
decrypting_demuxer_stream_ = decrypting_demuxer_stream;
// We're all good! Consider ourselves flushed. (ThreadMain() should never
// see us in the kUninitialized state).
// Since we had an initial Preroll(), we consider ourself flushed, because we
// have not populated any buffers yet.
state_ = kFlushed;
set_opaque_cb_.Run(!decoder_->HasAlpha());
set_opaque_cb_.Reset();
// Create our video thread.
if (!base::PlatformThread::Create(0, this, &thread_)) {
NOTREACHED() << "Video thread creation failed";
state_ = kError;
base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED);
return;
}
#if defined(OS_WIN)
// Bump up our priority so our sleeping is more accurate.
// TODO(scherkus): find out if this is necessary, but it seems to help.
::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
#endif // defined(OS_WIN)
base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
}
// PlatformThread::Delegate implementation.
void VideoRendererBase::ThreadMain() {
base::PlatformThread::SetName("CrVideoRenderer");
// The number of milliseconds to idle when we do not have anything to do.
// Nothing special about the value, other than we're being more OS-friendly
// than sleeping for 1 millisecond.
//
// TOOD(scherkus): switch to pure event-driven frame timing instead of this
// kIdleTimeDelta business http://crbug.com/106874
const base::TimeDelta kIdleTimeDelta =
base::TimeDelta::FromMilliseconds(10);
uint32 frames_dropped = 0;
for (;;) {
#if defined(__LB_SHELL__) || defined(COBALT)
frames_dropped =
static_cast<uint32>(frame_provider_->ResetAndReturnDroppedFrames());
if (frames_dropped > 0) {
SCOPED_MEDIA_STATISTICS(STAT_TYPE_VIDEO_FRAME_DROP);
}
#endif // defined(__LB_SHELL__) || defined(COBALT)
if (frames_dropped > 0) {
PipelineStatistics statistics;
statistics.video_frames_dropped = frames_dropped;
statistics_cb_.Run(statistics);
frames_dropped = 0;
}
base::AutoLock auto_lock(lock_);
// Thread exit condition.
if (state_ == kStopped)
return;
// Remain idle as long as we're not playing.
if (state_ != kPlaying || playback_rate_ == 0) {
frame_available_.TimedWait(kIdleTimeDelta);
continue;
}
#if defined(__LB_SHELL__) || defined(COBALT)
if (frame_provider_ && frame_provider_->QueryAndResetHasConsumedFrames()) {
// The consumer of the frame_provider_ has consumed frames. Post another
// AttemptRead task to ensure that new frames are being read in to keep
// the frame_provider_'s queue full.
message_loop_->PostTask(FROM_HERE, base::Bind(
&VideoRendererBase::AttemptRead, this));
}
#endif
// Remain idle until we have the next frame ready for rendering.
if (ready_frames_.empty()) {
frame_available_.TimedWait(kIdleTimeDelta);
continue;
}
// Remain idle until we've initialized |current_frame_| via prerolling.
if (!current_frame_) {
// This can happen if our preroll only contains end of stream frames.
if (ready_frames_.front()->IsEndOfStream()) {
state_ = kEnded;
ended_cb_.Run();
ready_frames_.clear();
// No need to sleep here as we idle when |state_ != kPlaying|.
continue;
}
frame_available_.TimedWait(kIdleTimeDelta);
continue;
}
// Calculate how long until we should advance the frame, which is
// typically negative but for playback rates < 1.0f may be long enough
// that it makes more sense to idle and check again.
base::TimeDelta remaining_time =
CalculateSleepDuration(ready_frames_.front(), playback_rate_);
// Sleep up to a maximum of our idle time until we're within the time to
// render the next frame.
if (remaining_time.InMicroseconds() > 0) {
remaining_time = std::min(remaining_time, kIdleTimeDelta);
frame_available_.TimedWait(remaining_time);
continue;
}
// We're almost there!
//
// At this point we've rendered |current_frame_| for the proper amount
// of time and also have the next frame that ready for rendering.
// If the next frame is end of stream then we are truly at the end of the
// video stream.
//
// TODO(scherkus): deduplicate this end of stream check after we get rid of
// |current_frame_|.
if (ready_frames_.front()->IsEndOfStream()) {
state_ = kEnded;
ended_cb_.Run();
ready_frames_.clear();
// No need to sleep here as we idle when |state_ != kPlaying|.
continue;
}
// We cannot update |current_frame_| until we've completed the pending
// paint. Furthermore, the pending paint might be really slow: check to
// see if we have any ready frames that we can drop if they've already
// expired.
if (pending_paint_) {
while (!ready_frames_.empty()) {
// Can't drop anything if we're at the end.
if (ready_frames_.front()->IsEndOfStream())
break;
base::TimeDelta remaining_time =
ready_frames_.front()->GetTimestamp() - get_time_cb_.Run();
// Still a chance we can render the frame!
if (remaining_time.InMicroseconds() > 0)
break;
if (!drop_frames_)
break;
// Frame dropped: read again.
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT1("media_stack", "VideoRendererBase drop frame",
"timestamp",
ready_frames_.front()->GetTimestamp().InMicroseconds());
#endif
++frames_dropped;
ready_frames_.pop_front();
#if defined(__LB_SHELL__) || defined(COBALT)
UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_RENDERER_BACKLOG,
ready_frames_.size());
#endif // defined(__LB_SHELL__) || defined(COBALT)
message_loop_->PostTask(FROM_HERE, base::Bind(
&VideoRendererBase::AttemptRead, this));
}
// Continue waiting for the current paint to finish.
frame_available_.TimedWait(kIdleTimeDelta);
continue;
}
// Congratulations! You've made it past the video frame timing gauntlet.
//
// We can now safely update the current frame, request another frame, and
// signal to the client that a new frame is available.
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase proceed to next frame");
#endif
DCHECK(!pending_paint_);
DCHECK(!ready_frames_.empty());
SetCurrentFrameToNextReadyFrame();
message_loop_->PostTask(FROM_HERE, base::Bind(
&VideoRendererBase::AttemptRead, this));
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase paint_cb_");
#endif
base::AutoUnlock auto_unlock(lock_);
paint_cb_.Run();
}
}
void VideoRendererBase::SetCurrentFrameToNextReadyFrame() {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT1("media_stack",
"VideoRendererBase::SetCurrentFrameToNextReadyFrame()",
"timestamp",
ready_frames_.front()->GetTimestamp().InMicroseconds());
#endif
current_frame_ = ready_frames_.front();
ready_frames_.pop_front();
#if defined(__LB_SHELL__) || defined(COBALT)
UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_RENDERER_BACKLOG,
ready_frames_.size());
#endif // defined(__LB_SHELL__) || defined(COBALT)
// Notify the pipeline of natural_size() changes.
const gfx::Size& natural_size = current_frame_->natural_size();
if (natural_size != last_natural_size_) {
size_changed_cb_.Run(natural_size);
last_natural_size_ = natural_size;
}
}
void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) {
base::AutoLock auto_lock(lock_);
DCHECK(!pending_paint_ && !pending_paint_with_last_available_);
if ((!current_frame_ || current_frame_->IsEndOfStream()) &&
(!last_available_frame_ || last_available_frame_->IsEndOfStream())) {
*frame_out = NULL;
return;
}
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase::GetCurrentFrame()");
#endif
// We should have initialized and have the current frame.
DCHECK_NE(state_, kUninitialized);
DCHECK_NE(state_, kStopped);
DCHECK_NE(state_, kError);
if (current_frame_) {
*frame_out = current_frame_;
last_available_frame_ = current_frame_;
pending_paint_ = true;
} else {
DCHECK(last_available_frame_);
*frame_out = last_available_frame_;
pending_paint_with_last_available_ = true;
}
}
void VideoRendererBase::PutCurrentFrame(scoped_refptr<VideoFrame> frame) {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase::PutCurrentFrame()");
#endif
base::AutoLock auto_lock(lock_);
// Note that we do not claim |pending_paint_| when we return NULL frame, in
// that case, |current_frame_| could be changed before PutCurrentFrame.
if (pending_paint_) {
DCHECK_EQ(current_frame_, frame);
DCHECK(!pending_paint_with_last_available_);
pending_paint_ = false;
} else if (pending_paint_with_last_available_) {
DCHECK_EQ(last_available_frame_, frame);
DCHECK(!pending_paint_);
pending_paint_with_last_available_ = false;
} else {
DCHECK(!frame);
}
// We had cleared the |pending_paint_| flag, there are chances that current
// frame is timed-out. We will wake up our main thread to advance the current
// frame when this is true.
frame_available_.Signal();
if (state_ == kFlushingDecoder)
return;
if (state_ == kFlushing) {
AttemptFlush_Locked();
return;
}
if (state_ == kError || state_ == kStopped) {
DoStopOrError_Locked();
}
}
VideoRendererBase::~VideoRendererBase() {
base::AutoLock auto_lock(lock_);
DCHECK(state_ == kUninitialized || state_ == kStopped) << state_;
#if !defined(__LB_SHELL__FOR_RELEASE__)
++videos_played_;
DLOG_IF(INFO, late_frames_ != 0)
<< "Finished playing back with " << late_frames_ << " late frames.";
DLOG_IF(INFO, late_frames_ == 0)
<< "Finished playing back with no late frame.";
#endif // !defined(__LB_SHELL__FOR_RELEASE__)
}
void VideoRendererBase::FrameReady(
VideoDecoder::Status status,
const scoped_refptr<VideoFrame>& incoming_frame) {
// Make a copy as we may assign newly created punch out frame to `frame` when
// frame provider is available.
scoped_refptr<VideoFrame> frame(incoming_frame);
base::AutoLock auto_lock(lock_);
DCHECK_NE(state_, kUninitialized);
CHECK(pending_read_);
pending_read_ = false;
if (status != VideoDecoder::kOk) {
DCHECK(!frame);
PipelineStatus error = PIPELINE_ERROR_DECODE;
if (status == VideoDecoder::kDecryptError)
error = PIPELINE_ERROR_DECRYPT;
if (!preroll_cb_.is_null()) {
base::ResetAndReturn(&preroll_cb_).Run(error);
return;
}
error_cb_.Run(error);
return;
}
// Already-queued Decoder ReadCB's can fire after various state transitions
// have happened; in that case just drop those frames immediately.
if (state_ == kStopped || state_ == kError || state_ == kFlushed ||
state_ == kFlushingDecoder)
return;
if (state_ == kFlushing) {
AttemptFlush_Locked();
return;
}
if (!frame) {
if (state_ != kPrerolling)
return;
// Abort preroll early for a NULL frame because we won't get more frames.
// A new preroll will be requested after this one completes so there is no
// point trying to collect more frames.
state_ = kPrerolled;
base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
return;
}
#if defined(__LB_SHELL__) || defined(COBALT)
scoped_refptr<VideoFrame> original_frame;
if (frame_provider_ && !frame->IsEndOfStream()) {
// Save the frame to original_frame so it can be added into frame_provider
// later.
original_frame = frame;
frame = VideoFrame::CreatePunchOutFrame(
original_frame->visible_rect().size());
frame->SetTimestamp(original_frame->GetTimestamp());
}
#endif // defined(__LB_SHELL__) || defined(COBALT)
// Discard frames until we reach our desired preroll timestamp.
if (state_ == kPrerolling && !frame->IsEndOfStream() &&
frame->GetTimestamp() <= preroll_timestamp_) {
prerolling_delayed_frame_ = frame;
AttemptRead_Locked();
return;
}
#if defined(__LB_SHELL__) || defined(COBALT)
if (frame_provider_ && original_frame) {
frame_provider_->AddFrame(original_frame);
}
#endif // defined(__LB_SHELL__) || defined(COBALT)
if (prerolling_delayed_frame_) {
DCHECK_EQ(state_, kPrerolling);
AddReadyFrame(prerolling_delayed_frame_);
prerolling_delayed_frame_ = NULL;
}
AddReadyFrame(frame);
PipelineStatistics statistics;
statistics.video_frames_decoded = 1;
statistics_cb_.Run(statistics);
// Always request more decoded video if we have capacity. This serves two
// purposes:
// 1) Prerolling while paused
// 2) Keeps decoding going if video rendering thread starts falling behind
#if defined(__LB_SHELL__) || defined(COBALT)
bool is_prerolling = state_ == kPrerolling;
int max_video_frames =
is_prerolling ? ShellMediaPlatform::Instance()->GetMaxVideoPrerollFrames()
: ShellMediaPlatform::Instance()->GetMaxVideoFrames();
if (NumFrames_Locked() < max_video_frames && !frame->IsEndOfStream()) {
#else // defined(__LB_SHELL__) || defined(COBALT)
if (NumFrames_Locked() < limits::kMaxVideoFrames && !frame->IsEndOfStream()) {
#endif // defined(__LB_SHELL__) || defined(COBALT)
AttemptRead_Locked();
return;
}
// If we're at capacity or end of stream while prerolling we need to
// transition to prerolled.
if (state_ == kPrerolling) {
DCHECK(!current_frame_);
state_ = kPrerolled;
// Because we might remain in the prerolled state for an undetermined amount
// of time (i.e., we were not playing before we started prerolling), we'll
// manually update the current frame and notify the subclass below.
if (!ready_frames_.front()->IsEndOfStream())
SetCurrentFrameToNextReadyFrame();
// ...and we're done prerolling!
DCHECK(!preroll_cb_.is_null());
base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase paint_cb_");
#endif
base::AutoUnlock ul(lock_);
paint_cb_.Run();
}
}
void VideoRendererBase::AddReadyFrame(const scoped_refptr<VideoFrame>& frame) {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT1("media_stack", "VideoRendererBase::AddReadyFrame()",
"timestamp", frame->GetTimestamp().InMicroseconds());
#endif
// Adjust the incoming frame if its rendering stop time is past the duration
// of the video itself. This is typically the last frame of the video and
// occurs if the container specifies a duration that isn't a multiple of the
// frame rate. Another way for this to happen is for the container to state a
// smaller duration than the largest packet timestamp.
base::TimeDelta duration = get_duration_cb_.Run();
if (frame->IsEndOfStream()) {
base::TimeDelta end_timestamp = kNoTimestamp();
if (!ready_frames_.empty()) {
end_timestamp = std::min(
duration,
ready_frames_.back()->GetTimestamp() + kMaxLastFrameDuration());
} else if (current_frame_) {
end_timestamp =
std::min(duration,
current_frame_->GetTimestamp() + kMaxLastFrameDuration());
}
frame->SetTimestamp(end_timestamp);
} else if (frame->GetTimestamp() > duration) {
frame->SetTimestamp(duration);
}
#if !defined(__LB_SHELL__FOR_RELEASE__)
if (frame->GetTimestamp() < get_time_cb_.Run()) {
SCOPED_MEDIA_STATISTICS(STAT_TYPE_VIDEO_FRAME_LATE);
++late_frames_;
}
#endif // !defined(__LB_SHELL__FOR_RELEASE__)
ready_frames_.push_back(frame);
#if defined(__LB_SHELL__) || defined(COBALT)
DCHECK_LE(NumFrames_Locked(),
ShellMediaPlatform::Instance()->GetMaxVideoFrames());
#else // defined(__LB_SHELL__) || defined(COBALT)
DCHECK_LE(NumFrames_Locked(), limits::kMaxVideoFrames);
#endif // defined(__LB_SHELL__) || defined(COBALT)
base::TimeDelta max_clock_time =
frame->IsEndOfStream() ? duration : frame->GetTimestamp();
DCHECK(max_clock_time != kNoTimestamp());
max_time_cb_.Run(max_clock_time);
frame_available_.Signal();
}
void VideoRendererBase::AttemptRead() {
base::AutoLock auto_lock(lock_);
AttemptRead_Locked();
}
void VideoRendererBase::AttemptRead_Locked() {
#if defined(__LB_SHELL__) || defined(COBALT)
TRACE_EVENT0("media_stack", "VideoRendererBase::AttemptRead_Locked()");
#endif
DCHECK(message_loop_->BelongsToCurrentThread());
lock_.AssertAcquired();
if (pending_read_ ||
#if defined(__LB_SHELL__) || defined(COBALT)
NumFrames_Locked() ==
ShellMediaPlatform::Instance()->GetMaxVideoFrames() ||
#else // defined(__LB_SHELL__) || defined(COBALT)
NumFrames_Locked() == limits::kMaxVideoFrames ||
#endif // defined(__LB_SHELL__) || defined(COBALT)
(!ready_frames_.empty() && ready_frames_.back()->IsEndOfStream())) {
return;
}
UpdateUnderflowStatusToDecoder_Locked();
switch (state_) {
case kPaused:
case kFlushing:
case kPrerolling:
case kPlaying:
pending_read_ = true;
decoder_->Read(base::Bind(&VideoRendererBase::FrameReady, this));
return;
case kUninitialized:
case kPrerolled:
case kFlushingDecoder:
case kFlushed:
case kEnded:
case kStopped:
case kError:
return;
}
}
void VideoRendererBase::UpdateUnderflowStatusToDecoder_Locked() {
DCHECK(message_loop_->BelongsToCurrentThread());
lock_.AssertAcquired();
if (!decoder_) {
return;
}
if (state_ != kPlaying) {
// If we are not playing, inform the decoder that we have enough frames so
// it will decode in high quality.
decoder_->HaveEnoughFrames();
return;
}
// There are two stages during playback: prerolling and playing. In the
// following example, we assume the preroll frame count is 4, the underflow
// frame count is 16 and the maximum frame count is 32.
// 1. Prerolling:
// When the video starts to play back, there will be 4 frames buffered. Then
// it will slowly grow up to 32 frames if the decoding speed is fast enough.
// When the cached frame count start to decrease consistently, there is an
// overflow. Note that we cannot simply compare the current cached frame
// against GetVideoUnderflowFrames() because as long as we are accumulating
// more frames, we don't want to trigger underflow during start up. So we
// record the maximum frame ever reached and when the cached frame count
// drops below `maximum frame count` - kUnderflowAdjustment, we trigger
// underflow.
// 2. Playing.
// In this case we already have maximum frames in cache so we have more room
// for slow decoding. We only trigger underflow when the cached frame count
// drops below GetVideoUnderflowFrames().
const int kUnderflowAdjustment = 2;
maximum_frames_cached_ = std::max(maximum_frames_cached_, NumFrames_Locked());
int underflow_threshold = std::min(
ShellMediaPlatform::Instance()->GetVideoUnderflowFrames(),
std::max(maximum_frames_cached_ - kUnderflowAdjustment, 0));
if (NumFrames_Locked() < underflow_threshold) {
decoder_->NearlyUnderflow();
} else {
decoder_->HaveEnoughFrames();
}
}
void VideoRendererBase::OnDecoderResetDone() {
base::AutoLock auto_lock(lock_);
// The state_ can be kStopped if video playback is stopped when a seek is in
// progress. The easist way to reproduce this is to repost to
// OnDecoderResetDone() here until state_ is kStopped and manually quit
// playing a video when the first seek is in progress.
// To keep running the function when state_ is kStopped is safe, because the
// flush_cb_ will be handled by SerialRunner::RunNextInSeries() which is
// posted on a weak pointer. The SerialRunner should already be destroyed in
// Pipeline::DoStop() when pending_callbacks_ is overwritten.
// Note that this part has been properly handled in the upstream as the video
// renderer is owned by Pipeline and all related tasks are posted with a weak
// pointer of the video renderer. When the Pipeline is stopped, it will
// destroy the video renderer and this callback will not be called. So don't
// apply this change during rebase.
if (state_ != kStopped) {
DCHECK_EQ(kFlushingDecoder, state_);
}
DCHECK(!pending_read_);
state_ = kFlushing;
AttemptFlush_Locked();
}
void VideoRendererBase::AttemptFlush_Locked() {
lock_.AssertAcquired();
DCHECK_EQ(kFlushing, state_);
prerolling_delayed_frame_ = NULL;
ready_frames_.clear();
maximum_frames_cached_ = 0;
#if defined(__LB_SHELL__) || defined(COBALT)
if (frame_provider_) { frame_provider_->Flush(); }
#endif // defined(__LB_SHELL__) || defined(COBALT)
if (!pending_paint_ && !pending_read_) {
state_ = kFlushed;
current_frame_ = NULL;
base::ResetAndReturn(&flush_cb_).Run();
}
}
base::TimeDelta VideoRendererBase::CalculateSleepDuration(
const scoped_refptr<VideoFrame>& next_frame,
float playback_rate) {
// Determine the current and next presentation timestamps.
base::TimeDelta now = get_time_cb_.Run();
base::TimeDelta next_pts = next_frame->GetTimestamp();
// Scale our sleep based on the playback rate.
base::TimeDelta sleep = next_pts - now;
return base::TimeDelta::FromMicroseconds(
static_cast<int64>(sleep.InMicroseconds() / playback_rate));
}
void VideoRendererBase::DoStopOrError_Locked() {
DCHECK(!pending_paint_);
DCHECK(!pending_paint_with_last_available_);
lock_.AssertAcquired();
current_frame_ = NULL;
last_available_frame_ = NULL;
ready_frames_.clear();
}
int VideoRendererBase::NumFrames_Locked() const {
lock_.AssertAcquired();
if (frame_provider_) {
return frame_provider_->GetNumOfFramesCached();
}
int outstanding_frames =
(current_frame_ ? 1 : 0) + (last_available_frame_ ? 1 : 0) +
(current_frame_ && (current_frame_ == last_available_frame_) ? -1 : 0);
return ready_frames_.size() + outstanding_frames;
}
} // namespace media