summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-22 22:23:19 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-22 22:23:19 +0000
commit1f500f5da1388687eb302d31f084fa70ac903121 (patch)
treedfa62b5a4791e6e13f0df08909713143694955fe /media
parentc6b20fa7be447131bd6643dbda3a4965db988a04 (diff)
downloadchromium_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.cc391
-rw-r--r--media/filters/video_renderer_base.h231
-rw-r--r--media/filters/video_renderer_unittest.cc56
-rw-r--r--media/filters/video_thread.cc274
-rw-r--r--media/filters/video_thread.h120
-rw-r--r--media/media.gyp3
-rw-r--r--media/player/wtl_renderer.h5
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_
-