summaryrefslogtreecommitdiffstats
path: root/media/base/pipeline_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/base/pipeline_unittest.cc')
-rw-r--r--media/base/pipeline_unittest.cc928
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