diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-19 18:23:49 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-19 18:23:49 +0000 |
commit | b1dc1dec10e03ca7fdf4219d7059dfc07973811a (patch) | |
tree | 87c44dfa015a6139daac9d027f906d9d6581d051 /media | |
parent | 68a983963a8ef1718400bb7a7f177f5a26ae4656 (diff) | |
download | chromium_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.cc | 71 | ||||
-rw-r--r-- | media/base/mock_ffmpeg.h | 78 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer.cc | 4 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer.h | 3 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer_unittest.cc | 851 |
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 |