diff options
author | jiesun@google.com <jiesun@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-14 21:00:25 +0000 |
---|---|---|
committer | jiesun@google.com <jiesun@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-14 21:00:25 +0000 |
commit | d3d048776df1ba9ad87a884a2cd5a0a944b52cda (patch) | |
tree | a95dbf055838997db5b5b88d0a04a51f1fcc4beb /media | |
parent | e7d9e170144853b9ef889ab5e5fcf44b4c9ef39d (diff) | |
download | chromium_src-d3d048776df1ba9ad87a884a2cd5a0a944b52cda.zip chromium_src-d3d048776df1ba9ad87a884a2cd5a0a944b52cda.tar.gz chromium_src-d3d048776df1ba9ad87a884a2cd5a0a944b52cda.tar.bz2 |
media: refactoring video_render_base to recycle buffers
To make recycle work, we had to define the usage scope of current frame. otherwise we are introducing tearing because we will begin to decode into the buffer before it is done by the renderer/painter/compositor.
current mechanism depends on hold reference of a copied picture. we had no that luxury if we do not copy output buffers. we had to compromise by
1. in pause() ( which is not the sense of pipeline->pause(), which is implemented by set playrate = 0 ) in filter->pause() ( or in the future flush() ), which is part of seeking ( and in the future, part of stop() too) , we had to return all the buffers to owner. we had no current buffer to display. we use NULL as current frame in this case.
2. remove black frame from render base, this is only valid for system memory based video frame, even that should we use color fill instead of color conversion and scale.
3. pause and stop has to wait for pending read (actually flush) and pending paint.
4. we only advance frame when there are two or more frames in ready queue.
Review URL: http://codereview.chromium.org/2836038
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52398 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/video_renderer_base.cc | 297 | ||||
-rw-r--r-- | media/filters/video_renderer_base.h | 17 | ||||
-rw-r--r-- | media/tools/player_x11/gl_video_renderer.cc | 6 | ||||
-rw-r--r-- | media/tools/player_x11/gles_video_renderer.cc | 5 | ||||
-rw-r--r-- | media/tools/player_x11/player_x11.cc | 5 | ||||
-rw-r--r-- | media/tools/player_x11/x11_video_renderer.cc | 7 |
6 files changed, 205 insertions, 132 deletions
diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc index da80221..c50dfd4 100644 --- a/media/filters/video_renderer_base.cc +++ b/media/filters/video_renderer_base.cc @@ -45,6 +45,7 @@ VideoRendererBase::VideoRendererBase() state_(kUninitialized), thread_(kNullThreadHandle), pending_reads_(0), + pending_paint_(false), playback_rate_(0) { } @@ -101,12 +102,15 @@ void VideoRendererBase::Pause(FilterCallback* callback) { pause_callback_.reset(callback); state_ = kPaused; - // We'll only pause when we've finished all pending reads. - if (pending_reads_ == 0) { + // TODO(jiesun): currently we use Pause() to fulfill Flush(). + // Filter is considered paused when we've finished all pending reads, which + // implies all buffers are returned to owner in Decoder/Renderer. Renderer + // is considered paused with one more contingency that |pending_paint_| is + // false, such that no client of us is holding any reference to VideoFrame. + if (pending_reads_ == 0 && pending_paint_ == false) { pause_callback_->Run(); pause_callback_.reset(); - } else { - state_ = kPaused; + FlushBuffers(); } } @@ -115,6 +119,10 @@ void VideoRendererBase::Stop(FilterCallback* callback) { AutoLock auto_lock(lock_); state_ = kStopped; + // TODO(jiesun): move this to flush. + // TODO(jiesun): we should wait until pending_paint_ is false; + FlushBuffers(); + // Clean up our thread if present. if (thread_) { // Signal the thread since it's possible to get stopped with the video @@ -145,10 +153,20 @@ void VideoRendererBase::Seek(base::TimeDelta time, FilterCallback* callback) { seek_callback_.reset(callback); // Throw away everything and schedule our reads. - frames_.clear(); + // TODO(jiesun): this should be guaranteed by pause/flush before seek happen. + frames_queue_ready_.clear(); + frames_queue_done_.clear(); for (size_t i = 0; i < kMaxFrames; ++i) { - ScheduleRead_Locked(); + // TODO(jiesun): this is dummy read for ffmpeg path until we truely recycle + // in that path. + scoped_refptr<VideoFrame> null_frame; + frames_queue_done_.push_back(null_frame); } + + // TODO(jiesun): if EGL image path make sure those video frames are already in + // frames_queue_done_, we could remove FillThisBuffer call from derived class. + // But currently that is trigger by first paint(), which is bad. + ScheduleRead_Locked(); } void VideoRendererBase::Initialize(VideoDecoder* decoder, @@ -182,10 +200,6 @@ void VideoRendererBase::Initialize(VideoDecoder* decoder, return; } - // Create a black frame so clients have something to render before we finish - // prerolling. - VideoFrame::CreateBlackFrame(width_, height_, ¤t_frame_); - // We're all good! Consider ourselves paused (ThreadMain() should never // see us in the kUninitialized state). state_ = kPaused; @@ -216,131 +230,149 @@ bool VideoRendererBase::HasEnded() { // PlatformThread::Delegate implementation. void VideoRendererBase::ThreadMain() { PlatformThread::SetName("CrVideoRenderer"); + base::TimeDelta remaining_time; + for (;;) { - // State and playback rate to assume for this iteration of the loop. - State state; - float playback_rate; - base::TimeDelta remaining_time; - { - AutoLock auto_lock(lock_); - state = state_; - playback_rate = playback_rate_; - - if (current_frame_->GetTimestamp() > host()->GetDuration()) { - // This is a special case when the stream is badly formatted that - // we get video frame with timestamp greater than the duration. - // In this case we should proceed anyway and try to obtain the - // end-of-stream packet. - remaining_time = base::TimeDelta(); - } else { - // 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. - remaining_time = current_frame_->GetTimestamp() - host()->GetTime(); - } - } - if (state == kStopped) { + AutoLock auto_lock(lock_); + + const base::TimeDelta kIdleTimeDelta = + base::TimeDelta::FromMilliseconds(kIdleMilliseconds); + + if (state_ == kStopped) return; + + if (state_ == kPaused || state_ == kSeeking || state_ == kEnded || + playback_rate_ == 0) { + remaining_time = kIdleTimeDelta; + } else if (frames_queue_ready_.empty() || + frames_queue_ready_.front()->IsEndOfStream()) { + if (current_frame_.get()) + remaining_time = CalculateSleepDuration(NULL, playback_rate_); + else + remaining_time = kIdleTimeDelta; + } else { + // 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. + scoped_refptr<VideoFrame> next_frame = frames_queue_ready_.front(); + remaining_time = CalculateSleepDuration(next_frame, playback_rate_); } - // Idle if we shouldn't be playing or advancing the frame yet. - if (state == kPaused || state == kSeeking || state == kEnded || - remaining_time.InMilliseconds() > kIdleMilliseconds || - playback_rate == 0) { - PlatformThread::Sleep(kIdleMilliseconds); + if (remaining_time > kIdleTimeDelta) + remaining_time = kIdleTimeDelta; + if (remaining_time.InMicroseconds() > 0) + frame_available_.TimedWait(remaining_time); + + if (state_ != kPlaying || playback_rate_ == 0) + continue; + + // Otherwise we're playing, so advance the frame and keep reading from the + // decoder when following condition is satisfied: + // 1. We had at least one backup frame. + // 2. We had not reached end of stream. + // 3. Current frame is out-dated. + if (frames_queue_ready_.empty()) + continue; + + scoped_refptr<VideoFrame> next_frame = frames_queue_ready_.front(); + if (next_frame->IsEndOfStream()) { + state_ = kEnded; + DLOG(INFO) << "Video render gets EOS"; + host()->NotifyEnded(); continue; } - // Advance |current_frame_| and try to determine |next_frame|. Note that - // this loop executes our "playing" logic. - DCHECK_EQ(kPlaying, state); - scoped_refptr<VideoFrame> next_frame; - { - AutoLock auto_lock(lock_); - // Check the actual state to see if we're trying to stop playing. - if (state_ != kPlaying) { - continue; + if (next_frame->GetTimestamp() <= host()->GetTime() + kIdleTimeDelta || + current_frame_.get() == NULL || + current_frame_->GetTimestamp() > host()->GetDuration()) { + // 1. either next frame's time-stamp is already current. + // 2. or we do not have any current frame yet anyway. + // 3. or a special case when the stream is badly formatted that + // we get video frame with time-stamp greater than the duration. + // In this case we should proceed anyway and try to obtain the + // end-of-stream packet. + scoped_refptr<VideoFrame> timeout_frame; + bool new_frame_available = false; + if (!pending_paint_) { + // In this case, current frame could be recycled. + timeout_frame = current_frame_; + current_frame_ = frames_queue_ready_.front(); + frames_queue_ready_.pop_front(); + new_frame_available = true; + } else if (pending_paint_ && frames_queue_ready_.size() >= 2 && + !frames_queue_ready_[1]->IsEndOfStream()) { + // Painter could be really slow, therefore we had to skip frames if + // 1. We had not reached end of stream. + // 2. The next frame of current frame is also out-dated. + base::TimeDelta next_remaining_time = + frames_queue_ready_[1]->GetTimestamp() - host()->GetTime(); + if (next_remaining_time.InMicroseconds() <= 0) { + // Since the current frame is still hold by painter/compositor, and + // the next frame is already timed-out, we should skip the next frame + // which is the first frame in the queue. + timeout_frame = frames_queue_ready_.front(); + frames_queue_ready_.pop_front(); + } } - - // Otherwise we're playing, so advance the frame and keep reading from the - // decoder if we haven't reach end of stream. - if (!frames_.empty() && !frames_.front()->IsEndOfStream()) { - DCHECK_EQ(current_frame_, frames_.front()); - frames_.pop_front(); - // TODO(wjia): Make sure |current_frame_| is not used by renderer any - // more. Could use fence or delay recycle by one frame. But both have - // problems. + if (timeout_frame.get()) { + // TODO(jiesun): we should really merge the following branch. That way + // we will remove the last EGLImage hack in this class. but the + // |pending_reads_| prevents use to do so (until we had implemented + // flush logic and get rid of pending reads.) if (uses_egl_image() && - media::VideoFrame::TYPE_EGL_IMAGE == current_frame_->type()) { - decoder_->FillThisBuffer(current_frame_); + media::VideoFrame::TYPE_EGL_IMAGE == timeout_frame->type()) { + decoder_->FillThisBuffer(timeout_frame); + } else { + // TODO(jiesun): could this be merged with EGLimage path? + frames_queue_done_.push_back(timeout_frame); + ScheduleRead_Locked(); } - ScheduleRead_Locked(); - } - - // While playing, we'll wait until a new frame arrives before updating - // |current_frame_|. - while (frames_.empty() && state_ == kPlaying) { - frame_available_.Wait(); } - - // If we ended up transitioning out of playing while waiting for a new - // frame, restart the iteration. - if (state_ != kPlaying) { - continue; - } - - // If the new front frame is end of stream, we've officially ended. - if (frames_.front()->IsEndOfStream()) { - state_ = kEnded; - LOG(INFO) << "Video render gets EOS"; - host()->NotifyEnded(); - continue; - } - - // Update our current frame and attempt to grab the next frame. - current_frame_ = frames_.front(); - if (frames_.size() >= 2 && !frames_[1]->IsEndOfStream()) { - next_frame = frames_[1]; + if (new_frame_available) { + AutoUnlock auto_unlock(lock_); + // Notify subclass that |current_frame_| has been updated. + OnFrameAvailable(); } } - - // Calculate our sleep duration. - base::TimeDelta sleep = CalculateSleepDuration(next_frame, playback_rate); - int sleep_ms = static_cast<int>(sleep.InMilliseconds()); - - // If we're too far behind to catch up, simply drop the frame. - // - // This has the effect of potentially dropping a few frames when playback - // resumes after being paused. The alternative (sleeping for 0 milliseconds - // and trying to catch up) looks worse. - if (sleep_ms < 0) - continue; - - // To be safe, limit our sleep duration. - // TODO(scherkus): handle seeking gracefully.. right now we tend to hit - // kMaxSleepMilliseconds a lot when we seek backwards. - if (sleep_ms > kMaxSleepMilliseconds) - sleep_ms = kMaxSleepMilliseconds; - - // Notify subclass that |current_frame_| has been updated. - OnFrameAvailable(); - - PlatformThread::Sleep(sleep_ms); } } void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) { AutoLock auto_lock(lock_); + DCHECK(!pending_paint_); - if (state_ == kStopped) { - VideoFrame::CreateBlackFrame(width_, height_, frame_out); + if (state_ == kStopped || !current_frame_.get() || + current_frame_->IsEndOfStream()) { + *frame_out = NULL; return; } + // We should have initialized and have the current frame. DCHECK(state_ == kPaused || state_ == kSeeking || state_ == kPlaying || state_ == kEnded); - DCHECK(current_frame_); *frame_out = current_frame_; + pending_paint_ = true; +} + +void VideoRendererBase::PutCurrentFrame(scoped_refptr<VideoFrame> frame) { + 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. + DCHECK(pending_paint_ || frame.get() == NULL); + DCHECK(current_frame_.get() == frame.get() || frame.get() == NULL); + + pending_paint_ = false; + // 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_ == kPaused && pending_reads_ == 0 && pause_callback_.get()) { + // No more pending reads! We're now officially "paused". + FlushBuffers(); + pause_callback_->Run(); + pause_callback_.reset(); + } } void VideoRendererBase::OnFillBufferDone(scoped_refptr<VideoFrame> frame) { @@ -349,6 +381,7 @@ void VideoRendererBase::OnFillBufferDone(scoped_refptr<VideoFrame> frame) { // TODO(ajwong): Work around cause we don't synchronize on stop. Correct // fix is to resolve http://crbug.com/16059. if (state_ == kStopped) { + // TODO(jiesun): Remove this when flush before stop landed! return; } @@ -358,33 +391,29 @@ void VideoRendererBase::OnFillBufferDone(scoped_refptr<VideoFrame> frame) { --pending_reads_; // Enqueue the frame. - frames_.push_back(frame); - DCHECK_LE(frames_.size(), kMaxFrames); + frames_queue_ready_.push_back(frame); + DCHECK_LE(frames_queue_ready_.size(), kMaxFrames); frame_available_.Signal(); // Check for our preroll complete condition. if (state_ == kSeeking) { DCHECK(seek_callback_.get()); - if (frames_.size() == kMaxFrames) { + if (frames_queue_ready_.size() == kMaxFrames) { // We're paused, so make sure we update |current_frame_| to represent // our new location. state_ = kPaused; - if (frames_.front()->IsEndOfStream()) { - VideoFrame::CreateBlackFrame(width_, height_, ¤t_frame_); - } else { - current_frame_ = frames_.front(); - } // 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. - DCHECK(current_frame_); + current_frame_ = frames_queue_ready_.front(); + frames_queue_ready_.pop_front(); OnFrameAvailable(); seek_callback_->Run(); seek_callback_.reset(); } - } else if (state_ == kPaused && pending_reads_ == 0) { + } else if (state_ == kPaused && pending_reads_ == 0 && !pending_paint_) { // No more pending reads! We're now officially "paused". if (pause_callback_.get()) { pause_callback_->Run(); @@ -396,13 +425,33 @@ void VideoRendererBase::OnFillBufferDone(scoped_refptr<VideoFrame> frame) { void VideoRendererBase::ScheduleRead_Locked() { lock_.AssertAcquired(); DCHECK_NE(kEnded, state_); - DCHECK_LT(pending_reads_, kMaxFrames); - ++pending_reads_; // 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. - scoped_refptr<VideoFrame> video_frame; - decoder_->FillThisBuffer(video_frame); + while (!frames_queue_done_.empty()) { + scoped_refptr<VideoFrame> video_frame = frames_queue_done_.front(); + frames_queue_done_.pop_front(); + decoder_->FillThisBuffer(video_frame); + DCHECK_LT(pending_reads_, kMaxFrames); + ++pending_reads_; + } +} + +void VideoRendererBase::FlushBuffers() { + DCHECK(!pending_paint_); + + // We should never put EOF frame into "done queue". + 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_.get() && !current_frame_->IsEndOfStream()) { + frames_queue_done_.push_back(current_frame_); + } + current_frame_ = NULL; } base::TimeDelta VideoRendererBase::CalculateSleepDuration( diff --git a/media/filters/video_renderer_base.h b/media/filters/video_renderer_base.h index a0bb0f4..af4f51c 100644 --- a/media/filters/video_renderer_base.h +++ b/media/filters/video_renderer_base.h @@ -56,9 +56,14 @@ class VideoRendererBase : public VideoRenderer, // PlatformThread::Delegate implementation. virtual void ThreadMain(); - // Assigns the current frame, which will never be NULL as long as this filter - // is initialized. + // Clients of this class (painter/compositor) should use GetCurrentFrame() + // obtain ownership of VideoFrame, it should always relinquish the ownership + // by use PutCurrentFrame(). Current frame is not guaranteed to be non-NULL. + // It expects clients to use color-fill the background if current frame + // is NULL. This could happen when before pipeline is pre-rolled or during + // pause/flush/seek. void GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out); + void PutCurrentFrame(scoped_refptr<VideoFrame> frame); protected: // Subclass interface. Called before any other initialization in the base @@ -111,6 +116,10 @@ class VideoRendererBase : public VideoRenderer, // Safe to call from any thread. void ScheduleRead_Locked(); + // Helper method that flushes all video frame in "ready queue" including + // current frame into "done queue". + void FlushBuffers(); + // Calculates the duration to sleep for based on |current_frame_|'s timestamp, // the next frame timestamp (may be NULL), and the provided playback rate. // @@ -131,7 +140,8 @@ class VideoRendererBase : public VideoRenderer, // Queue of incoming frames as well as the current frame since the last time // OnFrameAvailable() was called. typedef std::deque< scoped_refptr<VideoFrame> > VideoFrameQueue; - VideoFrameQueue frames_; + VideoFrameQueue frames_queue_ready_; + VideoFrameQueue frames_queue_done_; scoped_refptr<VideoFrame> current_frame_; // Used to signal |thread_| as frames are added to |frames_|. Rule of thumb: @@ -162,6 +172,7 @@ class VideoRendererBase : public VideoRenderer, // // We use size_t since we compare against std::deque::size(). size_t pending_reads_; + bool pending_paint_; float playback_rate_; diff --git a/media/tools/player_x11/gl_video_renderer.cc b/media/tools/player_x11/gl_video_renderer.cc index e2f4681..29b4f38 100644 --- a/media/tools/player_x11/gl_video_renderer.cc +++ b/media/tools/player_x11/gl_video_renderer.cc @@ -250,8 +250,11 @@ void GlVideoRenderer::Paint() { scoped_refptr<media::VideoFrame> video_frame; GetCurrentFrame(&video_frame); - if (!video_frame) + if (!video_frame) { + // TODO(jiesun): Use color fill rather than create black frame then scale. + PutCurrentFrame(video_frame); return; + } // Convert YUV frame to RGB. DCHECK(video_frame->format() == media::VideoFrame::YV12 || @@ -275,6 +278,7 @@ void GlVideoRenderer::Paint() { glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, video_frame->data(i)); } + PutCurrentFrame(video_frame); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glXSwapBuffers(display_, window_); diff --git a/media/tools/player_x11/gles_video_renderer.cc b/media/tools/player_x11/gles_video_renderer.cc index 0b36e74..f1942ba 100644 --- a/media/tools/player_x11/gles_video_renderer.cc +++ b/media/tools/player_x11/gles_video_renderer.cc @@ -175,8 +175,8 @@ void GlesVideoRenderer::Paint() { scoped_refptr<media::VideoFrame> video_frame; GetCurrentFrame(&video_frame); - if (!video_frame.get()) { + PutCurrentFrame(video_frame); return; } @@ -191,6 +191,8 @@ void GlesVideoRenderer::Paint() { eglSwapBuffers(egl_display_, egl_surface_); } } + // TODO(jiesun/wjia): use fence before call put. + PutCurrentFrame(video_frame); return; } @@ -237,6 +239,7 @@ void GlesVideoRenderer::Paint() { glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); } + PutCurrentFrame(video_frame); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); eglSwapBuffers(egl_display_, egl_surface_); diff --git a/media/tools/player_x11/player_x11.cc b/media/tools/player_x11/player_x11.cc index 16b453a..a361368 100644 --- a/media/tools/player_x11/player_x11.cc +++ b/media/tools/player_x11/player_x11.cc @@ -137,7 +137,10 @@ void PeriodicalUpdate( MessageLoop* message_loop, bool audio_only) { if (!g_running) { - message_loop->Quit(); + // interrupt signal is received during lat time period. + // Quit message_loop only when pipeline is fully stopped. + pipeline->Stop(media::TaskToCallbackAdapter::NewCallback( + NewRunnableFunction(Quit, message_loop))); return; } diff --git a/media/tools/player_x11/x11_video_renderer.cc b/media/tools/player_x11/x11_video_renderer.cc index 881a719..707a578 100644 --- a/media/tools/player_x11/x11_video_renderer.cc +++ b/media/tools/player_x11/x11_video_renderer.cc @@ -139,9 +139,11 @@ void X11VideoRenderer::OnFrameAvailable() { void X11VideoRenderer::Paint() { scoped_refptr<media::VideoFrame> video_frame; GetCurrentFrame(&video_frame); - - if (!image_ ||!video_frame) + if (!image_ || !video_frame) { + // TODO(jiesun): Use color fill rather than create black frame then scale. + PutCurrentFrame(video_frame); return; + } // Convert YUV frame to RGB. DCHECK(video_frame->format() == media::VideoFrame::YV12 || @@ -164,6 +166,7 @@ void X11VideoRenderer::Paint() { video_frame->stride(media::VideoFrame::kUPlane), image_->bytes_per_line, yuv_type); + PutCurrentFrame(video_frame); if (use_render_) { // If XRender is used, we'll upload the image to a pixmap. And then |