diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-22 22:23:19 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-22 22:23:19 +0000 |
commit | 1f500f5da1388687eb302d31f084fa70ac903121 (patch) | |
tree | dfa62b5a4791e6e13f0df08909713143694955fe /media | |
parent | c6b20fa7be447131bd6643dbda3a4965db988a04 (diff) | |
download | chromium_src-1f500f5da1388687eb302d31f084fa70ac903121.zip chromium_src-1f500f5da1388687eb302d31f084fa70ac903121.tar.gz chromium_src-1f500f5da1388687eb302d31f084fa70ac903121.tar.bz2 |
Renamed media::VideoThread to the more appropriate media::VideoRendererBase.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/141059
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18970 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/video_renderer_base.cc | 391 | ||||
-rw-r--r-- | media/filters/video_renderer_base.h | 231 | ||||
-rw-r--r-- | media/filters/video_renderer_unittest.cc | 56 | ||||
-rw-r--r-- | media/filters/video_thread.cc | 274 | ||||
-rw-r--r-- | media/filters/video_thread.h | 120 | ||||
-rw-r--r-- | media/media.gyp | 3 | ||||
-rw-r--r-- | media/player/wtl_renderer.h | 5 |
7 files changed, 299 insertions, 781 deletions
diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc index ccc31fb..4a5c0b5 100644 --- a/media/filters/video_renderer_base.cc +++ b/media/filters/video_renderer_base.cc @@ -4,228 +4,271 @@ #include "media/base/buffers.h" #include "media/base/filter_host.h" -#include "media/base/pipeline.h" #include "media/filters/video_renderer_base.h" namespace media { -const size_t VideoRendererBase::kDefaultNumberOfFrames = 4; +// Limit our read ahead to three frames. One frame is typically in flux at all +// times, as in frame n is discarded at the top of ThreadMain() while frame +// (n + kMaxFrames) is being asynchronously fetched. The remaining two frames +// allow us to advance the current frame as well as read the timestamp of the +// following frame for more accurate timing. +// +// Increasing this number beyond 3 simply creates a larger buffer to work with +// at the expense of memory (~0.5MB and ~1.3MB per frame for 480p and 720p +// resolutions, respectively). This can help on lower-end systems if there are +// difficult sections in the movie and decoding slows down. +static const size_t kMaxFrames = 3; -const base::TimeDelta VideoRendererBase::kDefaultSkipFrameDelta = - base::TimeDelta::FromMilliseconds(2); +// Sleeping for negative amounts actually hangs your thread on Windows! +static const int64 kMinSleepMilliseconds = 0; -const base::TimeDelta VideoRendererBase::kDefaultEmptyQueueSleep = - base::TimeDelta::FromMilliseconds(15); - -//------------------------------------------------------------------------------ +// This equates to ~13.33 fps, which is just under the typical 15 fps that +// lower quality cameras or shooting modes usually use for video encoding. +static const int64 kMaxSleepMilliseconds = 75; VideoRendererBase::VideoRendererBase() - : number_of_frames_(kDefaultNumberOfFrames), - skip_frame_delta_(kDefaultSkipFrameDelta), - empty_queue_sleep_(kDefaultEmptyQueueSleep), - number_of_reads_needed_(kDefaultNumberOfFrames), - submit_reads_task_(NULL), - preroll_complete_(false) { -} - -VideoRendererBase::VideoRendererBase(size_t number_of_frames, - base::TimeDelta skip_frame_delta, - base::TimeDelta empty_queue_sleep) - : number_of_frames_(number_of_frames), - skip_frame_delta_(skip_frame_delta), - empty_queue_sleep_(empty_queue_sleep), - number_of_reads_needed_(number_of_frames), - submit_reads_task_(NULL), - preroll_complete_(false) { + : frame_available_(&lock_), + state_(UNINITIALIZED), + thread_(NULL), + playback_rate_(0) { } VideoRendererBase::~VideoRendererBase() { - Stop(); -} - -// static -bool VideoRendererBase::IsMediaFormatSupported( - const MediaFormat& media_format) { - int width; - int height; - return ParseMediaFormat(media_format, &width, &height); + AutoLock auto_lock(lock_); + DCHECK(state_ == UNINITIALIZED || state_ == STOPPED); } // static bool VideoRendererBase::ParseMediaFormat(const MediaFormat& media_format, - int* width_out, - int* height_out) { - DCHECK(width_out && height_out); + int* width_out, int* height_out) { std::string mime_type; - return (media_format.GetAsString(MediaFormat::kMimeType, &mime_type) && - mime_type.compare(mime_type::kUncompressedVideo) == 0 && - media_format.GetAsInteger(MediaFormat::kWidth, width_out) && - media_format.GetAsInteger(MediaFormat::kHeight, height_out)); + if (!media_format.GetAsString(MediaFormat::kMimeType, &mime_type)) + return false; + if (mime_type.compare(mime_type::kUncompressedVideo) != 0) + return false; + if (!media_format.GetAsInteger(MediaFormat::kWidth, width_out)) + return false; + if (!media_format.GetAsInteger(MediaFormat::kHeight, height_out)) + return false; + return true; } void VideoRendererBase::Stop() { - OnStop(); AutoLock auto_lock(lock_); - DiscardAllFrames(); - if (submit_reads_task_) { - // The task is owned by the message loop, so we don't delete it here. We - // know the task won't call us because we canceled it, and we know we are - // on the pipeline thread, since we're in the filter's Stop method, so there - // is no threading problem. Just let the task be run by the message loop - // and then be killed. - submit_reads_task_->Cancel(); - submit_reads_task_ = NULL; + state_ = STOPPED; + if (thread_) { + // Signal the thread since it's possible to get stopped with the video + // thread waiting for a read to complete. + frame_available_.Signal(); + { + AutoUnlock auto_unlock(lock_); + PlatformThread::Join(thread_); + } + thread_ = NULL; } - decoder_ = NULL; } -bool VideoRendererBase::Initialize(VideoDecoder* decoder) { - int width, height; - decoder_ = decoder; - if (ParseMediaFormat(decoder_->media_format(), &width, &height) && - OnInitialize(width, height)) { - host_->SetVideoSize(width, height); - host_->SetTimeUpdateCallback( - NewCallback(this, &VideoRendererBase::TimeUpdateCallback)); - SubmitReads(); - return true; +void VideoRendererBase::SetPlaybackRate(float playback_rate) { + AutoLock auto_lock(lock_); + playback_rate_ = playback_rate; +} + +void VideoRendererBase::Seek(base::TimeDelta time) { + AutoLock auto_lock(lock_); + // We need the first frame in |frames_| to run the VideoRendererBase main + // loop, but we don't need decoded frames after the first frame since we are + // at a new time. We should get some new frames so issue reads to compensate + // for those discarded. + while (frames_.size() > 1) { + frames_.pop_back(); + ScheduleRead(); } - decoder_ = NULL; - return false; } -void VideoRendererBase::SubmitReads() { - int number_to_read; - { - AutoLock auto_lock(lock_); - submit_reads_task_ = NULL; - number_to_read = number_of_reads_needed_; - number_of_reads_needed_ = 0; +bool VideoRendererBase::Initialize(VideoDecoder* decoder) { + AutoLock auto_lock(lock_); + DCHECK_EQ(state_, UNINITIALIZED); + state_ = INITIALIZING; + decoder_ = decoder; + + // Notify the pipeline of the video dimensions. + int width = 0; + int height = 0; + if (!ParseMediaFormat(decoder->media_format(), &width, &height)) + return false; + host_->SetVideoSize(width, height); + + // Initialize the subclass. + // TODO(scherkus): do we trust subclasses not to do something silly while + // we're holding the lock? + if (!OnInitialize(decoder)) + return false; + + // Create our video thread. + if (!PlatformThread::Create(0, this, &thread_)) { + NOTREACHED() << "Video thread creation failed"; + return false; } - while (number_to_read > 0) { - decoder_->Read(NewCallback(this, &VideoRendererBase::ReadComplete)); - --number_to_read; + +#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) + + // Queue initial reads. + for (size_t i = 0; i < kMaxFrames; ++i) { + ScheduleRead(); } + + return true; } -bool VideoRendererBase::UpdateQueue(base::TimeDelta time, - VideoFrame* new_frame) { - lock_.AssertAcquired(); - bool updated_front = false; - - // If a new frame is passed in then put it at the back of the queue. If the - // queue was empty, then we've updated the front too. - if (new_frame) { - updated_front = queue_.empty(); - new_frame->AddRef(); - queue_.push_back(new_frame); - } +// PlatformThread::Delegate implementation. +void VideoRendererBase::ThreadMain() { + PlatformThread::SetName("VideoThread"); + + // Wait to be initialized so we can notify the first frame is available. + if (!WaitForInitialized()) + return; + OnFrameAvailable(); - // Now make sure that the front of the queue is the correct frame to display - // right now. Discard any frames that are past the current time. If any - // frames are discarded then increment the |number_of_reads_needed_| member. - if (preroll_complete_) { - while (queue_.size() > 1 && - queue_[1]->GetTimestamp() - skip_frame_delta_ <= time) { - queue_.front()->Release(); - queue_.pop_front(); - updated_front = true; - ++number_of_reads_needed_; + for (;;) { + // State and playback rate to assume for this iteration of the loop. + State state; + float playback_rate; + { + AutoLock auto_lock(lock_); + state = state_; + playback_rate = playback_rate_; } - } + if (state == STOPPED) { + return; + } + DCHECK_EQ(state, INITIALIZED); - // If any frames have been removed we need to call the decoder again. Note - // that the PostSubmitReadsTask method will only post the task if there are - // pending reads. - PostSubmitReadsTask(); + // Sleep for 10 milliseconds while paused. + if (playback_rate == 0) { + PlatformThread::Sleep(10); + continue; + } - // True if the front of the queue is a new frame. - return updated_front; -} + // Advance |current_frame_| and try to determine |next_frame|. + scoped_refptr<VideoFrame> next_frame; + { + AutoLock auto_lock(lock_); + DCHECK(!frames_.empty()); + DCHECK_EQ(current_frame_, frames_.front()); + frames_.pop_front(); + ScheduleRead(); + while (frames_.empty()) { + frame_available_.Wait(); -void VideoRendererBase::DiscardAllFrames() { - lock_.AssertAcquired(); - while (!queue_.empty()) { - queue_.front()->Release(); - queue_.pop_front(); - ++number_of_reads_needed_; + // We have the lock again, check the actual state to see if we're trying + // to stop. + if (state_ == STOPPED) { + return; + } + } + current_frame_ = frames_.front(); + if (frames_.size() >= 2) { + next_frame = frames_[1]; + } + } + + // Notify subclass that |current_frame_| has been updated. + OnFrameAvailable(); + + // Determine the current and next presentation timestamps. + base::TimeDelta now = host_->GetPipelineStatus()->GetTime(); + base::TimeDelta this_pts = current_frame_->GetTimestamp(); + base::TimeDelta next_pts; + if (next_frame) { + next_pts = next_frame->GetTimestamp(); + } else { + next_pts = this_pts + current_frame_->GetDuration(); + } + + // Determine our sleep duration based on whether time advanced. + base::TimeDelta sleep; + if (now == previous_time_) { + // Time has not changed, assume we sleep for the frame's duration. + sleep = next_pts - this_pts; + } else { + // Time has changed, figure out real sleep duration. + sleep = next_pts - now; + previous_time_ = now; + } + + // Scale our sleep based on the playback rate. + // TODO(scherkus): floating point badness and degrade gracefully. + int sleep_ms = static_cast<int>(sleep.InMicroseconds() / playback_rate / + base::Time::kMicrosecondsPerMillisecond); + + // To be safe, limit our sleep duration. + // TODO(scherkus): handle seeking gracefully.. right now a seek backwards + // will hit kMinSleepMilliseconds whereas a seek forward will hit + // kMaxSleepMilliseconds. + if (sleep_ms < kMinSleepMilliseconds) + sleep_ms = kMinSleepMilliseconds; + else if (sleep_ms > kMaxSleepMilliseconds) + sleep_ms = kMaxSleepMilliseconds; + + PlatformThread::Sleep(sleep_ms); } } -// Assumes |lock_| has been acquired! -void VideoRendererBase::PostSubmitReadsTask() { - if (number_of_reads_needed_ > 0 && !submit_reads_task_) { - submit_reads_task_ = NewRunnableMethod(this, - &VideoRendererBase::SubmitReads); - host_->PostTask(submit_reads_task_); - } +void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) { + AutoLock auto_lock(lock_); + // Either we have initialized or we have the current frame. + DCHECK(state_ != INITIALIZED || current_frame_); + *frame_out = current_frame_; } -void VideoRendererBase::TimeUpdateCallback(base::TimeDelta time) { - bool request_repaint; - { - AutoLock auto_lock(lock_); - request_repaint = (IsRunning() && UpdateQueue(time, NULL)); - } - if (request_repaint) { - OnPaintNeeded(); +void VideoRendererBase::OnReadComplete(VideoFrame* frame) { + AutoLock auto_lock(lock_); + // If this is an end of stream frame, don't enqueue it since it has no data. + if (!frame->IsEndOfStream()) { + frames_.push_back(frame); + DCHECK_LE(frames_.size(), kMaxFrames); + frame_available_.Signal(); } -} -void VideoRendererBase::ReadComplete(VideoFrame* video_frame) { - bool call_initialized = false; - bool request_repaint = false; - { - AutoLock auto_lock(lock_); - if (IsRunning()) { - if (video_frame->IsDiscontinuous()) { - DiscardAllFrames(); - } - // If this is not an end of stream frame, update the queue with it. - // An end of stream of frame has no data. - if (!video_frame->IsEndOfStream() && - UpdateQueue(host_->GetPipelineStatus()->GetInterpolatedTime(), - video_frame)) { - request_repaint = preroll_complete_; - } - if (!preroll_complete_ && (queue_.size() == number_of_frames_ || - video_frame->IsEndOfStream())) { - preroll_complete_ = true; - call_initialized = true; - request_repaint = true; - } + // Check for our initialization condition. + if (state_ == INITIALIZING && + (frames_.size() == kMaxFrames || frame->IsEndOfStream())) { + if (frames_.empty()) { + // We should have initialized but there's no decoded frames in the queue. + // Raise an error. + host_->Error(PIPELINE_ERROR_NO_DATA); + } else { + state_ = INITIALIZED; + current_frame_ = frames_.front(); + host_->InitializationComplete(); } } - // |lock_| no longer held. Call the pipeline if we've just entered a - // completed preroll state. - if (call_initialized) { - host_->InitializationComplete(); - } - if (request_repaint) { - OnPaintNeeded(); - } } -void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) { +void VideoRendererBase::ScheduleRead() { + decoder_->Read(NewCallback(this, &VideoRendererBase::OnReadComplete)); +} + +bool VideoRendererBase::WaitForInitialized() { + // This loop essentially handles preroll. We wait until we've been fully + // initialized so we can call OnFrameAvailable() to provide subclasses with + // the first frame. AutoLock auto_lock(lock_); - *frame_out = NULL; - if (IsRunning()) { - base::TimeDelta time = host_->GetPipelineStatus()->GetInterpolatedTime(); - base::TimeDelta time_next_frame; - UpdateQueue(time, NULL); - if (queue_.empty()) { - time_next_frame = time + empty_queue_sleep_; - } else { - *frame_out = queue_.front(); - if (queue_.size() == 1) { - time_next_frame = (*frame_out)->GetTimestamp() + - (*frame_out)->GetDuration(); - } else { - time_next_frame = queue_[1]->GetTimestamp(); - } + DCHECK_EQ(state_, INITIALIZING); + while (state_ == INITIALIZING) { + frame_available_.Wait(); + if (state_ == STOPPED) { + return false; } - host_->ScheduleTimeUpdateCallback(time_next_frame); } + DCHECK_EQ(state_, INITIALIZED); + DCHECK(current_frame_); + return true; } -} // namespace +} // namespace media diff --git a/media/filters/video_renderer_base.h b/media/filters/video_renderer_base.h index dccaf44..7e0868b 100644 --- a/media/filters/video_renderer_base.h +++ b/media/filters/video_renderer_base.h @@ -2,186 +2,115 @@ // source code is governed by a BSD-style license that can be found in the // LICENSE file. -// A base class that provides the plumbing for a video renderer. Derived -// classes must implement the following methods: -// OnInitialized -// OnStop (optional) -// OnPaintNeeded +// VideoRendererBase creates its own thread for the sole purpose of timing frame +// presentation. It handles reading from the decoder and stores the results in +// a queue of decoded frames, calling OnFrameAvailable() on subclasses to notify +// when a frame is ready to display. // -// The derived class can determine what frame needs to be painted by calling -// the GetCurrentFrame method. +// The media filter methods Initialize(), Stop(), SetPlaybackRate() and Seek() +// should be serialized, which they commonly are the pipeline thread. +// As long as VideoRendererBase is initialized, GetCurrentFrame() is safe to +// call from any thread, at any time, including inside of OnFrameAvailable(). #ifndef MEDIA_FILTERS_VIDEO_RENDERER_BASE_H_ #define MEDIA_FILTERS_VIDEO_RENDERER_BASE_H_ #include <deque> +#include "base/condition_variable.h" #include "base/lock.h" -#include "base/task.h" #include "media/base/filters.h" namespace media { -//------------------------------------------------------------------------------ - -// Contains the core logic for submitting reads to the video decoder, queueing -// the decoded frames, and waking up when the next frame needs to be drawn. -// The actual renderer class that is derived from this base class is responsible -// for actually drawing the video frames. When it is time for the next frame -// in the queue to be drawn, the OnPaintNeeded() will be called to notify the -// derived renderer that it needs to call GetCurrentFrame() and paint. -class VideoRendererBase : public VideoRenderer { +// TODO(scherkus): to avoid subclasses, consider using a peer/delegate interface +// and pass in a reference to the constructor. +class VideoRendererBase : public VideoRenderer, + public PlatformThread::Delegate { public: + VideoRendererBase(); + virtual ~VideoRendererBase(); + + // Helper method for subclasses to parse out video-related information from + // a MediaFormat. Returns true if |width_out| and |height_out| were assigned. + static bool ParseMediaFormat(const MediaFormat& media_format, + int* width_out, int* height_out); + // MediaFilter implementation. virtual void Stop(); + virtual void SetPlaybackRate(float playback_rate); + virtual void Seek(base::TimeDelta time); // VideoRenderer implementation. virtual bool Initialize(VideoDecoder* decoder); - protected: - // Default tuning parameter values used to initialize the base class. - static const size_t kDefaultNumberOfFrames; - static const base::TimeDelta kDefaultSkipFrameDelta; - static const base::TimeDelta kDefaultEmptyQueueSleep; + // PlatformThread::Delegate implementation. + virtual void ThreadMain(); - // Constructor that uses defaults for all tuning parameters. - VideoRendererBase(); + // Assigns the current frame, which will never be NULL as long as this filter + // is initialized. + void GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out); - // Constructor allows derived class to specify the tuning parameters used - // by the base class. See the comments for the member variables - // |number_of_frames_|, |skip_frame_delta_|, and |empty_queue_sleep_| for - // details on the meaning of these parameters. - VideoRendererBase(size_t number_of_frames, - base::TimeDelta skip_frame_delta, - base::TimeDelta empty_queue_sleep); + protected: + // Subclass interface. Called before any other initialization in the base + // class takes place. + // + // Implementors typically use the media format of |decoder| to create their + // output surfaces. Implementors should NOT call InitializationComplete(). + virtual bool OnInitialize(VideoDecoder* decoder) = 0; + + // Subclass interface. Called when a new frame is ready for display, which + // can be accessed via GetCurrentFrame(). + // + // Implementors should avoid doing any sort of heavy work in this method and + // instead post a task to a common/worker thread to handle rendering. Slowing + // down the video thread may result in losing synchronization with audio. + virtual void OnFrameAvailable() = 0; - virtual ~VideoRendererBase(); + private: + // Read complete callback from video decoder. + void OnReadComplete(VideoFrame* frame); - // Method that must be implemented by the derived class. Called from within - // the VideoRenderer::Initialize method before any reads are submitted to - // the decoder. Returns true if successful, otherwise false indicates a - // fatal error. - virtual bool OnInitialize(size_t width, size_t height) = 0; - - // Method that may be implemented by the derived class if desired. It will - // be called from within the MediaFilter::Stop method prior to stopping the - // base class. - virtual void OnStop() {} - - // Method that must be implemented by the derived class. When the base class - // detects a situation that requires a repaint, it calls this method to - // request that the derived class paint again. This method can be called on - // any thread, so typically derived classes would post a message or - // otherwise wake up a paint thread, but it is acceptable to call the - // GetCurrentFrame() method from within OnPaintNeeded(). - virtual void OnPaintNeeded() = 0; - - // Gets the frame based on the current pipeline's time. If the queue is - // empty, |*frame_out| will be NULL, otherwise it will contain the frame - // that should be displayed. - void GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out); + // Helper method that schedules an asynchronous read from the decoder. + // + // Safe to call from any thread. + void ScheduleRead(); - // Answers question from the factory to see if we accept |format|. - static bool IsMediaFormatSupported(const MediaFormat& format); + // Called by ThreadMain() to handle preroll. Returns false if the thread + // should exit due to Stop() being called. + bool WaitForInitialized(); - // Used by the IsMediaFormatSupported and Initialize methods. Examines the - // |media_format| and returns true if the format is supported. Both output - // parameters, |width_out| and |height_out| are required and must not be NULL. - static bool ParseMediaFormat(const MediaFormat& media_format, - int* width_out, - int* height_out); + scoped_refptr<VideoDecoder> decoder_; - private: - // Called from decoder's Read() method when a frame is available. - void ReadComplete(VideoFrame* video_frame); - - // Used internally to post a task that will call the SubmitReads() method. - // The |lock_| must be acquired before calling this method. If the value of - // |number_of_reads_needed_| is 0 or if there is already a pending task then - // this method simply returns and does not post a new task. - void PostSubmitReadsTask(); - - // Examines the |number_of_reads_needed_| member and calls the decoder to - // read the necessary number of frames. - void SubmitReads(); - - // Throw away all frames in the queue. The |lock_| must have been acquired - // before calling this method. - void DiscardAllFrames(); - - // This method is always called with the object's |lock_| acquired.. - // The bool return value indicates weather or not the front of the queue has - // been updated. If this method returns true, then the front of the queue - // is a new video frame, otherwise, the front is the same as the last call. - // Given the current |time|, this method updates the state of the video frame - // queue. The caller may pass in a |new_frame| which will be added to the - // queue in the appropriate position based on the frame's timestamp. - bool UpdateQueue(base::TimeDelta time, VideoFrame* new_frame); - - // Called when the clock is updated by the audio renderer of when a scheduled - // callback is called based on the interpolated current position of the media - // stream. - void TimeUpdateCallback(base::TimeDelta time); - - // For simplicity, we use the |decoder_| member to indicate if we have been - // stopped or not. If this method is called on a thread that is not the - // pipeline thread (the thread that the renderer was created on) then the - // caller must hold the |lock_| and must continue to hold the |lock_| after - // this check when accessing any member variables of this class. See comments - // on the |lock_| member for details. - bool IsRunning() const { return (decoder_.get() != NULL); } - - // Critical section. There is only one for this object. Used to serialize - // access to the following members: - // |queue_| for obvious reasons - // |decoder_| because it is used by methods that can be called from random - // threads to determine if the renderer has been stopped (it will be - // NULL if stopped) - // |submit_reads_task_| to prevent multiple scheduling of the task and to - // allow for safe cancelation of the task. - // |number_of_reads_needed_| is modified by UpdateQueue from the decoder - // thread, the renderer thread, and the pipeline thread. - // |preroll_complete_| has a very small potential race condition if the - // OnAssignment method were reentered for the last frame in the queue - // and an end-of-stream frame. + // 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_; + scoped_refptr<VideoFrame> current_frame_; + + // Used for accessing |frames_|. Lock lock_; - // Pointer to the decoder that will feed us with video frames. - scoped_refptr<VideoDecoder> decoder_; + // Used to signal |thread_| as frames are added to |frames_|. Rule of thumb: + // always check |state_| to see if it was set to STOPPED after waking up! + ConditionVariable frame_available_; + + // Simple state tracking variable. + enum State { + UNINITIALIZED, + INITIALIZING, + INITIALIZED, + STOPPED, + }; + State state_; + + // Video thread handle. + PlatformThreadHandle thread_; + + // Previous time returned from the pipeline. + base::TimeDelta previous_time_; - // Number of frames either in the |queue_| or pending in the video decoder's - // read queue. - const size_t number_of_frames_; - - // The amount of time to skip ahead to the next frame is one is available. - // If there are at least two frames in the queue, and the current time is - // within the |skip_frame_delta_| from the next frame's timestamp, then the - // UpdateQueue method will skip to the next frame. - const base::TimeDelta skip_frame_delta_; - - // The UpdateQueue method will return the current time + |empty_queue_sleep_| - // if there are no frames in the |queue_| but the caller has requested the - // timestamp of the next frame. - const base::TimeDelta empty_queue_sleep_; - - // The number of buffers we need to request. This member is updated by any - // method that removes frames from the queue, such as UpdateQueue and - // DiscardAllFrames. - int number_of_reads_needed_; - - // If non-NULL then a task has been scheduled to submit read requests to the - // video decoder. - CancelableTask* submit_reads_task_; - - // True if we have received a full queue of video frames from the decoder. - // We don't call FilterHost::InitializationComplete() until the the queue - // is full. - bool preroll_complete_; - - // The queue of video frames. The front of the queue is the frame that should - // be displayed. - typedef std::deque<VideoFrame*> VideoFrameQueue; - VideoFrameQueue queue_; + float playback_rate_; DISALLOW_COPY_AND_ASSIGN(VideoRendererBase); }; diff --git a/media/filters/video_renderer_unittest.cc b/media/filters/video_renderer_unittest.cc deleted file mode 100644 index 9790b24..0000000 --- a/media/filters/video_renderer_unittest.cc +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2009 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/base/factory.h" -#include "media/base/filters.h" -#include "media/base/mock_media_filters.h" -#include "media/base/mock_filter_host.h" -#include "media/filters/test_video_renderer.h" -#include "testing/gtest/include/gtest/gtest.h" - -using media::MockFilterHost; -using media::MockPipeline; -using media::TestVideoRenderer; -using media::old_mocks::MockFilterConfig; -using media::old_mocks::MockVideoDecoder; - -TEST(VideoRenderer, CreateAndPlay) { - // Prepare test data. - MockFilterConfig config; - scoped_refptr<MockVideoDecoder> decoder = new MockVideoDecoder(&config); - scoped_refptr<TestVideoRenderer> renderer = new TestVideoRenderer(); - - // Setup our mock pipeline. - MockPipeline pipeline; - MockFilterHost<MockVideoDecoder> filter_host_a(&pipeline, decoder); - MockFilterHost<TestVideoRenderer> filter_host_b(&pipeline, renderer); - - // Initialize the video renderer and run pending tasks. It should set its - // time update callback and scheduled its first callback time. - EXPECT_TRUE(renderer->Initialize(decoder)); - EXPECT_FALSE(filter_host_b.IsInitialized()); - pipeline.RunAllTasks(); - EXPECT_TRUE(filter_host_b.IsInitialized()); - EXPECT_TRUE(filter_host_b.GetTimeUpdateCallback()); - EXPECT_NE(0, filter_host_b.GetScheduledCallbackTime().InMicroseconds()); - - // We also expect one unique frame due to the preroll paint. - EXPECT_EQ(1u, renderer->unique_frames()); - - // Now lets simulate playing 10 frames... - for (int i = 0; i < 10; ++i) { - base::TimeDelta previous_time = filter_host_b.GetScheduledCallbackTime(); - size_t previous_unique_frames = renderer->unique_frames(); - - // Advance time to the callback time and execute. - pipeline.SetTime(previous_time); - filter_host_b.GetTimeUpdateCallback()->Run(previous_time); - pipeline.RunAllTasks(); - - // Renderer should have scheduled a new callback time and painted a frame. - EXPECT_GT(filter_host_b.GetScheduledCallbackTime().InMicroseconds(), - previous_time.InMicroseconds()); - EXPECT_EQ(previous_unique_frames + 1, renderer->unique_frames()); - } -} diff --git a/media/filters/video_thread.cc b/media/filters/video_thread.cc deleted file mode 100644 index 644ae73..0000000 --- a/media/filters/video_thread.cc +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) 2009 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/base/buffers.h" -#include "media/base/filter_host.h" -#include "media/filters/video_thread.h" - -namespace media { - -// Limit our read ahead to three frames. One frame is typically in flux at all -// times, as in frame n is discarded at the top of ThreadMain() while frame -// (n + kMaxFrames) is being asynchronously fetched. The remaining two frames -// allow us to advance the current frame as well as read the timestamp of the -// following frame for more accurate timing. -// -// Increasing this number beyond 3 simply creates a larger buffer to work with -// at the expense of memory (~0.5MB and ~1.3MB per frame for 480p and 720p -// resolutions, respectively). This can help on lower-end systems if there are -// difficult sections in the movie and decoding slows down. -static const size_t kMaxFrames = 3; - -// Sleeping for negative amounts actually hangs your thread on Windows! -static const int64 kMinSleepMilliseconds = 0; - -// This equates to ~13.33 fps, which is just under the typical 15 fps that lower -// quality cameras or shooting modes usually use for video encoding. -static const int64 kMaxSleepMilliseconds = 75; - -VideoThread::VideoThread() - : frame_available_(&lock_), - state_(UNINITIALIZED), - thread_(NULL), - playback_rate_(0) { -} - -VideoThread::~VideoThread() { - AutoLock auto_lock(lock_); - DCHECK(state_ == UNINITIALIZED || state_ == STOPPED); -} - -// static -bool VideoThread::ParseMediaFormat(const media::MediaFormat& media_format, - int* width_out, int* height_out) { - std::string mime_type; - if (!media_format.GetAsString(media::MediaFormat::kMimeType, &mime_type)) - return false; - if (mime_type.compare(media::mime_type::kUncompressedVideo) != 0) - return false; - if (!media_format.GetAsInteger(media::MediaFormat::kWidth, width_out)) - return false; - if (!media_format.GetAsInteger(media::MediaFormat::kHeight, height_out)) - return false; - return true; -} - -void VideoThread::Stop() { - AutoLock auto_lock(lock_); - state_ = STOPPED; - if (thread_) { - // Signal the thread since it's possible to get stopped with the video - // thread waiting for a read to complete. - frame_available_.Signal(); - { - AutoUnlock auto_unlock(lock_); - PlatformThread::Join(thread_); - } - thread_ = NULL; - } -} - -void VideoThread::SetPlaybackRate(float playback_rate) { - AutoLock auto_lock(lock_); - playback_rate_ = playback_rate; -} - -void VideoThread::Seek(base::TimeDelta time) { - AutoLock auto_lock(lock_); - // We need the first frame in |frames_| to run the VideoThread main loop, but - // we don't need decoded frames after the first frame since we are at a new - // time. We should get some new frames so issue reads to compensate for those - // discarded. - while (frames_.size() > 1) { - frames_.pop_back(); - ScheduleRead(); - } -} - -bool VideoThread::Initialize(VideoDecoder* decoder) { - AutoLock auto_lock(lock_); - DCHECK_EQ(state_, UNINITIALIZED); - state_ = INITIALIZING; - decoder_ = decoder; - - // Notify the pipeline of the video dimensions. - int width = 0; - int height = 0; - if (!ParseMediaFormat(decoder->media_format(), &width, &height)) - return false; - host_->SetVideoSize(width, height); - - // Initialize the subclass. - // TODO(scherkus): do we trust subclasses not to do something silly while - // we're holding the lock? - if (!OnInitialize(decoder)) - return false; - - // Create our video thread. - if (!PlatformThread::Create(0, this, &thread_)) { - NOTREACHED() << "Video thread creation failed"; - return false; - } - -#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) - - // Queue initial reads. - for (size_t i = 0; i < kMaxFrames; ++i) { - ScheduleRead(); - } - - return true; -} - -// PlatformThread::Delegate implementation. -void VideoThread::ThreadMain() { - PlatformThread::SetName("VideoThread"); - - // Wait to be initialized so we can notify the first frame is available. - if (!WaitForInitialized()) - return; - OnFrameAvailable(); - - for (;;) { - // State and playback rate to assume for this iteration of the loop. - State state; - float playback_rate; - { - AutoLock auto_lock(lock_); - state = state_; - playback_rate = playback_rate_; - } - if (state == STOPPED) { - return; - } - DCHECK_EQ(state, INITIALIZED); - - // Sleep for 10 milliseconds while paused. - if (playback_rate == 0) { - PlatformThread::Sleep(10); - continue; - } - - // Advance |current_frame_| and try to determine |next_frame|. - scoped_refptr<VideoFrame> next_frame; - { - AutoLock auto_lock(lock_); - DCHECK(!frames_.empty()); - DCHECK_EQ(current_frame_, frames_.front()); - frames_.pop_front(); - ScheduleRead(); - while (frames_.empty()) { - frame_available_.Wait(); - - // We have the lock again, check the actual state to see if we're trying - // to stop. - if (state_ == STOPPED) { - return; - } - } - current_frame_ = frames_.front(); - if (frames_.size() >= 2) { - next_frame = frames_[1]; - } - } - - // Notify subclass that |current_frame_| has been updated. - OnFrameAvailable(); - - // Determine the current and next presentation timestamps. - base::TimeDelta now = host_->GetPipelineStatus()->GetTime(); - base::TimeDelta this_pts = current_frame_->GetTimestamp(); - base::TimeDelta next_pts; - if (next_frame) { - next_pts = next_frame->GetTimestamp(); - } else { - next_pts = this_pts + current_frame_->GetDuration(); - } - - // Determine our sleep duration based on whether time advanced. - base::TimeDelta sleep; - if (now == previous_time_) { - // Time has not changed, assume we sleep for the frame's duration. - sleep = next_pts - this_pts; - } else { - // Time has changed, figure out real sleep duration. - sleep = next_pts - now; - previous_time_ = now; - } - - // Scale our sleep based on the playback rate. - // TODO(scherkus): floating point badness and degrade gracefully. - int sleep_ms = static_cast<int>(sleep.InMicroseconds() / playback_rate / - base::Time::kMicrosecondsPerMillisecond); - - // To be safe, limit our sleep duration. - // TODO(scherkus): handle seeking gracefully.. right now a seek backwards - // will hit kMinSleepMilliseconds whereas a seek forward will hit - // kMaxSleepMilliseconds. - if (sleep_ms < kMinSleepMilliseconds) - sleep_ms = kMinSleepMilliseconds; - else if (sleep_ms > kMaxSleepMilliseconds) - sleep_ms = kMaxSleepMilliseconds; - - PlatformThread::Sleep(sleep_ms); - } -} - -void VideoThread::GetCurrentFrame(scoped_refptr<media::VideoFrame>* frame_out) { - AutoLock auto_lock(lock_); - // Either we have initialized or we have the current frame. - DCHECK(state_ != INITIALIZED || current_frame_); - *frame_out = current_frame_; -} - -void VideoThread::OnReadComplete(VideoFrame* frame) { - AutoLock auto_lock(lock_); - // If this is an end of stream frame, don't enqueue it since it has no data. - if (!frame->IsEndOfStream()) { - frames_.push_back(frame); - DCHECK_LE(frames_.size(), kMaxFrames); - frame_available_.Signal(); - } - - // Check for our initialization condition. - if (state_ == INITIALIZING && - (frames_.size() == kMaxFrames || frame->IsEndOfStream())) { - if (frames_.empty()) { - // We should have initialized but there's no decoded frames in the queue. - // Raise an error. - host_->Error(PIPELINE_ERROR_NO_DATA); - } else { - state_ = INITIALIZED; - current_frame_ = frames_.front(); - host_->InitializationComplete(); - } - } -} - -void VideoThread::ScheduleRead() { - decoder_->Read(NewCallback(this, &VideoThread::OnReadComplete)); -} - -bool VideoThread::WaitForInitialized() { - // This loop essentially handles preroll. We wait until we've been fully - // initialized so we can call OnFrameAvailable() to provide subclasses with - // the first frame. - AutoLock auto_lock(lock_); - DCHECK_EQ(state_, INITIALIZING); - while (state_ == INITIALIZING) { - frame_available_.Wait(); - if (state_ == STOPPED) { - return false; - } - } - DCHECK_EQ(state_, INITIALIZED); - DCHECK(current_frame_); - return true; -} - -} // namespace media diff --git a/media/filters/video_thread.h b/media/filters/video_thread.h deleted file mode 100644 index 346f296..0000000 --- a/media/filters/video_thread.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2009 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. - -// An alternative to VideoRendererBase, VideoThread creates its own thread for -// the sole purpose of timing frame presentation. It handles reading from the -// decoder and stores the results in a queue of decoded frames, calling -// OnFrameAvailable() on subclasses to notify when a frame is ready to display. -// -// The media filter methods Initialize(), Stop(), SetPlaybackRate() and Seek() -// should be serialized, which they commonly are the pipeline thread. -// As long as VideoThread is initialized, GetCurrentFrame() is safe to call from -// any thread, at any time, including inside of OnFrameAvailable(). - -#ifndef MEDIA_FILTERS_VIDEO_THREAD_H_ -#define MEDIA_FILTERS_VIDEO_THREAD_H_ - -#include <deque> - -#include "base/condition_variable.h" -#include "base/lock.h" -#include "media/base/filters.h" - -namespace media { - -// TODO(scherkus): to avoid subclasses, consider using a peer/delegate interface -// and pass in a reference to the constructor. -class VideoThread : public VideoRenderer, - public PlatformThread::Delegate { - public: - VideoThread(); - virtual ~VideoThread(); - - // Helper method for subclasses to parse out video-related information from - // a MediaFormat. Returns true if |width_out| and |height_out| were assigned. - static bool ParseMediaFormat(const media::MediaFormat& media_format, - int* width_out, int* height_out); - - // MediaFilter implementation. - virtual void Stop(); - virtual void SetPlaybackRate(float playback_rate); - virtual void Seek(base::TimeDelta time); - - // VideoRenderer implementation. - virtual bool Initialize(VideoDecoder* decoder); - - // PlatformThread::Delegate implementation. - virtual void ThreadMain(); - - // Assigns the current frame, which will never be NULL as long as this filter - // is initialized. - void GetCurrentFrame(scoped_refptr<media::VideoFrame>* frame_out); - - protected: - // Subclass interface. Called before any other initialization in the base - // class takes place. - // - // Implementors typically use the media format of |decoder| to create their - // output surfaces. Implementors should NOT call InitializationComplete(). - virtual bool OnInitialize(VideoDecoder* decoder) = 0; - - // Subclass interface. Called when a new frame is ready for display, which - // can be accessed via GetCurrentFrame(). - // - // Implementors should avoid doing any sort of heavy work in this method and - // instead post a task to a common/worker thread to handle rendering. Slowing - // down the video thread may result in losing synchronization with audio. - virtual void OnFrameAvailable() = 0; - - private: - // Read complete callback from video decoder. - void OnReadComplete(VideoFrame* frame); - - // Helper method that schedules an asynchronous read from the decoder. - // - // Safe to call from any thread. - void ScheduleRead(); - - // Called by ThreadMain() to handle preroll. Returns false if the thread - // should exit due to Stop() being called. - bool WaitForInitialized(); - - scoped_refptr<VideoDecoder> decoder_; - - // 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_; - scoped_refptr<VideoFrame> current_frame_; - - // Used for accessing |frames_|. - Lock lock_; - - // Used to signal |thread_| as frames are added to |frames_|. Rule of thumb: - // always check |state_| to see if it was set to STOPPED after waking up! - ConditionVariable frame_available_; - - // Simple state tracking variable. - enum State { - UNINITIALIZED, - INITIALIZING, - INITIALIZED, - STOPPED, - }; - State state_; - - // Video thread handle. - PlatformThreadHandle thread_; - - // Previous time returned from the pipeline. - base::TimeDelta previous_time_; - - float playback_rate_; - - DISALLOW_COPY_AND_ASSIGN(VideoThread); -}; - -} // namespace media - -#endif // MEDIA_FILTERS_VIDEO_THREAD_H_ diff --git a/media/media.gyp b/media/media.gyp index d5c1a3d..e752b0b 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -101,8 +101,6 @@ 'filters/test_video_renderer.h', 'filters/video_renderer_base.cc', 'filters/video_renderer_base.h', - 'filters/video_thread.cc', - 'filters/video_thread.h', ], 'direct_dependent_settings': { 'include_dirs': [ @@ -167,7 +165,6 @@ 'filters/ffmpeg_video_decoder_unittest.cc', 'filters/file_data_source_unittest.cc', 'filters/video_decoder_unittest.cc', - 'filters/video_renderer_unittest.cc', ], 'conditions': [ ['OS=="linux"', { diff --git a/media/player/wtl_renderer.h b/media/player/wtl_renderer.h index 38beb2f..eba1fc8 100644 --- a/media/player/wtl_renderer.h +++ b/media/player/wtl_renderer.h @@ -7,11 +7,11 @@ #ifndef MEDIA_PLAYER_WTL_RENDERER_H_ #define MEDIA_PLAYER_WTL_RENDERER_H_ -#include "media/filters/video_thread.h" +#include "media/filters/video_renderer_base.h" class WtlVideoWindow; -class WtlVideoRenderer : public media::VideoThread { +class WtlVideoRenderer : public media::VideoRendererBase { public: explicit WtlVideoRenderer(WtlVideoWindow* window); @@ -32,4 +32,3 @@ class WtlVideoRenderer : public media::VideoThread { }; #endif // MEDIA_PLAYER_WTL_RENDERER_H_ - |