| Index: media/filters/video_renderer_base.cc
|
| diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc
|
| index fca51ca80f41cdd234075373512b8549d613c038..62f7b1750a11112dcf79ce55984a7db958010f46 100644
|
| --- a/media/filters/video_renderer_base.cc
|
| +++ b/media/filters/video_renderer_base.cc
|
| @@ -30,10 +30,12 @@ VideoRendererBase::VideoRendererBase()
|
| : frame_available_(&lock_),
|
| state_(kUninitialized),
|
| thread_(base::kNullThreadHandle),
|
| - pending_reads_(0),
|
| + pending_read_(false),
|
| pending_paint_(false),
|
| pending_paint_with_last_available_(false),
|
| - playback_rate_(0) {
|
| + playback_rate_(0),
|
| + read_cb_(base::Bind(&VideoRendererBase::FrameReady,
|
| + base::Unretained(this))) {
|
| }
|
|
|
| VideoRendererBase::~VideoRendererBase() {
|
| @@ -61,30 +63,29 @@ void VideoRendererBase::Flush(const base::Closure& callback) {
|
| flush_callback_ = callback;
|
| state_ = kFlushing;
|
|
|
| - if (!pending_paint_)
|
| - FlushBuffers_Locked();
|
| + AttemptFlush_Locked();
|
| }
|
|
|
| void VideoRendererBase::Stop(const base::Closure& callback) {
|
| - base::PlatformThreadHandle old_thread_handle = base::kNullThreadHandle;
|
| + base::PlatformThreadHandle thread_to_join = base::kNullThreadHandle;
|
| {
|
| base::AutoLock auto_lock(lock_);
|
| state_ = kStopped;
|
|
|
| if (!pending_paint_ && !pending_paint_with_last_available_)
|
| - DoStopOrErrorFlush_Locked();
|
| + DoStopOrError_Locked();
|
|
|
| // Clean up our thread if present.
|
| - if (thread_) {
|
| + 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();
|
| - old_thread_handle = thread_;
|
| + thread_to_join = thread_;
|
| thread_ = base::kNullThreadHandle;
|
| }
|
| }
|
| - if (old_thread_handle)
|
| - base::PlatformThread::Join(old_thread_handle);
|
| + if (thread_to_join != base::kNullThreadHandle)
|
| + base::PlatformThread::Join(thread_to_join);
|
|
|
| // Signal the subclass we're stopping.
|
| OnStop(callback);
|
| @@ -96,35 +97,17 @@ void VideoRendererBase::SetPlaybackRate(float playback_rate) {
|
| }
|
|
|
| void VideoRendererBase::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
|
| - bool run_callback = false;
|
| -
|
| {
|
| base::AutoLock auto_lock(lock_);
|
| - // There is a race condition between filters to receive SeekTask().
|
| - // It turns out we could receive buffer from decoder before seek()
|
| - // is called on us. so we do the following:
|
| - // kFlushed => ( Receive first buffer or Seek() ) => kSeeking and
|
| - // kSeeking => ( Receive enough buffers) => kPrerolled. )
|
| - DCHECK(state_ == kPrerolled || state_ == kFlushed || state_ == kSeeking);
|
| + DCHECK_EQ(state_, kFlushed) << "Must flush prior to seeking.";
|
| DCHECK(!cb.is_null());
|
| DCHECK(seek_cb_.is_null());
|
|
|
| - if (state_ == kPrerolled) {
|
| - // Already get enough buffers from decoder.
|
| - run_callback = true;
|
| - } else {
|
| - // Otherwise we are either kFlushed or kSeeking, but without enough
|
| - // buffers we should save the callback function and call it later.
|
| - state_ = kSeeking;
|
| - seek_cb_ = cb;
|
| - }
|
| -
|
| + state_ = kSeeking;
|
| + seek_cb_ = cb;
|
| seek_timestamp_ = time;
|
| - ScheduleRead_Locked();
|
| + AttemptRead_Locked();
|
| }
|
| -
|
| - if (run_callback)
|
| - cb.Run(PIPELINE_OK);
|
| }
|
|
|
| void VideoRendererBase::Initialize(VideoDecoder* decoder,
|
| @@ -139,10 +122,6 @@ void VideoRendererBase::Initialize(VideoDecoder* decoder,
|
|
|
| statistics_callback_ = stats_callback;
|
|
|
| - decoder_->set_consume_video_frame_callback(
|
| - base::Bind(&VideoRendererBase::ConsumeVideoFrame,
|
| - base::Unretained(this)));
|
| -
|
| // Notify the pipeline of the video dimensions.
|
| host()->SetNaturalVideoSize(decoder_->natural_size());
|
|
|
| @@ -150,7 +129,8 @@ void VideoRendererBase::Initialize(VideoDecoder* decoder,
|
| // TODO(scherkus): do we trust subclasses not to do something silly while
|
| // we're holding the lock?
|
| if (!OnInitialize(decoder)) {
|
| - EnterErrorState_Locked(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| + state_ = kError;
|
| + host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| callback.Run();
|
| return;
|
| }
|
| @@ -164,7 +144,8 @@ void VideoRendererBase::Initialize(VideoDecoder* decoder,
|
| // Create our video thread.
|
| if (!base::PlatformThread::Create(0, this, &thread_)) {
|
| NOTREACHED() << "Video thread creation failed";
|
| - EnterErrorState_Locked(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| + state_ = kError;
|
| + host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
|
| callback.Run();
|
| return;
|
| }
|
| @@ -269,7 +250,7 @@ void VideoRendererBase::ThreadMain() {
|
|
|
| // If we got here then:
|
| // 1. next frame's timestamp is already current; or
|
| - // 2. we do not have any current frame yet anyway; or
|
| + // 2. we do not have a current frame yet; or
|
| // 3. a special case when the stream is badly formatted and
|
| // we got a frame with timestamp greater than overall duration.
|
| // In this case we should proceed anyway and try to obtain the
|
| @@ -290,11 +271,10 @@ void VideoRendererBase::ThreadMain() {
|
| if (remaining_time.InMicroseconds() > 0)
|
| break;
|
|
|
| - // Frame dropped: transfer ready frame into done queue and read again.
|
| - frames_queue_done_.push_back(frames_queue_ready_.front());
|
| - frames_queue_ready_.pop_front();
|
| - ScheduleRead_Locked();
|
| + // Frame dropped: read again.
|
| ++frames_dropped;
|
| + frames_queue_ready_.pop_front();
|
| + AttemptRead_Locked();
|
| }
|
|
|
| // Continue waiting for the current paint to finish.
|
| @@ -305,10 +285,9 @@ void VideoRendererBase::ThreadMain() {
|
| //
|
| // We can now safely update the current frame, request another frame, and
|
| // signal to the client that a new frame is available.
|
| - frames_queue_done_.push_back(current_frame_);
|
| current_frame_ = frames_queue_ready_.front();
|
| frames_queue_ready_.pop_front();
|
| - ScheduleRead_Locked();
|
| + AttemptRead_Locked();
|
|
|
| base::AutoUnlock auto_unlock(lock_);
|
| OnFrameAvailable();
|
| @@ -320,8 +299,7 @@ void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) {
|
| DCHECK(!pending_paint_ && !pending_paint_with_last_available_);
|
|
|
| if ((!current_frame_ || current_frame_->IsEndOfStream()) &&
|
| - (!last_available_frame_ ||
|
| - last_available_frame_->IsEndOfStream())) {
|
| + (!last_available_frame_ || last_available_frame_->IsEndOfStream())) {
|
| *frame_out = NULL;
|
| return;
|
| }
|
| @@ -364,161 +342,104 @@ void VideoRendererBase::PutCurrentFrame(scoped_refptr<VideoFrame> frame) {
|
| // frame when this is true.
|
| frame_available_.Signal();
|
| if (state_ == kFlushing) {
|
| - FlushBuffers_Locked();
|
| + AttemptFlush_Locked();
|
| } else if (state_ == kError || state_ == kStopped) {
|
| - DoStopOrErrorFlush_Locked();
|
| + DoStopOrError_Locked();
|
| }
|
| }
|
|
|
| -void VideoRendererBase::ConsumeVideoFrame(scoped_refptr<VideoFrame> frame) {
|
| - if (frame) {
|
| - PipelineStatistics statistics;
|
| - statistics.video_frames_decoded = 1;
|
| - statistics_callback_.Run(statistics);
|
| - }
|
| +void VideoRendererBase::FrameReady(scoped_refptr<VideoFrame> frame) {
|
| + DCHECK(frame);
|
|
|
| base::AutoLock auto_lock(lock_);
|
| -
|
| - if (!frame) {
|
| - EnterErrorState_Locked(PIPELINE_ERROR_DECODE);
|
| - return;
|
| - }
|
| -
|
| - // Decoder could reach seek state before our Seek() get called.
|
| - // We will enter kSeeking
|
| - if (state_ == kFlushed)
|
| - state_ = kSeeking;
|
| -
|
| - // Synchronous flush between filters should prevent this from happening.
|
| - DCHECK_NE(state_, kStopped);
|
| - if (frame && !frame->IsEndOfStream())
|
| - --pending_reads_;
|
| -
|
| DCHECK_NE(state_, kUninitialized);
|
| DCHECK_NE(state_, kStopped);
|
| DCHECK_NE(state_, kError);
|
| + DCHECK_NE(state_, kFlushed);
|
| + CHECK(pending_read_);
|
|
|
| - if (state_ == kPaused || state_ == kFlushing) {
|
| - // Decoder are flushing rubbish video frame, we will not display them.
|
| - if (frame && !frame->IsEndOfStream())
|
| - frames_queue_done_.push_back(frame);
|
| - DCHECK_LE(frames_queue_done_.size(),
|
| - static_cast<size_t>(Limits::kMaxVideoFrames));
|
| -
|
| - // Excluding kPause here, because in pause state, we will never
|
| - // transfer out-bounding buffer. We do not flush buffer when Compositor
|
| - // hold reference to our video frame either.
|
| - if (state_ == kFlushing && pending_paint_ == false)
|
| - FlushBuffers_Locked();
|
| + pending_read_ = false;
|
|
|
| + if (state_ == kFlushing) {
|
| + AttemptFlush_Locked();
|
| return;
|
| }
|
|
|
| // Discard frames until we reach our desired seek timestamp.
|
| if (state_ == kSeeking && !frame->IsEndOfStream() &&
|
| (frame->GetTimestamp() + frame->GetDuration()) <= seek_timestamp_) {
|
| - frames_queue_done_.push_back(frame);
|
| - ScheduleRead_Locked();
|
| - } else {
|
| - frames_queue_ready_.push_back(frame);
|
| - DCHECK_LE(frames_queue_ready_.size(),
|
| - static_cast<size_t>(Limits::kMaxVideoFrames));
|
| - frame_available_.Signal();
|
| + AttemptRead_Locked();
|
| + return;
|
| }
|
|
|
| - // Check for our preroll complete condition.
|
| - bool new_frame_available = false;
|
| - if (state_ == kSeeking) {
|
| - if (frames_queue_ready_.size() == Limits::kMaxVideoFrames ||
|
| - frame->IsEndOfStream()) {
|
| - // We're paused, so make sure we update |current_frame_| to represent
|
| - // our new location.
|
| - state_ = kPrerolled;
|
| -
|
| - // Because we might remain paused (i.e., we were not playing before we
|
| - // received a seek), we can't rely on ThreadMain() to notify the subclass
|
| - // the frame has been updated.
|
| - scoped_refptr<VideoFrame> first_frame;
|
| - first_frame = frames_queue_ready_.front();
|
| - if (!first_frame->IsEndOfStream()) {
|
| - frames_queue_ready_.pop_front();
|
| - current_frame_ = first_frame;
|
| - }
|
| - new_frame_available = true;
|
| + // This one's a keeper! Place it in the ready queue.
|
| + frames_queue_ready_.push_back(frame);
|
| + DCHECK_LE(frames_queue_ready_.size(),
|
| + static_cast<size_t>(Limits::kMaxVideoFrames));
|
| + frame_available_.Signal();
|
|
|
| - // If we reach prerolled state before Seek() is called by pipeline,
|
| - // |seek_callback_| is not set, we will return immediately during
|
| - // when Seek() is eventually called.
|
| - if (!seek_cb_.is_null()) {
|
| - ResetAndRunCB(&seek_cb_, PIPELINE_OK);
|
| - }
|
| - }
|
| - } else if (state_ == kFlushing && pending_reads_ == 0 && !pending_paint_) {
|
| - OnFlushDone_Locked();
|
| + PipelineStatistics statistics;
|
| + statistics.video_frames_decoded = 1;
|
| + statistics_callback_.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 (frames_queue_ready_.size() < Limits::kMaxVideoFrames &&
|
| + !frame->IsEndOfStream()) {
|
| + AttemptRead_Locked();
|
| + return;
|
| }
|
|
|
| - if (new_frame_available) {
|
| - base::AutoUnlock auto_unlock(lock_);
|
| - OnFrameAvailable();
|
| - }
|
| -}
|
| + // If we're at capacity or end of stream while seeking we need to transition
|
| + // to prerolled.
|
| + if (state_ == kSeeking) {
|
| + state_ = kPrerolled;
|
| +
|
| + // Because we might remain in the prerolled state for an undetermined amount
|
| + // of time (i.e., we were not playing before we received a seek), we'll
|
| + // manually update the current frame and notify the subclass below.
|
| + if (!frames_queue_ready_.front()->IsEndOfStream()) {
|
| + current_frame_ = frames_queue_ready_.front();
|
| + frames_queue_ready_.pop_front();
|
| + }
|
|
|
| -void VideoRendererBase::ReadInput(scoped_refptr<VideoFrame> frame) {
|
| - // We should never return empty frames or EOS frame.
|
| - DCHECK(frame && !frame->IsEndOfStream());
|
| + // ...and we're done seeking!
|
| + DCHECK(!seek_cb_.is_null());
|
| + ResetAndRunCB(&seek_cb_, PIPELINE_OK);
|
|
|
| - decoder_->ProduceVideoFrame(frame);
|
| - ++pending_reads_;
|
| + base::AutoUnlock ul(lock_);
|
| + OnFrameAvailable();
|
| + }
|
| }
|
|
|
| -void VideoRendererBase::ScheduleRead_Locked() {
|
| +void VideoRendererBase::AttemptRead_Locked() {
|
| lock_.AssertAcquired();
|
| DCHECK_NE(kEnded, state_);
|
| - // TODO(jiesun): We use dummy buffer to feed decoder to let decoder to
|
| - // provide buffer pools. In the future, we may want to implement real
|
| - // buffer pool to recycle buffers.
|
| - while (!frames_queue_done_.empty()) {
|
| - scoped_refptr<VideoFrame> video_frame = frames_queue_done_.front();
|
| - frames_queue_done_.pop_front();
|
| - ReadInput(video_frame);
|
| +
|
| + if (pending_read_ || frames_queue_ready_.size() == Limits::kMaxVideoFrames) {
|
| + return;
|
| }
|
| +
|
| + pending_read_ = true;
|
| + decoder_->Read(read_cb_);
|
| }
|
|
|
| -void VideoRendererBase::FlushBuffers_Locked() {
|
| +void VideoRendererBase::AttemptFlush_Locked() {
|
| lock_.AssertAcquired();
|
| - DCHECK(!pending_paint_);
|
| + DCHECK_EQ(kFlushing, state_);
|
|
|
| - // We should never put EOF frame into "done queue".
|
| + // Get rid of any ready frames.
|
| while (!frames_queue_ready_.empty()) {
|
| - scoped_refptr<VideoFrame> video_frame = frames_queue_ready_.front();
|
| - if (!video_frame->IsEndOfStream()) {
|
| - frames_queue_done_.push_back(video_frame);
|
| - }
|
| frames_queue_ready_.pop_front();
|
| }
|
| - if (current_frame_ && !current_frame_->IsEndOfStream()) {
|
| - frames_queue_done_.push_back(current_frame_);
|
| - }
|
| - current_frame_ = NULL;
|
| -
|
| - // Flush all buffers out to decoder.
|
| - ScheduleRead_Locked();
|
| -
|
| - if (pending_reads_ == 0 && state_ == kFlushing)
|
| - OnFlushDone_Locked();
|
| -}
|
| -
|
| -void VideoRendererBase::OnFlushDone_Locked() {
|
| - lock_.AssertAcquired();
|
| - // Check all buffers are returned to owners.
|
| - DCHECK_EQ(frames_queue_done_.size(), 0u);
|
| - DCHECK(!current_frame_);
|
| - DCHECK(frames_queue_ready_.empty());
|
|
|
| - if (!flush_callback_.is_null()) // This ensures callback is invoked once.
|
| + if (!pending_paint_ && !pending_read_) {
|
| + state_ = kFlushed;
|
| + current_frame_ = NULL;
|
| ResetAndRunCB(&flush_callback_);
|
| -
|
| - state_ = kFlushed;
|
| + }
|
| }
|
|
|
| base::TimeDelta VideoRendererBase::CalculateSleepDuration(
|
| @@ -550,59 +471,12 @@ base::TimeDelta VideoRendererBase::CalculateSleepDuration(
|
| static_cast<int64>(sleep.InMicroseconds() / playback_rate));
|
| }
|
|
|
| -void VideoRendererBase::EnterErrorState_Locked(PipelineStatus status) {
|
| - lock_.AssertAcquired();
|
| -
|
| - base::Closure callback;
|
| - State old_state = state_;
|
| - state_ = kError;
|
| -
|
| - // Flush frames if we aren't in the middle of a paint. If we
|
| - // are painting then flushing will happen when the paint completes.
|
| - if (!pending_paint_ && !pending_paint_with_last_available_)
|
| - DoStopOrErrorFlush_Locked();
|
| -
|
| - switch (old_state) {
|
| - case kUninitialized:
|
| - case kPrerolled:
|
| - case kPaused:
|
| - case kFlushed:
|
| - case kEnded:
|
| - case kPlaying:
|
| - break;
|
| -
|
| - case kFlushing:
|
| - CHECK(!flush_callback_.is_null());
|
| - std::swap(callback, flush_callback_);
|
| - break;
|
| -
|
| - case kSeeking:
|
| - CHECK(!seek_cb_.is_null());
|
| - ResetAndRunCB(&seek_cb_, status);
|
| - return;
|
| - break;
|
| -
|
| - case kStopped:
|
| - NOTREACHED() << "Should not error if stopped.";
|
| - return;
|
| -
|
| - case kError:
|
| - return;
|
| - }
|
| -
|
| - host()->SetError(status);
|
| -
|
| - if (!callback.is_null())
|
| - callback.Run();
|
| -}
|
| -
|
| -void VideoRendererBase::DoStopOrErrorFlush_Locked() {
|
| +void VideoRendererBase::DoStopOrError_Locked() {
|
| DCHECK(!pending_paint_);
|
| DCHECK(!pending_paint_with_last_available_);
|
| lock_.AssertAcquired();
|
| - FlushBuffers_Locked();
|
| last_available_frame_ = NULL;
|
| - DCHECK_EQ(pending_reads_, 0);
|
| + DCHECK(!pending_read_);
|
| }
|
|
|
| } // namespace media
|
|
|