diff options
22 files changed, 1358 insertions, 491 deletions
diff --git a/media/base/media.h b/media/base/media.h index bd25cbb..6bbad4c 100644 --- a/media/base/media.h +++ b/media/base/media.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -25,11 +25,6 @@ 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 7be3255..adad88b 100644 --- a/media/base/media_posix.cc +++ b/media/base/media_posix.cc @@ -92,12 +92,6 @@ 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 9462c02..a58d977 100644 --- a/media/base/media_win.cc +++ b/media/base/media_win.cc @@ -78,12 +78,6 @@ 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 new file mode 100644 index 0000000..ebbfbf0 --- /dev/null +++ b/media/base/mock_ffmpeg.cc @@ -0,0 +1,224 @@ +// 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 new file mode 100644 index 0000000..df5e69b4 --- /dev/null +++ b/media/base/mock_ffmpeg.h @@ -0,0 +1,162 @@ +// 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 a24a278..b715a328b 100644 --- a/media/base/run_all_unittests.cc +++ b/media/base/run_all_unittests.cc @@ -1,14 +1,9 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2006-2008 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) { - base::TestSuite suite(argc, argv); - - media::InitializeMediaLibraryForTesting(); - - return suite.Run(); + return base::TestSuite(argc, argv).Run(); } diff --git a/media/base/test_data_util.cc b/media/base/test_data_util.cc deleted file mode 100644 index fc30744..0000000 --- a/media/base/test_data_util.cc +++ /dev/null @@ -1,44 +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/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 deleted file mode 100644 index 42878ae..0000000 --- a/media/base/test_data_util.h +++ /dev/null @@ -1,35 +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_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/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc index d03715e..0be30ed 100644 --- a/media/ffmpeg/ffmpeg_common.cc +++ b/media/ffmpeg/ffmpeg_common.cc @@ -43,8 +43,6 @@ 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 7386297..a1ca3c6 100644 --- a/media/filters/bitstream_converter.h +++ b/media/filters/bitstream_converter.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -69,6 +69,11 @@ 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 d7afd37..1e84341 100644 --- a/media/filters/bitstream_converter_unittest.cc +++ b/media/filters/bitstream_converter_unittest.cc @@ -4,92 +4,73 @@ #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" -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) {} +using ::testing::DoAll; +using ::testing::Mock; +using ::testing::Return; +using ::testing::ReturnNull; +using ::testing::SetArgumentPointee; +using ::testing::StrEq; +using ::testing::StrictMock; +using ::testing::_; -static AVBitStreamFilter g_stream_filter = { - kTestFilterName, - 0, // Private Data Size - DoFilter, - DoClose, - 0, // Next filter pointer. -}; +namespace media { 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 = kFailData; - test_packet_.size = kFailSize; + test_packet_.data = kData1; + test_packet_.size = kTestSize1; } 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); }; -TEST_F(BitstreamConverterTest, InitializeFailed) { - FFmpegBitstreamConverter converter("BAD_FILTER_NAME", &test_stream_context_); +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, 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()); -} -TEST_F(BitstreamConverterTest, InitializeSuccess) { - FFmpegBitstreamConverter converter(kTestFilterName, &test_stream_context_); + 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_)); EXPECT_TRUE(converter.Initialize()); } @@ -102,42 +83,81 @@ TEST_F(BitstreamConverterTest, ConvertPacket_NotInitialized) { TEST_F(BitstreamConverterTest, ConvertPacket_FailedFilter) { FFmpegBitstreamConverter converter(kTestFilterName, &test_stream_context_); - EXPECT_TRUE(converter.Initialize()); + // 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_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_); - EXPECT_TRUE(converter.Initialize()); + // Inject mock filter instance. + converter.stream_filter_ = &test_filter_; // Ensure our packet doesn't already have a destructor. ASSERT_TRUE(test_packet_.destruct == NULL); - test_packet_.data = kNewBufferData; - test_packet_.size = kNewBufferSize; + // 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_)); EXPECT_TRUE(converter.ConvertPacket(&test_packet_)); - EXPECT_NE(kNewBufferData, test_packet_.data); - EXPECT_EQ(kNewBufferSize + 1, test_packet_.size); + EXPECT_EQ(kData2, test_packet_.data); + EXPECT_EQ(kTestSize2, 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_); - EXPECT_TRUE(converter.Initialize()); + // Inject mock filter instance. + converter.stream_filter_ = &test_filter_; // Ensure our packet is in a sane start state. ASSERT_TRUE(test_packet_.destruct == NULL); - test_packet_.data = kInPlaceData; - test_packet_.size = kInPlaceSize; + 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))); EXPECT_TRUE(converter.ConvertPacket(&test_packet_)); - EXPECT_EQ(kInPlaceData, test_packet_.data); - EXPECT_EQ(kInPlaceSize, test_packet_.size); + EXPECT_EQ(kData1, test_packet_.data); + EXPECT_EQ(kTestSize1, 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 e6b3aba..b26b757 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -2,10 +2,14 @@ // 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" @@ -54,12 +58,47 @@ 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; @@ -69,11 +108,11 @@ class ChunkDemuxerTest : public testing::Test{ scoped_array<uint8> video_track_entry; int video_track_entry_size = 0; - 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); + 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); int tracks_element_size = 0; @@ -120,10 +159,24 @@ 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); } @@ -158,6 +211,12 @@ 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) { @@ -167,12 +226,41 @@ 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 920b877..1113e9b 100644 --- a/media/filters/ffmpeg_demuxer_unittest.cc +++ b/media/filters/ffmpeg_demuxer_unittest.cc @@ -4,17 +4,15 @@ #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; @@ -23,8 +21,7 @@ using ::testing::InSequence; using ::testing::Invoke; using ::testing::NotNull; using ::testing::Return; -using ::testing::SaveArg; -using ::testing::SetArgPointee; +using ::testing::SetArgumentPointee; using ::testing::StrictMock; using ::testing::WithArgs; using ::testing::_; @@ -35,6 +32,36 @@ 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. @@ -43,11 +70,45 @@ class FFmpegDemuxerTest : public testing::Test { // Inject a filter host and message loop and prepare a data source. demuxer_->set_host(&host_); - - EXPECT_CALL(host_, SetTotalBytes(_)).Times(AnyNumber()); - EXPECT_CALL(host_, SetBufferedBytes(_)).Times(AnyNumber()); - EXPECT_CALL(host_, SetCurrentReadPosition(_)) - .WillRepeatedly(SaveArg<0>(¤t_read_position_)); + 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; + } } virtual ~FFmpegDemuxerTest() { @@ -58,117 +119,160 @@ class FFmpegDemuxerTest : public testing::Test { message_loop_.RunAllPending(); // Release the reference to the demuxer. demuxer_ = NULL; - } - - scoped_refptr<DataSource> CreateDataSource(const std::string& name) { - 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); - scoped_refptr<FileDataSource> data_source = new FileDataSource(); - - EXPECT_EQ(PIPELINE_OK, data_source->Initialize(file_path.MaybeAsASCII())); + if (format_context_.streams) { + delete[] format_context_.streams; + format_context_.streams = NULL; + format_context_.nb_streams = 0; + } + } - return data_source.get(); + // 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_)); } - MOCK_METHOD1(CheckPoint, void(int v)); + // Initializes both MockFFmpeg and FFmpegDemuxer. + void InitializeDemuxer() { + InitializeDemuxerMocks(); - // Initializes FFmpegDemuxer. - void InitializeDemuxer(const scoped_refptr<DataSource>& data_source) { - EXPECT_CALL(host_, SetDuration(_)); - demuxer_->Initialize(data_source, NewExpectedStatusCB(PIPELINE_OK)); - message_loop_.RunAllPending(); - } + // 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)); - // 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()); + demuxer_->Initialize(data_source_.get(), + NewExpectedStatusCB(PIPELINE_OK)); + message_loop_.RunAllPending(); } // Fixture members. scoped_refptr<FFmpegDemuxer> demuxer_; + scoped_refptr<StrictMock<MockDataSource> > data_source_; StrictMock<MockFilterHost> host_; MessageLoop message_loop_; - int64 current_read_position_; + // FFmpeg fixtures. + AVFormatContext format_context_; + AVInputFormat input_format_; + AVCodecContext codecs_[AV_STREAM_MAX]; + AVStream streams_[AV_STREAM_MAX]; + MockFFmpeg mock_ffmpeg_; private: DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerTest); }; +// These durations are picked so that the demuxer chooses the longest supported +// stream, which would be 30 in this case for the audio stream. +const int FFmpegDemuxerTest::kDurations[AV_STREAM_MAX] = {100, 20, 30}; +const int FFmpegDemuxerTest::kChannels = 2; +const int FFmpegDemuxerTest::kSampleRate = 44100; +const int FFmpegDemuxerTest::kWidth = 1280; +const int FFmpegDemuxerTest::kHeight = 720; + +const size_t FFmpegDemuxerTest::kDataSize = 4; +const uint8 FFmpegDemuxerTest::kAudioData[kDataSize] = {0, 1, 2, 3}; +const uint8 FFmpegDemuxerTest::kVideoData[kDataSize] = {4, 5, 6, 7}; +const uint8* FFmpegDemuxerTest::kNullData = NULL; + TEST_F(FFmpegDemuxerTest, Initialize_OpenFails) { // Simulate av_open_input_file() failing. - EXPECT_CALL(host_, SetCurrentReadPosition(_)); - demuxer_->Initialize(CreateDataSource("ten_byte_file"), - NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN)); + EXPECT_CALL(mock_ffmpeg_, AVOpenInputFile(_, _, NULL, 0, NULL)) + .WillOnce(Return(-1)); + demuxer_->Initialize(data_source_.get(), + NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN)); message_loop_.RunAllPending(); } -// 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_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) { - // Open a file with no parseable streams. - EXPECT_CALL(host_, SetCurrentReadPosition(_)); + // Simulate media with no parseable streams. + { + SCOPED_TRACE(""); + InitializeDemuxerMocks(); + } + format_context_.nb_streams = 0; + demuxer_->Initialize( - CreateDataSource("no_streams.webm"), + 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_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(); +} TEST_F(FFmpegDemuxerTest, Initialize_Successful) { - InitializeDemuxer(CreateDataSource("bear-320x240.webm")); + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } // 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()); - ASSERT_TRUE(stream->GetAVStream()); + EXPECT_EQ(&streams_[AV_STREAM_VIDEO], 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()); - ASSERT_TRUE(stream->GetAVStream()); + EXPECT_EQ(&streams_[AV_STREAM_AUDIO], stream->GetAVStream()); } -TEST_F(FFmpegDemuxerTest, Read_Audio) { - // We test that on a successful audio packet read. - InitializeDemuxer(CreateDataSource("bear-320x240.webm")); +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))); // Attempt a read from the audio stream and run the message loop until done. scoped_refptr<DemuxerStream> audio = @@ -176,107 +280,231 @@ TEST_F(FFmpegDemuxerTest, Read_Audio) { scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); reader->Read(audio); message_loop_.RunAllPending(); + EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 29, 0); + ASSERT_TRUE(reader->buffer()); + EXPECT_TRUE(reader->buffer()->IsEndOfStream()); +} - reader->Reset(); +TEST_F(FFmpegDemuxerTest, Read_Audio) { + // We test that on a successful audio packet read, that the packet is + // duplicated (FFmpeg memory management safety), and a copy of it ends up in + // the DemuxerStream. + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } + + // Ignore all AVFreePacket() calls. We check this via valgrind. + EXPECT_CALL(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->Read(audio); message_loop_.RunAllPending(); + EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 27, 3000); + ASSERT_TRUE(reader->buffer()); + ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); + EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(), + reader->buffer()->GetDataSize())); } TEST_F(FFmpegDemuxerTest, Read_Video) { - // We test that on a successful video packet read. - InitializeDemuxer(CreateDataSource("bear-320x240.webm")); + // 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)); // 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()); - ValidateBuffer(FROM_HERE, reader->buffer(), 22084, 0); - reader->Reset(); - reader->Read(video); - message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 1057, 33000); + 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. - InitializeDemuxer(CreateDataSource("nonzero-start-time.webm")); + 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(396000); + 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)); + + // 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(kExpectedTimestamp, reader->buffer()->GetTimestamp()); + EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), + reader->buffer()->GetDataSize())); + + EXPECT_EQ(kExpectedTimestamp, demuxer_->GetStartTime()); +} + +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; + + 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)); // 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); - scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); + ASSERT_TRUE(video); + ASSERT_TRUE(audio); - // Check first buffer in video stream. + scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); reader->Read(video); message_loop_.RunAllPending(); + EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 5636, 400000); - const base::TimeDelta video_timestamp = reader->buffer()->GetTimestamp(); + 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()); - // Check first buffer in audio stream. reader->Reset(); reader->Read(audio); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 165, 396000); - const base::TimeDelta audio_timestamp = reader->buffer()->GetTimestamp(); + 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())); - // Verify that the start time is equal to the lowest timestamp. - EXPECT_EQ(std::min(audio_timestamp, video_timestamp), - demuxer_->GetStartTime()); + EXPECT_EQ(kExpectedAudioTimestamp, demuxer_->GetStartTime()); } TEST_F(FFmpegDemuxerTest, Read_EndOfStream) { - // Verify that end of stream buffers are created. - InitializeDemuxer(CreateDataSource("bear-320x240.webm")); + // 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))); // We should now expect an end of stream buffer. scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); scoped_refptr<DemuxerStreamReader> reader(new DemuxerStreamReader()); - - 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); + reader->Read(audio); + message_loop_.RunAllPending(); + EXPECT_TRUE(reader->called()); + ASSERT_TRUE(reader->buffer()); + EXPECT_TRUE(reader->buffer()->IsEndOfStream()); + EXPECT_TRUE(reader->buffer()->GetData() == NULL); + EXPECT_EQ(0u, reader->buffer()->GetDataSize()); } TEST_F(FFmpegDemuxerTest, Seek) { // We're testing that the demuxer frees all queued packets when it receives // a Seek(). - InitializeDemuxer(CreateDataSource("bear-320x240.webm")); + // + // Since we can't test which packets are being freed, we use check points to + // infer that the correct packets have been freed. + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } // Get our streams. scoped_refptr<DemuxerStream> video = @@ -286,52 +514,129 @@ 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()); - ValidateBuffer(FROM_HERE, reader->buffer(), 22084, 0); + ASSERT_TRUE(reader->buffer()); + ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); + EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), + reader->buffer()->GetDataSize())); // Release the video packet and verify the other packets are still queued. reader->Reset(); message_loop_.RunAllPending(); + mock_ffmpeg_.CheckPoint(1); // Issue a simple forward seek, which should discard queued packets. - demuxer_->Seek(base::TimeDelta::FromMicroseconds(1000000), - NewExpectedStatusCB(PIPELINE_OK)); + demuxer_->Seek(base::TimeDelta::FromMicroseconds(kExpectedTimestamp), + seek_cb); message_loop_.RunAllPending(); + mock_ffmpeg_.CheckPoint(2); // Audio read #1. reader->Read(audio); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 145, 803000); + ASSERT_TRUE(reader->buffer()); + ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); + EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(), + reader->buffer()->GetDataSize())); // Audio read #2. reader->Reset(); reader->Read(audio); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 148, 826000); + ASSERT_TRUE(reader->buffer()); + ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); + EXPECT_EQ(0, memcmp(kAudioData, reader->buffer()->GetData(), + reader->buffer()->GetDataSize())); // Video read #1. reader->Reset(); reader->Read(video); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 5425, 801000); + ASSERT_TRUE(reader->buffer()); + ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); + EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), + reader->buffer()->GetDataSize())); // Video read #2. reader->Reset(); reader->Read(video); message_loop_.RunAllPending(); EXPECT_TRUE(reader->called()); - ValidateBuffer(FROM_HERE, reader->buffer(), 1906, 834000); + ASSERT_TRUE(reader->buffer()); + ASSERT_EQ(kDataSize, reader->buffer()->GetDataSize()); + EXPECT_EQ(0, memcmp(kVideoData, reader->buffer()->GetData(), + reader->buffer()->GetDataSize())); // Manually release the last reference to the buffer and verify it was freed. reader->Reset(); message_loop_.RunAllPending(); + mock_ffmpeg_.CheckPoint(3); } // A mocked callback specialization for calling Read(). Since RunWithParams() @@ -354,13 +659,21 @@ class MockReadCallback : public base::RefCountedThreadSafe<MockReadCallback> { TEST_F(FFmpegDemuxerTest, Stop) { // Tests that calling Read() on a stopped demuxer immediately deletes the // callback. - InitializeDemuxer(CreateDataSource("bear-320x240.webm")); + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } // 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. @@ -373,7 +686,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(*this, CheckPoint(1)); + EXPECT_CALL(mock_ffmpeg_, CheckPoint(1)); // Attempt the read... audio->Read(base::Bind(&MockReadCallback::Run, callback)); @@ -381,7 +694,7 @@ TEST_F(FFmpegDemuxerTest, Stop) { message_loop_.RunAllPending(); // ...and verify that |callback| was deleted. - CheckPoint(1); + mock_ffmpeg_.CheckPoint(1); } TEST_F(FFmpegDemuxerTest, DisableAudioStream) { @@ -389,32 +702,38 @@ 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. - InitializeDemuxer(CreateDataSource("bear-320x240.webm")); + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } // Submit a "disable audio stream" message to the demuxer. demuxer_->OnAudioRendererDisabled(); message_loop_.RunAllPending(); + // Ignore all AVFreePacket() calls. We check this via valgrind. + EXPECT_CALL(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 { @@ -439,25 +758,19 @@ 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(SetArgPointee<0>(1024), Return(true))); - EXPECT_CALL(*data_source, Read(0, 512, kBuffer, NotNull())) + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source_, Read(0, 512, kBuffer, NotNull())) .WillOnce(WithArgs<1, 3>(Invoke(&RunCallback))); EXPECT_CALL(*demuxer, SignalReadCompleted(512)); EXPECT_CALL(*demuxer, WaitForRead()) @@ -465,9 +778,9 @@ TEST_F(FFmpegDemuxerTest, ProtocolRead) { EXPECT_CALL(host_, SetCurrentReadPosition(512)); // Second read. - EXPECT_CALL(*data_source, GetSize(_)) - .WillOnce(DoAll(SetArgPointee<0>(1024), Return(true))); - EXPECT_CALL(*data_source, Read(512, 512, kBuffer, NotNull())) + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source_, Read(512, 512, kBuffer, NotNull())) .WillOnce(WithArgs<1, 3>(Invoke(&RunCallback))); EXPECT_CALL(*demuxer, SignalReadCompleted(512)); EXPECT_CALL(*demuxer, WaitForRead()) @@ -475,8 +788,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(SetArgPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); // First read. EXPECT_EQ(512, demuxer->Read(512, kBuffer)); @@ -499,42 +812,52 @@ TEST_F(FFmpegDemuxerTest, ProtocolRead) { } TEST_F(FFmpegDemuxerTest, ProtocolGetSetPosition) { - scoped_refptr<DataSource> data_source = CreateDataSource("bear-320x240.webm"); - InitializeDemuxer(data_source); + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } InSequence s; - int64 size; + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + int64 position; - EXPECT_TRUE(demuxer_->GetSize(&size)); EXPECT_TRUE(demuxer_->GetPosition(&position)); - EXPECT_EQ(current_read_position_, position); + EXPECT_EQ(0, position); EXPECT_TRUE(demuxer_->SetPosition(512)); - EXPECT_FALSE(demuxer_->SetPosition(size)); - EXPECT_FALSE(demuxer_->SetPosition(size + 1)); + EXPECT_FALSE(demuxer_->SetPosition(2048)); EXPECT_FALSE(demuxer_->SetPosition(-1)); EXPECT_TRUE(demuxer_->GetPosition(&position)); EXPECT_EQ(512, position); } TEST_F(FFmpegDemuxerTest, ProtocolGetSize) { - 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); + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } + + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + + int64 size; + EXPECT_TRUE(demuxer_->GetSize(&size)); + EXPECT_EQ(1024, size); } TEST_F(FFmpegDemuxerTest, ProtocolIsStreaming) { - scoped_refptr<DataSource> data_source = CreateDataSource("bear-320x240.webm"); - InitializeDemuxer(data_source); - - EXPECT_FALSE(data_source->IsStreaming()); + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } + EXPECT_CALL(*data_source_, IsStreaming()) + .WillOnce(Return(false)); EXPECT_FALSE(demuxer_->IsStreaming()); } diff --git a/media/filters/ffmpeg_glue.cc b/media/filters/ffmpeg_glue.cc index bd55901..7f134e3 100644 --- a/media/filters/ffmpeg_glue.cc +++ b/media/filters/ffmpeg_glue.cc @@ -146,11 +146,6 @@ 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 0e91fd3..a98c502 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,9 +99,6 @@ 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 0ea91f1..158ddbb 100644 --- a/media/filters/ffmpeg_glue_unittest.cc +++ b/media/filters/ffmpeg_glue_unittest.cc @@ -4,6 +4,7 @@ #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" @@ -35,21 +36,17 @@ class MockProtocol : public FFmpegURLProtocol { class FFmpegGlueTest : public ::testing::Test { public: - FFmpegGlueTest() : protocol_(NULL) {} + FFmpegGlueTest() {} - static void SetUpTestCase() { + virtual void SetUp() { // Singleton should initialize FFmpeg. CHECK(FFmpegGlue::GetInstance()); - } - virtual void SetUp() { // Assign our static copy of URLProtocol for the rest of the tests. - protocol_ = FFmpegGlue::url_protocol(); + protocol_ = MockFFmpeg::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) { @@ -65,12 +62,15 @@ class FFmpegGlueTest : public ::testing::Test { protected: // Fixture members. - URLProtocol* protocol_; + MockFFmpeg mock_ffmpeg_; + static 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(*this, CheckPoint(0)); + EXPECT_CALL(mock_ffmpeg_, 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. - CheckPoint(0); + mock_ffmpeg_.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(*this, CheckPoint(0)); - EXPECT_CALL(*this, CheckPoint(1)); + EXPECT_CALL(mock_ffmpeg_, CheckPoint(0)); + EXPECT_CALL(mock_ffmpeg_, CheckPoint(1)); EXPECT_CALL(*protocol, OnDestroy()); - EXPECT_CALL(*this, CheckPoint(2)); + EXPECT_CALL(mock_ffmpeg_, CheckPoint(2)); // Remove the protocol from the glue layer, releasing a reference. glue->RemoveProtocol(protocol.get()); - CheckPoint(0); + mock_ffmpeg_.CheckPoint(0); // Remove our own reference -- URLContext should maintain a reference. - CheckPoint(1); + mock_ffmpeg_.CheckPoint(1); protocol.reset(); // Close the URLContext, which should release the final reference. EXPECT_EQ(0, protocol_->url_close(&context)); - CheckPoint(2); + mock_ffmpeg_.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(*this, CheckPoint(0)); + EXPECT_CALL(mock_ffmpeg_, CheckPoint(0)); EXPECT_CALL(*protocol, OnDestroy()); // Remove our own reference, we shouldn't be destroyed yet. - CheckPoint(0); + mock_ffmpeg_.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 e3d3766..5799123 100644 --- a/media/filters/ffmpeg_h264_bitstream_converter_unittest.cc +++ b/media/filters/ffmpeg_h264_bitstream_converter_unittest.cc @@ -2,13 +2,22 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "media/base/media.h" -#include "media/ffmpeg/ffmpeg_common.h" +#include "media/base/mock_ffmpeg.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, @@ -259,6 +268,14 @@ 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. @@ -271,10 +288,16 @@ 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_; @@ -294,9 +317,13 @@ 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. @@ -317,9 +344,38 @@ 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. @@ -348,9 +404,39 @@ 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 e3dd90d..7614d8b 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -10,6 +10,7 @@ #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" @@ -219,6 +220,7 @@ 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 600df2a..d93ad41 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -417,6 +417,8 @@ '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', @@ -426,8 +428,6 @@ '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 0958516..4af73a1 100644 --- a/media/video/ffmpeg_video_decode_engine.cc +++ b/media/video/ffmpeg_video_decode_engine.cc @@ -120,7 +120,6 @@ 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 && @@ -187,6 +186,7 @@ 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,6 +303,24 @@ 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 b7d7661..32eac45 100644 --- a/media/video/ffmpeg_video_decode_engine.h +++ b/media/video/ffmpeg_video_decode_engine.h @@ -33,6 +33,8 @@ 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 665fb86..261ca45 100644 --- a/media/video/ffmpeg_video_decode_engine_unittest.cc +++ b/media/video/ffmpeg_video_decode_engine_unittest.cc @@ -5,10 +5,9 @@ #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" @@ -22,12 +21,21 @@ using ::testing::StrictMock; namespace media { -static const size_t kWidth = 320; -static const size_t kHeight = 240; +static const int kWidth = 320; +static const int 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); } @@ -45,12 +53,18 @@ class FFmpegVideoDecodeEngineTest public VideoDecodeEngine::EventHandler { public: FFmpegVideoDecodeEngineTest() - : config_(kCodecVP8, kWidth, kHeight, kSurfaceWidth, kSurfaceHeight, + : config_(kCodecH264, 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()); @@ -59,8 +73,6 @@ class FFmpegVideoDecodeEngineTest kHeight, kNoTimestamp, kNoTimestamp); - - ReadTestDataFile("vp8-I-frame-320x240", &i_frame_buffer_); } ~FFmpegVideoDecodeEngineTest() { @@ -68,20 +80,48 @@ 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); + EXPECT_CALL(*this, OnInitializeComplete(_)) .WillOnce(SaveInitializeResult(this)); test_engine_->Initialize(MessageLoop::current(), this, NULL, config_); EXPECT_TRUE(info_.success); } - void Decode(const scoped_refptr<Buffer>& buffer) { + 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))); + 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_); } + 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; + } + // VideoDecodeEngine::EventHandler implementation. MOCK_METHOD2(ConsumeVideoFrame, void(scoped_refptr<VideoFrame> video_frame, @@ -110,7 +150,12 @@ class FFmpegVideoDecodeEngineTest scoped_refptr<VideoFrame> video_frame_; scoped_ptr<FFmpegVideoDecodeEngine> test_engine_; scoped_array<uint8_t> frame_buffer_; - scoped_refptr<Buffer> i_frame_buffer_; + StrictMock<MockFFmpeg> mock_ffmpeg_; + + AVFrame yuv_frame_; + AVCodecContext codec_context_; + AVCodec codec_; + scoped_refptr<DataBuffer> buffer_; private: DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecodeEngineTest); @@ -121,36 +166,61 @@ 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); + EXPECT_CALL(*this, OnInitializeComplete(_)) .WillOnce(SaveInitializeResult(this)); - test_engine_->Initialize(MessageLoop::current(), this, NULL, config); + test_engine_->Initialize(MessageLoop::current(), this, NULL, config_); EXPECT_FALSE(info_.success); } TEST_F(FFmpegVideoDecodeEngineTest, Initialize_OpenDecoderFails) { - // Specify Theora w/o extra data so that avcodec_open() fails. - VideoDecoderConfig config(kCodecTheora, kWidth, kHeight, kSurfaceWidth, - kSurfaceHeight, kFrameRate.num, kFrameRate.den, - NULL, 0); + // 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); + EXPECT_CALL(*this, OnInitializeComplete(_)) .WillOnce(SaveInitializeResult(this)); - test_engine_->Initialize(MessageLoop::current(), this, NULL, config); + 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. - const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(0); - const base::TimeDelta kDuration = base::TimeDelta::FromMicroseconds(10000); + // 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(); // Simulate decoding a single frame. - Decode(i_frame_buffer_); + Decode(); // |video_frame_| timestamp is 0 because we set the timestamp based off // the buffer timestamp. @@ -162,11 +232,19 @@ TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_Normal) { TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_0ByteFrame) { Initialize(); - scoped_refptr<Buffer> buffer_a = new DataBuffer(1); + // 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))); EXPECT_CALL(*this, ProduceVideoSample(_)) - .WillOnce(DemuxComplete(test_engine_.get(), buffer_a)) - .WillOnce(DemuxComplete(test_engine_.get(), i_frame_buffer_)); + .WillOnce(DemuxComplete(test_engine_.get(), buffer_)) + .WillOnce(DemuxComplete(test_engine_.get(), buffer_)); EXPECT_CALL(*this, ConsumeVideoFrame(_, _)) .WillOnce(DecodeComplete(this)); test_engine_->ProduceVideoFrame(video_frame_); @@ -177,19 +255,14 @@ TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_0ByteFrame) { TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_DecodeError) { Initialize(); - scoped_refptr<DataBuffer> buffer = - new DataBuffer(i_frame_buffer_->GetDataSize()); - buffer->SetDataSize(i_frame_buffer_->GetDataSize()); - - uint8* buf = buffer->GetWritableData(); - memcpy(buf, i_frame_buffer_->GetData(), buffer->GetDataSize()); - - // Corrupt bytes by flipping bits w/ xor. - for (size_t i = 0; i < buffer->GetDataSize(); i++) - buf[i] ^= 0xA5; + // 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(), buffer_)); EXPECT_CALL(*this, OnError()); test_engine_->ProduceVideoFrame(video_frame_); @@ -197,71 +270,46 @@ TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_DecodeError) { TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_LargerWidth) { Initialize(); - - // Decode a frame and verify the width. - Decode(i_frame_buffer_); - EXPECT_EQ(video_frame_->width(), kWidth); - EXPECT_EQ(video_frame_->height(), kHeight); - - // Now decode a frame with a larger width and verify the output size didn't - // change. - scoped_refptr<Buffer> buffer; - ReadTestDataFile("vp8-I-frame-640x240", &buffer); - Decode(buffer); - - EXPECT_EQ(kWidth, video_frame_->width()); - EXPECT_EQ(kHeight, video_frame_->height()); + ChangeDimensions(kWidth * 2, kHeight); + Decode(); } TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_SmallerWidth) { Initialize(); - - // Decode a frame and verify the width. - Decode(i_frame_buffer_); - EXPECT_EQ(video_frame_->width(), kWidth); - EXPECT_EQ(video_frame_->height(), kHeight); - - // Now decode a frame with a smaller width and verify the output size didn't - // change. - scoped_refptr<Buffer> buffer; - ReadTestDataFile("vp8-I-frame-160x240", &buffer); - Decode(buffer); - EXPECT_EQ(video_frame_->width(), kWidth); - EXPECT_EQ(video_frame_->height(), kHeight); + ChangeDimensions(kWidth / 2, kHeight); + Decode(); } TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_LargerHeight) { Initialize(); - - // Decode a frame and verify the width. - Decode(i_frame_buffer_); - EXPECT_EQ(video_frame_->width(), kWidth); - EXPECT_EQ(video_frame_->height(), kHeight); - - // Now decode a frame with a larger height and verify the output - // size didn't change. - scoped_refptr<Buffer> buffer; - ReadTestDataFile("vp8-I-frame-320x480", &buffer); - Decode(buffer); - EXPECT_EQ(kWidth, video_frame_->width()); - EXPECT_EQ(kHeight, video_frame_->height()); + ChangeDimensions(kWidth, kHeight * 2); + Decode(); } TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_SmallerHeight) { Initialize(); + ChangeDimensions(kWidth, kHeight / 2); + Decode(); +} + +TEST_F(FFmpegVideoDecodeEngineTest, GetSurfaceFormat) { + Initialize(); - // Decode a frame and verify the width. - Decode(i_frame_buffer_); - EXPECT_EQ(video_frame_->width(), kWidth); - EXPECT_EQ(video_frame_->height(), kHeight); - - // Now decode a frame with a smaller height and verify the output size - // didn't change. - scoped_refptr<Buffer> buffer; - ReadTestDataFile("vp8-I-frame-320x120", &buffer); - Decode(buffer); - EXPECT_EQ(kWidth, video_frame_->width()); - EXPECT_EQ(kHeight, video_frame_->height()); + // 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()); } } // namespace media |