summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/base/media.h7
-rw-r--r--media/base/media_posix.cc6
-rw-r--r--media/base/media_win.cc6
-rw-r--r--media/base/mock_ffmpeg.cc224
-rw-r--r--media/base/mock_ffmpeg.h162
-rw-r--r--media/base/run_all_unittests.cc9
-rw-r--r--media/base/test_data_util.cc44
-rw-r--r--media/base/test_data_util.h35
-rw-r--r--media/ffmpeg/ffmpeg_common.cc2
-rw-r--r--media/filters/bitstream_converter.h7
-rw-r--r--media/filters/bitstream_converter_unittest.cc164
-rw-r--r--media/filters/chunk_demuxer_unittest.cc100
-rw-r--r--media/filters/ffmpeg_demuxer_unittest.cc685
-rw-r--r--media/filters/ffmpeg_glue.cc5
-rw-r--r--media/filters/ffmpeg_glue.h11
-rw-r--r--media/filters/ffmpeg_glue_unittest.cc36
-rw-r--r--media/filters/ffmpeg_h264_bitstream_converter_unittest.cc90
-rw-r--r--media/filters/ffmpeg_video_decoder_unittest.cc2
-rw-r--r--media/media.gyp4
-rw-r--r--media/video/ffmpeg_video_decode_engine.cc20
-rw-r--r--media/video/ffmpeg_video_decode_engine.h2
-rw-r--r--media/video/ffmpeg_video_decode_engine_unittest.cc228
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>(&current_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