summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/filters/video_thread.cc259
-rw-r--r--media/filters/video_thread.h117
-rw-r--r--media/media.gyp2
3 files changed, 378 insertions, 0 deletions
diff --git a/media/filters/video_thread.cc b/media/filters/video_thread.cc
new file mode 100644
index 0000000..4422c82
--- /dev/null
+++ b/media/filters/video_thread.cc
@@ -0,0 +1,259 @@
+// 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) {
+ // Do nothing.
+}
+
+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_);
+ DCHECK_EQ(state_, INITIALIZED);
+ DCHECK(current_frame_);
+ *frame_out = current_frame_;
+}
+
+void VideoThread::OnReadComplete(VideoFrame* frame) {
+ AutoLock auto_lock(lock_);
+ frames_.push_back(frame);
+ DCHECK_LE(frames_.size(), kMaxFrames);
+
+ // Check for our initialization condition.
+ if (state_ == INITIALIZING && frames_.size() == kMaxFrames) {
+ state_ = INITIALIZED;
+ current_frame_ = frames_.front();
+ host_->InitializationComplete();
+ }
+
+ frame_available_.Signal();
+}
+
+void VideoThread::ScheduleRead() {
+ host_->PostTask(
+ NewRunnableMethod(decoder_.get(), &VideoDecoder::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
new file mode 100644
index 0000000..2cab71e
--- /dev/null
+++ b/media/filters/video_thread.h
@@ -0,0 +1,117 @@
+// 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 "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 asynchronously schedules a read on the pipeline 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 5ed4720..8b9df77 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -95,6 +95,8 @@
'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': [