summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-14 17:11:04 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-14 17:11:04 +0000
commitbd38825e08978ca1fb5dff01b4671d9d20ea4b60 (patch)
treed7beebab23f6cabde4c181cd79e9ef1f7fb66990
parentf14159cb6385f271bec21d4ddf04c353a869fb70 (diff)
downloadchromium_src-bd38825e08978ca1fb5dff01b4671d9d20ea4b60.zip
chromium_src-bd38825e08978ca1fb5dff01b4671d9d20ea4b60.tar.gz
chromium_src-bd38825e08978ca1fb5dff01b4671d9d20ea4b60.tar.bz2
Checking in VideoThread, which uses a dedicated thread for the purpose of timing frame presentation.
Simple class, but getting the details right are very tricky. There are additional tweaks we can make (such as counting ticks since the last audio callback), but for now this gets the job done. Review URL: http://codereview.chromium.org/113360 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16062 0039d316-1c4b-4281-b951-d872f2087c98
-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': [