blob: 2a9ba66547e573ec0a9b54bd041ffb22acca1919 [file] [log] [blame]
// 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_renderer_base.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;
VideoRendererBase::VideoRendererBase()
: frame_available_(&lock_),
state_(UNINITIALIZED),
thread_(NULL),
playback_rate_(0) {
}
VideoRendererBase::~VideoRendererBase() {
AutoLock auto_lock(lock_);
DCHECK(state_ == UNINITIALIZED || state_ == STOPPED);
}
// static
bool VideoRendererBase::ParseMediaFormat(const MediaFormat& media_format,
int* width_out, int* height_out) {
std::string mime_type;
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() {
AutoLock auto_lock(lock_);
state_ = STOPPED;
// Signal the subclass we're stopping.
// TODO(scherkus): do we trust subclasses not to do something silly while
// we're holding the lock?
OnStop();
// Clean up our thread if present.
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 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();
}
}
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;
}
#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 VideoRendererBase::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 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::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 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_);
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