blob: 66b430ab5a04bf8d8a7f7becf1e95f4250bd10c9 [file] [log] [blame]
// Copyright (c) 2011 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/callback.h"
#include "base/stl_util.h"
#include "base/threading/simple_thread.h"
#include "media/base/filter_host.h"
#include "media/base/filters.h"
#include "media/base/media_log.h"
#include "media/base/pipeline_impl.h"
#include "media/base/mock_callback.h"
#include "media/base/mock_filters.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::DeleteArg;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::StrictMock;
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);
};
// 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 PipelineImplTest : public ::testing::Test {
public:
PipelineImplTest()
: pipeline_(new PipelineImpl(&message_loop_, new MediaLog())) {
pipeline_->Init(
base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)),
base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)),
PipelineStatusCB());
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 ~PipelineImplTest() {
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(NotNull()))
.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, NotNull(), NotNull()))
.WillOnce(DoAll(Invoke(&RunFilterCallback3), DeleteArg<2>()));
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(NotNull()))
.WillOnce(Invoke(&RunStopFilterCallback));
}
// Sets up expectations to allow the audio decoder to initialize.
void InitializeAudioDecoder(MockDemuxerStream* stream) {
EXPECT_CALL(*mocks_->audio_decoder(),
Initialize(stream, NotNull(), NotNull()))
.WillOnce(DoAll(Invoke(&RunFilterCallback3), DeleteArg<2>()));
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(NotNull()))
.WillOnce(Invoke(&RunStopFilterCallback));
}
// Sets up expectations to allow the video renderer to initialize.
void InitializeVideoRenderer() {
EXPECT_CALL(*mocks_->video_renderer(),
Initialize(mocks_->video_decoder(), NotNull(), NotNull()))
.WillOnce(DoAll(Invoke(&RunFilterCallback3), DeleteArg<2>()));
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(NotNull()))
.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(), NotNull()))
.WillOnce(DoAll(Invoke(&RunFilterCallback),
DisableAudioRenderer(mocks_->audio_renderer())));
} else {
EXPECT_CALL(*mocks_->audio_renderer(),
Initialize(mocks_->audio_decoder(), NotNull()))
.WillOnce(Invoke(&RunFilterCallback));
}
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(NotNull()))
.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),
"",
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<PipelineImpl> pipeline_;
scoped_ptr<media::MockFilterCollection> mocks_;
scoped_refptr<StrictMock<MockDemuxerStream> > audio_stream_;
scoped_refptr<StrictMock<MockDemuxerStream> > video_stream_;
private:
DISALLOW_COPY_AND_ASSIGN(PipelineImplTest);
};
// Test that playback controls methods no-op when the pipeline hasn't been
// started.
TEST_F(PipelineImplTest, 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.
size_t width = 1u;
size_t height = 1u;
pipeline_->GetVideoSize(&width, &height);
EXPECT_EQ(0u, width);
EXPECT_EQ(0u, height);
}
TEST_F(PipelineImplTest, 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),
"",
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(PipelineImplTest, 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.
FilterCollection* collection =
mocks_->filter_collection(false,
true,
true,
PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
pipeline_->Start(collection, "",
base::Bind(&CallbackHelper::OnStart,
base::Unretained(&callbacks_)));
message_loop_.RunAllPending();
EXPECT_FALSE(pipeline_->IsInitialized());
}
TEST_F(PipelineImplTest, 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(PipelineImplTest, NoStreams) {
// Manually set these expectations because SetPlaybackRate() is not called if
// we cannot fully initialize the pipeline.
EXPECT_CALL(*mocks_->demuxer(), Stop(NotNull()))
.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(PipelineImplTest, 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(PipelineImplTest, 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(PipelineImplTest, 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(PipelineImplTest, 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(PipelineImplTest, 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(PipelineImplTest, 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(PipelineImplTest, 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());
// If media is loaded, we should return duration of media.
pipeline_->SetLoaded(true);
EXPECT_EQ(kDuration.ToInternalValue(),
pipeline_->GetBufferedTime().ToInternalValue());
}
TEST_F(PipelineImplTest, 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(PipelineImplTest, 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(PipelineImplTest, 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(PipelineImplTest, 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(PipelineImplTest, 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();
}
TEST_F(PipelineImplTest, 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(PipelineImplTest, 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(int delayInMs, PipelineStatus status,
const PipelineStatusCB& callback)
: delayInMs_(delayInMs), status_(status), callback_(callback) {
if (delayInMs_ < 0) {
callback_.Run(status_);
return;
}
}
virtual void Run() {
if (delayInMs_ < 0) return;
base::PlatformThread::Sleep(delayInMs_);
callback_.Run(status_);
}
private:
int delayInMs_;
PipelineStatus status_;
PipelineStatusCB callback_;
};
void TestPipelineStatusNotification(int delayInMs) {
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(delayInMs, 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(-1);
}
// Test that different-thread, no-delay callback works correctly.
TEST(PipelineStatusNotificationTest, ImmediateCallback) {
TestPipelineStatusNotification(0);
}
// Test that different-thread, some-delay callback (the expected common case)
// works correctly.
TEST(PipelineStatusNotificationTest, DelayedCallback) {
TestPipelineStatusNotification(20);
}
} // namespace media