summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-24 01:25:14 +0000
committerralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-24 01:25:14 +0000
commitce553ab757aa9f46bbced6392fda9bdf8da4bd8b (patch)
tree1e51f83cc956027064d06279f40a79b1aa83d5d1 /media
parentc96d5309cee28f5b839e10ba9525fdfc6913a61c (diff)
downloadchromium_src-ce553ab757aa9f46bbced6392fda9bdf8da4bd8b.zip
chromium_src-ce553ab757aa9f46bbced6392fda9bdf8da4bd8b.tar.gz
chromium_src-ce553ab757aa9f46bbced6392fda9bdf8da4bd8b.tar.bz2
Moved most functionality of video renderer into a base class (video_renderer_base) and implemented mock filter will full
functionality execpt that it never draws anything. Review URL: http://codereview.chromium.org/20343 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10239 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/base/factory.h42
-rw-r--r--media/build/media.vcproj9
-rw-r--r--media/build/media_unittests.vcproj8
-rw-r--r--media/filters/test_video_renderer.h64
-rw-r--r--media/filters/video_renderer_base.cc234
-rw-r--r--media/filters/video_renderer_base.h191
-rw-r--r--media/filters/video_renderer_unittest.cc54
-rw-r--r--media/media_lib.scons2
-rw-r--r--media/media_unittests.scons2
9 files changed, 606 insertions, 0 deletions
diff --git a/media/base/factory.h b/media/base/factory.h
index 41a73aa..66bc377 100644
--- a/media/base/factory.h
+++ b/media/base/factory.h
@@ -156,6 +156,48 @@ class FilterFactoryImpl1 : public FilterFactory {
DISALLOW_COPY_AND_ASSIGN(FilterFactoryImpl1);
};
+
+//------------------------------------------------------------------------------
+
+// This specialized factory is typically used by test programs that create
+// a filter instance, and want to return that specific instance of the test
+// filter to the pipeline. It takes an already created filter in the
+// constructor, and then hands that instance out when the Create method is
+// called. The factory makes sure to only return a single copy of the
+// filter. The normal patern of use for this factory is:
+// scoped_refptr<MyTestFilter> my_test_filter = new MyTestFilter();
+// filter_factory_collection->AddFactory(
+// new InstanceFilterFactory<MyTestFilter>(my_test_filter));
+template <class Filter>
+class InstanceFilterFactory : public FilterFactory {
+ public:
+ explicit InstanceFilterFactory(Filter* filter)
+ : filter_(filter),
+ create_called_(false) {
+ }
+
+ protected:
+ virtual MediaFilter* Create(FilterType filter_type,
+ const MediaFormat* media_format) {
+ if (Filter::filter_type() == filter_type &&
+ Filter::IsMediaFormatSupported(media_format)) {
+ if (!create_called_) {
+ create_called_ = true;
+ return filter_;
+ } else {
+ NOTREACHED();
+ }
+ }
+ return NULL;
+ }
+
+ private:
+ scoped_refptr<Filter> filter_;
+ bool create_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstanceFilterFactory);
+};
+
} // namespace media
#endif // MEDIA_BASE_FACTORY_H_
diff --git a/media/build/media.vcproj b/media/build/media.vcproj
index 5b1dff7..fbe6921 100644
--- a/media/build/media.vcproj
+++ b/media/build/media.vcproj
@@ -205,6 +205,15 @@
>
</File>
<File
+ RelativePath="..\filters\video_renderer_base.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\filters\video_renderer_base.h"
+ >
+ </File>
+
+ <File
RelativePath="..\filters\null_audio_renderer.cc"
>
</File>
diff --git a/media/build/media_unittests.vcproj b/media/build/media_unittests.vcproj
index 90c9cd9..f994568 100644
--- a/media/build/media_unittests.vcproj
+++ b/media/build/media_unittests.vcproj
@@ -191,6 +191,14 @@
RelativePath="..\filters\file_data_source_unittest.cc"
>
</File>
+ <File
+ RelativePath="..\filters\test_video_renderer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\filters\video_renderer_unittest.cc"
+ >
+ </File>
</Filter>
</Filter>
</Files>
diff --git a/media/filters/test_video_renderer.h b/media/filters/test_video_renderer.h
new file mode 100644
index 0000000..091acc9
--- /dev/null
+++ b/media/filters/test_video_renderer.h
@@ -0,0 +1,64 @@
+// 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.
+//
+// Simple test class used by unit tests. Tests create the filter on the test's
+// thread and then use InstanceFilterFactory to force the test's instance to
+// be returned to the pipeline.
+
+#ifndef MEDIA_FILTERS_TEST_VIDEO_RENDERER_H_
+#define MEDIA_FILTERS_TEST_VIDEO_RENDERER_H_
+
+#include "media/base/buffers.h"
+#include "media/base/factory.h"
+#include "media/base/filters.h"
+#include "media/filters/video_renderer_base.h"
+
+namespace media {
+
+class TestVideoRenderer : public VideoRendererBase {
+ public:
+ TestVideoRenderer()
+ : last_frame_(NULL),
+ paint_called_(0),
+ unique_frames_(0) {
+ }
+
+ virtual bool OnInitialize(size_t width, size_t height) { return true; }
+
+ virtual void OnPaintNeeded() {
+ ++paint_called_;
+ scoped_refptr<VideoFrame> frame;
+ GetCurrentFrame(&frame);
+ if (frame.get()) {
+ if (frame != last_frame_) {
+ ++unique_frames_;
+ last_frame_ = frame;
+ last_timestamp_ = frame->GetTimestamp();
+ }
+ }
+ }
+
+ size_t unique_frames() { return unique_frames_; }
+ size_t paint_called() { return paint_called_; }
+ base::TimeDelta last_timestamp() { return last_timestamp_; }
+
+ static bool IsMediaFormatSupported(const MediaFormat* format) {
+ return VideoRendererBase::IsMediaFormatSupported(format);
+ }
+
+ private:
+ friend class scoped_refptr<TestVideoRenderer>;
+ virtual ~TestVideoRenderer() {}
+
+ VideoFrame* last_frame_;
+ size_t paint_called_;
+ size_t unique_frames_;
+ base::TimeDelta last_timestamp_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestVideoRenderer);
+};
+
+} // namespace
+
+#endif // MEDIA_FILTERS_TEST_VIDEO_RENDERER_H_
diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc
new file mode 100644
index 0000000..fa715c1
--- /dev/null
+++ b/media/filters/video_renderer_base.cc
@@ -0,0 +1,234 @@
+// 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/base/pipeline.h"
+#include "media/filters/video_renderer_base.h"
+
+namespace media {
+
+const size_t VideoRendererBase::kDefaultNumberOfFrames = 4;
+
+const base::TimeDelta VideoRendererBase::kDefaultSkipFrameDelta =
+ base::TimeDelta::FromMilliseconds(2);
+
+const base::TimeDelta VideoRendererBase::kDefaultEmptyQueueSleep =
+ base::TimeDelta::FromMilliseconds(15);
+
+//------------------------------------------------------------------------------
+
+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) {
+}
+
+VideoRendererBase::~VideoRendererBase() {
+ Stop();
+}
+
+// static
+bool VideoRendererBase::IsMediaFormatSupported(
+ const MediaFormat* media_format) {
+ int width;
+ int height;
+ return ParseMediaFormat(media_format, &width, &height);
+}
+
+// static
+bool VideoRendererBase::ParseMediaFormat(const MediaFormat* media_format,
+ int* width_out,
+ int* height_out) {
+ DCHECK(media_format && width_out && 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));
+}
+
+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;
+ }
+ decoder_ = NULL;
+}
+
+bool VideoRendererBase::Initialize(VideoDecoder* decoder) {
+ int width, height;
+ decoder_ = decoder;
+ if (ParseMediaFormat(decoder_->GetMediaFormat(), &width, &height) &&
+ OnInitialize(width, height)) {
+ host_->SetVideoSize(width, height);
+ host_->SetTimeUpdateCallback(
+ NewCallback(this, &VideoRendererBase::TimeUpdateCallback));
+ SubmitReads();
+ return true;
+ }
+ 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;
+ }
+ while (number_to_read > 0) {
+ decoder_->Read(new AssignableBuffer<VideoRendererBase, VideoFrame>(this));
+ --number_to_read;
+ }
+}
+
+// Assumes |lock_| has been acquired!
+bool VideoRendererBase::UpdateQueue(base::TimeDelta time,
+ VideoFrame* new_frame) {
+ 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);
+ }
+
+ // 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_;
+ }
+ }
+
+ // 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();
+
+ // True if the front of the queue is a new frame.
+ return updated_front;
+}
+
+// Assumes |lock_| has been acquired!
+void VideoRendererBase::DiscardAllFrames() {
+ while (!queue_.empty()) {
+ queue_.front()->Release();
+ queue_.pop_front();
+ ++number_of_reads_needed_;
+ }
+}
+
+// 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::TimeUpdateCallback(base::TimeDelta time) {
+ bool request_repaint;
+ {
+ AutoLock auto_lock(lock_);
+ request_repaint = (IsRunning() && UpdateQueue(time, NULL));
+ }
+ if (request_repaint) {
+ OnPaintNeeded();
+ }
+}
+
+void VideoRendererBase::OnAssignment(VideoFrame* video_frame) {
+ bool call_initialized = false;
+ bool request_repaint = false;
+ {
+ AutoLock auto_lock(lock_);
+ if (IsRunning()) {
+ // TODO(ralphl): if (!preroll_complete_ && EndOfStream) call_init = true
+ // and preroll_complete_ = true.
+ // TODO(ralphl): If(Seek()) then discard but we don't have SeekFrame().
+ if (false) {
+ // TODO(ralphl): this is the seek() logic.
+ DiscardAllFrames();
+ ++number_of_reads_needed_;
+ PostSubmitReadsTask();
+ } else {
+ if (UpdateQueue(host_->GetPipelineStatus()->GetInterpolatedTime(),
+ video_frame)) {
+ request_repaint = preroll_complete_;
+ }
+ if (!preroll_complete_ && queue_.size() == number_of_frames_) {
+ preroll_complete_ = true;
+ call_initialized = true;
+ request_repaint = true;
+ }
+ }
+ }
+ }
+ // |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) {
+ 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();
+ }
+ }
+ host_->ScheduleTimeUpdateCallback(time_next_frame);
+ }
+}
+
+} // namespace
diff --git a/media/filters/video_renderer_base.h b/media/filters/video_renderer_base.h
new file mode 100644
index 0000000..16566fc
--- /dev/null
+++ b/media/filters/video_renderer_base.h
@@ -0,0 +1,191 @@
+// 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.
+
+// A base class that provides the plumbing for a video renderer. Derived
+// classes must implement the following methods:
+// OnInitialized
+// OnStop (optional)
+// OnPaintNeeded
+//
+// The derived class can determine what frame needs to be painted by calling
+// the GetCurrentFrame method.
+
+#ifndef MEDIA_FILTERS_VIDEO_RENDERER_BASE_H_
+#define MEDIA_FILTERS_VIDEO_RENDERER_BASE_H_
+
+#include <deque>
+
+#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 {
+ public:
+ // MediaFilter implementation.
+ virtual void Stop();
+
+ // VideoRenderer implementation.
+ virtual bool Initialize(VideoDecoder* decoder);
+
+ // Implementation of AssignableBuffer<this>::OnAssignment method.
+ void OnAssignment(VideoFrame* video_frame);
+
+ 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;
+
+ // Constructor that uses defaults for all tuning parameters.
+ VideoRendererBase();
+
+ // 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);
+
+ virtual ~VideoRendererBase();
+
+ // 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);
+
+ // Answers question from the factory to see if we accept |format|.
+ static bool IsMediaFormatSupported(const MediaFormat* format);
+
+ // 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);
+
+ private:
+ // 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.
+ Lock lock_;
+
+ // Pointer to the decoder that will feed us with video frames.
+ scoped_refptr<VideoDecoder> decoder_;
+
+ // 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoRendererBase);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_VIDEO_RENDERER_BASE_H_
diff --git a/media/filters/video_renderer_unittest.cc b/media/filters/video_renderer_unittest.cc
new file mode 100644
index 0000000..c114984
--- /dev/null
+++ b/media/filters/video_renderer_unittest.cc
@@ -0,0 +1,54 @@
+// 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 <string>
+
+#include "media/base/factory.h"
+#include "media/base/filter_host.h"
+#include "media/base/filters.h"
+#include "media/base/media_format.h"
+#include "media/base/mock_media_filters.h"
+#include "media/base/pipeline_impl.h"
+#include "media/filters/test_video_renderer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using media::FilterFactoryCollection;
+using media::InstanceFilterFactory;
+using media::MockAudioDecoder;
+using media::MockAudioRenderer;
+using media::MockDataSource;
+using media::MockDemuxer;
+using media::MockFilterConfig;
+using media::MockVideoDecoder;
+using media::PipelineImpl;
+using media::TestVideoRenderer;
+using media::VideoFrame;
+
+TEST(VideoRenderer, CreateTestRenderer) {
+ base::TimeDelta test_time = base::TimeDelta::FromMilliseconds(500);
+ std::string url("");
+ PipelineImpl p;
+ scoped_refptr<TestVideoRenderer> test_renderer = new TestVideoRenderer();
+ MockFilterConfig config;
+ scoped_refptr<FilterFactoryCollection> c = new FilterFactoryCollection();
+ c->AddFactory(MockDataSource::CreateFactory(&config));
+ c->AddFactory(MockDemuxer::CreateFactory(&config));
+ c->AddFactory(MockAudioDecoder::CreateFactory(&config));
+ c->AddFactory(MockAudioRenderer::CreateFactory(&config));
+ c->AddFactory(MockVideoDecoder::CreateFactory(&config));
+ c->AddFactory(new InstanceFilterFactory<TestVideoRenderer>(test_renderer));
+ media::InitializationHelper h;
+ h.Start(&p, c, url);
+ h.Wait();
+ EXPECT_TRUE(p.IsInitialized());
+ p.SetPlaybackRate(1.0f);
+ PlatformThread::Sleep(static_cast<int>(test_time.InMilliseconds()));
+ p.Stop();
+ // Allow a decent amount of variability here. We expect 15 or 16 frames
+ // but for now make sure it's within a reasonable range.
+ int64 num_expected_frames = test_time / config.frame_duration;
+ EXPECT_GT(test_renderer->unique_frames(), num_expected_frames - 3);
+ EXPECT_LT(test_renderer->unique_frames(), num_expected_frames + 3);
+}
+
diff --git a/media/media_lib.scons b/media/media_lib.scons
index 1f88e35..b8eac68 100644
--- a/media/media_lib.scons
+++ b/media/media_lib.scons
@@ -48,6 +48,8 @@ input_files = ChromeFileList([
'filters/file_data_source.h',
'filters/null_audio_renderer.cc',
'filters/null_audio_renderer.h',
+ 'filters/video_renderer_base.cc',
+ 'filters/video_renderer_base.h',
]),
MSVSFilter('audio', [
'audio/win/audio_manager_win.h',
diff --git a/media/media_unittests.scons b/media/media_unittests.scons
index 7ae0b95..64a2575 100644
--- a/media/media_unittests.scons
+++ b/media/media_unittests.scons
@@ -60,6 +60,8 @@ input_files = ChromeFileList([
]),
MSVSFilter('filters', [
'filters/file_data_source_unittest.cc',
+ 'filters/test_video_renderer.h',
+ 'filters/video_renderer_unittest.cc',
]),
MSVSFilter('audio', [
'audio/win/audio_output_win_unittest.cc',