diff options
author | ralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-24 01:25:14 +0000 |
---|---|---|
committer | ralphl@chromium.org <ralphl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-24 01:25:14 +0000 |
commit | ce553ab757aa9f46bbced6392fda9bdf8da4bd8b (patch) | |
tree | 1e51f83cc956027064d06279f40a79b1aa83d5d1 /media | |
parent | c96d5309cee28f5b839e10ba9525fdfc6913a61c (diff) | |
download | chromium_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.h | 42 | ||||
-rw-r--r-- | media/build/media.vcproj | 9 | ||||
-rw-r--r-- | media/build/media_unittests.vcproj | 8 | ||||
-rw-r--r-- | media/filters/test_video_renderer.h | 64 | ||||
-rw-r--r-- | media/filters/video_renderer_base.cc | 234 | ||||
-rw-r--r-- | media/filters/video_renderer_base.h | 191 | ||||
-rw-r--r-- | media/filters/video_renderer_unittest.cc | 54 | ||||
-rw-r--r-- | media/media_lib.scons | 2 | ||||
-rw-r--r-- | media/media_unittests.scons | 2 |
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', |