blob: b1cf33bba18b6f37c9e50231450c83eb658c76dd [file] [log] [blame]
// 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,
const PipelineStatistics& statistics) {
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