blob: 5ac1cbdb54c6720243d0834cce2723b37de957e0 [file] [log] [blame]
// Copyright (c) 2010 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 <deque>
#include "base/callback.h"
#include "base/thread.h"
#include "media/base/filters.h"
#include "media/base/mock_ffmpeg.h"
#include "media/base/mock_filter_host.h"
#include "media/base/mock_filters.h"
#include "media/base/mock_reader.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/ffmpeg_demuxer.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArgumentPointee;
using ::testing::StrictMock;
using ::testing::WithArgs;
using ::testing::_;
namespace media {
// Fixture class to facilitate writing tests. Takes care of setting up the
// FFmpeg, pipeline and filter host mocks.
class FFmpegDemuxerTest : public testing::Test {
protected:
// These constants refer to the stream ordering inside AVFormatContext. We
// simulate media with a data stream, audio stream and video stream. Having
// the data stream first forces the audio and video streams to get remapped
// from indices {1,2} to {0,1} respectively, which covers an important test
// case.
enum AVStreamIndex {
AV_STREAM_DATA,
AV_STREAM_VIDEO,
AV_STREAM_AUDIO,
AV_STREAM_MAX,
};
// These constants refer to the stream ordering inside an initialized
// FFmpegDemuxer based on the ordering of the AVStreamIndex constants.
enum DemuxerStreamIndex {
DS_STREAM_VIDEO,
DS_STREAM_AUDIO,
DS_STREAM_MAX,
};
static const int kDurations[];
static const int kChannels;
static const int kSampleRate;
static const int kWidth;
static const int kHeight;
static const size_t kDataSize;
static const uint8 kAudioData[];
static const uint8 kVideoData[];
static const uint8* kNullData;
FFmpegDemuxerTest() {
// Create an FFmpegDemuxer.
factory_ = FFmpegDemuxer::CreateFilterFactory();
MediaFormat media_format;
media_format.SetAsString(MediaFormat::kMimeType,
mime_type::kApplicationOctetStream);
demuxer_ = factory_->Create<FFmpegDemuxer>(media_format);
DCHECK(demuxer_);
// Inject a filter host and message loop and prepare a data source.
demuxer_->set_host(&host_);
demuxer_->set_message_loop(&message_loop_);
data_source_ = new StrictMock<MockDataSource>();
// Initialize FFmpeg fixtures.
memset(&format_context_, 0, sizeof(format_context_));
memset(&input_format_, 0, sizeof(input_format_));
memset(&streams_, 0, sizeof(streams_));
memset(&codecs_, 0, sizeof(codecs_));
// Initialize AVCodecContext structures.
codecs_[AV_STREAM_DATA].codec_type = CODEC_TYPE_DATA;
codecs_[AV_STREAM_DATA].codec_id = CODEC_ID_NONE;
codecs_[AV_STREAM_VIDEO].codec_type = CODEC_TYPE_VIDEO;
codecs_[AV_STREAM_VIDEO].codec_id = CODEC_ID_THEORA;
codecs_[AV_STREAM_VIDEO].width = kWidth;
codecs_[AV_STREAM_VIDEO].height = kHeight;
codecs_[AV_STREAM_AUDIO].codec_type = CODEC_TYPE_AUDIO;
codecs_[AV_STREAM_AUDIO].codec_id = CODEC_ID_VORBIS;
codecs_[AV_STREAM_AUDIO].channels = kChannels;
codecs_[AV_STREAM_AUDIO].sample_rate = kSampleRate;
input_format_.name = "foo";
format_context_.iformat = &input_format_;
// Initialize AVStream and AVFormatContext structures. We set the time base
// of the streams such that duration is reported in microseconds.
format_context_.nb_streams = AV_STREAM_MAX;
for (size_t i = 0; i < AV_STREAM_MAX; ++i) {
format_context_.streams[i] = &streams_[i];
streams_[i].codec = &codecs_[i];
streams_[i].duration = kDurations[i];
streams_[i].time_base.den = 1 * base::Time::kMicrosecondsPerSecond;
streams_[i].time_base.num = 1;
}
// Initialize MockFFmpeg.
MockFFmpeg::set(&mock_ffmpeg_);
}
virtual ~FFmpegDemuxerTest() {
// Call Stop() to shut down internal threads.
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
demuxer_->Stop(callback_.NewCallback());
// Finish up any remaining tasks.
message_loop_.RunAllPending();
// Release the reference to the demuxer.
demuxer_ = NULL;
// Reset MockFFmpeg.
MockFFmpeg::set(NULL);
}
// Sets up MockFFmpeg to allow FFmpegDemuxer to successfully initialize.
void InitializeDemuxerMocks() {
EXPECT_CALL(*MockFFmpeg::get(), AVOpenInputFile(_, _, NULL, 0, NULL))
.WillOnce(DoAll(SetArgumentPointee<0>(&format_context_), Return(0)));
EXPECT_CALL(*MockFFmpeg::get(), AVFindStreamInfo(&format_context_))
.WillOnce(Return(0));
EXPECT_CALL(*MockFFmpeg::get(), AVCloseInputFile(&format_context_));
}
// Initializes both MockFFmpeg and FFmpegDemuxer.
void InitializeDemuxer() {
InitializeDemuxerMocks();
// We expect a successful initialization.
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
// Since we ignore data streams, the duration should be equal to the longest
// supported stream's duration (audio, in this case).
base::TimeDelta expected_duration =
base::TimeDelta::FromMicroseconds(kDurations[AV_STREAM_AUDIO]);
EXPECT_CALL(host_, SetDuration(expected_duration));
demuxer_->Initialize(data_source_.get(), callback_.NewCallback());
message_loop_.RunAllPending();
}
// Fixture members.
scoped_refptr<FilterFactory> factory_;
scoped_refptr<FFmpegDemuxer> demuxer_;
scoped_refptr<StrictMock<MockDataSource> > data_source_;
StrictMock<MockFilterHost> host_;
StrictMock<MockFilterCallback> callback_;
MessageLoop message_loop_;
// FFmpeg fixtures.
AVFormatContext format_context_;
AVInputFormat input_format_;
AVCodecContext codecs_[AV_STREAM_MAX];
AVStream streams_[AV_STREAM_MAX];
MockFFmpeg mock_ffmpeg_;
private:
DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerTest);
};
// These durations are picked so that the demuxer chooses the longest supported
// stream, which would be 30 in this case for the audio stream.
const int FFmpegDemuxerTest::kDurations[AV_STREAM_MAX] = {100, 20, 30};
const int FFmpegDemuxerTest::kChannels = 2;
const int FFmpegDemuxerTest::kSampleRate = 44100;
const int FFmpegDemuxerTest::kWidth = 1280;
const int FFmpegDemuxerTest::kHeight = 720;
const size_t FFmpegDemuxerTest::kDataSize = 4;
const uint8 FFmpegDemuxerTest::kAudioData[kDataSize] = {0, 1, 2, 3};
const uint8 FFmpegDemuxerTest::kVideoData[kDataSize] = {4, 5, 6, 7};
const uint8* FFmpegDemuxerTest::kNullData = NULL;
TEST(FFmpegDemuxerFactoryTest, Create) {
// Should only accept application/octet-stream type.
scoped_refptr<FilterFactory> factory = FFmpegDemuxer::CreateFilterFactory();
MediaFormat media_format;
media_format.SetAsString(MediaFormat::kMimeType, "foo/x-bar");
scoped_refptr<Demuxer> demuxer(factory->Create<Demuxer>(media_format));
ASSERT_FALSE(demuxer);
// Try again with application/octet-stream mime type.
media_format.Clear();
media_format.SetAsString(MediaFormat::kMimeType,
mime_type::kApplicationOctetStream);
demuxer = factory->Create<Demuxer>(media_format);
ASSERT_TRUE(demuxer);
}
TEST_F(FFmpegDemuxerTest, Initialize_OpenFails) {
// Simulate av_open_input_file() failing.
EXPECT_CALL(*MockFFmpeg::get(), AVOpenInputFile(_, _, NULL, 0, NULL))
.WillOnce(Return(-1));
EXPECT_CALL(host_, SetError(DEMUXER_ERROR_COULD_NOT_OPEN));
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
demuxer_->Initialize(data_source_.get(), callback_.NewCallback());
message_loop_.RunAllPending();
}
TEST_F(FFmpegDemuxerTest, Initialize_ParseFails) {
// Simulate av_find_stream_info() failing.
EXPECT_CALL(*MockFFmpeg::get(), AVOpenInputFile(_, _, NULL, 0, NULL))
.WillOnce(DoAll(SetArgumentPointee<0>(&format_context_), Return(0)));
EXPECT_CALL(*MockFFmpeg::get(), AVFindStreamInfo(&format_context_))
.WillOnce(Return(AVERROR_IO));
EXPECT_CALL(*MockFFmpeg::get(), AVCloseInputFile(&format_context_));
EXPECT_CALL(host_, SetError(DEMUXER_ERROR_COULD_NOT_PARSE));
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
demuxer_->Initialize(data_source_.get(), callback_.NewCallback());
message_loop_.RunAllPending();
}
TEST_F(FFmpegDemuxerTest, Initialize_NoStreams) {
// Simulate media with no parseable streams.
{
SCOPED_TRACE("");
InitializeDemuxerMocks();
}
EXPECT_CALL(host_, SetError(DEMUXER_ERROR_NO_SUPPORTED_STREAMS));
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
format_context_.nb_streams = 0;
demuxer_->Initialize(data_source_.get(), callback_.NewCallback());
message_loop_.RunAllPending();
}
TEST_F(FFmpegDemuxerTest, Initialize_DataStreamOnly) {
// Simulate media with a data stream but no audio or video streams.
{
SCOPED_TRACE("");
InitializeDemuxerMocks();
}
EXPECT_CALL(host_, SetError(DEMUXER_ERROR_NO_SUPPORTED_STREAMS));
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
EXPECT_EQ(format_context_.streams[0], &streams_[AV_STREAM_DATA]);
format_context_.nb_streams = 1;
demuxer_->Initialize(data_source_.get(), callback_.NewCallback());
message_loop_.RunAllPending();
}
TEST_F(FFmpegDemuxerTest, Initialize_Successful) {
{
SCOPED_TRACE("");
InitializeDemuxer();
}
// Verify that our demuxer streams were created from our AVStream structures.
EXPECT_EQ(DS_STREAM_MAX, static_cast<int>(demuxer_->GetNumberOfStreams()));
// First stream should be video and support the FFmpegDemuxerStream interface.
scoped_refptr<DemuxerStream> stream = demuxer_->GetStream(DS_STREAM_VIDEO);
AVStreamProvider* av_stream_provider = NULL;
ASSERT_TRUE(stream);
std::string mime_type;
EXPECT_TRUE(
stream->media_format().GetAsString(MediaFormat::kMimeType, &mime_type));
EXPECT_STREQ(mime_type::kFFmpegVideo, mime_type.c_str());
EXPECT_TRUE(stream->QueryInterface(&av_stream_provider));
EXPECT_TRUE(av_stream_provider);
EXPECT_EQ(&streams_[AV_STREAM_VIDEO], av_stream_provider->GetAVStream());
// Other stream should be audio and support the FFmpegDemuxerStream interface.
stream = demuxer_->GetStream(DS_STREAM_AUDIO);
av_stream_provider = NULL;
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->media_format().GetAsString(MediaFormat::kMimeType,
&mime_type));
EXPECT_STREQ(mime_type::kFFmpegAudio, mime_type.c_str());
EXPECT_TRUE(stream->QueryInterface(&av_stream_provider));
EXPECT_TRUE(av_stream_provider);
EXPECT_EQ(&streams_[AV_STREAM_AUDIO], av_stream_provider->GetAVStream());
}
TEST_F(FFmpegDemuxerTest, Read_DiscardUninteresting) {
// We test that on a successful audio packet read, that the packet is
// duplicated (FFmpeg memory management safety), and a copy of it ends up in
// the DemuxerStream.
{
SCOPED_TRACE("");
InitializeDemuxer();
}
// Ignore all AVFreePacket() calls. We check this elsewhere.
EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).Times(AnyNumber());
// The demuxer will read a data packet which will get immediately freed,
// followed by a read error to end the reading.
InSequence s;
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_DATA, kNullData, 0));
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(Return(AVERROR_IO));
// Attempt a read from the audio stream and run the message loop until done.
scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DS_STREAM_AUDIO);
scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader());
reader->Read(audio);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_TRUE(reader->buffer()->IsEndOfStream());
}
TEST_F(FFmpegDemuxerTest, Read_Audio) {
// We test that on a successful audio packet read, that the packet is
// duplicated (FFmpeg memory management safety), and a copy of it ends up in
// the DemuxerStream.
{
SCOPED_TRACE("");
InitializeDemuxer();
}
// Ignore all AVFreePacket() calls. We check this via valgrind.
EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).Times(AnyNumber());
// The demuxer will read a data packet which will get immediately freed,
// followed by reading an audio packet...
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
// Attempt a read from the audio stream and run the message loop until done.
scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DS_STREAM_AUDIO);
scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader());
reader->Read(audio);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize());
EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(),
reader->buffer()->GetDataSize()));
}
TEST_F(FFmpegDemuxerTest, Read_Video) {
// We test that on a successful video packet read, that the packet is
// duplicated (FFmpeg memory management safety), and a copy of it ends up in
// the DemuxerStream.
{
SCOPED_TRACE("");
InitializeDemuxer();
}
// Ignore all AVFreePacket() calls. We check this via valgrind.
EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).Times(AnyNumber());
// Simulate a successful frame read.
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_VIDEO, kVideoData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
// Attempt a read from the video stream and run the message loop until done.
scoped_refptr<DemuxerStream> video = demuxer_->GetStream(DS_STREAM_VIDEO);
scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader());
reader->Read(video);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize());
EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(),
reader->buffer()->GetDataSize()));
}
TEST_F(FFmpegDemuxerTest, Read_EndOfStream) {
// On end of stream, a new, empty, AVPackets are created without any data for
// each stream and enqueued into the Buffer stream. Verify that these are
// indeed inserted.
{
SCOPED_TRACE("");
InitializeDemuxer();
}
// Ignore all AVFreePacket() calls. We check this via valgrind.
EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).Times(AnyNumber());
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(Return(AVERROR_IO));
// We should now expect an end of stream buffer.
scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DS_STREAM_AUDIO);
scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader());
reader->Read(audio);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_TRUE(reader->buffer()->IsEndOfStream());
EXPECT_TRUE(reader->buffer()->GetData() == NULL);
EXPECT_EQ(0u, reader->buffer()->GetDataSize());
}
TEST_F(FFmpegDemuxerTest, Seek) {
// We're testing the following:
//
// 1) The demuxer frees all queued packets when it receives a Seek().
// 2) The demuxer queues a single discontinuous packet on every stream.
//
// Since we can't test which packets are being freed, we use check points to
// infer that the correct packets have been freed.
{
SCOPED_TRACE("");
InitializeDemuxer();
}
// Get our streams.
scoped_refptr<DemuxerStream> video = demuxer_->GetStream(DS_STREAM_VIDEO);
scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DS_STREAM_AUDIO);
ASSERT_TRUE(video);
ASSERT_TRUE(audio);
// Expected values.
const int64 kExpectedTimestamp = 1234;
const int64 kExpectedFlags = AVSEEK_FLAG_BACKWARD;
// Ignore all AVFreePacket() calls. We check this via valgrind.
EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).Times(AnyNumber());
// Expect all calls in sequence.
InSequence s;
// First we'll read a video packet that causes two audio packets to be queued
// inside FFmpegDemuxer...
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_VIDEO, kVideoData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(1));
// ...then we'll expect a seek call...
EXPECT_CALL(*MockFFmpeg::get(),
AVSeekFrame(&format_context_, -1, kExpectedTimestamp, kExpectedFlags))
.WillOnce(Return(0));
// ...then our callback will be executed...
StrictMock<MockFilterCallback> seek_callback;
EXPECT_CALL(seek_callback, OnFilterCallback());
EXPECT_CALL(seek_callback, OnCallbackDestroyed());
EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(2));
// ...followed by two audio packet reads we'll trigger...
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
// ...followed by two video packet reads...
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_VIDEO, kVideoData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_VIDEO, kVideoData, kDataSize));
EXPECT_CALL(*MockFFmpeg::get(), AVDupPacket(_))
.WillOnce(Return(0));
// ...and finally a sanity checkpoint to make sure everything was released.
EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(3));
// Read a video packet and release it.
scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader());
reader->Read(video);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize());
EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(),
reader->buffer()->GetDataSize()));
// Release the video packet and verify the other packets are still queued.
reader->Reset();
message_loop_.RunAllPending();
MockFFmpeg::get()->CheckPoint(1);
// Issue a simple forward seek, which should discard queued packets.
demuxer_->Seek(base::TimeDelta::FromMicroseconds(kExpectedTimestamp),
seek_callback.NewCallback());
message_loop_.RunAllPending();
MockFFmpeg::get()->CheckPoint(2);
// The next read from each stream should now be discontinuous, but subsequent
// reads should not.
// Audio read #1, should be discontinuous.
reader->Read(audio);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_TRUE(reader->buffer()->IsDiscontinuous());
ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize());
EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(),
reader->buffer()->GetDataSize()));
// Audio read #2, should not be discontinuous.
reader->Reset();
reader->Read(audio);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize());
EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(),
reader->buffer()->GetDataSize()));
// Video read #1, should be discontinuous.
reader->Reset();
reader->Read(video);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_TRUE(reader->buffer()->IsDiscontinuous());
ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize());
EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(),
reader->buffer()->GetDataSize()));
// Video read #2, should not be discontinuous.
reader->Reset();
reader->Read(video);
message_loop_.RunAllPending();
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize());
EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(),
reader->buffer()->GetDataSize()));
// Manually release the last reference to the buffer and verify it was freed.
reader->Reset();
message_loop_.RunAllPending();
MockFFmpeg::get()->CheckPoint(3);
}
// A mocked callback specialization for calling Read(). Since RunWithParams()
// is mocked we don't need to pass in object or method pointers.
typedef CallbackImpl<FFmpegDemuxerTest,
void (FFmpegDemuxerTest::*)(Buffer*),
Tuple1<Buffer*> > ReadCallback;
class MockReadCallback : public ReadCallback {
public:
MockReadCallback()
: ReadCallback(NULL, NULL) {
}
virtual ~MockReadCallback() {
OnDelete();
}
MOCK_METHOD0(OnDelete, void());
MOCK_METHOD1(RunWithParams, void(const Tuple1<Buffer*>& params));
};
TEST_F(FFmpegDemuxerTest, Stop) {
// Tests that calling Read() on a stopped demuxer immediately deletes the
// callback.
{
SCOPED_TRACE("");
InitializeDemuxer();
}
// Create our mocked callback. The demuxer will take ownership of this
// pointer.
scoped_ptr<StrictMock<MockReadCallback> > callback(
new StrictMock<MockReadCallback>());
// Get our stream.
scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DS_STREAM_AUDIO);
ASSERT_TRUE(audio);
// Stop the demuxer.
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
demuxer_->Stop(callback_.NewCallback());
// Expect all calls in sequence.
InSequence s;
// The callback should be immediately deleted. We'll use a checkpoint to
// verify that it has indeed been deleted.
EXPECT_CALL(*callback, OnDelete());
EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(1));
// Attempt the read...
audio->Read(callback.release());
message_loop_.RunAllPending();
// ...and verify that |callback| was deleted.
MockFFmpeg::get()->CheckPoint(1);
}
TEST_F(FFmpegDemuxerTest, DisableAudioStream) {
// We are doing the following things here:
// 1. Initialize the demuxer with audio and video stream.
// 2. Send a "disable audio stream" message to the demuxer.
// 3. Demuxer will free audio packets even if audio stream was initialized.
{
SCOPED_TRACE("");
InitializeDemuxer();
}
// Submit a "disable audio stream" message to the demuxer.
demuxer_->OnAudioRendererDisabled();
message_loop_.RunAllPending();
// Ignore all AVFreePacket() calls. We check this via valgrind.
EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).Times(AnyNumber());
// Expect all calls in sequence.
InSequence s;
// The demuxer will read an audio packet which will get immediately freed.
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kNullData, 0));
// Then an end-of-stream packet is read.
EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
.WillOnce(Return(AVERROR_IO));
// Get our streams.
scoped_refptr<DemuxerStream> video = demuxer_->GetStream(DS_STREAM_VIDEO);
ASSERT_TRUE(video);
// Attempt a read from the video stream and run the message loop until done.
scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader());
reader->Read(video);
message_loop_.RunAllPending();
}
class MockFFmpegDemuxer : public FFmpegDemuxer {
public:
MockFFmpegDemuxer() {}
virtual ~MockFFmpegDemuxer() {}
MOCK_METHOD0(WaitForRead, size_t());
MOCK_METHOD1(SignalReadCompleted, void(size_t size));
private:
DISALLOW_COPY_AND_ASSIGN(MockFFmpegDemuxer);
};
// A gmock helper method to execute the callback and deletes it.
void RunCallback(size_t size, DataSource::ReadCallback* callback) {
DCHECK(callback);
callback->RunWithParams(Tuple1<size_t>(size));
delete callback;
}
TEST_F(FFmpegDemuxerTest, ProtocolRead) {
// Creates a demuxer.
scoped_refptr<MockFFmpegDemuxer> demuxer = new MockFFmpegDemuxer();
ASSERT_TRUE(demuxer);
demuxer->set_host(&host_);
demuxer->set_message_loop(&message_loop_);
demuxer->data_source_ = data_source_;
uint8 kBuffer[1];
InSequence s;
// Actions taken in the first read.
EXPECT_CALL(*data_source_, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
EXPECT_CALL(*data_source_, Read(0, 512, kBuffer, NotNull()))
.WillOnce(WithArgs<1, 3>(Invoke(&RunCallback)));
EXPECT_CALL(*demuxer, SignalReadCompleted(512));
EXPECT_CALL(*demuxer, WaitForRead())
.WillOnce(Return(512));
EXPECT_CALL(host_, SetCurrentReadPosition(512));
// Second read.
EXPECT_CALL(*data_source_, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
EXPECT_CALL(*data_source_, Read(512, 512, kBuffer, NotNull()))
.WillOnce(WithArgs<1, 3>(Invoke(&RunCallback)));
EXPECT_CALL(*demuxer, SignalReadCompleted(512));
EXPECT_CALL(*demuxer, WaitForRead())
.WillOnce(Return(512));
EXPECT_CALL(host_, SetCurrentReadPosition(1024));
// Third read will fail because it exceeds the file size.
EXPECT_CALL(*data_source_, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
// First read.
EXPECT_EQ(512, demuxer->Read(512, kBuffer));
int64 position;
EXPECT_TRUE(demuxer->GetPosition(&position));
EXPECT_EQ(512, position);
// Second read.
EXPECT_EQ(512, demuxer->Read(512, kBuffer));
EXPECT_TRUE(demuxer->GetPosition(&position));
EXPECT_EQ(1024, position);
// Third read will get an end-of-file error.
EXPECT_EQ(AVERROR_EOF, demuxer->Read(512, kBuffer));
// This read complete signal is generated when demuxer is stopped.
EXPECT_CALL(*demuxer, SignalReadCompleted(DataSource::kReadError));
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
demuxer->Stop(callback_.NewCallback());
message_loop_.RunAllPending();
}
TEST_F(FFmpegDemuxerTest, ProtocolGetSetPosition) {
{
SCOPED_TRACE("");
InitializeDemuxer();
}
InSequence s;
EXPECT_CALL(*data_source_, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
EXPECT_CALL(*data_source_, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
EXPECT_CALL(*data_source_, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
int64 position;
EXPECT_TRUE(demuxer_->GetPosition(&position));
EXPECT_EQ(0, position);
EXPECT_TRUE(demuxer_->SetPosition(512));
EXPECT_FALSE(demuxer_->SetPosition(2048));
EXPECT_FALSE(demuxer_->SetPosition(-1));
EXPECT_TRUE(demuxer_->GetPosition(&position));
EXPECT_EQ(512, position);
}
TEST_F(FFmpegDemuxerTest, ProtocolGetSize) {
{
SCOPED_TRACE("");
InitializeDemuxer();
}
EXPECT_CALL(*data_source_, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
int64 size;
EXPECT_TRUE(demuxer_->GetSize(&size));
EXPECT_EQ(1024, size);
}
TEST_F(FFmpegDemuxerTest, ProtocolIsStreaming) {
{
SCOPED_TRACE("");
InitializeDemuxer();
}
EXPECT_CALL(*data_source_, IsStreaming())
.WillOnce(Return(false));
EXPECT_FALSE(demuxer_->IsStreaming());
}
} // namespace media