diff options
author | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-23 17:53:58 +0000 |
---|---|---|
committer | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-23 17:53:58 +0000 |
commit | c25c7be488ea694d9173374b985e73a5a6eed867 (patch) | |
tree | 292a92b9c69fa73fe4ec0a0db22fe7ab8ece947e | |
parent | 461da7bce5a15d2c74237f0d284923fe5f2ebd9b (diff) | |
download | chromium_src-c25c7be488ea694d9173374b985e73a5a6eed867.zip chromium_src-c25c7be488ea694d9173374b985e73a5a6eed867.tar.gz chromium_src-c25c7be488ea694d9173374b985e73a5a6eed867.tar.bz2 |
Remove mock_ffmpeg and update media unittests.
BUG=92429
TEST=BitstreamConverterTest.*, ChunkDemuxerTest.*, FFmpegDemuxerTest.*, FFmpegGlueTest.*, FFmpegVideoDecoderTest.*, FFmpegH264BitstreamConverterTest.*, FFmpegVideoDecodeEngineTest.*
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=96974
Review URL: http://codereview.chromium.org/7587012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@97886 0039d316-1c4b-4281-b951-d872f2087c98
24 files changed, 557 insertions, 1405 deletions
diff --git a/media/base/media.h b/media/base/media.h index 6bbad4c..bd25cbb 100644 --- a/media/base/media.h +++ b/media/base/media.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -25,6 +25,11 @@ namespace media { // Returns true if everything was successfully initialized, false otherwise. bool InitializeMediaLibrary(const FilePath& module_dir); +// Helper function for unit tests to avoid boiler plate code everywhere. This +// function will crash if it fails to load the media library. This ensures tests +// fail if the media library is not available. +void InitializeMediaLibraryForTesting(); + // Use this if you need to check whether the media library is initialized // for the this process, without actually trying to initialize it. bool IsMediaLibraryInitialized(); diff --git a/media/base/media_posix.cc b/media/base/media_posix.cc index adad88b..7be3255 100644 --- a/media/base/media_posix.cc +++ b/media/base/media_posix.cc @@ -92,6 +92,12 @@ bool InitializeMediaLibrary(const FilePath& module_dir) { return g_media_library_is_initialized; } +void InitializeMediaLibraryForTesting() { + FilePath file_path; + CHECK(PathService::Get(base::DIR_EXE, &file_path)); + CHECK(InitializeMediaLibrary(file_path)); +} + bool IsMediaLibraryInitialized() { return g_media_library_is_initialized; } diff --git a/media/base/media_win.cc b/media/base/media_win.cc index a58d977..9462c02 100644 --- a/media/base/media_win.cc +++ b/media/base/media_win.cc @@ -78,6 +78,12 @@ bool InitializeMediaLibrary(const FilePath& base_path) { return g_media_library_is_initialized; } +void InitializeMediaLibraryForTesting() { + FilePath file_path; + CHECK(PathService::Get(base::DIR_EXE, &file_path)); + CHECK(InitializeMediaLibrary(file_path)); +} + bool IsMediaLibraryInitialized() { return g_media_library_is_initialized; } diff --git a/media/base/mock_ffmpeg.cc b/media/base/mock_ffmpeg.cc deleted file mode 100644 index ebbfbf0..0000000 --- a/media/base/mock_ffmpeg.cc +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/base/mock_ffmpeg.h" - -#include "base/logging.h" -#include "media/ffmpeg/ffmpeg_common.h" - -using ::testing::_; -using ::testing::AtMost; -using ::testing::DoAll; -using ::testing::Return; -using ::testing::SaveArg; - -namespace media { - -MockFFmpeg* MockFFmpeg::instance_ = NULL; -URLProtocol* MockFFmpeg::protocol_ = NULL; - -MockFFmpeg::MockFFmpeg() - : outstanding_packets_(0) { - CHECK(instance_ == NULL) << "Only a single MockFFmpeg instance can exist"; - instance_ = this; - - // If we haven't assigned our static copy of URLProtocol, set up expectations - // to catch the URLProtocol registered when the singleton instance of - // FFmpegGlue is created. - // - // TODO(scherkus): this feels gross and I need to think of a way to better - // inject/mock singletons. - if (!protocol_) { - EXPECT_CALL(*this, AVLogSetLevel(AV_LOG_QUIET)) - .Times(AtMost(1)) - .WillOnce(Return()); - EXPECT_CALL(*this, AVCodecInit()) - .Times(AtMost(1)) - .WillOnce(Return()); - EXPECT_CALL(*this, AVRegisterProtocol2(_,_)) - .Times(AtMost(1)) - .WillOnce(DoAll(SaveArg<0>(&protocol_), Return(0))); - EXPECT_CALL(*this, AVRegisterAll()) - .Times(AtMost(1)) - .WillOnce(Return()); - } -} - -MockFFmpeg::~MockFFmpeg() { - CHECK(!outstanding_packets_) - << "MockFFmpeg destroyed with outstanding packets"; - CHECK(instance_); - instance_ = NULL; -} - -void MockFFmpeg::inc_outstanding_packets() { - ++outstanding_packets_; -} - -void MockFFmpeg::dec_outstanding_packets() { - CHECK(outstanding_packets_ > 0); - --outstanding_packets_; -} - -// static -MockFFmpeg* MockFFmpeg::get() { - return instance_; -} - -// static -URLProtocol* MockFFmpeg::protocol() { - return protocol_; -} - -// 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" { -void avcodec_init() { - MockFFmpeg::get()->AVCodecInit(); -} - -int av_register_protocol2(URLProtocol* protocol, int size) { - return MockFFmpeg::get()->AVRegisterProtocol2(protocol, size); -} - -void av_register_all() { - MockFFmpeg::get()->AVRegisterAll(); -} - -int av_lockmgr_register(int (*cb)(void**, enum AVLockOp)) { - // Here |mock| may be NULL when this function is called from ~FFmpegGlue(). - if (MockFFmpeg::get()) { - return MockFFmpeg::get()->AVRegisterLockManager(cb); - } - return 0; -} - -AVCodec* avcodec_find_decoder(enum CodecID id) { - return MockFFmpeg::get()->AVCodecFindDecoder(id); -} - -int avcodec_open(AVCodecContext* avctx, AVCodec* codec) { - return MockFFmpeg::get()->AVCodecOpen(avctx, codec); -} - -int avcodec_close(AVCodecContext* avctx) { - return MockFFmpeg::get()->AVCodecClose(avctx); -} - -void avcodec_flush_buffers(AVCodecContext* avctx) { - return MockFFmpeg::get()->AVCodecFlushBuffers(avctx); -} - -AVCodecContext* avcodec_alloc_context() { - return MockFFmpeg::get()->AVCodecAllocContext(); -} - -AVFrame* avcodec_alloc_frame() { - return MockFFmpeg::get()->AVCodecAllocFrame(); -} - -int avcodec_decode_video2(AVCodecContext* avctx, AVFrame* picture, - int* got_picture_ptr, AVPacket* avpkt) { - return MockFFmpeg::get()-> - AVCodecDecodeVideo2(avctx, picture, got_picture_ptr, avpkt); -} - -AVBitStreamFilterContext* av_bitstream_filter_init(const char* name) { - return MockFFmpeg::get()->AVBitstreamFilterInit(name); -} - -int av_bitstream_filter_filter(AVBitStreamFilterContext* bsfc, - AVCodecContext* avctx, - const char* args, - uint8_t** poutbuf, - int* poutbuf_size, - const uint8_t* buf, - int buf_size, - int keyframe) { - return MockFFmpeg::get()-> - AVBitstreamFilterFilter(bsfc, avctx, args, poutbuf, poutbuf_size, buf, - buf_size, keyframe); -} - -void av_bitstream_filter_close(AVBitStreamFilterContext* bsf) { - return MockFFmpeg::get()->AVBitstreamFilterClose(bsf); -} - -int av_open_input_file(AVFormatContext** format, const char* filename, - AVInputFormat* input_format, int buffer_size, - AVFormatParameters* parameters) { - return MockFFmpeg::get()->AVOpenInputFile(format, filename, - input_format, buffer_size, - parameters); -} - -void av_close_input_file(AVFormatContext* format) { - MockFFmpeg::get()->AVCloseInputFile(format); -} - -int av_find_stream_info(AVFormatContext* format) { - return 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; - - // Rescale a by num/den. The den / 2 is for rounding. - return (a * num + den / 2) / den; -} - -int av_read_frame(AVFormatContext* format, AVPacket* packet) { - return MockFFmpeg::get()->AVReadFrame(format, packet); -} - -int av_seek_frame(AVFormatContext *format, int stream_index, int64_t timestamp, - int flags) { - return MockFFmpeg::get()->AVSeekFrame(format, stream_index, timestamp, - flags); -} - -void av_init_packet(AVPacket* pkt) { - return MockFFmpeg::get()->AVInitPacket(pkt); -} - -int av_new_packet(AVPacket* packet, int size) { - return MockFFmpeg::get()->AVNewPacket(packet, size); -} - -void av_free_packet(AVPacket* packet) { - MockFFmpeg::get()->AVFreePacket(packet); -} - -void av_free(void* ptr) { - // Freeing NULL pointers are valid, but they aren't interesting from a mock - // perspective. - if (ptr) { - MockFFmpeg::get()->AVFree(ptr); - } -} - -int av_dup_packet(AVPacket* packet) { - return MockFFmpeg::get()->AVDupPacket(packet); -} - -void av_log_set_level(int level) { - MockFFmpeg::get()->AVLogSetLevel(level); -} - -void av_destruct_packet(AVPacket *pkt) { - MockFFmpeg::get()->AVDestructPacket(pkt); -} - -} // extern "C" - -} // namespace media diff --git a/media/base/mock_ffmpeg.h b/media/base/mock_ffmpeg.h deleted file mode 100644 index df5e69b4..0000000 --- a/media/base/mock_ffmpeg.h +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_BASE_MOCK_FFMPEG_H_ -#define MEDIA_BASE_MOCK_FFMPEG_H_ - -// TODO(scherkus): See if we can remove ffmpeg_common from this file. -#include "media/ffmpeg/ffmpeg_common.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { - -class MockFFmpeg { - public: - MockFFmpeg(); - virtual ~MockFFmpeg(); - - // TODO(ajwong): Organize this class, and make sure that all mock entrypoints - // are still used. - MOCK_METHOD0(AVCodecInit, void()); - MOCK_METHOD2(AVRegisterProtocol2, int(URLProtocol* protocol, int size)); - MOCK_METHOD0(AVRegisterAll, void()); - MOCK_METHOD1(AVRegisterLockManager, int(int (*cb)(void**, enum AVLockOp))); - - MOCK_METHOD1(AVCodecFindDecoder, AVCodec*(enum CodecID id)); - MOCK_METHOD2(AVCodecOpen, int(AVCodecContext* avctx, AVCodec* codec)); - MOCK_METHOD1(AVCodecClose, int(AVCodecContext* avctx)); - MOCK_METHOD2(AVCodecThreadInit, int(AVCodecContext* avctx, int threads)); - MOCK_METHOD1(AVCodecFlushBuffers, void(AVCodecContext* avctx)); - MOCK_METHOD0(AVCodecAllocContext, AVCodecContext*()); - MOCK_METHOD0(AVCodecAllocFrame, AVFrame*()); - MOCK_METHOD4(AVCodecDecodeVideo2, - int(AVCodecContext* avctx, AVFrame* picture, - int* got_picture_ptr, AVPacket* avpkt)); - MOCK_METHOD1(AVBitstreamFilterInit, - AVBitStreamFilterContext*(const char *name)); - MOCK_METHOD8(AVBitstreamFilterFilter, - int(AVBitStreamFilterContext* bsfc, AVCodecContext* avctx, - const char* args, uint8_t** poutbuf, int* poutbuf_size, - const uint8_t* buf, int buf_size, int keyframe)); - MOCK_METHOD1(AVBitstreamFilterClose, void(AVBitStreamFilterContext* bsf)); - MOCK_METHOD1(AVDestructPacket, void(AVPacket* packet)); - - MOCK_METHOD5(AVOpenInputFile, int(AVFormatContext** format, - const char* filename, - AVInputFormat* input_format, - int buffer_size, - AVFormatParameters* parameters)); - MOCK_METHOD1(AVCloseInputFile, void(AVFormatContext* format)); - 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_METHOD1(AVInitPacket, void(AVPacket* pkt)); - MOCK_METHOD2(AVNewPacket, int(AVPacket* packet, int size)); - MOCK_METHOD1(AVFreePacket, void(AVPacket* packet)); - MOCK_METHOD1(AVFree, void(void* ptr)); - MOCK_METHOD1(AVDupPacket, int(AVPacket* packet)); - - MOCK_METHOD1(AVLogSetLevel, void(int level)); - - // Used for verifying check points during tests. - MOCK_METHOD1(CheckPoint, void(int id)); - - // Returns the current MockFFmpeg instance. - static MockFFmpeg* get(); - - // Returns the URLProtocol registered by the FFmpegGlue singleton. - static URLProtocol* protocol(); - - // 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_; - static URLProtocol* protocol_; - - // 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_read_frame(). -ACTION_P3(CreatePacketNoCount, 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; - - return 0; -} - -// Used for simulating av_read_frame(). -ACTION_P4(CreatePacketTimeNoCount, stream_index, data, size, pts) { - // 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; - arg1->pts = pts; - - 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/base/run_all_unittests.cc b/media/base/run_all_unittests.cc index b715a328b..a24a278 100644 --- a/media/base/run_all_unittests.cc +++ b/media/base/run_all_unittests.cc @@ -1,9 +1,14 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/test/test_suite.h" +#include "media/base/media.h" int main(int argc, char** argv) { - return base::TestSuite(argc, argv).Run(); + base::TestSuite suite(argc, argv); + + media::InitializeMediaLibraryForTesting(); + + return suite.Run(); } diff --git a/media/base/test_data_util.cc b/media/base/test_data_util.cc new file mode 100644 index 0000000..fc30744 --- /dev/null +++ b/media/base/test_data_util.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/test_data_util.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" + +namespace media { + +void ReadTestDataFile(const std::string& name, scoped_array<uint8>* buffer, + int* size) { + FilePath file_path; + CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); + + file_path = file_path.Append(FILE_PATH_LITERAL("media")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .AppendASCII(name); + + int64 tmp = 0; + CHECK(file_util::GetFileSize(file_path, &tmp)) + << "Failed to get file size for '" << name << "'"; + + int file_size = static_cast<int>(tmp); + buffer->reset(new uint8[file_size]); + + CHECK(file_size == file_util::ReadFile(file_path, + reinterpret_cast<char*>(buffer->get()), + file_size)) + << "Failed to read '" << name << "'"; + *size = file_size; +} + +void ReadTestDataFile(const std::string& name, scoped_refptr<Buffer>* buffer) { + scoped_array<uint8> buf; + int buf_size; + ReadTestDataFile(name, &buf, &buf_size); + *buffer = new DataBuffer(buf.release(), buf_size); +} + +} // namespace media diff --git a/media/base/test_data_util.h b/media/base/test_data_util.h new file mode 100644 index 0000000..42878ae --- /dev/null +++ b/media/base/test_data_util.h @@ -0,0 +1,35 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_TEST_DATA_UTIL_H_ +#define MEDIA_BASE_TEST_DATA_UTIL_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "media/base/data_buffer.h" + +namespace media { + +// Reads a test file from media/test/data directory and stores it in +// a scoped_array. +// +// |name| - The name of the file. +// |buffer| - The contents of the file. +// |size| - The size of the buffer. +void ReadTestDataFile(const std::string& name, + scoped_array<uint8>* buffer, + int* size); + +// Reads a test file from media/test/data directory and stored it in +// a Buffer. +// +// |name| - The name of the file. +// |buffer| - The contents of the file. +void ReadTestDataFile(const std::string& name, scoped_refptr<Buffer>* buffer); + +} // namespace media + +#endif // MEDIA_BASE_TEST_DATA_UTIL_H_ diff --git a/media/base/video_decoder_config.cc b/media/base/video_decoder_config.cc index 5ec7c02d..0d3ae76 100644 --- a/media/base/video_decoder_config.cc +++ b/media/base/video_decoder_config.cc @@ -15,7 +15,7 @@ VideoDecoderConfig::VideoDecoderConfig(VideoCodec codec, int surface_height, int frame_rate_numerator, int frame_rate_denominator, - uint8* extra_data, + const uint8* extra_data, size_t extra_data_size) : codec_(codec), width_(width), diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h index a077312..541f437 100644 --- a/media/base/video_decoder_config.h +++ b/media/base/video_decoder_config.h @@ -30,7 +30,7 @@ class VideoDecoderConfig { VideoDecoderConfig(VideoCodec codec, int width, int height, int surface_width, int surface_height, int frame_rate_numerator, int frame_rate_denominator, - uint8* extra_data, size_t extra_data_size); + const uint8* extra_data, size_t extra_data_size); ~VideoDecoderConfig(); VideoCodec codec() const; diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc index 0be30ed..d03715e 100644 --- a/media/ffmpeg/ffmpeg_common.cc +++ b/media/ffmpeg/ffmpeg_common.cc @@ -43,6 +43,8 @@ VideoCodec CodecIDToVideoCodec(CodecID codec_id) { CodecID VideoCodecToCodecID(VideoCodec video_codec) { switch (video_codec) { + case kUnknown: + return CODEC_ID_NONE; case kCodecVC1: return CODEC_ID_VC1; case kCodecH264: diff --git a/media/filters/bitstream_converter.h b/media/filters/bitstream_converter.h index a1ca3c6..7386297 100644 --- a/media/filters/bitstream_converter.h +++ b/media/filters/bitstream_converter.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -69,11 +69,6 @@ class FFmpegBitstreamConverter : public BitstreamConverter { virtual bool ConvertPacket(AVPacket* packet); private: - FRIEND_TEST_ALL_PREFIXES(BitstreamConverterTest, ConvertPacket_FailedFilter); - FRIEND_TEST_ALL_PREFIXES(BitstreamConverterTest, ConvertPacket_Success); - FRIEND_TEST_ALL_PREFIXES(BitstreamConverterTest, - ConvertPacket_SuccessInPlace); - std::string filter_name_; AVBitStreamFilterContext* stream_filter_; AVCodecContext* stream_context_; diff --git a/media/filters/bitstream_converter_unittest.cc b/media/filters/bitstream_converter_unittest.cc index 1e84341..3330df7 100644 --- a/media/filters/bitstream_converter_unittest.cc +++ b/media/filters/bitstream_converter_unittest.cc @@ -4,73 +4,94 @@ #include <deque> -#include "media/base/mock_ffmpeg.h" #include "media/ffmpeg/ffmpeg_common.h" #include "media/filters/bitstream_converter.h" #include "testing/gtest/include/gtest/gtest.h" -using ::testing::DoAll; -using ::testing::Mock; -using ::testing::Return; -using ::testing::ReturnNull; -using ::testing::SetArgumentPointee; -using ::testing::StrEq; -using ::testing::StrictMock; -using ::testing::_; - namespace media { +static const char kTestFilterName[] = "test_filter"; +static uint8_t kFailData[] = { 3, 2, 1 }; +static uint8_t kNewBufferData[] = { 2, 1 }; +static uint8_t kInPlaceData[] = { 1 }; +static const int kFailSize = 3; +static const int kNewBufferSize = 2; +static const int kInPlaceSize = 1; + + +// Test filter function that looks for specific input data and changes it's +// behavior based on what is passed to |buf| & |buf_size|. The three behaviors +// simulated are the following: +// - Create a new output buffer. Triggered by |buf| == |kNewBufferData|. +// - Use the existing output buffer. Triggered by |buf| == |kInPlaceData|. +// - Signal an error. Triggered by |buf| == |kFailData|. +static int DoFilter(AVBitStreamFilterContext* bsfc, + AVCodecContext* avctx, + const char* args, + uint8_t** poutbuf, + int* poutbuf_size, + const uint8_t* buf, + int buf_size, + int keyframe) { + if (buf_size == kNewBufferSize && + !memcmp(buf, kNewBufferData, kNewBufferSize)) { + *poutbuf_size = buf_size + 1; + *poutbuf = static_cast<uint8*>(av_malloc(*poutbuf_size)); + *poutbuf[0] = 0; + memcpy((*poutbuf) + 1, buf, buf_size); + return 0; + } else if (buf_size == kInPlaceSize && + !memcmp(buf, kInPlaceData, kInPlaceSize)) { + return 0; + } + + return -1; +} + +static void DoClose(AVBitStreamFilterContext* bsfc) {} + +static AVBitStreamFilter g_stream_filter = { + kTestFilterName, + 0, // Private Data Size + DoFilter, + DoClose, + 0, // Next filter pointer. +}; + class BitstreamConverterTest : public testing::Test { protected: BitstreamConverterTest() { memset(&test_stream_context_, 0, sizeof(test_stream_context_)); - memset(&test_filter_, 0, sizeof(test_filter_)); memset(&test_packet_, 0, sizeof(test_packet_)); - test_packet_.data = kData1; - test_packet_.size = kTestSize1; + test_packet_.data = kFailData; + test_packet_.size = kFailSize; + } + + virtual ~BitstreamConverterTest() { + av_free_packet(&test_packet_); } - virtual ~BitstreamConverterTest() {} + static void SetUpTestCase() { + // Register g_stream_filter if it isn't already registered. + if (!g_stream_filter.next) + av_register_bitstream_filter(&g_stream_filter); + } AVCodecContext test_stream_context_; - AVBitStreamFilterContext test_filter_; AVPacket test_packet_; - StrictMock<MockFFmpeg> mock_ffmpeg_; - - static const char kTestFilterName[]; - static uint8_t kData1[]; - static uint8_t kData2[]; - static const int kTestSize1; - static const int kTestSize2; - private: DISALLOW_COPY_AND_ASSIGN(BitstreamConverterTest); }; -const char BitstreamConverterTest::kTestFilterName[] = "test_filter"; -uint8_t BitstreamConverterTest::kData1[] = { 1 }; -uint8_t BitstreamConverterTest::kData2[] = { 2 }; -const int BitstreamConverterTest::kTestSize1 = 1; -const int BitstreamConverterTest::kTestSize2 = 2; +TEST_F(BitstreamConverterTest, InitializeFailed) { + FFmpegBitstreamConverter converter("BAD_FILTER_NAME", &test_stream_context_); -TEST_F(BitstreamConverterTest, Initialize) { - FFmpegBitstreamConverter converter(kTestFilterName, &test_stream_context_); - - // Test Initialize returns false on a bad initialization, and cleanup is not - // done. - EXPECT_CALL(mock_ffmpeg_, AVBitstreamFilterInit(StrEq(kTestFilterName))) - .WillOnce(ReturnNull()); EXPECT_FALSE(converter.Initialize()); +} - EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_ffmpeg_)); - - // Test Initialize returns true on successful initialization, and cleanup is - // done. The cleanup will be activated when the converter object goes out of - // scope. - EXPECT_CALL(mock_ffmpeg_, AVBitstreamFilterInit(StrEq(kTestFilterName))) - .WillOnce(Return(&test_filter_)); - EXPECT_CALL(mock_ffmpeg_, AVBitstreamFilterClose(&test_filter_)); +TEST_F(BitstreamConverterTest, InitializeSuccess) { + FFmpegBitstreamConverter converter(kTestFilterName, &test_stream_context_); EXPECT_TRUE(converter.Initialize()); } @@ -83,81 +104,42 @@ TEST_F(BitstreamConverterTest, ConvertPacket_NotInitialized) { TEST_F(BitstreamConverterTest, ConvertPacket_FailedFilter) { FFmpegBitstreamConverter converter(kTestFilterName, &test_stream_context_); - // Inject mock filter instance. - converter.stream_filter_ = &test_filter_; - - // Simulate a successful filter call, that allocates a new data buffer. - EXPECT_CALL(mock_ffmpeg_, - AVBitstreamFilterFilter(&test_filter_, &test_stream_context_, - NULL, _, _, - test_packet_.data, test_packet_.size, _)) - .WillOnce(Return(AVERROR(EINVAL))); + EXPECT_TRUE(converter.Initialize()); EXPECT_FALSE(converter.ConvertPacket(&test_packet_)); - - // Uninject mock filter instance to avoid cleanup code on destruction of - // converter. - converter.stream_filter_ = NULL; } TEST_F(BitstreamConverterTest, ConvertPacket_Success) { FFmpegBitstreamConverter converter(kTestFilterName, &test_stream_context_); - // Inject mock filter instance. - converter.stream_filter_ = &test_filter_; + EXPECT_TRUE(converter.Initialize()); // Ensure our packet doesn't already have a destructor. ASSERT_TRUE(test_packet_.destruct == NULL); - // Simulate a successful filter call, that allocates a new data buffer. - EXPECT_CALL(mock_ffmpeg_, - AVBitstreamFilterFilter(&test_filter_, &test_stream_context_, - NULL, _, _, - test_packet_.data, test_packet_.size, _)) - .WillOnce(DoAll(SetArgumentPointee<3>(&kData2[0]), - SetArgumentPointee<4>(kTestSize2), - Return(0))); - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(&test_packet_)); + test_packet_.data = kNewBufferData; + test_packet_.size = kNewBufferSize; EXPECT_TRUE(converter.ConvertPacket(&test_packet_)); - EXPECT_EQ(kData2, test_packet_.data); - EXPECT_EQ(kTestSize2, test_packet_.size); + EXPECT_NE(kNewBufferData, test_packet_.data); + EXPECT_EQ(kNewBufferSize + 1, test_packet_.size); EXPECT_TRUE(test_packet_.destruct != NULL); - - // Uninject mock filter instance to avoid cleanup code on destruction of - // converter. - converter.stream_filter_ = NULL; } TEST_F(BitstreamConverterTest, ConvertPacket_SuccessInPlace) { FFmpegBitstreamConverter converter(kTestFilterName, &test_stream_context_); - // Inject mock filter instance. - converter.stream_filter_ = &test_filter_; + EXPECT_TRUE(converter.Initialize()); // Ensure our packet is in a sane start state. ASSERT_TRUE(test_packet_.destruct == NULL); - ASSERT_EQ(kData1, test_packet_.data); - ASSERT_EQ(kTestSize1, test_packet_.size); - - // Simulate a successful filter call, that reuses the input buffer. We should - // not free the packet here or alter the packet's destructor. - EXPECT_CALL(mock_ffmpeg_, - AVBitstreamFilterFilter(&test_filter_, &test_stream_context_, - NULL, _, _, - test_packet_.data, test_packet_.size, _)) - .WillOnce(DoAll(SetArgumentPointee<3>(test_packet_.data), - SetArgumentPointee<4>(test_packet_.size), - Return(0))); + test_packet_.data = kInPlaceData; + test_packet_.size = kInPlaceSize; EXPECT_TRUE(converter.ConvertPacket(&test_packet_)); - EXPECT_EQ(kData1, test_packet_.data); - EXPECT_EQ(kTestSize1, test_packet_.size); + EXPECT_EQ(kInPlaceData, test_packet_.data); + EXPECT_EQ(kInPlaceSize, test_packet_.size); EXPECT_TRUE(test_packet_.destruct == NULL); - - // Uninject mock filter instance to avoid cleanup code on destruction of - // converter. - converter.stream_filter_ = NULL; } } // namespace media diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index b26b757..e6b3aba 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -2,14 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/base_paths.h" #include "base/bind.h" -#include "base/file_util.h" -#include "base/path_service.h" -#include "media/base/media.h" #include "media/base/mock_callback.h" -#include "media/base/mock_ffmpeg.h" #include "media/base/mock_filter_host.h" +#include "media/base/test_data_util.h" #include "media/filters/chunk_demuxer.h" #include "media/filters/chunk_demuxer_client.h" #include "media/webm/cluster_builder.h" @@ -58,47 +54,12 @@ class ChunkDemuxerTest : public testing::Test{ ChunkDemuxerTest() : client_(new MockChunkDemuxerClient()), demuxer_(new ChunkDemuxer(client_.get())) { - memset(&format_context_, 0, sizeof(format_context_)); - memset(&streams_, 0, sizeof(streams_)); - memset(&codecs_, 0, sizeof(codecs_)); - - codecs_[VIDEO].codec_type = AVMEDIA_TYPE_VIDEO; - codecs_[VIDEO].codec_id = CODEC_ID_VP8; - codecs_[VIDEO].width = 320; - codecs_[VIDEO].height = 240; - - codecs_[AUDIO].codec_type = AVMEDIA_TYPE_AUDIO; - codecs_[AUDIO].codec_id = CODEC_ID_VORBIS; - codecs_[AUDIO].channels = 2; - codecs_[AUDIO].sample_rate = 44100; } virtual ~ChunkDemuxerTest() { ShutdownDemuxer(); } - void ReadFile(const std::string& name, scoped_array<uint8>* buffer, - int* size) { - FilePath file_path; - EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); - file_path = file_path.Append(FILE_PATH_LITERAL("media")) - .Append(FILE_PATH_LITERAL("test")) - .Append(FILE_PATH_LITERAL("data")) - .AppendASCII(name); - - int64 tmp = 0; - EXPECT_TRUE(file_util::GetFileSize(file_path, &tmp)); - EXPECT_LT(tmp, 32768); - int file_size = static_cast<int>(tmp); - - buffer->reset(new uint8[file_size]); - EXPECT_EQ(file_size, - file_util::ReadFile(file_path, - reinterpret_cast<char*>(buffer->get()), - file_size)); - *size = file_size; - } - void CreateInfoTracks(bool has_audio, bool has_video, scoped_array<uint8>* buffer, int* size) { scoped_array<uint8> info; @@ -108,11 +69,11 @@ class ChunkDemuxerTest : public testing::Test{ scoped_array<uint8> video_track_entry; int video_track_entry_size = 0; - ReadFile("webm_info_element", &info, &info_size); - ReadFile("webm_vorbis_track_entry", &audio_track_entry, - &audio_track_entry_size); - ReadFile("webm_vp8_track_entry", &video_track_entry, - &video_track_entry_size); + ReadTestDataFile("webm_info_element", &info, &info_size); + ReadTestDataFile("webm_vorbis_track_entry", &audio_track_entry, + &audio_track_entry_size); + ReadTestDataFile("webm_vp8_track_entry", &video_track_entry, + &video_track_entry_size); int tracks_element_size = 0; @@ -159,24 +120,10 @@ class ChunkDemuxerTest : public testing::Test{ } void AppendInfoTracks(bool has_audio, bool has_video) { - EXPECT_CALL(mock_ffmpeg_, AVOpenInputFile(_, _, NULL, 0, NULL)) - .WillOnce(DoAll(SetArgumentPointee<0>(&format_context_), - Return(0))); - - EXPECT_CALL(mock_ffmpeg_, AVFindStreamInfo(&format_context_)) - .WillOnce(Return(0)); - - EXPECT_CALL(mock_ffmpeg_, AVCloseInputFile(&format_context_)); - - EXPECT_CALL(mock_ffmpeg_, AVRegisterLockManager(_)) - .WillRepeatedly(Return(0)); - scoped_array<uint8> info_tracks; int info_tracks_size = 0; CreateInfoTracks(has_audio, has_video, &info_tracks, &info_tracks_size); - SetupAVFormatContext(has_audio, has_video); - AppendData(info_tracks.get(), info_tracks_size); } @@ -211,12 +158,6 @@ class ChunkDemuxerTest : public testing::Test{ EXPECT_CALL(*client_, DemuxerClosed()); demuxer_->Shutdown(); } - - if (format_context_.streams) { - delete[] format_context_.streams; - format_context_.streams = NULL; - format_context_.nb_streams = 0; - } } void AddSimpleBlock(ClusterBuilder* cb, int track_num, int64 timecode) { @@ -226,41 +167,12 @@ class ChunkDemuxerTest : public testing::Test{ MOCK_METHOD1(Checkpoint, void(int id)); - MockFFmpeg mock_ffmpeg_; MockFilterHost mock_filter_host_; - AVFormatContext format_context_; - AVCodecContext codecs_[MAX_CODECS_INDEX]; - AVStream streams_[MAX_CODECS_INDEX]; - scoped_ptr<MockChunkDemuxerClient> client_; scoped_refptr<ChunkDemuxer> demuxer_; private: - void SetupAVFormatContext(bool has_audio, bool has_video) { - int i = 0; - format_context_.streams = new AVStream*[MAX_CODECS_INDEX]; - if (has_audio) { - format_context_.streams[i] = &streams_[i]; - streams_[i].codec = &codecs_[AUDIO]; - streams_[i].duration = 100; - streams_[i].time_base.den = base::Time::kMicrosecondsPerSecond; - streams_[i].time_base.num = 1; - i++; - } - - if (has_video) { - format_context_.streams[i] = &streams_[i]; - streams_[i].codec = &codecs_[VIDEO]; - streams_[i].duration = 100; - streams_[i].time_base.den = base::Time::kMicrosecondsPerSecond; - streams_[i].time_base.num = 1; - i++; - } - - format_context_.nb_streams = i; - } - DISALLOW_COPY_AND_ASSIGN(ChunkDemuxerTest); }; diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc index 1113e9b..920b877 100644 --- a/media/filters/ffmpeg_demuxer_unittest.cc +++ b/media/filters/ffmpeg_demuxer_unittest.cc @@ -4,15 +4,17 @@ #include <deque> +#include "base/file_path.h" +#include "base/path_service.h" #include "base/threading/thread.h" #include "media/base/filters.h" #include "media/base/mock_callback.h" -#include "media/base/mock_ffmpeg.h" #include "media/base/mock_filter_host.h" #include "media/base/mock_filters.h" #include "media/base/mock_reader.h" #include "media/ffmpeg/ffmpeg_common.h" #include "media/filters/ffmpeg_demuxer.h" +#include "media/filters/file_data_source.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::AnyNumber; @@ -21,7 +23,8 @@ using ::testing::InSequence; using ::testing::Invoke; using ::testing::NotNull; using ::testing::Return; -using ::testing::SetArgumentPointee; +using ::testing::SaveArg; +using ::testing::SetArgPointee; using ::testing::StrictMock; using ::testing::WithArgs; using ::testing::_; @@ -32,36 +35,6 @@ namespace media { // FFmpeg, pipeline and filter host mocks. class FFmpegDemuxerTest : public testing::Test { protected: - // These constants refer to the stream ordering inside AVFormatContext. We - // simulate media with a data stream, audio stream and video stream. Having - // the data stream first forces the audio and video streams to get remapped - // from indices {1,2} to {0,1} respectively, which covers an important test - // case. - enum AVStreamIndex { - AV_STREAM_DATA, - AV_STREAM_VIDEO, - AV_STREAM_AUDIO, - AV_STREAM_MAX, - }; - - // These constants refer to the stream ordering inside an initialized - // FFmpegDemuxer based on the ordering of the AVStreamIndex constants. - enum DemuxerStreamIndex { - DS_STREAM_VIDEO, - DS_STREAM_AUDIO, - DS_STREAM_MAX, - }; - - static const int kDurations[]; - static const int kChannels; - static const int kSampleRate; - static const int kWidth; - static const int kHeight; - - static const size_t kDataSize; - static const uint8 kAudioData[]; - static const uint8 kVideoData[]; - static const uint8* kNullData; FFmpegDemuxerTest() { // Create an FFmpegDemuxer. @@ -70,45 +43,11 @@ class FFmpegDemuxerTest : public testing::Test { // Inject a filter host and message loop and prepare a data source. demuxer_->set_host(&host_); - data_source_ = new StrictMock<MockDataSource>(); - - EXPECT_CALL(*data_source_, Stop(NotNull())) - .WillRepeatedly(Invoke(&RunStopFilterCallback)); - - // Initialize FFmpeg fixtures. - memset(&format_context_, 0, sizeof(format_context_)); - memset(&input_format_, 0, sizeof(input_format_)); - memset(&streams_, 0, sizeof(streams_)); - memset(&codecs_, 0, sizeof(codecs_)); - - // Initialize AVCodecContext structures. - codecs_[AV_STREAM_DATA].codec_type = AVMEDIA_TYPE_DATA; - codecs_[AV_STREAM_DATA].codec_id = CODEC_ID_NONE; - - codecs_[AV_STREAM_VIDEO].codec_type = AVMEDIA_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 = AVMEDIA_TYPE_AUDIO; - codecs_[AV_STREAM_AUDIO].codec_id = CODEC_ID_VORBIS; - codecs_[AV_STREAM_AUDIO].channels = kChannels; - codecs_[AV_STREAM_AUDIO].sample_rate = kSampleRate; - - input_format_.name = "foo"; - format_context_.iformat = &input_format_; - - // Initialize AVStream and AVFormatContext structures. We set the time base - // of the streams such that duration is reported in microseconds. - format_context_.nb_streams = AV_STREAM_MAX; - format_context_.streams = new AVStream*[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; - } + + EXPECT_CALL(host_, SetTotalBytes(_)).Times(AnyNumber()); + EXPECT_CALL(host_, SetBufferedBytes(_)).Times(AnyNumber()); + EXPECT_CALL(host_, SetCurrentReadPosition(_)) + .WillRepeatedly(SaveArg<0>(¤t_read_position_)); } virtual ~FFmpegDemuxerTest() { @@ -119,160 +58,117 @@ class FFmpegDemuxerTest : public testing::Test { message_loop_.RunAllPending(); // Release the reference to the demuxer. demuxer_ = NULL; - - if (format_context_.streams) { - delete[] format_context_.streams; - format_context_.streams = NULL; - format_context_.nb_streams = 0; - } } - // Sets up MockFFmpeg to allow FFmpegDemuxer to successfully initialize. - void InitializeDemuxerMocks() { - EXPECT_CALL(mock_ffmpeg_, AVOpenInputFile(_, _, NULL, 0, NULL)) - .WillOnce(DoAll(SetArgumentPointee<0>(&format_context_), Return(0))); - EXPECT_CALL(mock_ffmpeg_, AVFindStreamInfo(&format_context_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVCloseInputFile(&format_context_)); - } + scoped_refptr<DataSource> CreateDataSource(const std::string& name) { + FilePath file_path; + EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); - // Initializes both MockFFmpeg and FFmpegDemuxer. - void InitializeDemuxer() { - InitializeDemuxerMocks(); + file_path = file_path.Append(FILE_PATH_LITERAL("media")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .AppendASCII(name); + + scoped_refptr<FileDataSource> data_source = new FileDataSource(); + + EXPECT_EQ(PIPELINE_OK, data_source->Initialize(file_path.MaybeAsASCII())); + + return data_source.get(); + } - // Since we ignore data streams, the duration should be equal to the longest - // supported stream's duration (audio, in this case). - base::TimeDelta expected_duration = - base::TimeDelta::FromMicroseconds(kDurations[AV_STREAM_AUDIO]); - EXPECT_CALL(host_, SetDuration(expected_duration)); + MOCK_METHOD1(CheckPoint, void(int v)); - demuxer_->Initialize(data_source_.get(), - NewExpectedStatusCB(PIPELINE_OK)); + // Initializes FFmpegDemuxer. + void InitializeDemuxer(const scoped_refptr<DataSource>& data_source) { + EXPECT_CALL(host_, SetDuration(_)); + demuxer_->Initialize(data_source, NewExpectedStatusCB(PIPELINE_OK)); message_loop_.RunAllPending(); } + // Verifies that |buffer| has a specific |size| and |timestamp|. + // |location| simply indicates where the call to this function was made. + // This makes it easier to track down where test failures occur. + void ValidateBuffer(const tracked_objects::Location& location, + const scoped_refptr<Buffer>& buffer, + size_t size, int64 timestampInMicroseconds) { + std::string location_str; + location.Write(true, false, &location_str); + location_str += "\n"; + SCOPED_TRACE(location_str); + EXPECT_TRUE(buffer.get() != NULL); + EXPECT_EQ(size, buffer->GetDataSize()); + EXPECT_EQ(base::TimeDelta::FromMicroseconds(timestampInMicroseconds), + buffer->GetTimestamp()); + } + // Fixture members. scoped_refptr<FFmpegDemuxer> demuxer_; - scoped_refptr<StrictMock<MockDataSource> > data_source_; StrictMock<MockFilterHost> host_; MessageLoop message_loop_; - // FFmpeg fixtures. - AVFormatContext format_context_; - AVInputFormat input_format_; - AVCodecContext codecs_[AV_STREAM_MAX]; - AVStream streams_[AV_STREAM_MAX]; - MockFFmpeg mock_ffmpeg_; + int64 current_read_position_; private: DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerTest); }; -// These durations are picked so that the demuxer chooses the longest supported -// stream, which would be 30 in this case for the audio stream. -const int FFmpegDemuxerTest::kDurations[AV_STREAM_MAX] = {100, 20, 30}; -const int FFmpegDemuxerTest::kChannels = 2; -const int FFmpegDemuxerTest::kSampleRate = 44100; -const int FFmpegDemuxerTest::kWidth = 1280; -const int FFmpegDemuxerTest::kHeight = 720; - -const size_t FFmpegDemuxerTest::kDataSize = 4; -const uint8 FFmpegDemuxerTest::kAudioData[kDataSize] = {0, 1, 2, 3}; -const uint8 FFmpegDemuxerTest::kVideoData[kDataSize] = {4, 5, 6, 7}; -const uint8* FFmpegDemuxerTest::kNullData = NULL; - TEST_F(FFmpegDemuxerTest, Initialize_OpenFails) { // Simulate av_open_input_file() failing. - EXPECT_CALL(mock_ffmpeg_, AVOpenInputFile(_, _, NULL, 0, NULL)) - .WillOnce(Return(-1)); - - demuxer_->Initialize(data_source_.get(), + EXPECT_CALL(host_, SetCurrentReadPosition(_)); + demuxer_->Initialize(CreateDataSource("ten_byte_file"), NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN)); - message_loop_.RunAllPending(); -} - -TEST_F(FFmpegDemuxerTest, Initialize_ParseFails) { - // Simulate av_find_stream_info() failing. - EXPECT_CALL(mock_ffmpeg_, AVOpenInputFile(_, _, NULL, 0, NULL)) - .WillOnce(DoAll(SetArgumentPointee<0>(&format_context_), Return(0))); - EXPECT_CALL(mock_ffmpeg_, AVFindStreamInfo(&format_context_)) - .WillOnce(Return(AVERROR(EIO))); - EXPECT_CALL(mock_ffmpeg_, AVCloseInputFile(&format_context_)); - demuxer_->Initialize( - data_source_.get(), - NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_PARSE)); message_loop_.RunAllPending(); } -TEST_F(FFmpegDemuxerTest, Initialize_NoStreams) { - // Simulate media with no parseable streams. - { - SCOPED_TRACE(""); - InitializeDemuxerMocks(); - } - format_context_.nb_streams = 0; +// TODO(acolwell): Uncomment this test when we discover a file that passes +// av_open_input_file(), but has av_find_stream_info() fail. +// +//TEST_F(FFmpegDemuxerTest, Initialize_ParseFails) { +// demuxer_->Initialize( +// CreateDataSource("find_stream_info_fail.webm"), +// NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_PARSE)); +// message_loop_.RunAllPending(); +//} +TEST_F(FFmpegDemuxerTest, Initialize_NoStreams) { + // Open a file with no parseable streams. + EXPECT_CALL(host_, SetCurrentReadPosition(_)); demuxer_->Initialize( - data_source_.get(), + CreateDataSource("no_streams.webm"), NewExpectedStatusCB(DEMUXER_ERROR_NO_SUPPORTED_STREAMS)); message_loop_.RunAllPending(); } -TEST_F(FFmpegDemuxerTest, Initialize_DataStreamOnly) { - // Simulate media with a data stream but no audio or video streams. - { - SCOPED_TRACE(""); - InitializeDemuxerMocks(); - } - EXPECT_EQ(format_context_.streams[0], &streams_[AV_STREAM_DATA]); - format_context_.nb_streams = 1; - - demuxer_->Initialize( - data_source_.get(), - NewExpectedStatusCB(DEMUXER_ERROR_NO_SUPPORTED_STREAMS)); - message_loop_.RunAllPending(); -} +// TODO(acolwell): Find a subtitle only file so we can uncomment this test. +// +//TEST_F(FFmpegDemuxerTest, Initialize_DataStreamOnly) { +// demuxer_->Initialize( +// CreateDataSource("subtitles_only.mp4"),, +// NewExpectedStatusCB(DEMUXER_ERROR_NO_SUPPORTED_STREAMS)); +// message_loop_.RunAllPending(); +//} TEST_F(FFmpegDemuxerTest, Initialize_Successful) { - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } + InitializeDemuxer(CreateDataSource("bear-320x240.webm")); // First stream should be video and support the FFmpegDemuxerStream interface. scoped_refptr<DemuxerStream> stream = demuxer_->GetStream(DemuxerStream::VIDEO); ASSERT_TRUE(stream); EXPECT_EQ(DemuxerStream::VIDEO, stream->type()); - EXPECT_EQ(&streams_[AV_STREAM_VIDEO], stream->GetAVStream()); + ASSERT_TRUE(stream->GetAVStream()); // Other stream should be audio and support the FFmpegDemuxerStream interface. stream = demuxer_->GetStream(DemuxerStream::AUDIO); ASSERT_TRUE(stream); EXPECT_EQ(DemuxerStream::AUDIO, stream->type()); - EXPECT_EQ(&streams_[AV_STREAM_AUDIO], stream->GetAVStream()); + ASSERT_TRUE(stream->GetAVStream()); } -TEST_F(FFmpegDemuxerTest, Read_DiscardUninteresting) { - // We test that on a successful audio packet read, that the packet is - // duplicated (FFmpeg memory management safety), and a copy of it ends up in - // the DemuxerStream. - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } - - // Ignore all AVFreePacket() calls. We check this elsewhere. - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(_)).Times(AnyNumber()); - - // The demuxer will read a data packet which will get immediately freed, - // followed by a read error to end the reading. - InSequence s; - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_DATA, kNullData, 0)); - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(Return(AVERROR(EIO))); +TEST_F(FFmpegDemuxerTest, Read_Audio) { + // We test that on a successful audio packet read. + InitializeDemuxer(CreateDataSource("bear-320x240.webm")); // Attempt a read from the audio stream and run the message loop until done. scoped_refptr<DemuxerStream> audio = @@ -280,231 +176,107 @@ TEST_F(FFmpegDemuxerTest, Read_DiscardUninteresting) { scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); reader->Read(audio); message_loop_.RunAllPending(); - EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - EXPECT_TRUE(reader->buffer()->IsEndOfStream()); -} - -TEST_F(FFmpegDemuxerTest, Read_Audio) { - // We test that on a successful audio packet read, that the packet is - // duplicated (FFmpeg memory management safety), and a copy of it ends up in - // the DemuxerStream. - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } + ValidateBuffer(FROM_HERE, reader->buffer(), 29, 0); - // Ignore all AVFreePacket() calls. We check this via valgrind. - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(_)).Times(AnyNumber()); - - // The demuxer will read a data packet which will get immediately freed, - // followed by reading an audio packet... - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - - // Attempt a read from the audio stream and run the message loop until done. - scoped_refptr<DemuxerStream> audio = - demuxer_->GetStream(DemuxerStream::AUDIO); - scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); + reader->Reset(); reader->Read(audio); message_loop_.RunAllPending(); - EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); + ValidateBuffer(FROM_HERE, reader->buffer(), 27, 3000); } TEST_F(FFmpegDemuxerTest, Read_Video) { - // We test that on a successful video packet read, that the packet is - // duplicated (FFmpeg memory management safety), and a copy of it ends up in - // the DemuxerStream. - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } - - // Ignore all AVFreePacket() calls. We check this via valgrind. - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(_)).Times(AnyNumber()); - - // Simulate a successful frame read. - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_VIDEO, kVideoData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); + // We test that on a successful video packet read. + InitializeDemuxer(CreateDataSource("bear-320x240.webm")); // Attempt a read from the video stream and run the message loop until done. scoped_refptr<DemuxerStream> video = demuxer_->GetStream(DemuxerStream::VIDEO); scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); + reader->Read(video); message_loop_.RunAllPending(); - EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); -} - -TEST_F(FFmpegDemuxerTest, Read_VideoNonZeroStart) { - // Test the start time is the first timestamp of the video and audio stream. - const int64 kStartTime = 60000; - // Set the audio and video stream's first timestamp. - streams_[AV_STREAM_AUDIO].first_dts = kStartTime; - streams_[AV_STREAM_VIDEO].first_dts = kStartTime; - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } - - // Ignore all AVFreePacket() calls. We check this via valgrind. - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(_)).Times(AnyNumber()); - - const base::TimeDelta kExpectedTimestamp = - base::TimeDelta::FromMicroseconds(kStartTime); - - // Simulate a successful frame read. - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketTimeNoCount(AV_STREAM_VIDEO, - kVideoData, - kDataSize, - kStartTime)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); + ValidateBuffer(FROM_HERE, reader->buffer(), 22084, 0); - // Attempt a read from the video stream and run the message loop until done. - scoped_refptr<DemuxerStream> video = - demuxer_->GetStream(DemuxerStream::VIDEO); - scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); + reader->Reset(); reader->Read(video); message_loop_.RunAllPending(); - EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(kExpectedTimestamp, reader->buffer()->GetTimestamp()); - EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); - - EXPECT_EQ(kExpectedTimestamp, demuxer_->GetStartTime()); + ValidateBuffer(FROM_HERE, reader->buffer(), 1057, 33000); } -TEST_F(FFmpegDemuxerTest, CheckMinStartTime) { - // Test the start time is minimum timestamp of all streams. - const int64 kAudioStartTime = 5000000; - const int64 kVideoStartTime = 5033000; - // Set the audio and video stream's first timestamp. - streams_[AV_STREAM_AUDIO].first_dts = kAudioStartTime; - streams_[AV_STREAM_VIDEO].first_dts = kVideoStartTime; - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } - - // Ignore all AVFreePacket() calls. We check this via valgrind. - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(_)).Times(AnyNumber()); - - // Expect all calls in sequence. - InSequence s; +TEST_F(FFmpegDemuxerTest, Read_VideoNonZeroStart) { + // Test the start time is the first timestamp of the video and audio stream. + InitializeDemuxer(CreateDataSource("nonzero-start-time.webm")); - const base::TimeDelta kExpectedAudioTimestamp = - base::TimeDelta::FromMicroseconds(kAudioStartTime); - const base::TimeDelta kExpectedVideoTimestamp = - base::TimeDelta::FromMicroseconds(kVideoStartTime); - - // First read a video packet, then an audio packet. - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketTimeNoCount(AV_STREAM_AUDIO, - kAudioData, - kDataSize, - kAudioStartTime)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketTimeNoCount(AV_STREAM_VIDEO, - kVideoData, - kDataSize, - kVideoStartTime)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); + const base::TimeDelta kExpectedTimestamp = + base::TimeDelta::FromMicroseconds(396000); // Attempt a read from the video stream and run the message loop until done. scoped_refptr<DemuxerStream> video = demuxer_->GetStream(DemuxerStream::VIDEO); scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); - ASSERT_TRUE(video); - ASSERT_TRUE(audio); - scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); + + // Check first buffer in video stream. reader->Read(video); message_loop_.RunAllPending(); - EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(kExpectedVideoTimestamp, reader->buffer()->GetTimestamp()); - EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); - - EXPECT_EQ(kExpectedAudioTimestamp, demuxer_->GetStartTime()); + ValidateBuffer(FROM_HERE, reader->buffer(), 5636, 400000); + const base::TimeDelta video_timestamp = reader->buffer()->GetTimestamp(); + // Check first buffer in audio stream. reader->Reset(); reader->Read(audio); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(kExpectedAudioTimestamp, reader->buffer()->GetTimestamp()); - EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); + ValidateBuffer(FROM_HERE, reader->buffer(), 165, 396000); + const base::TimeDelta audio_timestamp = reader->buffer()->GetTimestamp(); - EXPECT_EQ(kExpectedAudioTimestamp, demuxer_->GetStartTime()); + // Verify that the start time is equal to the lowest timestamp. + EXPECT_EQ(std::min(audio_timestamp, video_timestamp), + demuxer_->GetStartTime()); } TEST_F(FFmpegDemuxerTest, Read_EndOfStream) { - // On end of stream, a new, empty, AVPackets are created without any data for - // each stream and enqueued into the Buffer stream. Verify that these are - // indeed inserted. - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } - - // Ignore all AVFreePacket() calls. We check this via valgrind. - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(_)).Times(AnyNumber()); - - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(Return(AVERROR(EIO))); + // Verify that end of stream buffers are created. + InitializeDemuxer(CreateDataSource("bear-320x240.webm")); // We should now expect an end of stream buffer. scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); - reader->Read(audio); - message_loop_.RunAllPending(); - EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - EXPECT_TRUE(reader->buffer()->IsEndOfStream()); - EXPECT_TRUE(reader->buffer()->GetData() == NULL); - EXPECT_EQ(0u, reader->buffer()->GetDataSize()); + + bool got_eos_buffer = false; + const int kMaxBuffers = 170; + for (int i = 0; !got_eos_buffer && i < kMaxBuffers; i++) { + reader->Read(audio); + message_loop_.RunAllPending(); + EXPECT_TRUE(reader->called()); + ASSERT_TRUE(reader->buffer()); + + if (reader->buffer()->IsEndOfStream()) { + got_eos_buffer = true; + EXPECT_TRUE(reader->buffer()->GetData() == NULL); + EXPECT_EQ(0u, reader->buffer()->GetDataSize()); + break; + } + + EXPECT_TRUE(reader->buffer()->GetData() != NULL); + EXPECT_GT(reader->buffer()->GetDataSize(), 0u); + reader->Reset(); + } + + EXPECT_TRUE(got_eos_buffer); } TEST_F(FFmpegDemuxerTest, Seek) { // We're testing that the demuxer frees all queued packets when it receives // a Seek(). - // - // 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(); - } + InitializeDemuxer(CreateDataSource("bear-320x240.webm")); // Get our streams. scoped_refptr<DemuxerStream> video = @@ -514,129 +286,52 @@ TEST_F(FFmpegDemuxerTest, Seek) { ASSERT_TRUE(video); ASSERT_TRUE(audio); - // Expected values. - const int64 kExpectedTimestamp = 1234; - const int64 kExpectedFlags = AVSEEK_FLAG_BACKWARD; - - // Ignore all AVFreePacket() calls. We check this via valgrind. - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(_)).Times(AnyNumber()); - - // Expect all calls in sequence. - InSequence s; - - // First we'll read a video packet that causes two audio packets to be queued - // inside FFmpegDemuxer... - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_VIDEO, kVideoData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - - EXPECT_CALL(mock_ffmpeg_, CheckPoint(1)); - - // ...then we'll expect a seek call... - EXPECT_CALL(mock_ffmpeg_, - AVSeekFrame(&format_context_, -1, kExpectedTimestamp, kExpectedFlags)) - .WillOnce(Return(0)); - - // ...then our callback will be executed... - FilterStatusCB seek_cb = NewExpectedStatusCB(PIPELINE_OK); - EXPECT_CALL(mock_ffmpeg_, CheckPoint(2)); - - // ...followed by two audio packet reads we'll trigger... - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kAudioData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - - // ...followed by two video packet reads... - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_VIDEO, kVideoData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_VIDEO, kVideoData, kDataSize)); - EXPECT_CALL(mock_ffmpeg_, AVDupPacket(_)) - .WillOnce(Return(0)); - - // ...and finally a sanity checkpoint to make sure everything was released. - EXPECT_CALL(mock_ffmpeg_, CheckPoint(3)); - // Read a video packet and release it. scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); reader->Read(video); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); + ValidateBuffer(FROM_HERE, reader->buffer(), 22084, 0); // Release the video packet and verify the other packets are still queued. reader->Reset(); message_loop_.RunAllPending(); - mock_ffmpeg_.CheckPoint(1); // Issue a simple forward seek, which should discard queued packets. - demuxer_->Seek(base::TimeDelta::FromMicroseconds(kExpectedTimestamp), - seek_cb); + demuxer_->Seek(base::TimeDelta::FromMicroseconds(1000000), + NewExpectedStatusCB(PIPELINE_OK)); message_loop_.RunAllPending(); - mock_ffmpeg_.CheckPoint(2); // Audio read #1. reader->Read(audio); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); + ValidateBuffer(FROM_HERE, reader->buffer(), 145, 803000); // Audio read #2. reader->Reset(); reader->Read(audio); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); + ValidateBuffer(FROM_HERE, reader->buffer(), 148, 826000); // Video read #1. reader->Reset(); reader->Read(video); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); + ValidateBuffer(FROM_HERE, reader->buffer(), 5425, 801000); // Video read #2. reader->Reset(); reader->Read(video); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ASSERT_TRUE(reader->buffer()); - ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); - EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), - reader->buffer()->GetDataSize())); + ValidateBuffer(FROM_HERE, reader->buffer(), 1906, 834000); // Manually release the last reference to the buffer and verify it was freed. reader->Reset(); message_loop_.RunAllPending(); - mock_ffmpeg_.CheckPoint(3); } // A mocked callback specialization for calling Read(). Since RunWithParams() @@ -659,21 +354,13 @@ class MockReadCallback : public base::RefCountedThreadSafe<MockReadCallback> { TEST_F(FFmpegDemuxerTest, Stop) { // Tests that calling Read() on a stopped demuxer immediately deletes the // callback. - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } + InitializeDemuxer(CreateDataSource("bear-320x240.webm")); // Get our stream. scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); ASSERT_TRUE(audio); - // Stop the demuxer, overriding the default expectation to assert that - // data_source_ really is Stop()'d. - EXPECT_CALL(*data_source_, Stop(_)) - .WillOnce(Invoke(&RunStopFilterCallback)) - .RetiresOnSaturation(); demuxer_->Stop(NewExpectedCallback()); // Expect all calls in sequence. @@ -686,7 +373,7 @@ TEST_F(FFmpegDemuxerTest, Stop) { // The callback should be immediately deleted. We'll use a checkpoint to // verify that it has indeed been deleted. EXPECT_CALL(*callback, OnDelete()); - EXPECT_CALL(mock_ffmpeg_, CheckPoint(1)); + EXPECT_CALL(*this, CheckPoint(1)); // Attempt the read... audio->Read(base::Bind(&MockReadCallback::Run, callback)); @@ -694,7 +381,7 @@ TEST_F(FFmpegDemuxerTest, Stop) { message_loop_.RunAllPending(); // ...and verify that |callback| was deleted. - mock_ffmpeg_.CheckPoint(1); + CheckPoint(1); } TEST_F(FFmpegDemuxerTest, DisableAudioStream) { @@ -702,38 +389,32 @@ TEST_F(FFmpegDemuxerTest, DisableAudioStream) { // 1. Initialize the demuxer with audio and video stream. // 2. Send a "disable audio stream" message to the demuxer. // 3. Demuxer will free audio packets even if audio stream was initialized. - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } + InitializeDemuxer(CreateDataSource("bear-320x240.webm")); // Submit a "disable audio stream" message to the demuxer. demuxer_->OnAudioRendererDisabled(); message_loop_.RunAllPending(); - // Ignore all AVFreePacket() calls. We check this via valgrind. - EXPECT_CALL(mock_ffmpeg_, AVFreePacket(_)).Times(AnyNumber()); - - // Expect all calls in sequence. - InSequence s; - - // The demuxer will read an audio packet which will get immediately freed. - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(CreatePacketNoCount(AV_STREAM_AUDIO, kNullData, 0)); - - // Then an end-of-stream packet is read. - EXPECT_CALL(mock_ffmpeg_, AVReadFrame(&format_context_, _)) - .WillOnce(Return(AVERROR(EIO))); - // Get our streams. scoped_refptr<DemuxerStream> video = demuxer_->GetStream(DemuxerStream::VIDEO); + scoped_refptr<DemuxerStream> audio = + demuxer_->GetStream(DemuxerStream::AUDIO); ASSERT_TRUE(video); + ASSERT_TRUE(audio); // Attempt a read from the video stream and run the message loop until done. scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); reader->Read(video); message_loop_.RunAllPending(); + EXPECT_TRUE(reader->called()); + ValidateBuffer(FROM_HERE, reader->buffer(), 22084, 0); + + reader->Reset(); + reader->Read(audio); + message_loop_.RunAllPending(); + EXPECT_TRUE(reader->called()); + EXPECT_TRUE(reader->buffer()->IsEndOfStream()); } class MockFFmpegDemuxer : public FFmpegDemuxer { @@ -758,19 +439,25 @@ void RunCallback(size_t size, DataSource::ReadCallback* callback) { } TEST_F(FFmpegDemuxerTest, ProtocolRead) { + scoped_refptr<StrictMock<MockDataSource> > data_source = + new StrictMock<MockDataSource>(); + + EXPECT_CALL(*data_source, Stop(NotNull())) + .WillRepeatedly(Invoke(&RunStopFilterCallback)); + // Creates a demuxer. scoped_refptr<MockFFmpegDemuxer> demuxer( new MockFFmpegDemuxer(&message_loop_)); ASSERT_TRUE(demuxer); demuxer->set_host(&host_); - demuxer->data_source_ = data_source_; + demuxer->data_source_ = data_source; uint8 kBuffer[1]; InSequence s; // Actions taken in the first read. - EXPECT_CALL(*data_source_, GetSize(_)) - .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); - EXPECT_CALL(*data_source_, Read(0, 512, kBuffer, NotNull())) + EXPECT_CALL(*data_source, GetSize(_)) + .WillOnce(DoAll(SetArgPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source, Read(0, 512, kBuffer, NotNull())) .WillOnce(WithArgs<1, 3>(Invoke(&RunCallback))); EXPECT_CALL(*demuxer, SignalReadCompleted(512)); EXPECT_CALL(*demuxer, WaitForRead()) @@ -778,9 +465,9 @@ TEST_F(FFmpegDemuxerTest, ProtocolRead) { EXPECT_CALL(host_, SetCurrentReadPosition(512)); // Second read. - EXPECT_CALL(*data_source_, GetSize(_)) - .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); - EXPECT_CALL(*data_source_, Read(512, 512, kBuffer, NotNull())) + EXPECT_CALL(*data_source, GetSize(_)) + .WillOnce(DoAll(SetArgPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source, Read(512, 512, kBuffer, NotNull())) .WillOnce(WithArgs<1, 3>(Invoke(&RunCallback))); EXPECT_CALL(*demuxer, SignalReadCompleted(512)); EXPECT_CALL(*demuxer, WaitForRead()) @@ -788,8 +475,8 @@ TEST_F(FFmpegDemuxerTest, ProtocolRead) { EXPECT_CALL(host_, SetCurrentReadPosition(1024)); // Third read will fail because it exceeds the file size. - EXPECT_CALL(*data_source_, GetSize(_)) - .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source, GetSize(_)) + .WillOnce(DoAll(SetArgPointee<0>(1024), Return(true))); // First read. EXPECT_EQ(512, demuxer->Read(512, kBuffer)); @@ -812,52 +499,42 @@ TEST_F(FFmpegDemuxerTest, ProtocolRead) { } TEST_F(FFmpegDemuxerTest, ProtocolGetSetPosition) { - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } + scoped_refptr<DataSource> data_source = CreateDataSource("bear-320x240.webm"); + InitializeDemuxer(data_source); InSequence s; - EXPECT_CALL(*data_source_, GetSize(_)) - .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); - EXPECT_CALL(*data_source_, GetSize(_)) - .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); - EXPECT_CALL(*data_source_, GetSize(_)) - .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); - + int64 size; int64 position; + EXPECT_TRUE(demuxer_->GetSize(&size)); EXPECT_TRUE(demuxer_->GetPosition(&position)); - EXPECT_EQ(0, position); + EXPECT_EQ(current_read_position_, position); EXPECT_TRUE(demuxer_->SetPosition(512)); - EXPECT_FALSE(demuxer_->SetPosition(2048)); + EXPECT_FALSE(demuxer_->SetPosition(size)); + EXPECT_FALSE(demuxer_->SetPosition(size + 1)); EXPECT_FALSE(demuxer_->SetPosition(-1)); EXPECT_TRUE(demuxer_->GetPosition(&position)); EXPECT_EQ(512, position); } TEST_F(FFmpegDemuxerTest, ProtocolGetSize) { - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } - - EXPECT_CALL(*data_source_, GetSize(_)) - .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); - - int64 size; - EXPECT_TRUE(demuxer_->GetSize(&size)); - EXPECT_EQ(1024, size); + scoped_refptr<DataSource> data_source = CreateDataSource("bear-320x240.webm"); + InitializeDemuxer(data_source); + + int64 data_source_size = 0; + int64 demuxer_size = 0; + EXPECT_TRUE(data_source->GetSize(&data_source_size)); + EXPECT_TRUE(demuxer_->GetSize(&demuxer_size)); + EXPECT_NE(0, data_source_size); + EXPECT_EQ(data_source_size, demuxer_size); } TEST_F(FFmpegDemuxerTest, ProtocolIsStreaming) { - { - SCOPED_TRACE(""); - InitializeDemuxer(); - } - EXPECT_CALL(*data_source_, IsStreaming()) - .WillOnce(Return(false)); + scoped_refptr<DataSource> data_source = CreateDataSource("bear-320x240.webm"); + InitializeDemuxer(data_source); + + EXPECT_FALSE(data_source->IsStreaming()); EXPECT_FALSE(demuxer_->IsStreaming()); } diff --git a/media/filters/ffmpeg_glue.cc b/media/filters/ffmpeg_glue.cc index 7f134e3..bd55901 100644 --- a/media/filters/ffmpeg_glue.cc +++ b/media/filters/ffmpeg_glue.cc @@ -146,6 +146,11 @@ FFmpegGlue* FFmpegGlue::GetInstance() { return Singleton<FFmpegGlue>::get(); } +// static +URLProtocol* FFmpegGlue::url_protocol() { + return &kFFmpegURLProtocol; +} + std::string FFmpegGlue::AddProtocol(FFmpegURLProtocol* protocol) { base::AutoLock auto_lock(lock_); std::string key = GetProtocolKey(protocol); diff --git a/media/filters/ffmpeg_glue.h b/media/filters/ffmpeg_glue.h index a98c502..0e91fd3 100644 --- a/media/filters/ffmpeg_glue.h +++ b/media/filters/ffmpeg_glue.h @@ -32,15 +32,15 @@ #include "base/memory/singleton.h" #include "base/synchronization/lock.h" +struct URLProtocol; + namespace media { class FFmpegURLProtocol { public: - FFmpegURLProtocol() { - } + FFmpegURLProtocol() {} - virtual ~FFmpegURLProtocol() { - } + virtual ~FFmpegURLProtocol() {} // Read the given amount of bytes into data, returns the number of bytes read // if successful, kReadError otherwise. @@ -99,6 +99,9 @@ class FFmpegGlue { typedef std::map<std::string, FFmpegURLProtocol*> ProtocolMap; ProtocolMap protocols_; + friend class FFmpegGlueTest; + static URLProtocol* url_protocol(); + DISALLOW_COPY_AND_ASSIGN(FFmpegGlue); }; diff --git a/media/filters/ffmpeg_glue_unittest.cc b/media/filters/ffmpeg_glue_unittest.cc index 158ddbb..0ea91f1 100644 --- a/media/filters/ffmpeg_glue_unittest.cc +++ b/media/filters/ffmpeg_glue_unittest.cc @@ -4,7 +4,6 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" -#include "media/base/mock_ffmpeg.h" #include "media/base/mock_filters.h" #include "media/ffmpeg/ffmpeg_common.h" #include "media/filters/ffmpeg_glue.h" @@ -36,17 +35,21 @@ class MockProtocol : public FFmpegURLProtocol { class FFmpegGlueTest : public ::testing::Test { public: - FFmpegGlueTest() {} + FFmpegGlueTest() : protocol_(NULL) {} - virtual void SetUp() { + static void SetUpTestCase() { // Singleton should initialize FFmpeg. CHECK(FFmpegGlue::GetInstance()); + } + virtual void SetUp() { // Assign our static copy of URLProtocol for the rest of the tests. - protocol_ = MockFFmpeg::protocol(); + protocol_ = FFmpegGlue::url_protocol(); CHECK(protocol_); } + MOCK_METHOD1(CheckPoint, void(int val)); + // Helper to open a URLContext pointing to the given mocked protocol. // Callers are expected to close the context at the end of their test. virtual void OpenContext(MockProtocol* protocol, URLContext* context) { @@ -62,15 +65,12 @@ class FFmpegGlueTest : public ::testing::Test { protected: // Fixture members. - MockFFmpeg mock_ffmpeg_; - static URLProtocol* protocol_; + URLProtocol* protocol_; private: DISALLOW_COPY_AND_ASSIGN(FFmpegGlueTest); }; -URLProtocol* FFmpegGlueTest::protocol_ = NULL; - TEST_F(FFmpegGlueTest, InitializeFFmpeg) { // Make sure URLProtocol was filled out correctly. EXPECT_STREQ("http", protocol_->name); @@ -118,7 +118,7 @@ TEST_F(FFmpegGlueTest, AddRemoveGetProtocol) { InSequence s; EXPECT_CALL(*protocol_a, OnDestroy()); EXPECT_CALL(*protocol_b, OnDestroy()); - EXPECT_CALL(mock_ffmpeg_, CheckPoint(0)); + EXPECT_CALL(*this, CheckPoint(0)); glue->RemoveProtocol(protocol_a.get()); glue->GetProtocol(key_a, &protocol_c); @@ -132,7 +132,7 @@ TEST_F(FFmpegGlueTest, AddRemoveGetProtocol) { protocol_b.reset(); // Data sources should be deleted by this point. - mock_ffmpeg_.CheckPoint(0); + CheckPoint(0); } TEST_F(FFmpegGlueTest, OpenClose) { @@ -162,22 +162,22 @@ TEST_F(FFmpegGlueTest, OpenClose) { // held by FFmpeg. Once we close the URLContext, the protocol should be // destroyed. InSequence s; - EXPECT_CALL(mock_ffmpeg_, CheckPoint(0)); - EXPECT_CALL(mock_ffmpeg_, CheckPoint(1)); + EXPECT_CALL(*this, CheckPoint(0)); + EXPECT_CALL(*this, CheckPoint(1)); EXPECT_CALL(*protocol, OnDestroy()); - EXPECT_CALL(mock_ffmpeg_, CheckPoint(2)); + EXPECT_CALL(*this, CheckPoint(2)); // Remove the protocol from the glue layer, releasing a reference. glue->RemoveProtocol(protocol.get()); - mock_ffmpeg_.CheckPoint(0); + CheckPoint(0); // Remove our own reference -- URLContext should maintain a reference. - mock_ffmpeg_.CheckPoint(1); + CheckPoint(1); protocol.reset(); // Close the URLContext, which should release the final reference. EXPECT_EQ(0, protocol_->url_close(&context)); - mock_ffmpeg_.CheckPoint(2); + CheckPoint(2); } TEST_F(FFmpegGlueTest, Write) { @@ -309,11 +309,11 @@ TEST_F(FFmpegGlueTest, Destroy) { // We should expect the protocol to get destroyed when the unit test // exits. InSequence s; - EXPECT_CALL(mock_ffmpeg_, CheckPoint(0)); + EXPECT_CALL(*this, CheckPoint(0)); EXPECT_CALL(*protocol, OnDestroy()); // Remove our own reference, we shouldn't be destroyed yet. - mock_ffmpeg_.CheckPoint(0); + CheckPoint(0); protocol.reset(); // ~FFmpegGlue() will be called when this unit test finishes execution. By diff --git a/media/filters/ffmpeg_h264_bitstream_converter_unittest.cc b/media/filters/ffmpeg_h264_bitstream_converter_unittest.cc index 5799123..e3d3766 100644 --- a/media/filters/ffmpeg_h264_bitstream_converter_unittest.cc +++ b/media/filters/ffmpeg_h264_bitstream_converter_unittest.cc @@ -2,22 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "media/base/mock_ffmpeg.h" +#include "media/base/media.h" +#include "media/ffmpeg/ffmpeg_common.h" #include "media/filters/ffmpeg_h264_bitstream_converter.h" #include "testing/gtest/include/gtest/gtest.h" -using ::testing::_; -using ::testing::Invoke; -using ::testing::Return; -using ::testing::StrictMock; - namespace media { -// Forward declarations for fake FFmpeg packet handling functions. -static void fake_av_destruct_packet(AVPacket* pkt); -static void fake_av_init_packet(AVPacket* pkt); -static int fake_av_new_packet(AVPacket* pkt, int size); - // Test data arrays. static const uint8 kHeaderDataOkWithFieldLen4[] = { 0x01, 0x42, 0x00, 0x28, 0xFF, 0xE1, 0x00, 0x08, 0x67, 0x42, 0x00, 0x28, @@ -268,14 +259,6 @@ static const uint8 kPacketDataOkWithFieldLen4[] = { class FFmpegH264BitstreamConverterTest : public testing::Test { protected: FFmpegH264BitstreamConverterTest() { - // Set up the ffmpeg mock and use our local fake functions to do the - // actual implementation for packet allocation / freeing. - ON_CALL(ffmpeg_mock_, AVInitPacket(_)) - .WillByDefault(Invoke(fake_av_init_packet)); - ON_CALL(ffmpeg_mock_, AVNewPacket(_, _)) - .WillByDefault(Invoke(fake_av_new_packet)); - ON_CALL(ffmpeg_mock_, AVDestructPacket(_)) - .WillByDefault(Invoke(fake_av_destruct_packet)); // Set up AVCConfigurationRecord correctly for tests. // It's ok to do const cast here as data in kHeaderDataOkWithFieldLen4 is // never written to. @@ -288,16 +271,10 @@ class FFmpegH264BitstreamConverterTest : public testing::Test { void CreatePacket(AVPacket* packet, const uint8* data, uint32 data_size) { // Create new packet sized of |data_size| from |data|. - EXPECT_CALL(ffmpeg_mock_, AVNewPacket(_, _)); - EXPECT_CALL(ffmpeg_mock_, AVInitPacket(_)); EXPECT_EQ(av_new_packet(packet, data_size), 0); memcpy(packet->data, data, data_size); } - // FFmpeg mock implementation. We want strict mock since we will strictly - // define the order of calls and do not want any extra calls. - StrictMock<MockFFmpeg> ffmpeg_mock_; - // Variable to hold valid dummy context for testing. AVCodecContext test_context_; @@ -317,13 +294,9 @@ TEST_F(FFmpegH264BitstreamConverterTest, Conversion_Success) { // Try out the actual conversion (should be successful and allocate new // packet and destroy the old one). - EXPECT_CALL(ffmpeg_mock_, AVNewPacket(_, _)); - EXPECT_CALL(ffmpeg_mock_, AVInitPacket(_)); - EXPECT_CALL(ffmpeg_mock_, AVDestructPacket(_)); EXPECT_TRUE(converter.ConvertPacket(&test_packet)); // Clean-up the test packet. - EXPECT_CALL(ffmpeg_mock_, AVDestructPacket(_)); av_destruct_packet(&test_packet); // Converter will be automatically cleaned up. @@ -344,38 +317,9 @@ TEST_F(FFmpegH264BitstreamConverterTest, Conversion_SuccessBigPacket) { // Try out the actual conversion (should be successful and allocate new // packet and destroy the old one as we do NOT support in place transform). - EXPECT_CALL(ffmpeg_mock_, AVNewPacket(_, _)); - EXPECT_CALL(ffmpeg_mock_, AVInitPacket(_)); - EXPECT_CALL(ffmpeg_mock_, AVDestructPacket(_)); EXPECT_TRUE(converter.ConvertPacket(&test_packet)); // Clean-up the test packet. - EXPECT_CALL(ffmpeg_mock_, AVDestructPacket(_)); - av_destruct_packet(&test_packet); - - // Converter will be automatically cleaned up. -} - -TEST_F(FFmpegH264BitstreamConverterTest, Conversion_FailureOutOfMem) { - FFmpegH264BitstreamConverter converter(&test_context_); - - // Initialization should be always successful. - EXPECT_TRUE(converter.Initialize()); - - // Create new packet. - AVPacket test_packet; - CreatePacket(&test_packet, kPacketDataOkWithFieldLen4, - sizeof(kPacketDataOkWithFieldLen4)); - - // Try out the actual conversion (should be successful and allocate new - // packet and destroy the old one). - EXPECT_CALL(ffmpeg_mock_, AVNewPacket(_, _)) - .WillOnce(Return(-1)); - EXPECT_FALSE(converter.ConvertPacket(&test_packet)) - << "ConvertPacket() did not return expected failure due to out of mem"; - - // Clean-up the test packet. - EXPECT_CALL(ffmpeg_mock_, AVDestructPacket(_)); av_destruct_packet(&test_packet); // Converter will be automatically cleaned up. @@ -404,39 +348,9 @@ TEST_F(FFmpegH264BitstreamConverterTest, Conversion_FailureNullParams) { EXPECT_FALSE(converter.ConvertPacket(&test_packet)); // Clean-up the test packet. - EXPECT_CALL(ffmpeg_mock_, AVDestructPacket(_)); av_destruct_packet(&test_packet); // Converted will be automatically cleaned up. } -static void fake_av_destruct_packet(AVPacket* pkt) { - free(pkt->data); - pkt->data = NULL; - pkt->size = 0; -} - -static void fake_av_init_packet(AVPacket* pkt) { - pkt->pts = AV_NOPTS_VALUE; - pkt->dts = AV_NOPTS_VALUE; - pkt->pos = -1; - pkt->duration = 0; - pkt->convergence_duration = 0; - pkt->flags = 0; - pkt->stream_index = 0; - pkt->destruct= NULL; -} - -static int fake_av_new_packet(AVPacket* pkt, int size) { - uint8* data = reinterpret_cast<uint8*>(malloc(size)); - av_init_packet(pkt); - pkt->data = data; - pkt->size = size; - pkt->destruct = av_destruct_packet; - if (data == NULL) - return AVERROR(ENOMEM); - return 0; -} - } // namespace media - diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index 7614d8b..e3dd90d 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -10,7 +10,6 @@ #include "media/base/data_buffer.h" #include "media/base/filters.h" #include "media/base/mock_callback.h" -#include "media/base/mock_ffmpeg.h" #include "media/base/mock_filter_host.h" #include "media/base/mock_filters.h" #include "media/base/mock_task.h" @@ -220,7 +219,6 @@ class FFmpegVideoDecoderTest : public testing::Test { AVCodec codec_; AVFrame yuv_frame_; scoped_refptr<VideoFrame> video_frame_; - StrictMock<MockFFmpeg> mock_ffmpeg_; private: DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoderTest); diff --git a/media/media.gyp b/media/media.gyp index 7cc36e4..c27b430 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -442,8 +442,6 @@ 'base/djb2_unittest.cc', 'base/filter_collection_unittest.cc', 'base/h264_bitstream_converter_unittest.cc', - 'base/mock_ffmpeg.cc', - 'base/mock_ffmpeg.h', 'base/mock_reader.h', 'base/mock_task.cc', 'base/mock_task.h', @@ -453,6 +451,8 @@ 'base/run_all_unittests.cc', 'base/seekable_buffer_unittest.cc', 'base/state_matrix_unittest.cc', + 'base/test_data_util.cc', + 'base/test_data_util.h', 'base/video_frame_unittest.cc', 'base/video_util_unittest.cc', 'base/yuv_convert_unittest.cc', diff --git a/media/video/ffmpeg_video_decode_engine.cc b/media/video/ffmpeg_video_decode_engine.cc index 4af73a1..0958516 100644 --- a/media/video/ffmpeg_video_decode_engine.cc +++ b/media/video/ffmpeg_video_decode_engine.cc @@ -120,6 +120,7 @@ void FFmpegVideoDecodeEngine::Initialize( kNoTimestamp); frame_queue_available_.push_back(video_frame); } + codec_context_->thread_count = decode_threads; if (codec && avcodec_open(codec_context_, codec) >= 0 && @@ -186,7 +187,6 @@ void FFmpegVideoDecodeEngine::DecodeFrame(scoped_refptr<Buffer> buffer) { av_frame_.get(), &frame_decoded, &packet); - // Log the problem if we can't decode a video frame and exit early. if (result < 0) { LOG(ERROR) << "Error decoding a video frame with timestamp: " @@ -303,24 +303,6 @@ void FFmpegVideoDecodeEngine::ReadInput() { event_handler_->ProduceVideoSample(NULL); } -VideoFrame::Format FFmpegVideoDecodeEngine::GetSurfaceFormat() const { - // J (Motion JPEG) versions of YUV are full range 0..255. - // Regular (MPEG) YUV is 16..240. - // For now we will ignore the distinction and treat them the same. - switch (codec_context_->pix_fmt) { - case PIX_FMT_YUV420P: - case PIX_FMT_YUVJ420P: - return VideoFrame::YV12; - case PIX_FMT_YUV422P: - case PIX_FMT_YUVJ422P: - return VideoFrame::YV16; - default: - // TODO(scherkus): More formats here? - break; - } - return VideoFrame::INVALID; -} - } // namespace media // Disable refcounting for this object because this object only lives diff --git a/media/video/ffmpeg_video_decode_engine.h b/media/video/ffmpeg_video_decode_engine.h index 32eac45..b7d7661 100644 --- a/media/video/ffmpeg_video_decode_engine.h +++ b/media/video/ffmpeg_video_decode_engine.h @@ -33,8 +33,6 @@ class FFmpegVideoDecodeEngine : public VideoDecodeEngine { virtual void Flush(); virtual void Seek(); - VideoFrame::Format GetSurfaceFormat() const; - private: void DecodeFrame(scoped_refptr<Buffer> buffer); void ReadInput(); diff --git a/media/video/ffmpeg_video_decode_engine_unittest.cc b/media/video/ffmpeg_video_decode_engine_unittest.cc index 261ca45..611bbfa 100644 --- a/media/video/ffmpeg_video_decode_engine_unittest.cc +++ b/media/video/ffmpeg_video_decode_engine_unittest.cc @@ -5,9 +5,10 @@ #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "media/base/data_buffer.h" -#include "media/base/mock_ffmpeg.h" #include "media/base/mock_task.h" #include "media/base/pipeline.h" +#include "media/base/test_data_util.h" +#include "media/filters/ffmpeg_glue.h" #include "media/video/ffmpeg_video_decode_engine.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -16,63 +17,40 @@ using ::testing::_; using ::testing::DoAll; using ::testing::Return; using ::testing::ReturnNull; +using ::testing::SaveArg; using ::testing::SetArgumentPointee; using ::testing::StrictMock; namespace media { -static const int kWidth = 320; -static const int kHeight = 240; +static const size_t kWidth = 320; +static const size_t kHeight = 240; static const int kSurfaceWidth = 522; static const int kSurfaceHeight = 288; static const AVRational kFrameRate = { 100, 1 }; -static void InitializeFrame(uint8_t* data, int width, AVFrame* frame) { - frame->data[0] = data; - frame->data[1] = data; - frame->data[2] = data; - frame->linesize[0] = width; - frame->linesize[1] = width / 2; - frame->linesize[2] = width / 2; -} - -ACTION_P(DecodeComplete, decoder) { - decoder->set_video_frame(arg0); -} - ACTION_P2(DemuxComplete, engine, buffer) { engine->ConsumeVideoSample(buffer); } -ACTION_P(SaveInitializeResult, engine) { - engine->set_video_codec_info(arg0); -} - class FFmpegVideoDecodeEngineTest : public testing::Test, public VideoDecodeEngine::EventHandler { public: FFmpegVideoDecodeEngineTest() - : config_(kCodecH264, kWidth, kHeight, kSurfaceWidth, kSurfaceHeight, + : config_(kCodecVP8, kWidth, kHeight, kSurfaceWidth, kSurfaceHeight, kFrameRate.num, kFrameRate.den, NULL, 0) { + CHECK(FFmpegGlue::GetInstance()); // Setup FFmpeg structures. frame_buffer_.reset(new uint8[kWidth * kHeight]); - memset(&yuv_frame_, 0, sizeof(yuv_frame_)); - InitializeFrame(frame_buffer_.get(), kWidth, &yuv_frame_); - - memset(&codec_context_, 0, sizeof(codec_context_)); - memset(&codec_, 0, sizeof(codec_)); - - buffer_ = new DataBuffer(1); test_engine_.reset(new FFmpegVideoDecodeEngine()); - video_frame_ = VideoFrame::CreateFrame(VideoFrame::YV12, - kWidth, - kHeight, - kNoTimestamp, - kNoTimestamp); + ReadTestDataFile("vp8-I-frame-320x240", &i_frame_buffer_); + ReadTestDataFile("vp8-corrupt-I-frame", &corrupt_i_frame_buffer_); + + end_of_stream_buffer_ = new DataBuffer(0); } ~FFmpegVideoDecodeEngineTest() { @@ -80,46 +58,57 @@ class FFmpegVideoDecodeEngineTest } void Initialize() { - EXPECT_CALL(mock_ffmpeg_, AVCodecAllocContext()) - .WillOnce(Return(&codec_context_)); - EXPECT_CALL(mock_ffmpeg_, AVCodecFindDecoder(CODEC_ID_H264)) - .WillOnce(Return(&codec_)); - EXPECT_CALL(mock_ffmpeg_, AVCodecAllocFrame()) - .WillOnce(Return(&yuv_frame_)); - EXPECT_CALL(mock_ffmpeg_, AVCodecOpen(&codec_context_, &codec_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVCodecClose(&codec_context_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVFree(&yuv_frame_)) - .Times(1); - EXPECT_CALL(mock_ffmpeg_, AVFree(&codec_context_)) - .Times(1); - + VideoCodecInfo info; EXPECT_CALL(*this, OnInitializeComplete(_)) - .WillOnce(SaveInitializeResult(this)); + .WillOnce(SaveArg<0>(&info)); test_engine_->Initialize(MessageLoop::current(), this, NULL, config_); - EXPECT_TRUE(info_.success); + EXPECT_TRUE(info.success); } - void Decode() { - EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_)); - EXPECT_CALL(mock_ffmpeg_, - AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<2>(1), // Simulate 1 byte frame. - Return(0))); - + // Decodes the single compressed frame in |buffer| and writes the + // uncompressed output to |video_frame|. This method works with single + // and multithreaded decoders. End of stream buffers are used to trigger + // the frame to be returned in the multithreaded decoder case. + void DecodeASingleFrame(const scoped_refptr<Buffer>& buffer, + scoped_refptr<VideoFrame>* video_frame) { EXPECT_CALL(*this, ProduceVideoSample(_)) - .WillOnce(DemuxComplete(test_engine_.get(), buffer_)); + .WillOnce(DemuxComplete(test_engine_.get(), buffer)) + .WillRepeatedly(DemuxComplete(test_engine_.get(), + end_of_stream_buffer_)); + EXPECT_CALL(*this, ConsumeVideoFrame(_, _)) - .WillOnce(DecodeComplete(this)); - test_engine_->ProduceVideoFrame(video_frame_); + .WillOnce(SaveArg<0>(video_frame)); + CallProduceVideoFrame(); } - void ChangeDimensions(int width, int height) { - frame_buffer_.reset(new uint8[width * height]); - InitializeFrame(frame_buffer_.get(), width, &yuv_frame_); - codec_context_.width = width; - codec_context_.height = height; + // Decodes |i_frame_buffer_| and then decodes the data contained in + // the file named |test_file_name|. This function expects both buffers + // to decode to frames that are the same size. + void DecodeIFrameThenTestFile(const std::string& test_file_name) { + Initialize(); + + scoped_refptr<VideoFrame> video_frame_a; + scoped_refptr<VideoFrame> video_frame_b; + + scoped_refptr<Buffer> buffer; + ReadTestDataFile(test_file_name, &buffer); + + EXPECT_CALL(*this, ProduceVideoSample(_)) + .WillOnce(DemuxComplete(test_engine_.get(), i_frame_buffer_)) + .WillOnce(DemuxComplete(test_engine_.get(), buffer)) + .WillRepeatedly(DemuxComplete(test_engine_.get(), + end_of_stream_buffer_)); + + EXPECT_CALL(*this, ConsumeVideoFrame(_, _)) + .WillOnce(SaveArg<0>(&video_frame_a)) + .WillOnce(SaveArg<0>(&video_frame_b)); + CallProduceVideoFrame(); + CallProduceVideoFrame(); + + EXPECT_EQ(kWidth, video_frame_a->width()); + EXPECT_EQ(kHeight, video_frame_a->height()); + EXPECT_EQ(kWidth, video_frame_b->width()); + EXPECT_EQ(kHeight, video_frame_b->height()); } // VideoDecodeEngine::EventHandler implementation. @@ -135,27 +124,21 @@ class FFmpegVideoDecodeEngineTest MOCK_METHOD0(OnSeekComplete, void()); MOCK_METHOD0(OnError, void()); - // Used by gmock actions. - void set_video_frame(scoped_refptr<VideoFrame> video_frame) { - video_frame_ = video_frame; - } - - void set_video_codec_info(const VideoCodecInfo& info) { - info_ = info; + void CallProduceVideoFrame() { + test_engine_->ProduceVideoFrame(VideoFrame::CreateFrame(VideoFrame::YV12, + kWidth, + kHeight, + kNoTimestamp, + kNoTimestamp)); } protected: VideoDecoderConfig config_; - VideoCodecInfo info_; - scoped_refptr<VideoFrame> video_frame_; scoped_ptr<FFmpegVideoDecodeEngine> test_engine_; scoped_array<uint8_t> frame_buffer_; - StrictMock<MockFFmpeg> mock_ffmpeg_; - - AVFrame yuv_frame_; - AVCodecContext codec_context_; - AVCodec codec_; - scoped_refptr<DataBuffer> buffer_; + scoped_refptr<Buffer> i_frame_buffer_; + scoped_refptr<Buffer> corrupt_i_frame_buffer_; + scoped_refptr<Buffer> end_of_stream_buffer_; private: DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecodeEngineTest); @@ -166,150 +149,136 @@ TEST_F(FFmpegVideoDecodeEngineTest, Initialize_Normal) { } TEST_F(FFmpegVideoDecodeEngineTest, Initialize_FindDecoderFails) { + VideoDecoderConfig config(kUnknown, kWidth, kHeight, kSurfaceWidth, + kSurfaceHeight, kFrameRate.num, kFrameRate.den, + NULL, 0); // Test avcodec_find_decoder() returning NULL. - EXPECT_CALL(mock_ffmpeg_, AVCodecAllocContext()) - .WillOnce(Return(&codec_context_)); - EXPECT_CALL(mock_ffmpeg_, AVCodecFindDecoder(CODEC_ID_H264)) - .WillOnce(ReturnNull()); - EXPECT_CALL(mock_ffmpeg_, AVCodecAllocFrame()) - .WillOnce(Return(&yuv_frame_)); - EXPECT_CALL(mock_ffmpeg_, AVCodecClose(&codec_context_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVFree(&yuv_frame_)) - .Times(1); - EXPECT_CALL(mock_ffmpeg_, AVFree(&codec_context_)) - .Times(1); - + VideoCodecInfo info; EXPECT_CALL(*this, OnInitializeComplete(_)) - .WillOnce(SaveInitializeResult(this)); - test_engine_->Initialize(MessageLoop::current(), this, NULL, config_); - EXPECT_FALSE(info_.success); + .WillOnce(SaveArg<0>(&info)); + test_engine_->Initialize(MessageLoop::current(), this, NULL, config); + EXPECT_FALSE(info.success); } TEST_F(FFmpegVideoDecodeEngineTest, Initialize_OpenDecoderFails) { - // Test avcodec_open() failing. - EXPECT_CALL(mock_ffmpeg_, AVCodecAllocContext()) - .WillOnce(Return(&codec_context_)); - EXPECT_CALL(mock_ffmpeg_, AVCodecFindDecoder(CODEC_ID_H264)) - .WillOnce(Return(&codec_)); - EXPECT_CALL(mock_ffmpeg_, AVCodecAllocFrame()) - .WillOnce(Return(&yuv_frame_)); - EXPECT_CALL(mock_ffmpeg_, AVCodecOpen(&codec_context_, &codec_)) - .WillOnce(Return(-1)); - EXPECT_CALL(mock_ffmpeg_, AVCodecClose(&codec_context_)) - .WillOnce(Return(0)); - EXPECT_CALL(mock_ffmpeg_, AVFree(&yuv_frame_)) - .Times(1); - EXPECT_CALL(mock_ffmpeg_, AVFree(&codec_context_)) - .Times(1); - + // Specify Theora w/o extra data so that avcodec_open() fails. + VideoDecoderConfig config(kCodecTheora, kWidth, kHeight, kSurfaceWidth, + kSurfaceHeight, kFrameRate.num, kFrameRate.den, + NULL, 0); + VideoCodecInfo info; EXPECT_CALL(*this, OnInitializeComplete(_)) - .WillOnce(SaveInitializeResult(this)); - test_engine_->Initialize(MessageLoop::current(), this, NULL, config_); - EXPECT_FALSE(info_.success); + .WillOnce(SaveArg<0>(&info)); + test_engine_->Initialize(MessageLoop::current(), this, NULL, config); + EXPECT_FALSE(info.success); } TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_Normal) { Initialize(); - // We rely on FFmpeg for timestamp and duration reporting. The one tricky - // bit is calculating the duration when |repeat_pict| > 0. - const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(123); - const base::TimeDelta kDuration = base::TimeDelta::FromMicroseconds(15000); - yuv_frame_.repeat_pict = 1; - yuv_frame_.reordered_opaque = kTimestamp.InMicroseconds(); + // We rely on FFmpeg for timestamp and duration reporting. + const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(0); + const base::TimeDelta kDuration = base::TimeDelta::FromMicroseconds(10000); // Simulate decoding a single frame. - Decode(); + scoped_refptr<VideoFrame> video_frame; + DecodeASingleFrame(i_frame_buffer_, &video_frame); - // |video_frame_| timestamp is 0 because we set the timestamp based off + // |video_frame| timestamp is 0 because we set the timestamp based off // the buffer timestamp. - EXPECT_EQ(0, video_frame_->GetTimestamp().ToInternalValue()); + ASSERT_TRUE(video_frame); + EXPECT_EQ(0, video_frame->GetTimestamp().ToInternalValue()); EXPECT_EQ(kDuration.ToInternalValue(), - video_frame_->GetDuration().ToInternalValue()); + video_frame->GetDuration().ToInternalValue()); } + +// Verify current behavior for 0 byte frames. FFmpeg simply ignores +// the 0 byte frames. TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_0ByteFrame) { Initialize(); - // Expect a bunch of avcodec calls. - EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_)) - .Times(2); - EXPECT_CALL(mock_ffmpeg_, - AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _)) - .WillOnce(DoAll(SetArgumentPointee<2>(0), // Simulate 0 byte frame. - Return(0))) - .WillOnce(DoAll(SetArgumentPointee<2>(1), // Simulate 1 byte frame. - Return(0))); + scoped_refptr<DataBuffer> zero_byte_buffer = new DataBuffer(1); + + scoped_refptr<VideoFrame> video_frame_a; + scoped_refptr<VideoFrame> video_frame_b; + scoped_refptr<VideoFrame> video_frame_c; EXPECT_CALL(*this, ProduceVideoSample(_)) - .WillOnce(DemuxComplete(test_engine_.get(), buffer_)) - .WillOnce(DemuxComplete(test_engine_.get(), buffer_)); - EXPECT_CALL(*this, ConsumeVideoFrame(_, _)) - .WillOnce(DecodeComplete(this)); - test_engine_->ProduceVideoFrame(video_frame_); + .WillOnce(DemuxComplete(test_engine_.get(), i_frame_buffer_)) + .WillOnce(DemuxComplete(test_engine_.get(), zero_byte_buffer)) + .WillOnce(DemuxComplete(test_engine_.get(), i_frame_buffer_)) + .WillRepeatedly(DemuxComplete(test_engine_.get(), + end_of_stream_buffer_)); - EXPECT_TRUE(video_frame_.get()); + EXPECT_CALL(*this, ConsumeVideoFrame(_, _)) + .WillOnce(SaveArg<0>(&video_frame_a)) + .WillOnce(SaveArg<0>(&video_frame_b)) + .WillOnce(SaveArg<0>(&video_frame_c)); + CallProduceVideoFrame(); + CallProduceVideoFrame(); + CallProduceVideoFrame(); + + EXPECT_TRUE(video_frame_a); + EXPECT_TRUE(video_frame_b); + EXPECT_FALSE(video_frame_c); } + TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_DecodeError) { Initialize(); - // Expect a bunch of avcodec calls. - EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_)); - EXPECT_CALL(mock_ffmpeg_, - AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _)) - .WillOnce(Return(-1)); - EXPECT_CALL(*this, ProduceVideoSample(_)) - .WillOnce(DemuxComplete(test_engine_.get(), buffer_)); + .WillOnce(DemuxComplete(test_engine_.get(), corrupt_i_frame_buffer_)) + .WillRepeatedly(DemuxComplete(test_engine_.get(), i_frame_buffer_)); EXPECT_CALL(*this, OnError()); - test_engine_->ProduceVideoFrame(video_frame_); + CallProduceVideoFrame(); } -TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_LargerWidth) { +// Multi-threaded decoders have different behavior than single-threaded +// decoders at the end of the stream. Multithreaded decoders hide errors +// that happen on the last |codec_context_->thread_count| frames to avoid +// prematurely signalling EOS. This test just exposes that behavior so we can +// detect if it changes. +TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_DecodeErrorAtEndOfStream) { Initialize(); - ChangeDimensions(kWidth * 2, kHeight); - Decode(); -} -TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_SmallerWidth) { - Initialize(); - ChangeDimensions(kWidth / 2, kHeight); - Decode(); -} + EXPECT_CALL(*this, ProduceVideoSample(_)) + .WillOnce(DemuxComplete(test_engine_.get(), corrupt_i_frame_buffer_)) + .WillRepeatedly(DemuxComplete(test_engine_.get(), end_of_stream_buffer_)); -TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_LargerHeight) { - Initialize(); - ChangeDimensions(kWidth, kHeight * 2); - Decode(); + scoped_refptr<VideoFrame> video_frame; + EXPECT_CALL(*this, ConsumeVideoFrame(_, _)) + .WillOnce(SaveArg<0>(&video_frame)); + CallProduceVideoFrame(); + + EXPECT_FALSE(video_frame); } -TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_SmallerHeight) { - Initialize(); - ChangeDimensions(kWidth, kHeight / 2); - Decode(); +// Decode |i_frame_buffer_| and then a frame with a larger width and verify +// the output size didn't change. +// TODO(acolwell): Fix InvalidRead detected by Valgrind +//TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_LargerWidth) { +// DecodeIFrameThenTestFile("vp8-I-frame-640x240"); +//} + +// Decode |i_frame_buffer_| and then a frame with a smaller width and verify +// the output size didn't change. +TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_SmallerWidth) { + DecodeIFrameThenTestFile("vp8-I-frame-160x240"); } -TEST_F(FFmpegVideoDecodeEngineTest, GetSurfaceFormat) { - Initialize(); +// Decode |i_frame_buffer_| and then a frame with a larger height and verify +// the output size didn't change. +// TODO(acolwell): Fix InvalidRead detected by Valgrind +//TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_LargerHeight) { +// DecodeIFrameThenTestFile("vp8-I-frame-320x480"); +//} - // YV12 formats. - codec_context_.pix_fmt = PIX_FMT_YUV420P; - EXPECT_EQ(VideoFrame::YV12, test_engine_->GetSurfaceFormat()); - codec_context_.pix_fmt = PIX_FMT_YUVJ420P; - EXPECT_EQ(VideoFrame::YV12, test_engine_->GetSurfaceFormat()); - - // YV16 formats. - codec_context_.pix_fmt = PIX_FMT_YUV422P; - EXPECT_EQ(VideoFrame::YV16, test_engine_->GetSurfaceFormat()); - codec_context_.pix_fmt = PIX_FMT_YUVJ422P; - EXPECT_EQ(VideoFrame::YV16, test_engine_->GetSurfaceFormat()); - - // Invalid value. - codec_context_.pix_fmt = PIX_FMT_NONE; - EXPECT_EQ(VideoFrame::INVALID, test_engine_->GetSurfaceFormat()); +// Decode |i_frame_buffer_| and then a frame with a smaller height and verify +// the output size didn't change. +TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_SmallerHeight) { + DecodeIFrameThenTestFile("vp8-I-frame-320x120"); } } // namespace media |