diff options
Diffstat (limited to 'media/base/pipeline_unittest.cc')
-rw-r--r-- | media/base/pipeline_unittest.cc | 928 |
1 files changed, 928 insertions, 0 deletions
diff --git a/media/base/pipeline_unittest.cc b/media/base/pipeline_unittest.cc new file mode 100644 index 0000000..58f028f --- /dev/null +++ b/media/base/pipeline_unittest.cc @@ -0,0 +1,928 @@ +// Copyright (c) 2012 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 "base/bind.h" +#include "base/message_loop.h" +#include "base/stl_util.h" +#include "base/threading/simple_thread.h" +#include "media/base/clock.h" +#include "media/base/filter_host.h" +#include "media/base/filters.h" +#include "media/base/media_log.h" +#include "media/base/pipeline.h" +#include "media/base/mock_callback.h" +#include "media/base/mock_filters.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/size.h" + +using ::testing::_; +using ::testing::DeleteArg; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeArgument; +using ::testing::Mock; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::StrictMock; +using ::testing::WithArg; + +namespace media { + +// Total bytes of the data source. +static const int kTotalBytes = 1024; + +// Buffered bytes of the data source. +static const int kBufferedBytes = 1024; + +// Used for setting expectations on pipeline callbacks. Using a StrictMock +// also lets us test for missing callbacks. +class CallbackHelper { + public: + CallbackHelper() {} + virtual ~CallbackHelper() {} + + MOCK_METHOD1(OnStart, void(PipelineStatus)); + MOCK_METHOD1(OnSeek, void(PipelineStatus)); + MOCK_METHOD1(OnStop, void(PipelineStatus)); + MOCK_METHOD1(OnEnded, void(PipelineStatus)); + MOCK_METHOD1(OnError, void(PipelineStatus)); + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackHelper); +}; + +// Run |cb| w/ OK status. +static void RunPipelineStatusOKCB(const PipelineStatusCB& cb) { + cb.Run(PIPELINE_OK); +} + +// TODO(scherkus): even though some filters are initialized on separate +// threads these test aren't flaky... why? It's because filters' Initialize() +// is executed on |message_loop_| and the mock filters instantly call +// InitializationComplete(), which keeps the pipeline humming along. If +// either filters don't call InitializationComplete() immediately or filter +// initialization is moved to a separate thread this test will become flaky. +class PipelineTest : public ::testing::Test { + public: + PipelineTest() + : pipeline_(new Pipeline(&message_loop_, new MediaLog())) { + pipeline_->Init( + base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)), + base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)), + Pipeline::NetworkEventCB()); + mocks_.reset(new MockFilterCollection()); + + // InitializeDemuxer adds overriding expectations for expected non-NULL + // streams. + DemuxerStream* null_pointer = NULL; + EXPECT_CALL(*mocks_->demuxer(), GetStream(_)) + .WillRepeatedly(Return(null_pointer)); + + EXPECT_CALL(*mocks_->demuxer(), GetStartTime()) + .WillRepeatedly(Return(base::TimeDelta())); + } + + virtual ~PipelineTest() { + if (!pipeline_->IsRunning()) { + return; + } + + // Expect a stop callback if we were started. + EXPECT_CALL(callbacks_, OnStop(PIPELINE_OK)); + pipeline_->Stop(base::Bind(&CallbackHelper::OnStop, + base::Unretained(&callbacks_))); + message_loop_.RunAllPending(); + + mocks_.reset(); + } + + protected: + // Sets up expectations to allow the demuxer to initialize. + typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector; + void InitializeDemuxer(MockDemuxerStreamVector* streams, + const base::TimeDelta& duration) { + mocks_->demuxer()->SetTotalAndBufferedBytesAndDuration( + kTotalBytes, kBufferedBytes, duration); + EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->demuxer(), SetPreload(AUTO)); + EXPECT_CALL(*mocks_->demuxer(), Seek(mocks_->demuxer()->GetStartTime(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->demuxer(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + + // Configure the demuxer to return the streams. + for (size_t i = 0; i < streams->size(); ++i) { + scoped_refptr<DemuxerStream> stream((*streams)[i]); + EXPECT_CALL(*mocks_->demuxer(), GetStream(stream->type())) + .WillRepeatedly(Return(stream)); + } + } + + StrictMock<MockDemuxerStream>* CreateStream(DemuxerStream::Type type) { + StrictMock<MockDemuxerStream>* stream = + new StrictMock<MockDemuxerStream>(); + EXPECT_CALL(*stream, type()) + .WillRepeatedly(Return(type)); + return stream; + } + + // Sets up expectations to allow the video decoder to initialize. + void InitializeVideoDecoder(MockDemuxerStream* stream) { + EXPECT_CALL(*mocks_->video_decoder(), + Initialize(stream, _, _)) + .WillOnce(WithArg<1>(Invoke(&RunPipelineStatusOKCB))); + EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->video_decoder(), + Seek(mocks_->demuxer()->GetStartTime(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->video_decoder(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + } + + // Sets up expectations to allow the audio decoder to initialize. + void InitializeAudioDecoder(MockDemuxerStream* stream) { + EXPECT_CALL(*mocks_->audio_decoder(), Initialize(stream, _, _)) + .WillOnce(Invoke(&RunFilterCallback3)); + EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->audio_decoder(), Seek(base::TimeDelta(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->audio_decoder(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + } + + // Sets up expectations to allow the video renderer to initialize. + void InitializeVideoRenderer() { + EXPECT_CALL(*mocks_->video_renderer(), + Initialize(mocks_->video_decoder(), _, _)) + .WillOnce(Invoke(&RunFilterCallback3)); + EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->video_renderer(), + Seek(mocks_->demuxer()->GetStartTime(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->video_renderer(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + } + + // Sets up expectations to allow the audio renderer to initialize. + void InitializeAudioRenderer(bool disable_after_init_callback = false) { + if (disable_after_init_callback) { + EXPECT_CALL(*mocks_->audio_renderer(), + Initialize(mocks_->audio_decoder(), _, _)) + .WillOnce(DoAll(Invoke(&RunFilterCallback3), + DisableAudioRenderer(mocks_->audio_renderer()))); + } else { + EXPECT_CALL(*mocks_->audio_renderer(), + Initialize(mocks_->audio_decoder(), _, _)) + .WillOnce(Invoke(&RunFilterCallback3)); + } + EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f)); + EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f)); + EXPECT_CALL(*mocks_->audio_renderer(), Seek(base::TimeDelta(), _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->audio_renderer(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + } + + // Sets up expectations on the callback and initializes the pipeline. Called + // after tests have set expectations any filters they wish to use. + void InitializePipeline() { + InitializePipeline(PIPELINE_OK); + } + // Most tests can expect the |filter_collection|'s |build_status| to get + // reflected in |Start()|'s argument. + void InitializePipeline(PipelineStatus start_status) { + InitializePipeline(start_status, start_status); + } + // But some tests require different statuses in build & Start. + void InitializePipeline(PipelineStatus build_status, + PipelineStatus start_status) { + // Expect an initialization callback. + EXPECT_CALL(callbacks_, OnStart(start_status)); + + pipeline_->Start(mocks_->filter_collection(true, + true, + true, + build_status).Pass(), + "", + base::Bind(&CallbackHelper::OnStart, + base::Unretained(&callbacks_))); + + message_loop_.RunAllPending(); + } + + void CreateAudioStream() { + audio_stream_ = CreateStream(DemuxerStream::AUDIO); + } + + void CreateVideoStream() { + video_stream_ = CreateStream(DemuxerStream::VIDEO); + } + + MockDemuxerStream* audio_stream() { + return audio_stream_; + } + + MockDemuxerStream* video_stream() { + return video_stream_; + } + + void ExpectSeek(const base::TimeDelta& seek_time) { + // Every filter should receive a call to Seek(). + EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + + if (audio_stream_) { + EXPECT_CALL(*mocks_->audio_decoder(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->audio_renderer(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + } + + if (video_stream_) { + EXPECT_CALL(*mocks_->video_decoder(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + EXPECT_CALL(*mocks_->video_renderer(), Seek(seek_time, _)) + .WillOnce(Invoke(&RunFilterStatusCB)); + } + + // We expect a successful seek callback. + EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK)); + } + + void DoSeek(const base::TimeDelta& seek_time) { + pipeline_->Seek(seek_time, + base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); + + // We expect the time to be updated only after the seek has completed. + EXPECT_NE(seek_time, pipeline_->GetCurrentTime()); + message_loop_.RunAllPending(); + EXPECT_EQ(seek_time, pipeline_->GetCurrentTime()); + } + + // Fixture members. + StrictMock<CallbackHelper> callbacks_; + MessageLoop message_loop_; + scoped_refptr<Pipeline> pipeline_; + scoped_ptr<media::MockFilterCollection> mocks_; + scoped_refptr<StrictMock<MockDemuxerStream> > audio_stream_; + scoped_refptr<StrictMock<MockDemuxerStream> > video_stream_; + + private: + DISALLOW_COPY_AND_ASSIGN(PipelineTest); +}; + +// Test that playback controls methods no-op when the pipeline hasn't been +// started. +TEST_F(PipelineTest, NotStarted) { + const base::TimeDelta kZero; + + // StrictMock<> will ensure these never get called, and valgrind will + // make sure the callbacks are instantly deleted. + pipeline_->Stop(base::Bind(&CallbackHelper::OnStop, + base::Unretained(&callbacks_))); + pipeline_->Seek(kZero, + base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); + + EXPECT_FALSE(pipeline_->IsRunning()); + EXPECT_FALSE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_FALSE(pipeline_->HasVideo()); + + // Setting should still work. + EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); + pipeline_->SetPlaybackRate(-1.0f); + EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); + pipeline_->SetPlaybackRate(1.0f); + EXPECT_EQ(1.0f, pipeline_->GetPlaybackRate()); + + // Setting should still work. + EXPECT_EQ(1.0f, pipeline_->GetVolume()); + pipeline_->SetVolume(-1.0f); + EXPECT_EQ(1.0f, pipeline_->GetVolume()); + pipeline_->SetVolume(0.0f); + EXPECT_EQ(0.0f, pipeline_->GetVolume()); + + EXPECT_TRUE(kZero == pipeline_->GetCurrentTime()); + EXPECT_TRUE(kZero == pipeline_->GetBufferedTime()); + EXPECT_TRUE(kZero == pipeline_->GetMediaDuration()); + + EXPECT_EQ(0, pipeline_->GetBufferedBytes()); + EXPECT_EQ(0, pipeline_->GetTotalBytes()); + + // Should always get set to zero. + gfx::Size size(1, 1); + pipeline_->GetNaturalVideoSize(&size); + EXPECT_EQ(0, size.width()); + EXPECT_EQ(0, size.height()); +} + +TEST_F(PipelineTest, NeverInitializes) { + // This test hangs during initialization by never calling + // InitializationComplete(). StrictMock<> will ensure that the callback is + // never executed. + pipeline_->Start(mocks_->filter_collection(false, + false, + true, + PIPELINE_OK).Pass(), + "", + base::Bind(&CallbackHelper::OnStart, + base::Unretained(&callbacks_))); + message_loop_.RunAllPending(); + + EXPECT_FALSE(pipeline_->IsInitialized()); + + // Because our callback will get executed when the test tears down, we'll + // verify that nothing has been called, then set our expectation for the call + // made during tear down. + Mock::VerifyAndClear(&callbacks_); + EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK)); +} + +TEST_F(PipelineTest, RequiredFilterMissing) { + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); + + // Sets up expectations on the callback and initializes the pipeline. Called + // after tests have set expectations any filters they wish to use. + // Expect an initialization callback. + EXPECT_CALL(callbacks_, OnStart(PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); + + // Create a filter collection with missing filter. + scoped_ptr<FilterCollection> collection(mocks_->filter_collection( + false, true, true, PIPELINE_ERROR_REQUIRED_FILTER_MISSING)); + pipeline_->Start(collection.Pass(), "", + base::Bind(&CallbackHelper::OnStart, + base::Unretained(&callbacks_))); + message_loop_.RunAllPending(); + + EXPECT_FALSE(pipeline_->IsInitialized()); +} + +TEST_F(PipelineTest, URLNotFound) { + // TODO(acolwell,fischman): Since OnStart() is getting called with an error + // code already, OnError() doesn't also need to get called. Fix the pipeline + // (and it's consumers!) so that OnError doesn't need to be called after + // another callback has already reported the error. Same applies to NoStreams + // below. + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_URL_NOT_FOUND)); + InitializePipeline(PIPELINE_ERROR_URL_NOT_FOUND); + EXPECT_FALSE(pipeline_->IsInitialized()); +} + +TEST_F(PipelineTest, NoStreams) { + // Manually set these expectations because SetPlaybackRate() is not called if + // we cannot fully initialize the pipeline. + EXPECT_CALL(*mocks_->demuxer(), Stop(_)) + .WillOnce(Invoke(&RunStopFilterCallback)); + // TODO(acolwell,fischman): see TODO in URLNotFound above. + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_COULD_NOT_RENDER)); + + InitializePipeline(PIPELINE_OK, PIPELINE_ERROR_COULD_NOT_RENDER); + EXPECT_FALSE(pipeline_->IsInitialized()); +} + +TEST_F(PipelineTest, AudioStream) { + CreateAudioStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_TRUE(pipeline_->HasAudio()); + EXPECT_FALSE(pipeline_->HasVideo()); +} + +TEST_F(PipelineTest, VideoStream) { + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); +} + +TEST_F(PipelineTest, AudioVideoStream) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_TRUE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); +} + +TEST_F(PipelineTest, Seek) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000)); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + // Every filter should receive a call to Seek(). + base::TimeDelta expected = base::TimeDelta::FromSeconds(2000); + ExpectSeek(expected); + + // Initialize then seek! + InitializePipeline(PIPELINE_OK); + DoSeek(expected); +} + +TEST_F(PipelineTest, SetVolume) { + CreateAudioStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + + // The audio renderer should receive a call to SetVolume(). + float expected = 0.5f; + EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(expected)); + + // Initialize then set volume! + InitializePipeline(PIPELINE_OK); + pipeline_->SetVolume(expected); +} + +TEST_F(PipelineTest, Properties) { + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); + InitializeDemuxer(&streams, kDuration); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_EQ(kDuration.ToInternalValue(), + pipeline_->GetMediaDuration().ToInternalValue()); + EXPECT_EQ(kTotalBytes, pipeline_->GetTotalBytes()); + EXPECT_EQ(kBufferedBytes, pipeline_->GetBufferedBytes()); + + // Because kTotalBytes and kBufferedBytes are equal to each other, + // the entire video should be buffered. + EXPECT_EQ(kDuration.ToInternalValue(), + pipeline_->GetBufferedTime().ToInternalValue()); +} + +TEST_F(PipelineTest, GetBufferedTime) { + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); + InitializeDemuxer(&streams, kDuration); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + + // TODO(vrk): The following mini-test cases are order-dependent, and should + // probably be separated into independent test cases. + + // Buffered time is 0 if no bytes are buffered. + pipeline_->SetBufferedBytes(0); + EXPECT_EQ(0, pipeline_->GetBufferedTime().ToInternalValue()); + + // We should return buffered_time_ if it is set, valid and less than + // the current time. + const base::TimeDelta buffered = base::TimeDelta::FromSeconds(10); + pipeline_->SetBufferedTime(buffered); + EXPECT_EQ(buffered.ToInternalValue(), + pipeline_->GetBufferedTime().ToInternalValue()); + + // Test the case where the current time is beyond the buffered time. + base::TimeDelta kSeekTime = buffered + base::TimeDelta::FromSeconds(5); + ExpectSeek(kSeekTime); + DoSeek(kSeekTime); + + // Verify that buffered time is equal to the current time. + EXPECT_EQ(kSeekTime, pipeline_->GetCurrentTime()); + EXPECT_EQ(kSeekTime, pipeline_->GetBufferedTime()); + + // Clear buffered time. + pipeline_->SetBufferedTime(base::TimeDelta()); + + double time_percent = + static_cast<double>(pipeline_->GetCurrentTime().ToInternalValue()) / + kDuration.ToInternalValue(); + + int estimated_bytes = static_cast<int>(time_percent * kTotalBytes); + + // Test VBR case where bytes have been consumed slower than the average rate. + pipeline_->SetBufferedBytes(estimated_bytes - 10); + EXPECT_EQ(pipeline_->GetCurrentTime(), pipeline_->GetBufferedTime()); + + // Test VBR case where the bytes have been consumed faster than the average + // rate. + pipeline_->SetBufferedBytes(estimated_bytes + 10); + EXPECT_LT(pipeline_->GetCurrentTime(), pipeline_->GetBufferedTime()); + + // If media has been fully received, we should return the duration + // of the media. + pipeline_->SetBufferedBytes(kTotalBytes); + EXPECT_EQ(kDuration.ToInternalValue(), + pipeline_->GetBufferedTime().ToInternalValue()); +} + +TEST_F(PipelineTest, DisableAudioRenderer) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_TRUE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); + + EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(1.0f)) + .WillOnce(DisableAudioRenderer(mocks_->audio_renderer())); + EXPECT_CALL(*mocks_->demuxer(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->audio_decoder(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->audio_renderer(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->video_decoder(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->video_renderer(), + OnAudioRendererDisabled()); + + mocks_->audio_renderer()->SetPlaybackRate(1.0f); + + // Verify that ended event is fired when video ends. + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); + FilterHost* host = pipeline_; + host->NotifyEnded(); +} + +TEST_F(PipelineTest, DisableAudioRendererDuringInit) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(true); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + EXPECT_CALL(*mocks_->demuxer(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->audio_decoder(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->audio_renderer(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->video_decoder(), + OnAudioRendererDisabled()); + EXPECT_CALL(*mocks_->video_renderer(), + OnAudioRendererDisabled()); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); + + // Verify that ended event is fired when video ends. + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); + FilterHost* host = pipeline_; + host->NotifyEnded(); +} + +TEST_F(PipelineTest, EndedCallback) { + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, base::TimeDelta()); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + InitializePipeline(PIPELINE_OK); + + // For convenience to simulate filters calling the methods. + FilterHost* host = pipeline_; + + // Due to short circuit evaluation we only need to test a subset of cases. + InSequence s; + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(false)); + host->NotifyEnded(); + + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(false)); + host->NotifyEnded(); + + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); + host->NotifyEnded(); +} + +// Static function & time variable used to simulate changes in wallclock time. +static int64 g_static_clock_time; +static base::Time StaticClockFunction() { + return base::Time::FromInternalValue(g_static_clock_time); +} + +TEST_F(PipelineTest, AudioStreamShorterThanVideo) { + base::TimeDelta duration = base::TimeDelta::FromSeconds(10); + + CreateAudioStream(); + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, duration); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + InitializePipeline(PIPELINE_OK); + + // For convenience to simulate filters calling the methods. + FilterHost* host = pipeline_; + + // Replace the clock so we can simulate wallclock time advancing w/o using + // Sleep(). + pipeline_->SetClockForTesting(new Clock(&StaticClockFunction)); + + EXPECT_EQ(0, host->GetTime().ToInternalValue()); + + float playback_rate = 1.0f; + EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate)); + pipeline_->SetPlaybackRate(playback_rate); + message_loop_.RunAllPending(); + + InSequence s; + + // Verify that the clock doesn't advance since it hasn't been started by + // a time update from the audio stream. + int64 start_time = host->GetTime().ToInternalValue(); + g_static_clock_time += + base::TimeDelta::FromMilliseconds(100).ToInternalValue(); + EXPECT_EQ(host->GetTime().ToInternalValue(), start_time); + + // Signal end of audio stream. + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(false)); + host->NotifyEnded(); + message_loop_.RunAllPending(); + + // Verify that the clock advances. + start_time = host->GetTime().ToInternalValue(); + g_static_clock_time += + base::TimeDelta::FromMilliseconds(100).ToInternalValue(); + EXPECT_GT(host->GetTime().ToInternalValue(), start_time); + + // Signal end of video stream and make sure OnEnded() callback occurs. + EXPECT_CALL(*mocks_->audio_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(*mocks_->video_renderer(), HasEnded()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK)); + host->NotifyEnded(); +} + +void SendReadErrorToCB(::testing::Unused, const FilterStatusCB& cb) { + cb.Run(PIPELINE_ERROR_READ); +} + +TEST_F(PipelineTest, ErrorDuringSeek) { + CreateAudioStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + + InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(10)); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializePipeline(PIPELINE_OK); + + float playback_rate = 1.0f; + EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(playback_rate)); + EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate)); + pipeline_->SetPlaybackRate(playback_rate); + message_loop_.RunAllPending(); + + InSequence s; + + base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); + + EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) + .WillOnce(Invoke(&SendReadErrorToCB)); + + pipeline_->Seek(seek_time,base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); + EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)); + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); + message_loop_.RunAllPending(); +} + +// Invoked function OnError. This asserts that the pipeline does not enqueue +// non-teardown related tasks while tearing down. +static void TestNoCallsAfterError( + Pipeline* pipeline, MessageLoop* message_loop, + PipelineStatus /* status */) { + CHECK(pipeline); + CHECK(message_loop); + + // When we get to this stage, the message loop should be empty. + message_loop->AssertIdle(); + + // Make calls on pipeline after error has occurred. + pipeline->SetPlaybackRate(0.5f); + pipeline->SetVolume(0.5f); + pipeline->SetPreload(AUTO); + + // No additional tasks should be queued as a result of these calls. + message_loop->AssertIdle(); +} + +TEST_F(PipelineTest, NoMessageDuringTearDownFromError) { + CreateAudioStream(); + MockDemuxerStreamVector streams; + streams.push_back(audio_stream()); + + InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(10)); + InitializeAudioDecoder(audio_stream()); + InitializeAudioRenderer(); + InitializePipeline(PIPELINE_OK); + + // Trigger additional requests on the pipeline during tear down from error. + base::Callback<void(PipelineStatus)> cb = base::Bind( + &TestNoCallsAfterError, pipeline_, &message_loop_); + ON_CALL(callbacks_, OnError(_)) + .WillByDefault(Invoke(&cb, &base::Callback<void(PipelineStatus)>::Run)); + + InSequence s; + + base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); + + EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _)) + .WillOnce(Invoke(&SendReadErrorToCB)); + + pipeline_->Seek(seek_time,base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); + EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)); + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); + message_loop_.RunAllPending(); +} + +TEST_F(PipelineTest, StartTimeIsZero) { + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); + InitializeDemuxer(&streams, kDuration); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); + + EXPECT_EQ(base::TimeDelta(), pipeline_->GetCurrentTime()); +} + +TEST_F(PipelineTest, StartTimeIsNonZero) { + const base::TimeDelta kStartTime = base::TimeDelta::FromSeconds(4); + const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); + + EXPECT_CALL(*mocks_->demuxer(), GetStartTime()) + .WillRepeatedly(Return(kStartTime)); + + CreateVideoStream(); + MockDemuxerStreamVector streams; + streams.push_back(video_stream()); + + InitializeDemuxer(&streams, kDuration); + InitializeVideoDecoder(video_stream()); + InitializeVideoRenderer(); + + InitializePipeline(PIPELINE_OK); + EXPECT_TRUE(pipeline_->IsInitialized()); + EXPECT_FALSE(pipeline_->HasAudio()); + EXPECT_TRUE(pipeline_->HasVideo()); + + EXPECT_EQ(kStartTime, pipeline_->GetCurrentTime()); +} + +class FlexibleCallbackRunner : public base::DelegateSimpleThread::Delegate { + public: + FlexibleCallbackRunner(base::TimeDelta delay, PipelineStatus status, + const PipelineStatusCB& callback) + : delay_(delay), + status_(status), + callback_(callback) { + if (delay_ < base::TimeDelta()) { + callback_.Run(status_); + return; + } + } + virtual void Run() { + if (delay_ < base::TimeDelta()) return; + base::PlatformThread::Sleep(delay_); + callback_.Run(status_); + } + + private: + base::TimeDelta delay_; + PipelineStatus status_; + PipelineStatusCB callback_; +}; + +void TestPipelineStatusNotification(base::TimeDelta delay) { + PipelineStatusNotification note; + // Arbitrary error value we expect to fish out of the notification after the + // callback is fired. + const PipelineStatus expected_error = PIPELINE_ERROR_URL_NOT_FOUND; + FlexibleCallbackRunner runner(delay, expected_error, note.Callback()); + base::DelegateSimpleThread thread(&runner, "FlexibleCallbackRunner"); + thread.Start(); + note.Wait(); + EXPECT_EQ(note.status(), expected_error); + thread.Join(); +} + +// Test that in-line callback (same thread, no yield) works correctly. +TEST(PipelineStatusNotificationTest, InlineCallback) { + TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(-1)); +} + +// Test that different-thread, no-delay callback works correctly. +TEST(PipelineStatusNotificationTest, ImmediateCallback) { + TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(0)); +} + +// Test that different-thread, some-delay callback (the expected common case) +// works correctly. +TEST(PipelineStatusNotificationTest, DelayedCallback) { + TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(20)); +} + +} // namespace media |