summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-19 18:23:49 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-19 18:23:49 +0000
commitb1dc1dec10e03ca7fdf4219d7059dfc07973811a (patch)
tree87c44dfa015a6139daac9d027f906d9d6581d051 /media
parent68a983963a8ef1718400bb7a7f177f5a26ae4656 (diff)
downloadchromium_src-b1dc1dec10e03ca7fdf4219d7059dfc07973811a.zip
chromium_src-b1dc1dec10e03ca7fdf4219d7059dfc07973811a.tar.gz
chromium_src-b1dc1dec10e03ca7fdf4219d7059dfc07973811a.tar.bz2
Refactor FFmpegDemuxerTest to use gmock and eliminate flakiness.
This eliminates all final traces of the old global-functions-and-variables style of mocking, which was a massive headache to maintain and verify correctness. No new tests have been added, however gmock creates much stronger assertions for the tests themselves. I also made FFmpegDemuxerTest a friend of FFmpegDemuxer to let tests verify that all tasks on the internal demuxing thread have completed. BUG=13933 TEST=FFmpegDemuxerTest should more awesome and less flaky Review URL: http://codereview.chromium.org/126306 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18833 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/base/mock_ffmpeg.cc71
-rw-r--r--media/base/mock_ffmpeg.h78
-rw-r--r--media/filters/ffmpeg_demuxer.cc4
-rw-r--r--media/filters/ffmpeg_demuxer.h3
-rw-r--r--media/filters/ffmpeg_demuxer_unittest.cc851
5 files changed, 569 insertions, 438 deletions
diff --git a/media/base/mock_ffmpeg.cc b/media/base/mock_ffmpeg.cc
index 4e9fc03..b1de0c4 100644
--- a/media/base/mock_ffmpeg.cc
+++ b/media/base/mock_ffmpeg.cc
@@ -11,6 +11,24 @@ namespace media {
MockFFmpeg* MockFFmpeg::instance_ = NULL;
+MockFFmpeg::MockFFmpeg()
+ : outstanding_packets_(0) {
+}
+
+MockFFmpeg::~MockFFmpeg() {
+ CHECK(!outstanding_packets_)
+ << "MockFFmpeg destroyed with outstanding packets";
+}
+
+void MockFFmpeg::inc_outstanding_packets() {
+ ++outstanding_packets_;
+}
+
+void MockFFmpeg::dec_outstanding_packets() {
+ CHECK(outstanding_packets_ > 0);
+ --outstanding_packets_;
+}
+
// static
void MockFFmpeg::set(MockFFmpeg* instance) {
instance_ = instance;
@@ -21,6 +39,13 @@ MockFFmpeg* MockFFmpeg::get() {
return instance_;
}
+// static
+void MockFFmpeg::DestructPacket(AVPacket* packet) {
+ delete [] packet->data;
+ packet->data = NULL;
+ packet->size = 0;
+}
+
// FFmpeg stubs that delegate to the FFmpegMock instance.
extern "C" {
@@ -55,6 +80,52 @@ void av_init_packet(AVPacket* pkt) {
NOTREACHED();
}
+int av_open_input_file(AVFormatContext** format, const char* filename,
+ AVInputFormat* input_format, int buffer_size,
+ AVFormatParameters* parameters) {
+ return media::MockFFmpeg::get()->AVOpenInputFile(format, filename,
+ input_format, buffer_size,
+ parameters);
+}
+
+int av_find_stream_info(AVFormatContext* format) {
+ return media::MockFFmpeg::get()->AVFindStreamInfo(format);
+}
+
+int64 av_rescale_q(int64 a, AVRational bq, AVRational cq) {
+ // Because this is a math function there's little point in mocking it, so we
+ // implement a cheap version that's capable of overflowing.
+ int64 num = bq.num * cq.den;
+ int64 den = cq.num * bq.den;
+ return a * num / den;
+}
+
+void av_free_packet(AVPacket* packet) {
+ media::MockFFmpeg::get()->AVFreePacket(packet);
+}
+
+int av_new_packet(AVPacket* packet, int size) {
+ return media::MockFFmpeg::get()->AVNewPacket(packet, size);
+}
+
+void av_free(void* ptr) {
+ // Freeing NULL pointers are valid, but they aren't interesting from a mock
+ // perspective.
+ if (ptr) {
+ media::MockFFmpeg::get()->AVFree(ptr);
+ }
+}
+
+int av_read_frame(AVFormatContext* format, AVPacket* packet) {
+ return media::MockFFmpeg::get()->AVReadFrame(format, packet);
+}
+
+int av_seek_frame(AVFormatContext *format, int stream_index, int64_t timestamp,
+ int flags) {
+ return media::MockFFmpeg::get()->AVSeekFrame(format, stream_index, timestamp,
+ flags);
+}
+
} // extern "C"
} // namespace media
diff --git a/media/base/mock_ffmpeg.h b/media/base/mock_ffmpeg.h
index 711cf15..597898e 100644
--- a/media/base/mock_ffmpeg.h
+++ b/media/base/mock_ffmpeg.h
@@ -7,25 +7,101 @@
// TODO(scherkus): See if we can remove ffmpeg_common from this file.
#include "media/filters/ffmpeg_common.h"
-
#include "testing/gmock/include/gmock/gmock.h"
namespace media {
class MockFFmpeg {
public:
+ MockFFmpeg();
+ ~MockFFmpeg();
+
MOCK_METHOD1(AVCodecFindDecoder, AVCodec*(enum CodecID id));
MOCK_METHOD2(AVCodecOpen, int(AVCodecContext* avctx, AVCodec* codec));
MOCK_METHOD2(AVCodecThreadInit, int(AVCodecContext* avctx, int threads));
+ MOCK_METHOD5(AVOpenInputFile, int(AVFormatContext** format,
+ const char* filename,
+ AVInputFormat* input_format,
+ int buffer_size,
+ AVFormatParameters* parameters));
+ MOCK_METHOD1(AVFindStreamInfo, int(AVFormatContext* format));
+ MOCK_METHOD2(AVReadFrame, int(AVFormatContext* format, AVPacket* packet));
+ MOCK_METHOD4(AVSeekFrame, int(AVFormatContext *format,
+ int stream_index,
+ int64_t timestamp,
+ int flags));
+
+ MOCK_METHOD2(AVNewPacket, int(AVPacket* packet, int size));
+ MOCK_METHOD1(AVFree, void(void* ptr));
+ MOCK_METHOD1(AVFreePacket, void(AVPacket* packet));
+
+ // Used for verifying check points during tests.
+ MOCK_METHOD1(CheckPoint, void(int id));
+
// Setter/getter for the global instance of MockFFmpeg.
static void set(MockFFmpeg* instance);
static MockFFmpeg* get();
+ // AVPacket destructor for packets allocated by av_new_packet().
+ static void DestructPacket(AVPacket* packet);
+
+ // Modifies the number of outstanding packets.
+ void inc_outstanding_packets();
+ void dec_outstanding_packets();
+
private:
static MockFFmpeg* instance_;
+
+ // Tracks the number of packets allocated by calls to av_read_frame() and
+ // av_free_packet(). We crash the unit test if this is not zero at time of
+ // destruction.
+ int outstanding_packets_;
};
+// Used for simulating av_read_frame().
+ACTION_P3(CreatePacket, stream_index, data, size) {
+ // Confirm we're dealing with AVPacket so we can safely const_cast<>.
+ ::testing::StaticAssertTypeEq<AVPacket*, arg1_type>();
+ memset(arg1, 0, sizeof(*arg1));
+ arg1->stream_index = stream_index;
+ arg1->data = const_cast<uint8*>(data);
+ arg1->size = size;
+
+ // Increment number of packets allocated.
+ MockFFmpeg::get()->inc_outstanding_packets();
+
+ return 0;
+}
+
+// Used for simulating av_new_packet().
+ACTION(NewPacket) {
+ ::testing::StaticAssertTypeEq<AVPacket*, arg0_type>();
+ int size = arg1;
+ memset(arg0, 0, sizeof(*arg0));
+ arg0->data = new uint8[size];
+ arg0->size = size;
+ arg0->destruct = &MockFFmpeg::DestructPacket;
+
+ // Increment number of packets allocated.
+ MockFFmpeg::get()->inc_outstanding_packets();
+
+ return 0;
+}
+
+// Used for simulating av_free_packet().
+ACTION(FreePacket) {
+ ::testing::StaticAssertTypeEq<AVPacket*, arg0_type>();
+
+ // Call the destructor if present, such as the one assigned in NewPacket().
+ if (arg0->destruct) {
+ arg0->destruct(arg0);
+ }
+
+ // Decrement number of packets allocated.
+ MockFFmpeg::get()->dec_outstanding_packets();
+}
+
} // namespace media
#endif // MEDIA_BASE_MOCK_FFMPEG_H_
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index 2e04b62..d4c7fc55 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -171,7 +171,8 @@ FFmpegDemuxer::FFmpegDemuxer()
}
FFmpegDemuxer::~FFmpegDemuxer() {
- Stop();
+ DCHECK(!thread_.IsRunning());
+ DCHECK(!format_context_.get());
// TODO(scherkus): I believe we need to use av_close_input_file() here
// instead of scoped_ptr_malloc calling av_free().
//
@@ -186,6 +187,7 @@ void FFmpegDemuxer::PostDemuxTask() {
void FFmpegDemuxer::Stop() {
thread_.Stop();
+ format_context_.reset();
}
void FFmpegDemuxer::Seek(base::TimeDelta time) {
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index 3f34b9c..509333a 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -121,6 +121,9 @@ class FFmpegDemuxer : public Demuxer {
virtual scoped_refptr<DemuxerStream> GetStream(int stream_id);
private:
+ // Accesses |thread_| to create a helper method used by every test.
+ friend class FFmpegDemuxerTest;
+
// Only allow a factory to create this class.
friend class FilterFactoryImpl0<FFmpegDemuxer>;
FFmpegDemuxer();
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc
index b689616..df427f8 100644
--- a/media/filters/ffmpeg_demuxer_unittest.cc
+++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -8,6 +8,7 @@
#include "base/tuple.h"
#include "media/base/filter_host.h"
#include "media/base/filters.h"
+#include "media/base/mock_ffmpeg.h"
#include "media/base/mock_filter_host.h"
#include "media/base/mock_media_filters.h"
#include "media/base/mock_reader.h"
@@ -15,183 +16,11 @@
#include "media/filters/ffmpeg_demuxer.h"
#include "testing/gtest/include/gtest/gtest.h"
-namespace {
-
-// Simulates a queue of media packets that get "demuxed" when av_read_frame()
-// is called. It also tracks the number of packets read but not released,
-// which lets us test for memory leaks and handling seeks.
-class PacketQueue : public Singleton<PacketQueue> {
- public:
- bool IsEmpty() {
- return packets_.empty();
- }
-
- void Enqueue(int stream, size_t size, uint8* data) {
- packets_.push_back(PacketTuple(stream, size, data));
- }
-
- void Dequeue(AVPacket* packet) {
- CHECK(!packets_.empty());
- memset(packet, 0, sizeof(*packet));
- packet->stream_index = packets_.front().a;
- packet->size = packets_.front().b;
- packet->data = packets_.front().c;
- packet->destruct = &PacketQueue::DestructPacket;
- packets_.pop_front();
-
- // We now have an outstanding packet which must be freed at some point.
- ++outstanding_packets_;
- }
-
- bool WaitForOutstandingPackets(int count) {
- const base::TimeDelta kTimedWait = base::TimeDelta::FromMilliseconds(500);
- while (outstanding_packets_ != count) {
- if (!wait_for_outstanding_packets_.TimedWait(kTimedWait)) {
- return false;
- }
- }
- return true;
- }
-
- private:
- static void DestructPacket(AVPacket* packet) {
- PacketQueue::get()->DestructPacket();
- }
-
- void DestructPacket() {
- --outstanding_packets_;
- wait_for_outstanding_packets_.Signal();
- }
-
- // Only allow Singleton to create and delete PacketQueue.
- friend struct DefaultSingletonTraits<PacketQueue>;
-
- PacketQueue()
- : outstanding_packets_(0),
- wait_for_outstanding_packets_(false, false) {
- }
-
- ~PacketQueue() {
- CHECK(outstanding_packets_ == 0);
- }
-
- // Packet queue for tests to enqueue mock packets, which are dequeued when
- // FFmpegDemuxer calls av_read_frame().
- typedef Tuple3<int, size_t, uint8*> PacketTuple;
- std::deque<PacketTuple> packets_;
-
- // Counts the number of packets "allocated" by av_read_frame() and "released"
- // by av_free_packet(). This should always be zero after everything is
- // cleaned up.
- int outstanding_packets_;
-
- // Tests can wait on this event until a specific number of outstanding packets
- // have been reached. Used to ensure other threads release their references
- // to objects so we don't get false positive test results when comparing the
- // number of outstanding packets.
- base::WaitableEvent wait_for_outstanding_packets_;
-
- DISALLOW_COPY_AND_ASSIGN(PacketQueue);
-};
-
-} // namespace
-
-// FFmpeg mocks to remove dependency on having the DLLs present.
-extern "C" {
-static const size_t kMaxStreams = 3;
-static AVFormatContext g_format;
-static AVStream g_streams[kMaxStreams];
-static AVCodecContext g_audio_codec;
-static AVCodecContext g_video_codec;
-static AVCodecContext g_data_codec;
-
-// FFmpeg return codes for various functions.
-static int g_av_open_input_file = 0;
-static int g_av_find_stream_info = 0;
-static int g_av_read_frame = 0;
-static int g_av_seek_frame = 0;
-
-// Expected values when seeking.
-static base::WaitableEvent* g_seek_event = NULL;
-static int64_t g_expected_seek_timestamp = 0;
-static int g_expected_seek_flags = 0;
-
-// Counts outstanding packets allocated by av_new_frame().
-static int g_outstanding_packets_av_new_frame = 0;
-
-int av_open_input_file(AVFormatContext** format, const char* filename,
- AVInputFormat* input_format, int buffer_size,
- AVFormatParameters* parameters) {
- EXPECT_FALSE(input_format) << "AVInputFormat should be NULL.";
- EXPECT_FALSE(buffer_size) << "buffer_size should be 0.";
- EXPECT_FALSE(parameters) << "AVFormatParameters should be NULL.";
- if (g_av_open_input_file < 0) {
- *format = NULL;
- } else {
- *format = &g_format;
- }
- return g_av_open_input_file;
-}
-
-int av_find_stream_info(AVFormatContext* format) {
- EXPECT_EQ(&g_format, format);
- return g_av_find_stream_info;
-}
-
-int64 av_rescale_q(int64 a, AVRational bq, AVRational cq) {
- int64 num = bq.num * cq.den;
- int64 den = cq.num * bq.den;
- return a * num / den;
-}
-
-void av_free_packet(AVPacket* packet) {
- if (packet->destruct) {
- packet->destruct(packet);
- packet->data = NULL;
- packet->size = 0;
- }
-}
-
-void DestructPacket(AVPacket* packet) {
- delete [] packet->data;
- --g_outstanding_packets_av_new_frame;
-}
-
-int av_new_packet(AVPacket* packet, int size) {
- memset(packet, 0, sizeof(*packet));
- packet->data = new uint8[size];
- packet->size = size;
- packet->destruct = &DestructPacket;
- ++g_outstanding_packets_av_new_frame;
- return 0;
-}
-
-void av_free(void* ptr) {
- if (ptr) {
- EXPECT_EQ(&g_format, ptr);
- }
-}
-
-int av_read_frame(AVFormatContext* format, AVPacket* packet) {
- EXPECT_EQ(&g_format, format);
- if (g_av_read_frame == 0) {
- PacketQueue::get()->Dequeue(packet);
- }
- return g_av_read_frame;
-}
-
-int av_seek_frame(AVFormatContext *format, int stream_index, int64_t timestamp,
- int flags) {
- EXPECT_EQ(&g_format, format);
- EXPECT_EQ(-1, stream_index); // Should always use -1 for default stream.
- EXPECT_EQ(g_expected_seek_timestamp, timestamp);
- EXPECT_EQ(g_expected_seek_flags, flags);
- EXPECT_FALSE(g_seek_event->IsSignaled());
- g_seek_event->Signal();
- return g_av_seek_frame;
-}
-
-} // extern "C"
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
namespace media {
@@ -199,18 +28,45 @@ namespace media {
// FFmpeg, pipeline and filter host mocks.
class FFmpegDemuxerTest : public testing::Test {
protected:
- FFmpegDemuxerTest() {}
- virtual ~FFmpegDemuxerTest() {}
-
- virtual void SetUp() {
- InitializeFFmpegMocks();
-
+ // 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()
+ : wait_for_demuxer_(false, false) {
// Create an FFmpegDemuxer.
factory_ = FFmpegDemuxer::CreateFilterFactory();
MediaFormat media_format;
media_format.SetAsString(MediaFormat::kMimeType,
mime_type::kApplicationOctetStream);
- demuxer_ = factory_->Create<Demuxer>(media_format);
+ demuxer_ = factory_->Create<FFmpegDemuxer>(media_format);
DCHECK(demuxer_);
// Prepare a filter host and data source for the demuxer.
@@ -218,58 +74,113 @@ class FFmpegDemuxerTest : public testing::Test {
filter_host_.reset(new MockFilterHost<Demuxer>(pipeline_.get(), demuxer_));
old_mocks::MockFilterConfig config;
data_source_ = new old_mocks::MockDataSource(&config);
+
+ // Initialize FFmpeg fixtures.
+ memset(&format_context_, 0, sizeof(format_context_));
+ memset(&streams_, 0, sizeof(streams_));
+ memset(&codecs_, 0, sizeof(codecs_));
+
+ // Initialize AVCodexContext 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;
+
+ // 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 void TearDown() {
+ virtual ~FFmpegDemuxerTest() {
// Call Stop() to shut down internal threads.
demuxer_->Stop();
+
+ // 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(), AVFree(&format_context_));
+ }
+
+ // Initializes both MockFFmpeg and FFmpegDemuxer.
+ void InitializeDemuxer() {
+ InitializeDemuxerMocks();
+ EXPECT_TRUE(demuxer_->Initialize(data_source_.get()));
+ EXPECT_TRUE(filter_host_->WaitForInitialized());
+ EXPECT_TRUE(filter_host_->IsInitialized());
+ EXPECT_EQ(PIPELINE_OK, pipeline_->GetError());
+ }
+
+ // To eliminate flakiness, this method will wait for the demuxer's message
+ // loop to finish any currently executing and queued tasks.
+ void WaitForDemuxerThread() {
+ demuxer_->thread_.message_loop()->PostTask(FROM_HERE,
+ NewRunnableFunction(&FFmpegDemuxerTest::Notify, &wait_for_demuxer_));
+ wait_for_demuxer_.Wait();
}
// Fixture members.
scoped_refptr<FilterFactory> factory_;
- scoped_refptr<Demuxer> demuxer_;
+ scoped_refptr<FFmpegDemuxer> demuxer_;
scoped_ptr<MockPipeline> pipeline_;
scoped_ptr<MockFilterHost<Demuxer> > filter_host_;
scoped_refptr<old_mocks::MockDataSource> data_source_;
- private:
- static void InitializeFFmpegMocks() {
- // Initialize function return codes.
- g_av_open_input_file = 0;
- g_av_find_stream_info = 0;
- g_av_read_frame = 0;
-
- // Initialize AVFormatContext structure.
- memset(&g_format, 0, sizeof(g_format));
-
- // Initialize AVStream structures.
- for (size_t i = 0; i < kMaxStreams; ++i) {
- memset(&g_streams[i], 0, sizeof(g_streams[i]));
- g_streams[i].time_base.den = 1 * base::Time::kMicrosecondsPerSecond;
- g_streams[i].time_base.num = 1;
- }
+ // FFmpeg fixtures.
+ AVFormatContext format_context_;
+ AVCodecContext codecs_[AV_STREAM_MAX];
+ AVStream streams_[AV_STREAM_MAX];
+ MockFFmpeg mock_ffmpeg_;
- // Initialize AVCodexContext structures.
- memset(&g_audio_codec, 0, sizeof(g_audio_codec));
- g_audio_codec.codec_type = CODEC_TYPE_AUDIO;
- g_audio_codec.codec_id = CODEC_ID_VORBIS;
- g_audio_codec.channels = 2;
- g_audio_codec.sample_rate = 44100;
-
- memset(&g_video_codec, 0, sizeof(g_video_codec));
- g_video_codec.codec_type = CODEC_TYPE_VIDEO;
- g_video_codec.codec_id = CODEC_ID_THEORA;
- g_video_codec.height = 720;
- g_video_codec.width = 1280;
-
- memset(&g_data_codec, 0, sizeof(g_data_codec));
- g_data_codec.codec_type = CODEC_TYPE_DATA;
- g_data_codec.codec_id = CODEC_ID_NONE;
+ private:
+ // Used with NewRunnableFunction() -- we don't use NewRunnableMethod() since
+ // it would force this class to be refcounted causing double deletions.
+ static void Notify(base::WaitableEvent* event) {
+ event->Signal();
}
+ base::WaitableEvent wait_for_demuxer_;
+
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();
@@ -286,72 +197,72 @@ TEST(FFmpegDemuxerFactoryTest, Create) {
ASSERT_TRUE(demuxer);
}
-TEST_F(FFmpegDemuxerTest, InitializeCouldNotOpen) {
- // Simulate av_open_input_fail failing.
- g_av_open_input_file = AVERROR_IO;
- g_av_find_stream_info = 0;
+TEST_F(FFmpegDemuxerTest, Initialize_OpenFails) {
+ // Simulate av_open_input_file() failing.
+ EXPECT_CALL(*MockFFmpeg::get(), AVOpenInputFile(_, _, NULL, 0, NULL))
+ .WillOnce(Return(-1));
+
EXPECT_TRUE(demuxer_->Initialize(data_source_.get()));
EXPECT_TRUE(filter_host_->WaitForError(DEMUXER_ERROR_COULD_NOT_OPEN));
EXPECT_FALSE(filter_host_->IsInitialized());
}
-TEST_F(FFmpegDemuxerTest, InitializeCouldNotParse) {
- // Simulate av_find_stream_info failing.
- g_av_open_input_file = 0;
- g_av_find_stream_info = AVERROR_IO;
+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(), AVFree(&format_context_));
EXPECT_TRUE(demuxer_->Initialize(data_source_.get()));
EXPECT_TRUE(filter_host_->WaitForError(DEMUXER_ERROR_COULD_NOT_PARSE));
EXPECT_FALSE(filter_host_->IsInitialized());
}
-TEST_F(FFmpegDemuxerTest, InitializeNoStreams) {
+TEST_F(FFmpegDemuxerTest, Initialize_NoStreams) {
// Simulate media with no parseable streams.
+ {
+ SCOPED_TRACE("");
+ InitializeDemuxerMocks();
+ }
+ format_context_.nb_streams = 0;
+
EXPECT_TRUE(demuxer_->Initialize(data_source_.get()));
EXPECT_TRUE(filter_host_->WaitForError(DEMUXER_ERROR_NO_SUPPORTED_STREAMS));
EXPECT_FALSE(filter_host_->IsInitialized());
}
-TEST_F(FFmpegDemuxerTest, InitializeDataStreamOnly) {
+TEST_F(FFmpegDemuxerTest, Initialize_DataStreamOnly) {
// Simulate media with a data stream but no audio or video streams.
- g_format.nb_streams = 1;
- g_format.streams[0] = &g_streams[0];
- g_streams[0].codec = &g_data_codec;
- g_streams[0].duration = 10;
+ {
+ SCOPED_TRACE("");
+ InitializeDemuxerMocks();
+ }
+ EXPECT_EQ(format_context_.streams[0], &streams_[AV_STREAM_DATA]);
+ format_context_.nb_streams = 1;
EXPECT_TRUE(demuxer_->Initialize(data_source_.get()));
EXPECT_TRUE(filter_host_->WaitForError(DEMUXER_ERROR_NO_SUPPORTED_STREAMS));
EXPECT_FALSE(filter_host_->IsInitialized());
}
-TEST_F(FFmpegDemuxerTest, InitializeStreams) {
- // Simulate media with a data stream, a video stream and audio stream.
- g_format.nb_streams = 3;
- g_format.streams[0] = &g_streams[0];
- g_format.streams[1] = &g_streams[1];
- g_format.streams[2] = &g_streams[2];
- g_streams[0].duration = 1000;
- g_streams[0].codec = &g_data_codec;
- g_streams[1].duration = 100;
- g_streams[1].codec = &g_video_codec;
- g_streams[2].duration = 10;
- g_streams[2].codec = &g_audio_codec;
-
- // Initialize the demuxer.
- EXPECT_TRUE(demuxer_->Initialize(data_source_.get()));
- EXPECT_TRUE(filter_host_->WaitForInitialized());
- EXPECT_TRUE(filter_host_->IsInitialized());
- EXPECT_EQ(PIPELINE_OK, pipeline_->GetError());
+TEST_F(FFmpegDemuxerTest, Initialize_Successful) {
+ {
+ SCOPED_TRACE("");
+ InitializeDemuxer();
+ }
- // Since we ignore data streams, the duration should be equal to the video
- // stream's duration.
- EXPECT_EQ(g_streams[1].duration, pipeline_->GetDuration().InMicroseconds());
+ // Verify that our demuxer streams were created from our AVStream structures.
+ EXPECT_EQ(DS_STREAM_MAX, static_cast<int>(demuxer_->GetNumberOfStreams()));
- // Verify that 2 out of 3 streams were created.
- EXPECT_EQ(2u, demuxer_->GetNumberOfStreams());
+ // Since we ignore data streams, the duration should be equal to the longest
+ // supported stream's duration (audio, in this case).
+ EXPECT_EQ(kDurations[AV_STREAM_AUDIO],
+ pipeline_->GetDuration().InMicroseconds());
- // First stream should be video and support FFmpegDemuxerStream interface.
- scoped_refptr<DemuxerStream> stream = demuxer_->GetStream(0);
+ // 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;
@@ -360,10 +271,10 @@ TEST_F(FFmpegDemuxerTest, InitializeStreams) {
EXPECT_STREQ(mime_type::kFFmpegVideo, mime_type.c_str());
EXPECT_TRUE(stream->QueryInterface(&av_stream_provider));
EXPECT_TRUE(av_stream_provider);
- EXPECT_EQ(&g_streams[1], av_stream_provider->GetAVStream());
+ EXPECT_EQ(&streams_[AV_STREAM_VIDEO], av_stream_provider->GetAVStream());
- // Second stream should be audio and support FFmpegDemuxerStream interface.
- stream = demuxer_->GetStream(1);
+ // 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,
@@ -371,267 +282,335 @@ TEST_F(FFmpegDemuxerTest, InitializeStreams) {
EXPECT_STREQ(mime_type::kFFmpegAudio, mime_type.c_str());
EXPECT_TRUE(stream->QueryInterface(&av_stream_provider));
EXPECT_TRUE(av_stream_provider);
- EXPECT_EQ(&g_streams[2], av_stream_provider->GetAVStream());
+ EXPECT_EQ(&streams_[AV_STREAM_AUDIO], av_stream_provider->GetAVStream());
}
-TEST_F(FFmpegDemuxerTest, ReadAndSeek) {
- // Prepare some test data.
- const int kPacketData = 0;
- const int kPacketAudio = 1;
- const int kPacketVideo = 2;
- const int kAudio = 0;
- const int kVideo = 1;
- const size_t kDataSize = 4;
- uint8 audio_data[kDataSize] = {0, 1, 2, 3};
- uint8 video_data[kDataSize] = {4, 5, 6, 7};
-
- // 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.
- g_format.nb_streams = 3;
- g_format.streams[kPacketData] = &g_streams[0];
- g_format.streams[kPacketAudio] = &g_streams[1];
- g_format.streams[kPacketVideo] = &g_streams[2];
- g_streams[0].duration = 10;
- g_streams[0].codec = &g_data_codec;
- g_streams[1].duration = 10;
- g_streams[1].codec = &g_audio_codec;
- g_streams[2].duration = 10;
- g_streams[2].codec = &g_video_codec;
-
- // Initialize the demuxer.
- EXPECT_TRUE(demuxer_->Initialize(data_source_.get()));
- EXPECT_TRUE(filter_host_->WaitForInitialized());
- EXPECT_TRUE(filter_host_->IsInitialized());
- EXPECT_EQ(PIPELINE_OK, pipeline_->GetError());
-
- // Verify both streams were created.
- EXPECT_EQ(2u, demuxer_->GetNumberOfStreams());
+TEST_F(FFmpegDemuxerTest, Read) {
+ // We're testing the following:
+ //
+ // 1) The demuxer immediately frees packets it doesn't care about and keeps
+ // reading until it finds a packet it cares about.
+ // 2) The demuxer doesn't free packets that we read from it.
+ // 3) On end of stream, the demuxer queues end of stream packets 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> audio_stream = demuxer_->GetStream(kAudio);
- scoped_refptr<DemuxerStream> video_stream = demuxer_->GetStream(kVideo);
- ASSERT_TRUE(audio_stream);
- ASSERT_TRUE(video_stream);
-
- // Prepare data packets, which should all get immediately released.
- PacketQueue::get()->Enqueue(kPacketData, kDataSize, audio_data);
- PacketQueue::get()->Enqueue(kPacketData, kDataSize, audio_data);
- PacketQueue::get()->Enqueue(kPacketData, kDataSize, audio_data);
-
- // Prepare our test audio packet.
- PacketQueue::get()->Enqueue(kPacketAudio, kDataSize, audio_data);
+ scoped_refptr<DemuxerStream> video = demuxer_->GetStream(DS_STREAM_VIDEO);
+ scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DS_STREAM_AUDIO);
+ ASSERT_TRUE(video);
+ ASSERT_TRUE(audio);
+
+ // Expect all calls in sequence.
+ InSequence s;
+
+ // 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(CreatePacket(AV_STREAM_DATA, kNullData, 0));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_AUDIO, kAudioData, kDataSize));
+
+ // ...then we'll free it with some sanity checkpoints...
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(1));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(2));
+
+ // ...then we'll read a video packet...
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_VIDEO, kVideoData, kDataSize));
+
+ // ...then we'll free it with some sanity checkpoints...
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(3));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(4));
+
+ // ...then we'll simulate end of stream. Note that a packet isn't "created"
+ // in this situation so there is no outstanding packet. However an end of
+ // stream packet is created for each stream, which means av_free_packet()
+ // will still be called twice.
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(Return(AVERROR_IO));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_));
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(5));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_));
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(6));
// Attempt a read from the audio stream and run the message loop until done.
scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader());
- reader->Read(audio_stream);
+ reader->Read(audio);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
- EXPECT_EQ(audio_data, reader->buffer()->GetData());
+ EXPECT_EQ(kAudioData, reader->buffer()->GetData());
EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
- // Prepare our test video packet.
- PacketQueue::get()->Enqueue(kPacketVideo, kDataSize, video_data);
+ // We shouldn't have freed the audio packet yet.
+ MockFFmpeg::get()->CheckPoint(1);
- // Attempt a read from the video stream and run the message loop until done.
+ // Manually release the last reference to the buffer.
reader->Reset();
- reader->Read(video_stream);
+ WaitForDemuxerThread();
+ MockFFmpeg::get()->CheckPoint(2);
+
+ // Attempt a read from the video stream and run the message loop until done.
+ reader->Read(video);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
- EXPECT_EQ(video_data, reader->buffer()->GetData());
+ EXPECT_EQ(kVideoData, reader->buffer()->GetData());
EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
- // Manually release buffer, which should release any remaining AVPackets.
- reader = NULL;
- EXPECT_TRUE(PacketQueue::get()->WaitForOutstandingPackets(0));
+ // We shouldn't have freed the video packet yet.
+ MockFFmpeg::get()->CheckPoint(3);
- //----------------------------------------------------------------------------
- // Seek tests.
- EXPECT_FALSE(g_seek_event);
- g_seek_event = new base::WaitableEvent(false, false);
+ // Manually release the last reference to the buffer and verify it was freed.
+ reader->Reset();
+ WaitForDemuxerThread();
+ MockFFmpeg::get()->CheckPoint(4);
- // Let's trigger a simple forward seek with no outstanding packets.
- g_expected_seek_timestamp = 1234;
- g_expected_seek_flags = 0;
- demuxer_->Seek(base::TimeDelta::FromMicroseconds(g_expected_seek_timestamp));
- EXPECT_TRUE(g_seek_event->TimedWait(base::TimeDelta::FromSeconds(1)));
+ // We should now expect an end of stream buffer in both the audio and video
+ // streams.
- // The next read from each stream should now be discontinuous, but subsequent
- // reads should not.
+ // Attempt a read from the audio stream and run the message loop until done.
+ reader->Read(audio);
+ pipeline_->RunAllTasks();
+ EXPECT_TRUE(reader->WaitForRead());
+ EXPECT_TRUE(reader->called());
+ ASSERT_TRUE(reader->buffer());
+ EXPECT_TRUE(reader->buffer()->IsEndOfStream());
+ EXPECT_EQ(NULL, reader->buffer()->GetData());
+ EXPECT_EQ(0u, reader->buffer()->GetDataSize());
- // Prepare our test audio packet.
- PacketQueue::get()->Enqueue(kPacketAudio, kDataSize, audio_data);
- PacketQueue::get()->Enqueue(kPacketAudio, kDataSize, audio_data);
+ // Manually release buffer, which should release any remaining AVPackets.
+ reader->Reset();
+ WaitForDemuxerThread();
+ MockFFmpeg::get()->CheckPoint(5);
- // Audio read #1, should be discontinuous.
- reader = new DemuxerStreamReader();
- reader->Read(audio_stream);
+ // Attempt a read from the audio stream and run the message loop until done.
+ reader->Read(video);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
- EXPECT_TRUE(reader->buffer()->IsDiscontinuous());
- EXPECT_EQ(audio_data, reader->buffer()->GetData());
- EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
+ EXPECT_TRUE(reader->buffer()->IsEndOfStream());
+ EXPECT_EQ(NULL, reader->buffer()->GetData());
+ EXPECT_EQ(0u, reader->buffer()->GetDataSize());
- // Audio read #2, should not be discontinuous.
+ // Manually release buffer, which should release any remaining AVPackets.
reader->Reset();
- reader->Read(audio_stream);
+ WaitForDemuxerThread();
+ MockFFmpeg::get()->CheckPoint(6);
+}
+
+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 = 0;
+
+ // 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(CreatePacket(AV_STREAM_AUDIO, kAudioData, kDataSize));
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_AUDIO, kAudioData, kDataSize));
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_VIDEO, kVideoData, kDataSize));
+
+ // ...then we'll release our video packet...
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(1));
+
+ // ...then we'll seek, which should release the previously queued packets...
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+
+ // ...then we'll expect the actual seek call...
+ EXPECT_CALL(*MockFFmpeg::get(),
+ AVSeekFrame(&format_context_, -1, kExpectedTimestamp, kExpectedFlags))
+ .WillOnce(Return(0));
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(2));
+
+ // ...followed by two audio packet reads we'll trigger...
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_AUDIO, kAudioData, kDataSize));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_AUDIO, kAudioData, kDataSize));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+
+ // ...followed by two video packet reads...
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_VIDEO, kVideoData, kDataSize));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_VIDEO, kVideoData, kDataSize));
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+
+ // ...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);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
- EXPECT_EQ(audio_data, reader->buffer()->GetData());
+ EXPECT_EQ(kVideoData, reader->buffer()->GetData());
EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
- // Prepare our test video packet.
- PacketQueue::get()->Enqueue(kPacketVideo, kDataSize, video_data);
- PacketQueue::get()->Enqueue(kPacketVideo, kDataSize, video_data);
-
- // Video read #1, should be discontinuous.
+ // Release the video packet and verify the other packets are still queued.
reader->Reset();
- reader->Read(video_stream);
+ WaitForDemuxerThread();
+ MockFFmpeg::get()->CheckPoint(1);
+
+ // Now issue a simple forward seek, which should discard queued packets.
+ demuxer_->Seek(base::TimeDelta::FromMicroseconds(kExpectedTimestamp));
+ WaitForDemuxerThread();
+ 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);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_TRUE(reader->buffer()->IsDiscontinuous());
- EXPECT_EQ(video_data, reader->buffer()->GetData());
+ EXPECT_EQ(kAudioData, reader->buffer()->GetData());
EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
- // Video read #2, should not be discontinuous.
+ // Audio read #2, should not be discontinuous.
reader->Reset();
- reader->Read(video_stream);
+ reader->Read(audio);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
- EXPECT_EQ(video_data, reader->buffer()->GetData());
+ EXPECT_EQ(kAudioData, reader->buffer()->GetData());
EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
- // Manually release buffer, which should release any remaining AVPackets.
- reader = NULL;
- EXPECT_TRUE(PacketQueue::get()->WaitForOutstandingPackets(0));
-
- // Let's trigger another simple forward seek, but with outstanding packets.
- // The outstanding packets should get freed after the Seek() is issued.
- PacketQueue::get()->Enqueue(kPacketAudio, kDataSize, audio_data);
- PacketQueue::get()->Enqueue(kPacketAudio, kDataSize, audio_data);
- PacketQueue::get()->Enqueue(kPacketAudio, kDataSize, audio_data);
- PacketQueue::get()->Enqueue(kPacketVideo, kDataSize, video_data);
-
- // Attempt a read from video stream, which will force the demuxer to queue
- // the audio packets preceding the video packet.
- reader = new DemuxerStreamReader();
- reader->Read(video_stream);
+ // Video read #1, should be discontinuous.
+ reader->Reset();
+ reader->Read(video);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
- EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
- EXPECT_EQ(video_data, reader->buffer()->GetData());
+ EXPECT_TRUE(reader->buffer()->IsDiscontinuous());
+ EXPECT_EQ(kVideoData, reader->buffer()->GetData());
EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
- // Manually release video buffer, remaining audio packets are outstanding.
- reader = NULL;
- EXPECT_TRUE(PacketQueue::get()->WaitForOutstandingPackets(3));
-
- // Trigger the seek.
- g_expected_seek_timestamp = 1234;
- g_expected_seek_flags = 0;
- demuxer_->Seek(base::TimeDelta::FromMicroseconds(g_expected_seek_timestamp));
- EXPECT_TRUE(g_seek_event->TimedWait(base::TimeDelta::FromSeconds(1)));
-
- // All outstanding packets should have been freed.
- EXPECT_TRUE(PacketQueue::get()->WaitForOutstandingPackets(0));
-
- // Clean up.
- delete g_seek_event;
- g_seek_event = NULL;
-
- //----------------------------------------------------------------------------
- // End of stream tests.
-
- // Simulate end of stream.
- g_av_read_frame = AVERROR_IO;
-
- // Attempt a read from the audio stream and run the message loop until done.
- reader = new DemuxerStreamReader();
- reader->Read(audio_stream);
+ // Video read #2, should not be discontinuous.
+ reader->Reset();
+ reader->Read(video);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
- EXPECT_TRUE(reader->buffer()->IsEndOfStream());
+ EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
+ EXPECT_EQ(kVideoData, reader->buffer()->GetData());
+ EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
- // Manually release buffer, which should release any remaining AVPackets.
- reader = NULL;
- EXPECT_TRUE(PacketQueue::get()->WaitForOutstandingPackets(0));
+ // Manually release the last reference to the buffer and verify it was freed.
+ reader->Reset();
+ WaitForDemuxerThread();
+ MockFFmpeg::get()->CheckPoint(3);
}
-// This tests our deep-copying workaround for FFmpeg's MP3 demuxer. When we fix
-// the root cause this test will fail and should be removed.
-//
-// TODO(scherkus): disabled due to flakiness http://crbug.com/13933
-TEST_F(FFmpegDemuxerTest, DISABLED_MP3Hack) {
- // Prepare some test data.
- const int kPacketAudio = 0; // Stream index relative to the container.
- const int kAudio = 0; // Stream index relative to Demuxer::GetStream().
- const size_t kDataSize = 4;
- uint8 audio_data[kDataSize] = {0, 1, 2, 3};
-
- // Simulate media with a single MP3 audio stream.
- g_format.nb_streams = 1;
- g_format.streams[kPacketAudio] = &g_streams[0];
- g_streams[0].duration = 10;
- g_streams[0].codec = &g_audio_codec;
- g_audio_codec.codec_id = CODEC_ID_MP3;
-
- // Initialize the demuxer.
- EXPECT_TRUE(demuxer_->Initialize(data_source_.get()));
- EXPECT_TRUE(filter_host_->WaitForInitialized());
- EXPECT_TRUE(filter_host_->IsInitialized());
- EXPECT_EQ(PIPELINE_OK, pipeline_->GetError());
+TEST_F(FFmpegDemuxerTest, MP3Hack) {
+ // This tests our deep-copying workaround for FFmpeg's MP3 demuxer. When we
+ // fix the root cause this test will fail and should be removed.
+ //
+ // TODO(scherkus): according to the documentation, deep-copying the packet is
+ // actually the correct action -- remove this test when we fix our demuxer.
+
+ // Simulate an MP3 stream.
+ codecs_[AV_STREAM_AUDIO].codec_id = CODEC_ID_MP3;
+ {
+ SCOPED_TRACE("");
+ InitializeDemuxer();
+ }
- // Verify the stream was created.
- EXPECT_EQ(1u, demuxer_->GetNumberOfStreams());
- scoped_refptr<DemuxerStream> audio_stream = demuxer_->GetStream(kAudio);
- ASSERT_TRUE(audio_stream);
+ // Get our stream.
+ scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DS_STREAM_AUDIO);
+ ASSERT_TRUE(audio);
- // Prepare our test audio packet.
- PacketQueue::get()->Enqueue(kPacketAudio, kDataSize, audio_data);
+ // Expect all calls in sequence.
+ InSequence s;
+
+ // We'll read an MP3 packet and allocate a new packet, then instantly free
+ // the original packet due to deep copying...
+ EXPECT_CALL(*MockFFmpeg::get(), AVReadFrame(&format_context_, _))
+ .WillOnce(CreatePacket(AV_STREAM_AUDIO, kAudioData, kDataSize));
+ EXPECT_CALL(*MockFFmpeg::get(), AVNewPacket(_, _)).WillOnce(NewPacket());
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+
+ // ...then we'll have a sanity checkpoint...
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(1));
+
+ // ...then we'll free the deep copied packet.
+ EXPECT_CALL(*MockFFmpeg::get(), AVFreePacket(_)).WillOnce(FreePacket());
+ EXPECT_CALL(*MockFFmpeg::get(), CheckPoint(2));
// Audio read should perform a deep copy on the packet and instantly release
// the original packet. The data pointers should not be the same, but the
// contents should match.
scoped_refptr<DemuxerStreamReader> reader = new DemuxerStreamReader();
- reader->Read(audio_stream);
+ reader->Read(audio);
pipeline_->RunAllTasks();
EXPECT_TRUE(reader->WaitForRead());
EXPECT_TRUE(reader->called());
ASSERT_TRUE(reader->buffer());
EXPECT_FALSE(reader->buffer()->IsDiscontinuous());
- EXPECT_NE(audio_data, reader->buffer()->GetData());
+ EXPECT_NE(kAudioData, reader->buffer()->GetData());
EXPECT_EQ(kDataSize, reader->buffer()->GetDataSize());
- EXPECT_EQ(0, memcmp(audio_data, reader->buffer()->GetData(), kDataSize));
+ EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(), kDataSize));
- // Original AVPacket from the queue should have been released due to copying.
- EXPECT_TRUE(PacketQueue::get()->WaitForOutstandingPackets(0));
- EXPECT_EQ(1, g_outstanding_packets_av_new_frame);
+ // We shouldn't have freed the MP3 packet yet.
+ MockFFmpeg::get()->CheckPoint(1);
- // Now release our reference, which should destruct the packet allocated by
- // av_new_packet().
- reader = NULL;
- EXPECT_EQ(0, g_outstanding_packets_av_new_frame);
+ // Manually release the last reference to the buffer and verify it was freed.
+ reader->Reset();
+ WaitForDemuxerThread();
+ MockFFmpeg::get()->CheckPoint(2);
}
} // namespace media