diff options
Diffstat (limited to 'media/video/mft_h264_decode_engine_unittest.cc')
-rw-r--r-- | media/video/mft_h264_decode_engine_unittest.cc | 410 |
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 |