diff options
-rw-r--r-- | media/filters/video_thread.cc | 259 | ||||
-rw-r--r-- | media/filters/video_thread.h | 117 | ||||
-rw-r--r-- | media/media.gyp | 2 |
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': [ |