summaryrefslogtreecommitdiffstats
path: root/media/video/mft_h264_decode_engine_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/video/mft_h264_decode_engine_unittest.cc')
-rw-r--r--media/video/mft_h264_decode_engine_unittest.cc410
1 files changed, 410 insertions, 0 deletions
diff --git a/media/video/mft_h264_decode_engine_unittest.cc b/media/video/mft_h264_decode_engine_unittest.cc
new file mode 100644
index 0000000..fcf7d69
--- /dev/null
+++ b/media/video/mft_h264_decode_engine_unittest.cc
@@ -0,0 +1,410 @@
+// 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.
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "media/base/data_buffer.h"
+#include "media/base/video_frame.h"
+#include "media/tools/mft_h264_example/file_reader_util.h"
+#include "media/video/mft_h264_decode_engine.h"
+#include "media/video/mft_h264_decode_engine_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+namespace media {
+
+static const int kDecoderMaxWidth = 1920;
+static const int kDecoderMaxHeight = 1088;
+
+// Helper classes
+
+class BaseMftReader : public base::RefCountedThreadSafe<BaseMftReader> {
+ public:
+ virtual ~BaseMftReader() {}
+ virtual void ReadCallback(scoped_refptr<DataBuffer>* input) = 0;
+};
+
+class FakeMftReader : public BaseMftReader {
+ public:
+ FakeMftReader() : frames_remaining_(20) {}
+ explicit FakeMftReader(int count) : frames_remaining_(count) {}
+ virtual ~FakeMftReader() {}
+
+ // Provides garbage input to the decoder.
+ virtual void ReadCallback(scoped_refptr<DataBuffer>* input) {
+ if (frames_remaining_ > 0) {
+ int sz = 4096;
+ uint8* buf = new uint8[sz];
+ memset(buf, 42, sz);
+ *input = new DataBuffer(buf, sz);
+ (*input)->SetDuration(base::TimeDelta::FromMicroseconds(5000));
+ (*input)->SetTimestamp(
+ base::TimeDelta::FromMicroseconds(
+ 50000000 - frames_remaining_ * 10000));
+ --frames_remaining_;
+ } else {
+ // Emulate end of stream on the last "frame".
+ *input = new DataBuffer(0);
+ }
+ }
+ int frames_remaining() const { return frames_remaining_; }
+
+ private:
+ int frames_remaining_;
+};
+
+class SimpleMftH264DecodeEngineHandler
+ : public VideoDecodeEngine::EventHandler {
+ public:
+ SimpleMftH264DecodeEngineHandler()
+ : init_count_(0),
+ uninit_count_(0),
+ flush_count_(0),
+ format_change_count_(0),
+ empty_buffer_callback_count_(0),
+ fill_buffer_callback_count_(0) {
+ memset(&info_, 0, sizeof(info_));
+ }
+ virtual ~SimpleMftH264DecodeEngineHandler() {}
+ virtual void OnInitializeComplete(const VideoCodecInfo& info) {
+ info_ = info;
+ init_count_++;
+ }
+ virtual void OnUninitializeComplete() {
+ uninit_count_++;
+ }
+ virtual void OnFlushComplete() {
+ flush_count_++;
+ }
+ virtual void OnSeekComplete() {}
+ virtual void OnError() {}
+ virtual void OnFormatChange(VideoStreamInfo stream_info) {
+ format_change_count_++;
+ info_.stream_info = stream_info;
+ }
+ virtual void ProduceVideoSample(scoped_refptr<Buffer> buffer) {
+ if (reader_.get() && decoder_) {
+ empty_buffer_callback_count_++;
+ scoped_refptr<DataBuffer> input;
+ reader_->ReadCallback(&input);
+ decoder_->ConsumeVideoSample(input);
+ }
+ }
+ virtual void ConsumeVideoFrame(scoped_refptr<VideoFrame> frame) {
+ fill_buffer_callback_count_++;
+ current_frame_ = frame;
+ }
+ void SetReader(scoped_refptr<BaseMftReader> reader) {
+ reader_ = reader;
+ }
+ void SetDecodeEngine(MftH264DecodeEngine* decoder) {
+ decoder_ = decoder;
+ }
+
+ int init_count_;
+ int uninit_count_;
+ int flush_count_;
+ int format_change_count_;
+ int empty_buffer_callback_count_;
+ int fill_buffer_callback_count_;
+ VideoCodecInfo info_;
+ scoped_refptr<BaseMftReader> reader_;
+ MftH264DecodeEngine* decoder_;
+ scoped_refptr<VideoFrame> current_frame_;
+};
+
+class FFmpegFileReaderWrapper : public BaseMftReader {
+ public:
+ FFmpegFileReaderWrapper() {}
+ virtual ~FFmpegFileReaderWrapper() {}
+ bool InitReader(const std::string& filename) {
+ reader_.reset(new FFmpegFileReader(filename));
+ if (!reader_.get() || !reader_->Initialize()) {
+ reader_.reset();
+ return false;
+ }
+ return true;
+ }
+ virtual void ReadCallback(scoped_refptr<DataBuffer>* input) {
+ if (reader_.get()) {
+ reader_->Read(input);
+ }
+ }
+ bool GetWidth(int* width) {
+ if (!reader_.get())
+ return false;
+ return reader_->GetWidth(width);
+ }
+ bool GetHeight(int* height) {
+ if (!reader_.get())
+ return false;
+ return reader_->GetHeight(height);
+ }
+ scoped_ptr<FFmpegFileReader> reader_;
+};
+
+// Helper functions
+
+static FilePath GetVideoFilePath(const std::string& file_name) {
+ FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("media")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII(file_name.c_str());
+ return path;
+}
+
+class MftH264DecodeEngineTest : public testing::Test {
+ protected:
+ MftH264DecodeEngineTest()
+ : loop_(),
+ window_(NULL),
+ handler_(NULL),
+ engine_(NULL),
+ context_(NULL) {
+ }
+ virtual ~MftH264DecodeEngineTest() {}
+ virtual void SetUp() {
+ handler_.reset(new SimpleMftH264DecodeEngineHandler());
+ }
+ virtual void TearDown() {
+ if (context_.get()) {
+ context_->ReleaseAllVideoFrames();
+ context_->Destroy(NULL);
+ }
+ if (window_)
+ DestroyWindow(window_);
+ }
+ void GetDecodeEngine(bool dxva) {
+ if (dxva) {
+ if (!window_)
+ CreateDrawWindow();
+ context_.reset(new MftH264DecodeEngineContext(window_));
+ ASSERT_TRUE(context_.get());
+ context_->Initialize(NULL);
+ ASSERT_TRUE(context_->initialized());
+ }
+ engine_.reset(new MftH264DecodeEngine(dxva));
+ ASSERT_TRUE(engine_.get());
+ }
+ void InitDecodeEngine(int width, int height) {
+ VideoCodecConfig config;
+ config.width = width;
+ config.height = height;
+
+ // Note that although |config| is passed as reference, |config| is copied
+ // into the decode engine, so it is okay to make |config| a local variable.
+ engine_->Initialize(&loop_, handler_.get(), context_.get(), config);
+ EXPECT_EQ(1, handler_->init_count_);
+ EXPECT_EQ(MftH264DecodeEngine::kNormal, engine_->state());
+ }
+ void InitDecodeEngine() {
+ InitDecodeEngine(800, 600);
+ }
+ void TestInitAndUninit(bool dxva) {
+ GetDecodeEngine(dxva);
+ InitDecodeEngine();
+ engine_->Uninitialize();
+ }
+ void DecodeAll(scoped_refptr<BaseMftReader> reader) {
+ handler_->SetReader(reader);
+ handler_->SetDecodeEngine(engine_.get());
+ while (MftH264DecodeEngine::kStopped != engine_->state()) {
+ scoped_refptr<VideoFrame> frame;
+ engine_->ProduceVideoFrame(frame);
+ }
+ }
+ void DecodeValidVideo(const std::string& filename, int num_frames,
+ bool dxva) {
+ scoped_refptr<FFmpegFileReaderWrapper> reader(
+ new FFmpegFileReaderWrapper());
+ ASSERT_TRUE(reader.get());
+ FilePath path = GetVideoFilePath(filename);
+ ASSERT_TRUE(file_util::PathExists(path));
+ ASSERT_TRUE(reader->InitReader(WideToASCII(path.value())));
+ int actual_width;
+ int actual_height;
+ ASSERT_TRUE(reader->GetWidth(&actual_width));
+ ASSERT_TRUE(reader->GetHeight(&actual_height));
+
+ VideoCodecConfig config;
+ CreateDrawWindow(config.width, config.height);
+ GetDecodeEngine(dxva);
+ InitDecodeEngine();
+ DecodeAll(reader);
+
+ // We expect a format change when decoder receives enough data to determine
+ // the actual frame width/height.
+ EXPECT_GT(handler_->format_change_count_, 0);
+ EXPECT_EQ(actual_width, handler_->info_.stream_info.surface_width);
+ EXPECT_EQ(actual_height, handler_->info_.stream_info.surface_height);
+ EXPECT_GE(handler_->empty_buffer_callback_count_, num_frames);
+ EXPECT_EQ(num_frames, handler_->fill_buffer_callback_count_ - 1);
+ engine_->Uninitialize();
+ }
+ void ExpectDefaultDimensionsOnInput(int width, int height) {
+ GetDecodeEngine(false);
+ InitDecodeEngine(width, height);
+ EXPECT_EQ(kDecoderMaxWidth, handler_->info_.stream_info.surface_width);
+ EXPECT_EQ(kDecoderMaxHeight, handler_->info_.stream_info.surface_height);
+ engine_->Uninitialize();
+ }
+
+ scoped_ptr<SimpleMftH264DecodeEngineHandler> handler_;
+ scoped_ptr<MftH264DecodeEngine> engine_;
+ scoped_ptr<MftH264DecodeEngineContext> context_;
+
+ private:
+ void CreateDrawWindow(int width, int height) {
+ static const wchar_t kClassName[] = L"Test";
+ static const wchar_t kWindowTitle[] = L"MFT Unittest Draw Window";
+ WNDCLASS window_class = {0};
+ window_class.lpszClassName = kClassName;
+ window_class.hInstance = NULL;
+ window_class.hbrBackground = 0;
+ window_class.lpfnWndProc = DefWindowProc;
+ window_class.hCursor = 0;
+ RegisterClass(&window_class);
+ window_ = CreateWindow(kClassName,
+ kWindowTitle,
+ (WS_OVERLAPPEDWINDOW | WS_VISIBLE) &
+ ~(WS_MAXIMIZEBOX | WS_THICKFRAME),
+ 100, 100, width, height,
+ NULL, NULL, NULL, NULL);
+ ASSERT_TRUE(window_);
+ }
+ void CreateDrawWindow() {
+ CreateDrawWindow(800, 600);
+ }
+
+ MessageLoop loop_;
+ HWND window_;
+};
+
+// A simple test case for init/deinit of MF/COM libraries.
+TEST_F(MftH264DecodeEngineTest, LibraryInit) {
+ EXPECT_TRUE(MftH264DecodeEngine::StartupComLibraries());
+ MftH264DecodeEngine::ShutdownComLibraries();
+}
+
+TEST_F(MftH264DecodeEngineTest, DecoderUninitializedAtFirst) {
+ GetDecodeEngine(true);
+ EXPECT_EQ(MftH264DecodeEngine::kUninitialized, engine_->state());
+}
+
+TEST_F(MftH264DecodeEngineTest, DecoderInitMissingArgs) {
+ VideoCodecConfig config;
+ GetDecodeEngine(false);
+ engine_->Initialize(NULL, NULL, NULL, config);
+ EXPECT_EQ(MftH264DecodeEngine::kUninitialized, engine_->state());
+}
+
+TEST_F(MftH264DecodeEngineTest, DecoderInitNoDxva) {
+ TestInitAndUninit(false);
+}
+
+TEST_F(MftH264DecodeEngineTest, DecoderInitDxva) {
+ TestInitAndUninit(true);
+}
+
+TEST_F(MftH264DecodeEngineTest, DecoderUninit) {
+ TestInitAndUninit(false);
+ EXPECT_EQ(1, handler_->uninit_count_);
+ EXPECT_EQ(MftH264DecodeEngine::kUninitialized, engine_->state());
+}
+
+TEST_F(MftH264DecodeEngineTest, UninitBeforeInit) {
+ GetDecodeEngine(false);
+ engine_->Uninitialize();
+ EXPECT_EQ(0, handler_->uninit_count_);
+}
+
+TEST_F(MftH264DecodeEngineTest, InitWithNegativeDimensions) {
+ ExpectDefaultDimensionsOnInput(-123, -456);
+}
+
+TEST_F(MftH264DecodeEngineTest, InitWithTooHighDimensions) {
+ ExpectDefaultDimensionsOnInput(kDecoderMaxWidth + 1, kDecoderMaxHeight + 1);
+}
+
+TEST_F(MftH264DecodeEngineTest, DrainOnEmptyBuffer) {
+ GetDecodeEngine(false);
+ InitDecodeEngine();
+
+ // Decoder should switch to drain mode because of this NULL buffer, and then
+ // switch to kStopped when it says it needs more input during drain mode.
+ scoped_refptr<Buffer> buffer(new DataBuffer(0));
+ engine_->ConsumeVideoSample(buffer);
+ EXPECT_EQ(MftH264DecodeEngine::kStopped, engine_->state());
+
+ // Should have called back with one empty frame.
+ EXPECT_EQ(1, handler_->fill_buffer_callback_count_);
+ ASSERT_TRUE(handler_->current_frame_.get());
+ EXPECT_EQ(VideoFrame::EMPTY, handler_->current_frame_->format());
+ engine_->Uninitialize();
+}
+
+TEST_F(MftH264DecodeEngineTest, NoOutputOnGarbageInput) {
+ // 100 samples of garbage.
+ const int kNumFrames = 100;
+ scoped_refptr<FakeMftReader> reader(new FakeMftReader(kNumFrames));
+ ASSERT_TRUE(reader.get());
+
+ GetDecodeEngine(false);
+ InitDecodeEngine();
+ DecodeAll(reader);
+
+ // Output callback should only be invoked once - the empty frame to indicate
+ // end of stream.
+ EXPECT_EQ(1, handler_->fill_buffer_callback_count_);
+ ASSERT_TRUE(handler_->current_frame_.get());
+ EXPECT_EQ(VideoFrame::EMPTY, handler_->current_frame_->format());
+
+ // One extra count because of the end of stream NULL sample.
+ EXPECT_EQ(kNumFrames, handler_->empty_buffer_callback_count_ - 1);
+ engine_->Uninitialize();
+}
+
+TEST_F(MftH264DecodeEngineTest, FlushAtStart) {
+ GetDecodeEngine(false);
+ InitDecodeEngine();
+ engine_->Flush();
+
+ // Flush should succeed even if input/output are empty.
+ EXPECT_EQ(1, handler_->flush_count_);
+ engine_->Uninitialize();
+}
+
+TEST_F(MftH264DecodeEngineTest, NoFlushAtStopped) {
+ scoped_refptr<BaseMftReader> reader(new FakeMftReader());
+ ASSERT_TRUE(reader.get());
+
+ GetDecodeEngine(false);
+ InitDecodeEngine();
+ DecodeAll(reader);
+
+ EXPECT_EQ(0, handler_->flush_count_);
+ int old_flush_count = handler_->flush_count_;
+ engine_->Flush();
+ EXPECT_EQ(old_flush_count, handler_->flush_count_);
+ engine_->Uninitialize();
+}
+
+TEST_F(MftH264DecodeEngineTest, DecodeValidVideoDxva) {
+ DecodeValidVideo("bear.1280x720.mp4", 82, true);
+}
+
+TEST_F(MftH264DecodeEngineTest, DecodeValidVideoNoDxva) {
+ DecodeValidVideo("bear.1280x720.mp4", 82, false);
+}
+
+} // namespace media