From 71e43a27d4c6018a9502753566d228cbf2dc2ca5 Mon Sep 17 00:00:00 2001 From: "hclam@chromium.org" Date: Fri, 8 Oct 2010 04:52:15 +0000 Subject: Move MFT H264 video decoder implementation and connect it to GpuVideoDecoder Remove media/mf folder and move the video decode engine to media/video. There are some temporary changes in MftH264DecodeEngine to work with GpuVideoDecoder correctly. Removed tests will be added later. Example program will likely be removed permanently due to maintence problem. This patch depends WebKit and ANGLE changes to be able to present video frames onto screen and still have lot of bugs, those problems will be address later. BUG=53714 TEST=Tree is green. This patch doesn't work yet. Review URL: http://codereview.chromium.org/3432030 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@61918 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/chrome.gyp | 4 + chrome/common/gpu_messages_internal.h | 4 +- chrome/common/gpu_video_common.cc | 2 + chrome/common/gpu_video_common.h | 3 + chrome/gpu/gpu_video_decoder.cc | 23 +- chrome/gpu/media/mft_angle_video_device.cc | 52 ++ chrome/gpu/media/mft_angle_video_device.h | 42 ++ chrome/renderer/gpu_video_decoder_host.cc | 4 +- .../renderer/media/gles2_video_decode_context.cc | 6 +- media/media.gyp | 76 +-- media/mf/README.chromium | 23 - media/mf/file_reader_util.cc | 211 ------ media/mf/file_reader_util.h | 70 -- media/mf/mft_h264_decoder.cc | 706 --------------------- media/mf/mft_h264_decoder.h | 97 --- media/mf/mft_h264_decoder_example.cc | 421 ------------ media/mf/test/mft_h264_decoder_unittest.cc | 460 -------------- media/mf/test/run_all_unittests.cc | 26 - media/video/mft_h264_decode_engine.cc | 696 ++++++++++++++++++++ media/video/mft_h264_decode_engine.h | 102 +++ media/video/mft_h264_decode_engine_context.cc | 179 ------ media/video/mft_h264_decode_engine_context.h | 70 -- media/video/mft_h264_decode_engine_unittest.cc | 410 ++++++++++++ 23 files changed, 1344 insertions(+), 2343 deletions(-) create mode 100644 chrome/gpu/media/mft_angle_video_device.cc create mode 100644 chrome/gpu/media/mft_angle_video_device.h delete mode 100644 media/mf/README.chromium delete mode 100644 media/mf/file_reader_util.cc delete mode 100644 media/mf/file_reader_util.h delete mode 100644 media/mf/mft_h264_decoder.cc delete mode 100644 media/mf/mft_h264_decoder.h delete mode 100644 media/mf/mft_h264_decoder_example.cc delete mode 100644 media/mf/test/mft_h264_decoder_unittest.cc delete mode 100644 media/mf/test/run_all_unittests.cc create mode 100644 media/video/mft_h264_decode_engine.cc create mode 100644 media/video/mft_h264_decode_engine.h delete mode 100644 media/video/mft_h264_decode_engine_context.cc delete mode 100644 media/video/mft_h264_decode_engine_context.h create mode 100644 media/video/mft_h264_decode_engine_unittest.cc diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 4e2a038..3ccc202 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -787,6 +787,10 @@ ], }, ], + 'sources': [ + 'gpu/media/mft_angle_video_device.cc', + 'gpu/media/mft_angle_video_device.h', + ], }], ['OS=="linux" and target_arch!="arm"', { 'sources': [ diff --git a/chrome/common/gpu_messages_internal.h b/chrome/common/gpu_messages_internal.h index f61be25b1..b95a534 100644 --- a/chrome/common/gpu_messages_internal.h +++ b/chrome/common/gpu_messages_internal.h @@ -348,8 +348,8 @@ IPC_BEGIN_MESSAGES(GpuVideoDecoderHost) // GpuVideoDecoder reports that a video frame is ready to be consumed. IPC_MESSAGE_ROUTED4(GpuVideoDecoderHostMsg_ConsumeVideoFrame, int32, /* Video Frame ID */ - int64, /* Timestamp in ms */ - int64, /* Duration in ms */ + int64, /* Timestamp in microseconds */ + int64, /* Duration in microseconds */ int32) /* Flags */ // Allocate video frames for output of the hardware video decoder. diff --git a/chrome/common/gpu_video_common.cc b/chrome/common/gpu_video_common.cc index e0279de..67466b9 100644 --- a/chrome/common/gpu_video_common.cc +++ b/chrome/common/gpu_video_common.cc @@ -4,6 +4,8 @@ #include "chrome/common/gpu_video_common.h" +const int32 kGpuVideoInvalidFrameId = -1; + namespace IPC { /////////////////////////////////////////////////////////////////////////////// diff --git a/chrome/common/gpu_video_common.h b/chrome/common/gpu_video_common.h index 17d5498..d5a2ced 100644 --- a/chrome/common/gpu_video_common.h +++ b/chrome/common/gpu_video_common.h @@ -10,6 +10,9 @@ #include "chrome/common/common_param_traits.h" #include "media/base/video_frame.h" +// This is used in messages when only buffer flag is meaningful. +extern const int32 kGpuVideoInvalidFrameId; + // Flags assigned to a video buffer for both input and output. enum GpuVideoBufferFlag { kGpuVideoEndOfStream = 1 << 0, diff --git a/chrome/gpu/gpu_video_decoder.cc b/chrome/gpu/gpu_video_decoder.cc index 86bcd04..5ae7dfc 100644 --- a/chrome/gpu/gpu_video_decoder.cc +++ b/chrome/gpu/gpu_video_decoder.cc @@ -12,6 +12,12 @@ #include "media/base/data_buffer.h" #include "media/base/video_frame.h" +#if defined(OS_WIN) +#include "chrome/gpu/media/mft_angle_video_device.h" +#include "media/video/mft_h264_decode_engine.h" +#include +#endif + void GpuVideoDecoder::OnChannelConnected(int32 peer_pid) { } @@ -105,7 +111,12 @@ void GpuVideoDecoder::ProduceVideoSample(scoped_refptr buffer) { } void GpuVideoDecoder::ConsumeVideoFrame(scoped_refptr frame) { - int32 frame_id = -1; + if (frame->IsEndOfStream()) { + SendConsumeVideoFrame(kGpuVideoInvalidFrameId, 0, 0, kGpuVideoEndOfStream); + return; + } + + int32 frame_id = kGpuVideoInvalidFrameId; for (VideoFrameMap::iterator i = video_frame_map_.begin(); i != video_frame_map_.end(); ++i) { if (i->second == frame) { @@ -116,8 +127,7 @@ void GpuVideoDecoder::ConsumeVideoFrame(scoped_refptr frame) { DCHECK_NE(-1, frame_id) << "VideoFrame not recognized"; SendConsumeVideoFrame(frame_id, frame->GetTimestamp().InMicroseconds(), - frame->GetDuration().InMicroseconds(), - frame->IsEndOfStream() ? kGpuVideoEndOfStream : 0); + frame->GetDuration().InMicroseconds(), 0); } void* GpuVideoDecoder::GetDevice() { @@ -225,8 +235,13 @@ GpuVideoDecoder::GpuVideoDecoder( // TODO(jiesun): find a better way to determine which VideoDecodeEngine // to return on current platform. +#if defined(OS_WIN) + decode_engine_.reset(new media::MftH264DecodeEngine(true)); + video_device_.reset(new MftAngleVideoDevice()); +#else decode_engine_.reset(new FakeGlVideoDecodeEngine()); video_device_.reset(new FakeGlVideoDevice()); +#endif } void GpuVideoDecoder::OnInitialize(const GpuVideoDecoderInitParam& param) { @@ -235,7 +250,7 @@ void GpuVideoDecoder::OnInitialize(const GpuVideoDecoderInitParam& param) { config_.width = param.width; config_.height = param.height; config_.opaque_context = NULL; - decode_engine_->Initialize(NULL, this, this, config_); + decode_engine_->Initialize(message_loop_, this, this, config_); } void GpuVideoDecoder::OnUninitialize() { diff --git a/chrome/gpu/media/mft_angle_video_device.cc b/chrome/gpu/media/mft_angle_video_device.cc new file mode 100644 index 0000000..1faf9dc --- /dev/null +++ b/chrome/gpu/media/mft_angle_video_device.cc @@ -0,0 +1,52 @@ +// 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 "chrome/gpu/media/mft_angle_video_device.h" + +#include + +#include "media/base/video_frame.h" +#include "third_party/angle/src/libGLESv2/main.h" + +MftAngleVideoDevice::MftAngleVideoDevice() + : device_(reinterpret_cast( + eglGetCurrentDisplay())->getDevice()) { +} + +void* MftAngleVideoDevice::GetDevice() { + return device_; +} + +bool MftAngleVideoDevice::CreateVideoFrameFromGlTextures( + size_t width, size_t height, media::VideoFrame::Format format, + const std::vector& textures, + scoped_refptr* frame) { + media::VideoFrame::GlTexture texture_array[media::VideoFrame::kMaxPlanes]; + memset(texture_array, 0, sizeof(texture_array)); + + for (size_t i = 0; i < textures.size(); ++i) { + texture_array[i] = textures[i]; + } + + media::VideoFrame::CreateFrameGlTexture(format, + width, + height, + texture_array, + frame); + return *frame != NULL; +} + +void MftAngleVideoDevice::ReleaseVideoFrame( + const scoped_refptr& frame) { + // We didn't need to anything here because we didn't allocate any resources + // for the VideoFrame(s) generated. +} + +bool MftAngleVideoDevice::UploadToVideoFrame( + void* buffer, scoped_refptr frame) { + gl::Context* context = (gl::Context*)eglGetCurrentContext(); + // TODO(hclam): Connect ANGLE to upload the surface to texture when changes + // to ANGLE is done. + return true; +} diff --git a/chrome/gpu/media/mft_angle_video_device.h b/chrome/gpu/media/mft_angle_video_device.h new file mode 100644 index 0000000..fb1e0e8 --- /dev/null +++ b/chrome/gpu/media/mft_angle_video_device.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef CHROME_GPU_MEDIA_MFT_ANGLE_VIDEO_DEVICE_H_ +#define CHROME_GPU_MEDIA_MFT_ANGLE_VIDEO_DEVICE_H_ + +#include "base/scoped_comptr_win.h" +#include "chrome/gpu/media/gpu_video_device.h" + +struct IDirect3DDevice9; +extern "C" const GUID IID_IDirect3DDevice9; + +namespace media { +class VideoFrame; +} // namespace media + +// This class is used to provide hardware video device, video frames and +// allow video frames to be uploaded to their final render target. +// +// This specifically serves MftH264DecodeEngine in the context of ANGLE. +class MftAngleVideoDevice : public GpuVideoDevice { + public: + MftAngleVideoDevice(); + virtual ~MftAngleVideoDevice() {} + + // GpuVideoDevice implementation. + virtual void* GetDevice(); + virtual bool CreateVideoFrameFromGlTextures( + size_t width, size_t height, media::VideoFrame::Format format, + const std::vector& textures, + scoped_refptr* frame); + virtual void ReleaseVideoFrame( + const scoped_refptr& frame); + virtual bool UploadToVideoFrame(void* buffer, + scoped_refptr frame); + + private: + ScopedComPtr device_; +}; + +#endif // CHROME_GPU_MEDIA_MFT_ANGLE_VIDEO_DEVICE_H_ diff --git a/chrome/renderer/gpu_video_decoder_host.cc b/chrome/renderer/gpu_video_decoder_host.cc index ad81004..bf6c79f 100644 --- a/chrome/renderer/gpu_video_decoder_host.cc +++ b/chrome/renderer/gpu_video_decoder_host.cc @@ -272,8 +272,8 @@ void GpuVideoDecoderHost::OnConsumeVideoFrame(int32 frame_id, int64 timestamp, frame = video_frame_map_[frame_id]; DCHECK(frame) << "Invalid frame ID received"; - frame->SetDuration(base::TimeDelta::FromMilliseconds(duration)); - frame->SetTimestamp(base::TimeDelta::FromMilliseconds(timestamp)); + frame->SetDuration(base::TimeDelta::FromMicroseconds(duration)); + frame->SetTimestamp(base::TimeDelta::FromMicroseconds(timestamp)); } event_handler_->ConsumeVideoFrame(frame); diff --git a/chrome/renderer/media/gles2_video_decode_context.cc b/chrome/renderer/media/gles2_video_decode_context.cc index bd60ea1..e32e22b 100644 --- a/chrome/renderer/media/gles2_video_decode_context.cc +++ b/chrome/renderer/media/gles2_video_decode_context.cc @@ -56,8 +56,10 @@ void Gles2VideoDecodeContext::AllocateVideoFrames( glGenTextures(planes, textures); for (int j = 0; j < planes; ++j) { glBindTexture(GL_TEXTURE_2D, textures[j]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, gl_format, width, height, 0, gl_format, GL_UNSIGNED_BYTE, NULL); } diff --git a/media/media.gyp b/media/media.gyp index ee8acbc..49679b1 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -150,6 +150,12 @@ ], }, 'conditions': [ + ['OS=="win"', { + 'sources': [ + 'video/mft_h264_decode_engine.cc', + 'video/mft_h264_decode_engine.h', + ], + }], ['OS=="linux" or OS=="freebsd"', { 'link_settings': { 'libraries': [ @@ -446,76 +452,6 @@ }, }, }, - { - 'target_name': 'mft_h264_decoder', - 'type': '<(library)', - 'dependencies': [ - 'media', - '../base/base.gyp:base', - '../base/base.gyp:test_support_base', - ], - 'include_dirs': [ - '..', - ], - 'sources': [ - 'mf/mft_h264_decoder.cc', - 'mf/mft_h264_decoder.h', - ], - 'msvs_settings': { - 'VCLinkerTool': { - 'SubSystem': '1', # Set /SUBSYSTEM:CONSOLE - }, - }, - }, - { - 'target_name': 'mft_h264_decoder_example', - 'type': 'executable', - 'dependencies': [ - 'media', - 'mft_h264_decoder', - '../base/base.gyp:base', - '../third_party/ffmpeg/ffmpeg.gyp:ffmpeg', - ], - 'include_dirs': [ - '..', - ], - 'sources': [ - 'mf/file_reader_util.cc', - 'mf/file_reader_util.h', - 'mf/mft_h264_decoder_example.cc', - ], - 'msvs_settings': { - 'VCLinkerTool': { - 'SubSystem': '1', # Set /SUBSYSTEM:CONSOLE - }, - }, - }, - { - 'target_name': 'mft_h264_decoder_unittests', - 'type': 'executable', - 'dependencies': [ - 'media', - 'mft_h264_decoder', - '../base/base.gyp:base', - '../base/base.gyp:base_i18n', - '../testing/gtest.gyp:gtest', - '../third_party/ffmpeg/ffmpeg.gyp:ffmpeg', - ], - 'include_dirs': [ - '..', - ], - 'sources': [ - 'mf/file_reader_util.cc', - 'mf/file_reader_util.h', - 'mf/test/mft_h264_decoder_unittest.cc', - 'mf/test/run_all_unittests.cc', - ], - 'msvs_settings': { - 'VCLinkerTool': { - 'SubSystem': '1', # Set /SUBSYSTEM:CONSOLE - }, - }, - }, ], }], ['OS=="linux" or OS=="freebsd" or OS=="openbsd"', { diff --git a/media/mf/README.chromium b/media/mf/README.chromium deleted file mode 100644 index 73f1deb..0000000 --- a/media/mf/README.chromium +++ /dev/null @@ -1,23 +0,0 @@ -This tool demonstrates the use of the Media Foundation H.264 decoder as a -standalone Media Foundation Transform (MFT). The H.264 decoder takes sample -objects (IMFSample) containing Annex B streams as input, and outputs decoded -YV12/NV12 video frames as output, contained in a buffer object (if DXVA is not -enabled) or a Direct3D surface (if DXVA is enabled.) - -This tool uses ffmpeg's parser and bitstream converter to read a file -containing H.264 video and outputs packets containing Annex B streams which are -then fed into the H.264 decoder. This tool also demonstrates the use of the -H.264 decoder using callbacks. - -Requirements: Windows 7 - -Note1: On some video files, there is a mysterious 1-off decoded frame count -when DXVA is enabled. - -Note2: This tool requires the ffmpeg library to have the H.264 codec and Annex -B bitstream filter. You might need build your own, or grab one from -http://ffmpeg.arrozcru.org/autobuilds/ - -Note3: A single H264Mft instance is only for 1 H.264 video stream only. -Inputting streams consisting of more than 1 video to a single instance -may result in undefined behavior. diff --git a/media/mf/file_reader_util.cc b/media/mf/file_reader_util.cc deleted file mode 100644 index d18afe6..0000000 --- a/media/mf/file_reader_util.cc +++ /dev/null @@ -1,211 +0,0 @@ -// 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. -// -// Borrowed from media/tools/omx_test/file_reader_util.cc. -// Added some functionalities related to timestamps on packets. - -#include "media/mf/file_reader_util.h" - -#include - -#include - -#include "base/logging.h" -#include "media/base/data_buffer.h" -#include "media/ffmpeg/ffmpeg_common.h" -#include "media/filters/bitstream_converter.h" - -namespace media { - -////////////////////////////////////////////////////////////////////////////// -// FFmpegFileReader -FFmpegFileReader::FFmpegFileReader(const std::string& filename) - : filename_(filename), - format_context_(NULL), - codec_context_(NULL), - target_stream_(-1), - converter_(NULL), - last_timestamp_(0) { -} - -FFmpegFileReader::~FFmpegFileReader() { - if (format_context_) - av_close_input_file(format_context_); -} - -bool FFmpegFileReader::Initialize() { - int result = av_open_input_file(&format_context_, filename_.c_str(), - NULL, 0, NULL); - if (result < 0) { - switch (result) { - case AVERROR_NOFMT: - LOG(ERROR) << "Error: File format not supported " - << filename_; - break; - default: - LOG(ERROR) << "Error: Could not open input for " - << filename_ << ": " << result; - break; - } - return false; - } - if (av_find_stream_info(format_context_) < 0) { - LOG(ERROR) << "can't use FFmpeg to parse stream info"; - return false; - } - - for (size_t i = 0; i < format_context_->nb_streams; ++i) { - codec_context_ = format_context_->streams[i]->codec; - - // Find the video stream. - if (codec_context_->codec_type == CODEC_TYPE_VIDEO) { - target_stream_ = i; - break; - } - } - if (target_stream_ == -1) { - LOG(ERROR) << "no video in the stream"; - return false; - } - - // Initialize the bitstream filter if needed. - // TODO(hclam): find a better way to identify mp4 container. - if (codec_context_->codec_id == CODEC_ID_H264) { - converter_.reset(new media::FFmpegBitstreamConverter( - "h264_mp4toannexb", codec_context_)); - } else if (codec_context_->codec_id == CODEC_ID_MPEG4) { - converter_.reset(new media::FFmpegBitstreamConverter( - "mpeg4video_es", codec_context_)); - } else if (codec_context_->codec_id == CODEC_ID_WMV3) { - converter_.reset(new media::FFmpegBitstreamConverter( - "vc1_asftorcv", codec_context_)); - } else if (codec_context_->codec_id == CODEC_ID_VC1) { - converter_.reset(new media::FFmpegBitstreamConverter( - "vc1_asftoannexg", codec_context_)); - } - if (converter_.get() && !converter_->Initialize()) { - converter_.reset(); - LOG(ERROR) << "failed to initialize h264_mp4toannexb filter"; - return false; - } - return true; -} - -void FFmpegFileReader::Read(scoped_refptr* output) { - if (!format_context_ || !codec_context_ || target_stream_ == -1) { - *output = new DataBuffer(0); - return; - } - AVPacket packet; - bool found = false; - while (!found) { - int result = av_read_frame(format_context_, &packet); - if (result < 0) { - *output = new DataBuffer(0); - return; - } - if (packet.stream_index == target_stream_) { - if (converter_.get() && !converter_->ConvertPacket(&packet)) { - LOG(ERROR) << "failed to convert AVPacket"; - } - last_timestamp_ = std::max(last_timestamp_, packet.pts); - CopyPacketToBuffer(&packet, output); - found = true; - } - av_free_packet(&packet); - } -} - -bool FFmpegFileReader::SeekForward(int64 seek_amount_us) { - if (!format_context_ || !codec_context_ || target_stream_ == -1) { - return false; - } - int64 new_us = TimeBaseToMicroseconds(last_timestamp_) + seek_amount_us; - int64 new_timestamp = MicrosecondsToTimeBase(new_us); - last_timestamp_ = new_timestamp; - return av_seek_frame(format_context_, target_stream_, new_timestamp, 0) >= 0; -} - -bool FFmpegFileReader::GetFrameRate(int* num, int* denom) const { - if (!codec_context_) - return false; - *denom = codec_context_->time_base.num; - *num = codec_context_->time_base.den; - if (*denom == 0) { - *num = 0; - return false; - } - return true; -} - -bool FFmpegFileReader::GetWidth(int* width) const { - if (!codec_context_) - return false; - *width = codec_context_->width; - return true; -} - -bool FFmpegFileReader::GetHeight(int* height) const { - if (!codec_context_) - return false; - *height = codec_context_->height; - return true; -} - -bool FFmpegFileReader::GetAspectRatio(int* num, int* denom) const { - if (!codec_context_) - return false; - AVRational aspect_ratio = codec_context_->sample_aspect_ratio; - if (aspect_ratio.num == 0 || aspect_ratio.den == 0) - return false; - *num = aspect_ratio.num; - *denom = aspect_ratio.den; - return true; -} - -int64 FFmpegFileReader::TimeBaseToMicroseconds( - int64 time_base_unit) const { - // FFmpeg units after time base conversion seems to be actually given in - // milliseconds (instead of seconds...) so we need to multiply it by a factor - // of 1,000. - // Note we need to double this because the frame rate is doubled in - // ffmpeg. - CHECK(codec_context_) << "Codec context needs to be initialized"; - return time_base_unit * 2000 * codec_context_->time_base.num / - codec_context_->time_base.den; -} - -int64 FFmpegFileReader::MicrosecondsToTimeBase( - int64 time_base_unit) const { - CHECK(codec_context_) << "Codec context needs to be initialized"; - return time_base_unit * codec_context_->time_base.den / 2000 / - codec_context_->time_base.num; -} - -void FFmpegFileReader::CopyPacketToBuffer(AVPacket* packet, - scoped_refptr* output) { - uint8* buffer = new uint8[packet->size]; - if (buffer == NULL) { - LOG(ERROR) << "Failed to allocate buffer for annex b stream"; - *output = NULL; - return; - } - memcpy(buffer, packet->data, packet->size); - *output = new DataBuffer(buffer, packet->size); - if (packet->pts != AV_NOPTS_VALUE) { - (*output)->SetTimestamp( - base::TimeDelta::FromMicroseconds( - TimeBaseToMicroseconds(packet->pts))); - } else { - (*output)->SetTimestamp(StreamSample::kInvalidTimestamp); - } - if (packet->duration == 0) { - LOG(WARNING) << "Packet duration not known"; - } - (*output)->SetDuration( - base::TimeDelta::FromMicroseconds( - TimeBaseToMicroseconds(packet->duration))); -} - -} // namespace media diff --git a/media/mf/file_reader_util.h b/media/mf/file_reader_util.h deleted file mode 100644 index a5fa9ec..0000000 --- a/media/mf/file_reader_util.h +++ /dev/null @@ -1,70 +0,0 @@ -// 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. -// -// Borrowed from media/tools/omx_test/file_reader_util.h. -// Added some functionalities related to timestamps on packets and Media -// Foundation. - -#ifndef MEDIA_MF_FILE_READER_UTIL_H_ -#define MEDIA_MF_FILE_READER_UTIL_H_ - -#include - -#include "base/basictypes.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" - -struct AVCodecContext; -struct AVFormatContext; -struct AVPacket; - -namespace media { - -class BitstreamConverter; -class DataBuffer; - -// A class to help reading and parsing input file for use in omx_test. -class FileReader { - public: - virtual ~FileReader() {} - - // Initialize FileReader object, returns true if successful. - virtual bool Initialize() = 0; - - // Read the file into |output|, and output the number of bytes read to - // |size|. - virtual void Read(scoped_refptr* output) = 0; -}; - -class FFmpegFileReader : public FileReader { - public: - explicit FFmpegFileReader(const std::string& filename); - virtual ~FFmpegFileReader(); - virtual bool Initialize(); - virtual void Read(scoped_refptr* output); - virtual bool SeekForward(int64 seek_amount_us); - - bool GetFrameRate(int* num, int* denom) const; - bool GetWidth(int* width) const; - bool GetHeight(int* height) const; - bool GetAspectRatio(int* num, int* denom) const; - int64 TimeBaseToMicroseconds(int64 time_base_unit) const; - int64 MicrosecondsToTimeBase(int64 time_base_unit) const; - - private: - void CopyPacketToBuffer(AVPacket* packet, scoped_refptr* output); - - std::string filename_; - AVFormatContext* format_context_; - AVCodecContext* codec_context_; - int target_stream_; - scoped_ptr converter_; - int64 last_timestamp_; - - DISALLOW_COPY_AND_ASSIGN(FFmpegFileReader); -}; - -} // namespace media - -#endif // MEDIA_MF_FILE_READER_UTIL_H_ diff --git a/media/mf/mft_h264_decoder.cc b/media/mf/mft_h264_decoder.cc deleted file mode 100644 index 2c1970d..0000000 --- a/media/mf/mft_h264_decoder.cc +++ /dev/null @@ -1,706 +0,0 @@ -// 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 "media/mf/mft_h264_decoder.h" - -#include -#include -#include -#include -// Placed after mfapi.h to avoid linking strmiids.lib for MR_BUFFER_SERVICE. -#include -#include -#include - -#include "base/time.h" -#include "base/message_loop.h" - -#pragma comment(lib, "dxva2.lib") -#pragma comment(lib, "d3d9.lib") -#pragma comment(lib, "mf.lib") -#pragma comment(lib, "mfplat.lib") - -using base::TimeDelta; - -namespace { - -// Creates an empty Media Foundation sample with no buffers. -static IMFSample* CreateEmptySample() { - HRESULT hr; - ScopedComPtr sample; - hr = MFCreateSample(sample.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Unable to create an empty sample"; - return NULL; - } - return sample.Detach(); -} - -// Creates a Media Foundation sample with one buffer of length |buffer_length| -// on a |align|-byte boundary. Alignment must be a perfect power of 2 or 0. -// If |align| is 0, then no alignment is specified. -static IMFSample* CreateEmptySampleWithBuffer(int buffer_length, int align) { - CHECK_GT(buffer_length, 0); - ScopedComPtr sample; - sample.Attach(CreateEmptySample()); - if (!sample.get()) - return NULL; - ScopedComPtr buffer; - HRESULT hr; - if (align == 0) { - // Note that MFCreateMemoryBuffer is same as MFCreateAlignedMemoryBuffer - // with the align argument being 0. - hr = MFCreateMemoryBuffer(buffer_length, buffer.Receive()); - } else { - hr = MFCreateAlignedMemoryBuffer(buffer_length, - align - 1, - buffer.Receive()); - } - if (FAILED(hr)) { - LOG(ERROR) << "Unable to create an empty buffer"; - return NULL; - } - hr = sample->AddBuffer(buffer.get()); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to add empty buffer to sample"; - return NULL; - } - return sample.Detach(); -} - -// Creates a Media Foundation sample with one buffer containing a copy of the -// given Annex B stream data. -// If duration and sample time are not known, provide 0. -// |min_size| specifies the minimum size of the buffer (might be required by -// the decoder for input). The times here should be given in 100ns units. -// |alignment| specifies the buffer in the sample to be aligned. If no -// alignment is required, provide 0 or 1. -static IMFSample* CreateInputSample(const uint8* stream, int size, - int64 timestamp, int64 duration, - int min_size, int alignment) { - CHECK(stream); - CHECK_GT(size, 0); - ScopedComPtr sample; - sample.Attach(CreateEmptySampleWithBuffer(std::max(min_size, size), - alignment)); - if (!sample.get()) { - LOG(ERROR) << "Failed to create empty buffer for input"; - return NULL; - } - HRESULT hr; - if (duration > 0) { - hr = sample->SetSampleDuration(duration); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to set sample duration"; - return NULL; - } - } - if (timestamp > 0) { - hr = sample->SetSampleTime(timestamp); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to set sample time"; - return NULL; - } - } - ScopedComPtr buffer; - hr = sample->GetBufferByIndex(0, buffer.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get buffer in sample"; - return NULL; - } - DWORD max_length, current_length; - uint8* destination; - hr = buffer->Lock(&destination, &max_length, ¤t_length); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to lock buffer"; - return NULL; - } - CHECK_EQ(current_length, 0u); - CHECK_GE(static_cast(max_length), size); - memcpy(destination, stream, size); - CHECK(SUCCEEDED(buffer->Unlock())); - hr = buffer->SetCurrentLength(size); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to set current length to " << size; - return NULL; - } - LOG(INFO) << __FUNCTION__ << " wrote " << size << " bytes into input sample"; - return sample.Detach(); -} - -const GUID ConvertVideoFrameFormatToGuid(media::VideoFrame::Format format) { - switch (format) { - case media::VideoFrame::NV12: - return MFVideoFormat_NV12; - case media::VideoFrame::YV12: - return MFVideoFormat_YV12; - default: - NOTREACHED() << "Unsupported VideoFrame format"; - return GUID_NULL; - } - NOTREACHED(); - return GUID_NULL; -} - -} // namespace - -namespace media { - -// public methods - -MftH264Decoder::MftH264Decoder(bool use_dxva, HWND draw_window) - : use_dxva_(use_dxva), - d3d9_(NULL), - device_(NULL), - device_manager_(NULL), - draw_window_(draw_window), - decoder_(NULL), - input_stream_info_(), - output_stream_info_(), - state_(kUninitialized), - event_handler_(NULL) { - memset(&config_, 0, sizeof(config_)); - memset(&info_, 0, sizeof(info_)); -} - -MftH264Decoder::~MftH264Decoder() { -} - -void MftH264Decoder::Initialize( - MessageLoop* message_loop, - VideoDecodeEngine::EventHandler* event_handler, - VideoDecodeContext* context, - const VideoCodecConfig& config) { - LOG(INFO) << "MftH264Decoder::Initialize"; - if (state_ != kUninitialized) { - LOG(ERROR) << "Initialize: invalid state"; - return; - } - if (!message_loop || !event_handler) { - LOG(ERROR) << "MftH264Decoder::Initialize: parameters cannot be NULL"; - return; - } - - config_ = config; - event_handler_ = event_handler; - - info_.provides_buffers = true; - - // TODO(jiesun): Actually it is more likely an NV12 D3DSuface9. - // Until we had hardware composition working. - if (use_dxva_) { - info_.stream_info.surface_format = VideoFrame::NV12; - info_.stream_info.surface_type = VideoFrame::TYPE_GL_TEXTURE; - } else { - info_.stream_info.surface_format = VideoFrame::YV12; - info_.stream_info.surface_type = VideoFrame::TYPE_SYSTEM_MEMORY; - } - - // codec_info.stream_info_.surface_width_/height_ are initialized - // in InitInternal(). - info_.success = InitInternal(); - if (info_.success) { - state_ = kNormal; - event_handler_->OnInitializeComplete(info_); - } else { - LOG(ERROR) << "MftH264Decoder::Initialize failed"; - } -} - -void MftH264Decoder::Uninitialize() { - LOG(INFO) << "MftH264Decoder::Uninitialize"; - if (state_ == kUninitialized) { - LOG(ERROR) << "Uninitialize: invalid state"; - return; - } - - // TODO(imcheng): - // Cannot shutdown COM libraries here because the COM objects still needs - // to be Release()'ed. We can explicitly release them here, or move the - // uninitialize to GpuVideoService... - decoder_.Release(); - device_manager_.Release(); - device_.Release(); - d3d9_.Release(); - ShutdownComLibraries(); - state_ = kUninitialized; - event_handler_->OnUninitializeComplete(); -} - -void MftH264Decoder::Flush() { - LOG(INFO) << "MftH264Decoder::Flush"; - if (state_ != kNormal) { - LOG(ERROR) << "Flush: invalid state"; - return; - } - state_ = kFlushing; - if (!SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH)) { - LOG(WARNING) << "MftH264Decoder::Flush failed to send message"; - } - state_ = kNormal; - event_handler_->OnFlushComplete(); -} - -void MftH264Decoder::Seek() { - if (state_ != kNormal) { - LOG(ERROR) << "Seek: invalid state"; - return; - } - LOG(INFO) << "MftH264Decoder::Seek"; - // Seek not implemented. - event_handler_->OnSeekComplete(); -} - -void MftH264Decoder::ConsumeVideoSample(scoped_refptr buffer) { - LOG(INFO) << "MftH264Decoder::ConsumeVideoSample"; - if (state_ == kUninitialized) { - LOG(ERROR) << "ConsumeVideoSample: invalid state"; - } - ScopedComPtr sample; - if (!buffer->IsEndOfStream()) { - sample.Attach( - CreateInputSample(buffer->GetData(), - buffer->GetDataSize(), - buffer->GetTimestamp().InMicroseconds() * 10, - buffer->GetDuration().InMicroseconds() * 10, - input_stream_info_.cbSize, - input_stream_info_.cbAlignment)); - if (!sample.get()) { - LOG(ERROR) << "Failed to create an input sample"; - } else { - if (FAILED(decoder_->ProcessInput(0, sample.get(), 0))) { - event_handler_->OnError(); - } - } - } else { - if (state_ != MftH264Decoder::kEosDrain) { - // End of stream, send drain messages. - if (!SendMFTMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM) || - !SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN)) { - LOG(ERROR) << "Failed to send EOS / drain messages to MFT"; - event_handler_->OnError(); - } else { - state_ = MftH264Decoder::kEosDrain; - } - } - } - DoDecode(); -} - -void MftH264Decoder::ProduceVideoFrame(scoped_refptr frame) { - LOG(INFO) << "MftH264Decoder::ProduceVideoFrame"; - if (state_ == kUninitialized) { - LOG(ERROR) << "ProduceVideoFrame: invalid state"; - return; - } - scoped_refptr buffer; - event_handler_->ProduceVideoSample(buffer); -} - -// private methods - -// static -bool MftH264Decoder::StartupComLibraries() { - HRESULT hr; - hr = CoInitializeEx(NULL, - COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); - if (FAILED(hr)) { - LOG(ERROR) << "CoInit fail"; - return false; - } - - hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); - if (FAILED(hr)) { - LOG(ERROR) << "MFStartup fail"; - CoUninitialize(); - return false; - } - return true; -} - -// static -void MftH264Decoder::ShutdownComLibraries() { - HRESULT hr; - hr = MFShutdown(); - if (FAILED(hr)) { - LOG(WARNING) << "Warning: MF failed to shutdown"; - } - CoUninitialize(); -} - -bool MftH264Decoder::CreateD3DDevManager() { - CHECK(draw_window_); - d3d9_.Attach(Direct3DCreate9(D3D_SDK_VERSION)); - if (d3d9_.get() == NULL) { - LOG(ERROR) << "Failed to create D3D9"; - return false; - } - - D3DPRESENT_PARAMETERS present_params = {0}; - present_params.BackBufferWidth = 0; - present_params.BackBufferHeight = 0; - present_params.BackBufferFormat = D3DFMT_UNKNOWN; - present_params.BackBufferCount = 1; - present_params.SwapEffect = D3DSWAPEFFECT_DISCARD; - present_params.hDeviceWindow = draw_window_; - present_params.Windowed = TRUE; - present_params.Flags = D3DPRESENTFLAG_VIDEO; - present_params.FullScreen_RefreshRateInHz = 0; - present_params.PresentationInterval = 0; - - // D3DCREATE_HARDWARE_VERTEXPROCESSING specifies hardware vertex processing. - // (Is it even needed for just video decoding?) - HRESULT hr = d3d9_->CreateDevice(D3DADAPTER_DEFAULT, - D3DDEVTYPE_HAL, - draw_window_, - (D3DCREATE_HARDWARE_VERTEXPROCESSING | - D3DCREATE_MULTITHREADED), - &present_params, - device_.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to create D3D Device"; - return false; - } - - UINT dev_manager_reset_token = 0; - hr = DXVA2CreateDirect3DDeviceManager9(&dev_manager_reset_token, - device_manager_.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Couldn't create D3D Device manager"; - return false; - } - - hr = device_manager_->ResetDevice(device_.get(), - dev_manager_reset_token); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to set device to device manager"; - return false; - } - return true; -} - -bool MftH264Decoder::InitInternal() { - if (!StartupComLibraries()) - return false; - if (use_dxva_ && !CreateD3DDevManager()) - return false; - if (!InitDecoder()) - return false; - if (!GetStreamsInfoAndBufferReqs()) - return false; - return SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING); -} - -bool MftH264Decoder::InitDecoder() { - // TODO(jiesun): use MFEnum to get decoder CLSID. - HRESULT hr = CoCreateInstance(__uuidof(CMSH264DecoderMFT), - NULL, - CLSCTX_INPROC_SERVER, - __uuidof(IMFTransform), - reinterpret_cast(decoder_.Receive())); - if (FAILED(hr) || !decoder_.get()) { - LOG(ERROR) << "CoCreateInstance failed " << std::hex << std::showbase << hr; - return false; - } - - if (!CheckDecoderDxvaSupport()) - return false; - - if (use_dxva_) { - hr = decoder_->ProcessMessage( - MFT_MESSAGE_SET_D3D_MANAGER, - reinterpret_cast(device_manager_.get())); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to set D3D9 device to decoder " << std::hex << hr; - return false; - } - } - - return SetDecoderMediaTypes(); -} - -bool MftH264Decoder::CheckDecoderDxvaSupport() { - ScopedComPtr attributes; - HRESULT hr = decoder_->GetAttributes(attributes.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Unlock: Failed to get attributes, hr = " - << std::hex << std::showbase << hr; - return false; - } - - UINT32 dxva; - hr = attributes->GetUINT32(MF_SA_D3D_AWARE, &dxva); - if (FAILED(hr) || !dxva) { - LOG(ERROR) << "Failed to get DXVA attr or decoder is not DXVA-aware, hr = " - << std::hex << std::showbase << hr - << " this might not be the right decoder."; - return false; - } - return true; -} - -bool MftH264Decoder::SetDecoderMediaTypes() { - if (!SetDecoderInputMediaType()) - return false; - return SetDecoderOutputMediaType(ConvertVideoFrameFormatToGuid( - info_.stream_info.surface_format)); -} - -bool MftH264Decoder::SetDecoderInputMediaType() { - ScopedComPtr media_type; - HRESULT hr = MFCreateMediaType(media_type.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to create empty media type object"; - return false; - } - - hr = media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); - if (FAILED(hr)) { - LOG(ERROR) << "SetGUID for major type failed"; - return false; - } - - hr = media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); - if (FAILED(hr)) { - LOG(ERROR) << "SetGUID for subtype failed"; - return false; - } - - hr = decoder_->SetInputType(0, media_type.get(), 0); // No flags - if (FAILED(hr)) { - LOG(ERROR) << "Failed to set decoder's input type"; - return false; - } - - return true; -} - -bool MftH264Decoder::SetDecoderOutputMediaType(const GUID subtype) { - DWORD i = 0; - IMFMediaType* out_media_type; - bool found = false; - while (SUCCEEDED(decoder_->GetOutputAvailableType(0, i, &out_media_type))) { - GUID out_subtype; - HRESULT hr = out_media_type->GetGUID(MF_MT_SUBTYPE, &out_subtype); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to GetGUID() on GetOutputAvailableType() " << i; - out_media_type->Release(); - continue; - } - if (out_subtype == subtype) { - hr = decoder_->SetOutputType(0, out_media_type, 0); // No flags - hr = MFGetAttributeSize(out_media_type, MF_MT_FRAME_SIZE, - reinterpret_cast(&info_.stream_info.surface_width), - reinterpret_cast(&info_.stream_info.surface_height)); - config_.width = info_.stream_info.surface_width; - config_.height = info_.stream_info.surface_height; - if (FAILED(hr)) { - LOG(ERROR) << "Failed to SetOutputType to |subtype| or obtain " - << "width/height " << std::hex << hr; - } else { - out_media_type->Release(); - return true; - } - } - i++; - out_media_type->Release(); - } - return false; -} - -bool MftH264Decoder::SendMFTMessage(MFT_MESSAGE_TYPE msg) { - HRESULT hr = decoder_->ProcessMessage(msg, NULL); - return SUCCEEDED(hr); -} - -// Prints out info about the input/output streams, gets the minimum buffer sizes -// for input and output samples. -// The MFT will not allocate buffer for neither input nor output, so we have -// to do it ourselves and make sure they're the correct size. -// Exception is when dxva is enabled, the decoder will allocate output. -bool MftH264Decoder::GetStreamsInfoAndBufferReqs() { - HRESULT hr = decoder_->GetInputStreamInfo(0, &input_stream_info_); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get input stream info"; - return false; - } - LOG(INFO) << "Input stream info: "; - LOG(INFO) << "Max latency: " << input_stream_info_.hnsMaxLatency; - - // There should be three flags, one for requiring a whole frame be in a - // single sample, one for requiring there be one buffer only in a single - // sample, and one that specifies a fixed sample size. (as in cbSize) - LOG(INFO) << "Flags: " - << std::hex << std::showbase << input_stream_info_.dwFlags; - CHECK_EQ(input_stream_info_.dwFlags, 0x7u); - LOG(INFO) << "Min buffer size: " << input_stream_info_.cbSize; - LOG(INFO) << "Max lookahead: " << input_stream_info_.cbMaxLookahead; - LOG(INFO) << "Alignment: " << input_stream_info_.cbAlignment; - - hr = decoder_->GetOutputStreamInfo(0, &output_stream_info_); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get output stream info"; - return false; - } - LOG(INFO) << "Output stream info: "; - // The flags here should be the same and mean the same thing, except when - // DXVA is enabled, there is an extra 0x100 flag meaning decoder will - // allocate its own sample. - LOG(INFO) << "Flags: " - << std::hex << std::showbase << output_stream_info_.dwFlags; - CHECK_EQ(output_stream_info_.dwFlags, use_dxva_ ? 0x107u : 0x7u); - LOG(INFO) << "Min buffer size: " << output_stream_info_.cbSize; - LOG(INFO) << "Alignment: " << output_stream_info_.cbAlignment; - - return true; -} - -bool MftH264Decoder::DoDecode() { - if (state_ != kNormal && state_ != kEosDrain) { - LOG(ERROR) << "DoDecode: not in normal or drain state"; - return false; - } - scoped_refptr frame; - ScopedComPtr output_sample; - if (!use_dxva_) { - output_sample.Attach( - CreateEmptySampleWithBuffer(output_stream_info_.cbSize, - output_stream_info_.cbAlignment)); - if (!output_sample.get()) { - LOG(ERROR) << "GetSample: failed to create empty output sample"; - event_handler_->OnError(); - return false; - } - } - MFT_OUTPUT_DATA_BUFFER output_data_buffer; - memset(&output_data_buffer, 0, sizeof(output_data_buffer)); - output_data_buffer.dwStreamID = 0; - output_data_buffer.pSample = output_sample; - - DWORD status; - HRESULT hr = decoder_->ProcessOutput(0, // No flags - 1, // # of out streams to pull from - &output_data_buffer, - &status); - - IMFCollection* events = output_data_buffer.pEvents; - if (events != NULL) { - LOG(INFO) << "Got events from ProcessOuput, but discarding"; - events->Release(); - } - - if (FAILED(hr)) { - if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { - hr = SetDecoderOutputMediaType(ConvertVideoFrameFormatToGuid( - info_.stream_info.surface_format)); - if (SUCCEEDED(hr)) { - event_handler_->OnFormatChange(info_.stream_info); - return true; - } else { - event_handler_->OnError(); - return false; - } - } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { - if (state_ == kEosDrain) { - // No more output from the decoder. Notify EOS and stop playback. - scoped_refptr frame; - VideoFrame::CreateEmptyFrame(&frame); - event_handler_->ConsumeVideoFrame(frame); - state_ = MftH264Decoder::kStopped; - return false; - } - return true; - } else { - LOG(ERROR) << "Unhandled error in DoDecode()"; - state_ = MftH264Decoder::kStopped; - event_handler_->OnError(); - return false; - } - } - - // We succeeded in getting an output sample. - if (use_dxva_) { - // For DXVA we didn't provide the sample, i.e. output_sample was NULL. - output_sample.Attach(output_data_buffer.pSample); - } - if (!output_sample.get()) { - LOG(ERROR) << "ProcessOutput succeeded, but did not get a sample back"; - event_handler_->OnError(); - return true; - } - - int64 timestamp = 0, duration = 0; - if (FAILED(output_sample->GetSampleTime(×tamp)) || - FAILED(output_sample->GetSampleDuration(&duration))) { - LOG(WARNING) << "Failed to get timestamp/duration from output"; - } - - // The duration and timestamps are in 100-ns units, so divide by 10 - // to convert to microseconds. - timestamp /= 10; - duration /= 10; - - // Sanity checks for checking if there is really something in the sample. - DWORD buf_count; - hr = output_sample->GetBufferCount(&buf_count); - if (FAILED(hr) || buf_count != 1) { - LOG(ERROR) << "Failed to get buffer count, or buffer count mismatch"; - return true; - } - - ScopedComPtr output_buffer; - hr = output_sample->GetBufferByIndex(0, output_buffer.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get buffer from sample"; - return true; - } - - - - if (use_dxva_) { - ScopedComPtr surface; - hr = MFGetService(output_buffer, MR_BUFFER_SERVICE, - IID_PPV_ARGS(surface.Receive())); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get surface from buffer"; - return true; - } - - if (!frame.get()) { - LOG(ERROR) << "Failed to allocate video frame for d3d texture"; - event_handler_->OnError(); - return true; - } - - // The reference is now in the VideoFrame. - surface.Detach(); - } else { - // Not DXVA. - VideoFrame::CreateFrame(info_.stream_info.surface_format, - info_.stream_info.surface_width, - info_.stream_info.surface_height, - TimeDelta::FromMicroseconds(timestamp), - TimeDelta::FromMicroseconds(duration), - &frame); - if (!frame.get()) { - LOG(ERROR) << "Failed to allocate video frame for yuv plane"; - event_handler_->OnError(); - return true; - } - uint8* src_y; - DWORD max_length, current_length; - HRESULT hr = output_buffer->Lock(&src_y, &max_length, ¤t_length); - if (FAILED(hr)) - return true; - uint8* dst_y = static_cast(frame->data(VideoFrame::kYPlane)); - - memcpy(dst_y, src_y, current_length); - CHECK(SUCCEEDED(output_buffer->Unlock())); - } - // TODO(jiesun): non-System memory case - event_handler_->ConsumeVideoFrame(frame); - return true; -} - -} // namespace media diff --git a/media/mf/mft_h264_decoder.h b/media/mf/mft_h264_decoder.h deleted file mode 100644 index 57c9e9f..0000000 --- a/media/mf/mft_h264_decoder.h +++ /dev/null @@ -1,97 +0,0 @@ -// 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. -// -// MFT H.264 decoder. - -#ifndef MEDIA_MF_MFT_H264_DECODER_H_ -#define MEDIA_MF_MFT_H264_DECODER_H_ - -#include "build/build_config.h" // For OS_WIN. - -#if defined(OS_WIN) - -#include -#include -#include - -#include "base/gtest_prod_util.h" -#include "base/scoped_comptr_win.h" -#include "media/video/video_decode_engine.h" - -class MessageLoop; - -namespace media { - -class MftH264Decoder : public media::VideoDecodeEngine { - public: - typedef enum { - kUninitialized, // un-initialized. - kNormal, // normal playing state. - kFlushing, // upon received Flush(), before FlushDone() - kEosDrain, // upon input EOS received. - kStopped, // upon output EOS received. - } State; - - explicit MftH264Decoder(bool use_dxva, HWND draw_window); - ~MftH264Decoder(); - virtual void Initialize(MessageLoop* message_loop, - VideoDecodeEngine::EventHandler* event_handler, - VideoDecodeContext* context, - const VideoCodecConfig& config); - virtual void Uninitialize(); - virtual void Flush(); - virtual void Seek(); - virtual void ConsumeVideoSample(scoped_refptr buffer); - virtual void ProduceVideoFrame(scoped_refptr frame); - - bool use_dxva() const { return use_dxva_; } - IDirect3DDevice9* device() const { return device_.get(); } - State state() const { return state_; } - - private: - friend class MftH264DecoderTest; - FRIEND_TEST_ALL_PREFIXES(MftH264DecoderTest, LibraryInit); - - // TODO(jiesun): Find a way to move all these to GpuVideoService.. - static bool StartupComLibraries(); - static void ShutdownComLibraries(); - bool CreateD3DDevManager(); - - bool InitInternal(); - bool InitDecoder(); - bool CheckDecoderDxvaSupport(); - bool SetDecoderMediaTypes(); - bool SetDecoderInputMediaType(); - bool SetDecoderOutputMediaType(const GUID subtype); - bool SendMFTMessage(MFT_MESSAGE_TYPE msg); - bool GetStreamsInfoAndBufferReqs(); - - bool DoDecode(); - - - bool use_dxva_; - - ScopedComPtr d3d9_; - ScopedComPtr device_; - ScopedComPtr device_manager_; - HWND draw_window_; - ScopedComPtr decoder_; - - MFT_INPUT_STREAM_INFO input_stream_info_; - MFT_OUTPUT_STREAM_INFO output_stream_info_; - - State state_; - - VideoDecodeEngine::EventHandler* event_handler_; - VideoCodecConfig config_; - VideoCodecInfo info_; - - DISALLOW_COPY_AND_ASSIGN(MftH264Decoder); -}; - -} // namespace media - -#endif // defined(OS_WIN) - -#endif // MEDIA_MF_MFT_H264_DECODER_H_ diff --git a/media/mf/mft_h264_decoder_example.cc b/media/mf/mft_h264_decoder_example.cc deleted file mode 100644 index b5b6b10..0000000 --- a/media/mf/mft_h264_decoder_example.cc +++ /dev/null @@ -1,421 +0,0 @@ -// 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. -// -// Demonstrates the use of MftH264Decoder. - -#include - -#include - -#include -#include - -#include "base/at_exit.h" -#include "base/command_line.h" -#include "base/file_path.h" -#include "base/logging.h" -#include "base/message_loop.h" -#include "base/scoped_comptr_win.h" -#include "base/scoped_ptr.h" -#include "base/time.h" -#include "media/base/data_buffer.h" -#include "media/base/media.h" -#include "media/base/video_frame.h" -#include "media/base/yuv_convert.h" -#include "media/ffmpeg/ffmpeg_common.h" -#include "media/ffmpeg/file_protocol.h" -#include "media/mf/file_reader_util.h" -#include "media/mf/mft_h264_decoder.h" - -using base::AtExitManager; -using base::Time; -using base::TimeDelta; -using media::Buffer; -using media::DataBuffer; -using media::FFmpegFileReader; -using media::MftH264Decoder; -using media::VideoCodecConfig; -using media::VideoCodecInfo; -using media::VideoDecodeEngine; -using media::VideoFrame; -using media::VideoStreamInfo; - -namespace { - -const wchar_t* const kWindowClass = L"Chrome_H264_MFT"; -const wchar_t* const kWindowTitle = L"H264_MFT"; -const int kWindowStyleFlags = (WS_OVERLAPPEDWINDOW | WS_VISIBLE) & - ~(WS_MAXIMIZEBOX | WS_THICKFRAME); - -void usage() { - static char* usage_msg = - "Usage: mft_h264_decoder [--enable-dxva] [--render] --input-file=FILE\n" - "enable-dxva: Enables hardware accelerated decoding\n" - "render: Render to window\n" - "During rendering, press spacebar to skip forward at least 5 seconds.\n" - "To display this message: mft_h264_decoder --help"; - fprintf(stderr, "%s\n", usage_msg); -} - -static bool InitFFmpeg() { - if (!media::InitializeMediaLibrary(FilePath())) - return false; - avcodec_init(); - av_register_all(); - av_register_protocol2(&kFFmpegFileProtocol, sizeof(kFFmpegFileProtocol)); - return true; -} - -// Creates a window with the given width and height. -// Returns: A handle to the window on success, NULL otherwise. -static HWND CreateDrawWindow(int width, int height) { - WNDCLASS window_class = {0}; - window_class.lpszClassName = kWindowClass; - window_class.hInstance = NULL; - window_class.hbrBackground = 0; - window_class.lpfnWndProc = DefWindowProc; - window_class.hCursor = 0; - - RegisterClass(&window_class); - - HWND window = CreateWindow(kWindowClass, - kWindowTitle, - kWindowStyleFlags, - 100, - 100, - width, - height, - NULL, - NULL, - NULL, - NULL); - if (window == NULL) { - LOG(ERROR) << "Failed to create window"; - return NULL; - } - RECT rect; - rect.left = 0; - rect.right = width; - rect.top = 0; - rect.bottom = height; - AdjustWindowRect(&rect, kWindowStyleFlags, FALSE); - MoveWindow(window, 0, 0, rect.right - rect.left, rect.bottom - rect.top, - TRUE); - return window; -} - -class WindowObserver : public base::MessagePumpWin::Observer { - public: - WindowObserver(FFmpegFileReader* reader, MftH264Decoder* decoder) - : reader_(reader), - decoder_(decoder) { - } - - virtual void WillProcessMessage(const MSG& msg) { - if (msg.message == WM_CHAR && msg.wParam == ' ') { - // Seek forward 5 seconds. - decoder_->Flush(); - reader_->SeekForward(5000000); - } - } - - virtual void DidProcessMessage(const MSG& msg) { - } - - private: - FFmpegFileReader* reader_; - MftH264Decoder* decoder_; -}; - -class MftH264DecoderHandler - : public VideoDecodeEngine::EventHandler, - public base::RefCountedThreadSafe { - public: - MftH264DecoderHandler() : frames_read_(0), frames_decoded_(0) { - memset(&info_, 0, sizeof(info_)); - } - virtual ~MftH264DecoderHandler() {} - virtual void OnInitializeComplete(const VideoCodecInfo& info) { - info_ = info; - } - virtual void OnUninitializeComplete() { - } - virtual void OnFlushComplete() { - } - virtual void OnSeekComplete() {} - virtual void OnError() {} - virtual void OnFormatChange(VideoStreamInfo stream_info) { - info_.stream_info = stream_info; - } - virtual void ProduceVideoSample(scoped_refptr buffer) { - if (reader_ && decoder_) { - scoped_refptr input; - reader_->Read(&input); - if (!input->IsEndOfStream()) - frames_read_++; - decoder_->ConsumeVideoSample(input); - } - } - virtual void ConsumeVideoFrame(scoped_refptr frame) { - if (frame.get()) { - if (frame->format() != VideoFrame::EMPTY) { - frames_decoded_++; - } - } - } - virtual void SetReader(FFmpegFileReader* reader) { - reader_ = reader; - } - virtual void SetDecoder(MftH264Decoder* decoder) { - decoder_= decoder; - } - virtual void DecodeSingleFrame() { - scoped_refptr frame; - decoder_->ProduceVideoFrame(frame); - } - virtual void Start() { - while (decoder_->state() != MftH264Decoder::kStopped) - DecodeSingleFrame(); - } - - VideoCodecInfo info_; - int frames_read_; - int frames_decoded_; - FFmpegFileReader* reader_; - MftH264Decoder* decoder_; -}; - -class RenderToWindowHandler : public MftH264DecoderHandler { - public: - RenderToWindowHandler(HWND window, MessageLoop* loop) - : MftH264DecoderHandler(), - window_(window), - loop_(loop), - has_output_(false) { - } - virtual ~RenderToWindowHandler() {} - bool RenderSoftwareFrame(scoped_refptr frame) { - int width = frame->width(); - int height = frame->height(); - - // Assume height does not change. - static uint8* rgb_frame = new uint8[height * frame->stride(0) * 4]; - uint8* frame_y = static_cast(frame->data(VideoFrame::kYPlane)); - uint8* frame_u = static_cast(frame->data(VideoFrame::kUPlane)); - uint8* frame_v = static_cast(frame->data(VideoFrame::kVPlane)); - media::ConvertYUVToRGB32(frame_y, frame_v, frame_u, rgb_frame, - width, height, - frame->stride(0), frame->stride(1), - 4 * frame->stride(0), media::YV12); - PAINTSTRUCT ps; - InvalidateRect(window_, NULL, TRUE); - HDC hdc = BeginPaint(window_, &ps); - BITMAPINFOHEADER hdr; - hdr.biSize = sizeof(BITMAPINFOHEADER); - hdr.biWidth = width; - hdr.biHeight = -height; // minus means top-down bitmap - hdr.biPlanes = 1; - hdr.biBitCount = 32; - hdr.biCompression = BI_RGB; // no compression - hdr.biSizeImage = 0; - hdr.biXPelsPerMeter = 1; - hdr.biYPelsPerMeter = 1; - hdr.biClrUsed = 0; - hdr.biClrImportant = 0; - int rv = StretchDIBits(hdc, 0, 0, width, height, 0, 0, width, height, - rgb_frame, reinterpret_cast(&hdr), - DIB_RGB_COLORS, SRCCOPY); - EndPaint(window_, &ps); - return rv != 0; - } - bool RenderD3dSurface(scoped_refptr frame) { - ScopedComPtr surface; - IDirect3DDevice9* device = decoder_->device(); - // TODO(hclam): Comment this since this file will be removed later. - // surface.Attach(static_cast(frame->d3d_texture(0))); - HRESULT hr; - hr = device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), - 1.0f, 0); - if (FAILED(hr)) { - LOG(ERROR) << "Device->Clear() failed"; - return false; - } - ScopedComPtr backbuffer; - hr = device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, - backbuffer.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Device->GetBackBuffer() failed"; - return false; - } - hr = device->StretchRect(surface.get(), NULL, backbuffer.get(), NULL, - D3DTEXF_NONE); - if (FAILED(hr)) { - LOG(ERROR) << "Device->StretchRect() failed"; - return false; - } - hr = device->Present(NULL, NULL, NULL, NULL); - if (FAILED(hr)) { - if (hr == E_FAIL) { - LOG(WARNING) << "Present() returned E_FAIL"; - } else { - static int frames_dropped = 0; - LOG(ERROR) << "Device->Present() failed " - << std::hex << std::showbase << hr; - if (++frames_dropped == 10) { - LOG(ERROR) << "Dropped too many frames, quitting"; - MessageLoopForUI::current()->QuitNow(); - return false; - } - } - } - return true; - } - virtual void ConsumeVideoFrame(scoped_refptr frame) { - has_output_ = true; - if (frame.get()) { - if (frame->format() != VideoFrame::EMPTY) { - frames_decoded_++; - loop_->PostDelayedTask( - FROM_HERE, - NewRunnableMethod(this, &RenderToWindowHandler::DecodeSingleFrame), - frame->GetDuration().InMilliseconds()); - bool success; - if (decoder_->use_dxva()) { - success = RenderD3dSurface(frame); - } else { - success = RenderSoftwareFrame(frame); - } - if (!success) { - LOG(ERROR) << "Render failed"; - loop_->QuitNow(); - } - } else { // if frame is type EMPTY, there will be no more frames. - loop_->QuitNow(); - } - } - } - virtual void DecodeSingleFrame() { - if (decoder_->state() != MftH264Decoder::kStopped) { - while (decoder_->state() != MftH264Decoder::kStopped && !has_output_) { - scoped_refptr frame; - decoder_->ProduceVideoFrame(frame); - } - if (decoder_->state() == MftH264Decoder::kStopped) - loop_->QuitNow(); - has_output_ = false; - } else { - loop_->QuitNow(); - } - } - virtual void Start() { - loop_->PostTask( - FROM_HERE, - NewRunnableMethod(this, &RenderToWindowHandler::DecodeSingleFrame)); - loop_->Run(); - } - - private: - HWND window_; - MessageLoop* loop_; - bool has_output_; -}; - -static int Run(bool use_dxva, bool render, const std::string& input_file) { - scoped_ptr reader(new FFmpegFileReader(input_file)); - if (reader.get() == NULL || !reader->Initialize()) { - LOG(ERROR) << "Failed to create/initialize reader"; - return -1; - } - int width = 0, height = 0; - if (!reader->GetWidth(&width) || !reader->GetHeight(&height)) { - LOG(WARNING) << "Failed to get width/height from reader"; - } - VideoCodecConfig config; - config.width = width; - config.height = height; - HWND window = NULL; - if (use_dxva || render) { - window = CreateDrawWindow(width, height); - if (!render) - ShowWindow(window, SW_HIDE); - if (window == NULL) { - LOG(ERROR) << "Failed to create window"; - return -1; - } - } - - scoped_ptr mft(new MftH264Decoder(use_dxva, window)); - if (!mft.get()) { - LOG(ERROR) << "Failed to create MFT"; - return -1; - } - - scoped_refptr handler; - if (render) - handler = new RenderToWindowHandler(window, MessageLoop::current()); - else - handler = new MftH264DecoderHandler(); - handler->SetDecoder(mft.get()); - handler->SetReader(reader.get()); - if (!handler.get()) { - LOG(ERROR) << "Failed to create handler"; - return -1; - } - - mft->Initialize(MessageLoop::current(), handler.get(), NULL, config); - scoped_ptr observer; - if (render) { - observer.reset(new WindowObserver(reader.get(), mft.get())); - MessageLoopForUI::current()->AddObserver(observer.get()); - } - - Time decode_start(Time::Now()); - handler->Start(); - TimeDelta decode_time = Time::Now() - decode_start; - - printf("All done, frames read: %d, frames decoded: %d\n", - handler->frames_read_, handler->frames_decoded_); - printf("Took %lldms\n", decode_time.InMilliseconds()); - if (window) - DestroyWindow(window); - return 0; -} - -} // namespace - -int main(int argc, char** argv) { - AtExitManager at_exit; - MessageLoopForUI message_loop; - CommandLine::Init(argc, argv); - if (argc == 1) { - fprintf(stderr, "Not enough arguments\n"); - usage(); - return -1; - } - const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); - if (cmd_line.HasSwitch("help")) { - usage(); - return -1; - } - bool use_dxva = cmd_line.HasSwitch("enable-dxva"); - bool render = cmd_line.HasSwitch("render"); - std::string input_file = cmd_line.GetSwitchValueASCII("input-file"); - if (input_file.empty()) { - fprintf(stderr, "No input file provided\n"); - usage(); - return -1; - } - printf("enable-dxva: %d\n", use_dxva); - printf("render: %d\n", render); - printf("input-file: %s\n", input_file.c_str()); - - if (!InitFFmpeg()) { - LOG(ERROR) << "InitFFMpeg() failed"; - return -1; - } - int ret = Run(use_dxva, render, input_file); - - printf("Done\n"); - return ret; -} diff --git a/media/mf/test/mft_h264_decoder_unittest.cc b/media/mf/test/mft_h264_decoder_unittest.cc deleted file mode 100644 index 11959f7..0000000 --- a/media/mf/test/mft_h264_decoder_unittest.cc +++ /dev/null @@ -1,460 +0,0 @@ -// 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/mf/file_reader_util.h" -#include "media/mf/mft_h264_decoder.h" -#include "media/video/video_decode_engine.h" -#include "testing/gtest/include/gtest/gtest.h" - -using base::TimeDelta; - -namespace media { - -static const int kDecoderMaxWidth = 1920; -static const int kDecoderMaxHeight = 1088; - -static HWND 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); - - HWND window = CreateWindow(kClassName, - kWindowTitle, - (WS_OVERLAPPEDWINDOW | WS_VISIBLE) & - ~(WS_MAXIMIZEBOX | WS_THICKFRAME), - 100, - 100, - width, - height, - NULL, - NULL, - NULL, - NULL); - if (window == NULL) { - LOG(ERROR) << "Failed to create window"; - return NULL; - } - return window; -} - -class BaseMftReader : public base::RefCountedThreadSafe { - public: - virtual ~BaseMftReader() {} - virtual void ReadCallback(scoped_refptr* 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* 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 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* 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 reader_; -}; - -class MftH264DecoderTest : public testing::Test { - public: - MftH264DecoderTest() {} - virtual ~MftH264DecoderTest() {} - - protected: - virtual void SetUp() {} - virtual void TearDown() {} -}; - -class SimpleMftH264DecoderHandler : public VideoDecodeEngine::EventHandler { - public: - SimpleMftH264DecoderHandler() - : 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 ~SimpleMftH264DecoderHandler() {} - 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) { - if (reader_.get() && decoder_) { - empty_buffer_callback_count_++; - scoped_refptr input; - reader_->ReadCallback(&input); - decoder_->ConsumeVideoSample(input); - } - } - virtual void ConsumeVideoFrame(scoped_refptr frame) { - fill_buffer_callback_count_++; - current_frame_ = frame; - } - void SetReader(scoped_refptr reader) { - reader_ = reader; - } - void SetDecoder(MftH264Decoder* 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 reader_; - MftH264Decoder* decoder_; - scoped_refptr current_frame_; -}; - -// A simple test case for init/deinit of MF/COM libraries. -TEST_F(MftH264DecoderTest, LibraryInit) { - EXPECT_TRUE(MftH264Decoder::StartupComLibraries()); - MftH264Decoder::ShutdownComLibraries(); -} - -TEST_F(MftH264DecoderTest, DecoderUninitializedAtFirst) { - scoped_ptr decoder(new MftH264Decoder(true, NULL)); - ASSERT_TRUE(decoder.get()); - EXPECT_EQ(MftH264Decoder::kUninitialized, decoder->state()); -} - -TEST_F(MftH264DecoderTest, DecoderInitMissingArgs) { - VideoCodecConfig config; - config.width = 800; - config.height = 600; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(NULL, NULL, NULL, config); - EXPECT_EQ(MftH264Decoder::kUninitialized, decoder->state()); -} - -TEST_F(MftH264DecoderTest, DecoderInitNoDxva) { - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 800; - config.height = 600; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(1, handler.init_count_); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - decoder->Uninitialize(); -} - -TEST_F(MftH264DecoderTest, DecoderInitDxva) { - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 800; - config.height = 600; - HWND hwnd = CreateDrawWindow(config.width, config.height); - ASSERT_TRUE(hwnd); - scoped_ptr decoder(new MftH264Decoder(true, hwnd)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(1, handler.init_count_); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - decoder->Uninitialize(); - DestroyWindow(hwnd); -} - -TEST_F(MftH264DecoderTest, DecoderUninit) { - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 800; - config.height = 600; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - decoder->Uninitialize(); - EXPECT_EQ(1, handler.uninit_count_); - EXPECT_EQ(MftH264Decoder::kUninitialized, decoder->state()); -} - -TEST_F(MftH264DecoderTest, UninitBeforeInit) { - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 800; - config.height = 600; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Uninitialize(); - EXPECT_EQ(0, handler.uninit_count_); -} - -TEST_F(MftH264DecoderTest, InitWithNegativeDimensions) { - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = -123; - config.height = -456; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - EXPECT_EQ(kDecoderMaxWidth, handler.info_.stream_info.surface_width); - EXPECT_EQ(kDecoderMaxHeight, handler.info_.stream_info.surface_height); - decoder->Uninitialize(); -} - -TEST_F(MftH264DecoderTest, InitWithTooHighDimensions) { - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = kDecoderMaxWidth + 1; - config.height = kDecoderMaxHeight + 1; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - EXPECT_EQ(kDecoderMaxWidth, handler.info_.stream_info.surface_width); - EXPECT_EQ(kDecoderMaxHeight, handler.info_.stream_info.surface_height); - decoder->Uninitialize(); -} - -TEST_F(MftH264DecoderTest, DrainOnEmptyBuffer) { - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 1024; - config.height = 768; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - scoped_refptr buffer(new DataBuffer(0)); - - // 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. - decoder->ConsumeVideoSample(buffer); - EXPECT_EQ(MftH264Decoder::kStopped, decoder->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()); - decoder->Uninitialize(); -} - -TEST_F(MftH264DecoderTest, NoOutputOnGarbageInput) { - // 100 samples of garbage. - const int kNumFrames = 100; - scoped_refptr reader(new FakeMftReader(kNumFrames)); - ASSERT_TRUE(reader.get()); - - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 1024; - config.height = 768; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - handler.SetReader(reader); - handler.SetDecoder(decoder.get()); - while (MftH264Decoder::kStopped != decoder->state()) { - scoped_refptr frame; - decoder->ProduceVideoFrame(frame); - } - - // 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); - decoder->Uninitialize(); -} - -TEST_F(MftH264DecoderTest, FlushAtStart) { - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 1024; - config.height = 768; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - decoder->Flush(); - - // Flush should succeed even if input/output are empty. - EXPECT_EQ(1, handler.flush_count_); - decoder->Uninitialize(); -} - -TEST_F(MftH264DecoderTest, NoFlushAtStopped) { - scoped_refptr reader(new FakeMftReader()); - ASSERT_TRUE(reader.get()); - - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 1024; - config.height = 768; - scoped_ptr decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - handler.SetReader(reader); - handler.SetDecoder(decoder.get()); - while (MftH264Decoder::kStopped != decoder->state()) { - scoped_refptr frame; - decoder->ProduceVideoFrame(frame); - } - EXPECT_EQ(0, handler.flush_count_); - int old_flush_count = handler.flush_count_; - decoder->Flush(); - EXPECT_EQ(old_flush_count, handler.flush_count_); - decoder->Uninitialize(); -} - -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; -} - -void DecodeValidVideo(const std::string& filename, int num_frames, bool dxva) { - scoped_refptr 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)); - - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 1; - config.height = 1; - HWND hwnd = CreateDrawWindow(config.width, config.height); - ASSERT_TRUE(hwnd); - scoped_ptr decoder(new MftH264Decoder(dxva, hwnd)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - handler.SetReader(reader); - handler.SetDecoder(decoder.get()); - while (MftH264Decoder::kStopped != decoder->state()) { - scoped_refptr frame; - decoder->ProduceVideoFrame(frame); - } - - // 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); - decoder->Uninitialize(); - DestroyWindow(hwnd); -} - -TEST_F(MftH264DecoderTest, DecodeValidVideoDxva) { - DecodeValidVideo("bear.1280x720.mp4", 82, true); -} - -TEST_F(MftH264DecoderTest, DecodeValidVideoNoDxva) { - DecodeValidVideo("bear.1280x720.mp4", 82, false); -} - -} // namespace media diff --git a/media/mf/test/run_all_unittests.cc b/media/mf/test/run_all_unittests.cc deleted file mode 100644 index 4126108..0000000 --- a/media/mf/test/run_all_unittests.cc +++ /dev/null @@ -1,26 +0,0 @@ -// 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/test/test_suite.h" -#include "media/base/media.h" -#include "media/ffmpeg/ffmpeg_common.h" -#include "media/ffmpeg/file_protocol.h" - -static bool InitFFmpeg() { - if (!media::InitializeMediaLibrary(FilePath())) - return false; - avcodec_init(); - av_register_all(); - av_register_protocol2(&kFFmpegFileProtocol, sizeof(kFFmpegFileProtocol)); - return true; -} - -int main(int argc, char** argv) { - if (!InitFFmpeg()) { - fprintf(stderr, "Failed to init ffmpeg\n"); - return -1; - } - return TestSuite(argc, argv).Run(); -} diff --git a/media/video/mft_h264_decode_engine.cc b/media/video/mft_h264_decode_engine.cc new file mode 100644 index 0000000..ea59ad6 --- /dev/null +++ b/media/video/mft_h264_decode_engine.cc @@ -0,0 +1,696 @@ +// 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 "media/video/mft_h264_decode_engine.h" + +#include +#include +#include +#include +// Placed after mfapi.h to avoid linking strmiids.lib for MR_BUFFER_SERVICE. +#include +#include +#include + +#include "base/time.h" +#include "base/message_loop.h" +#include "media/base/limits.h" +#include "media/video/video_decode_context.h" + +#pragma comment(lib, "dxva2.lib") +#pragma comment(lib, "d3d9.lib") +#pragma comment(lib, "mf.lib") +#pragma comment(lib, "mfplat.lib") + +using base::TimeDelta; + +namespace { + +// Creates an empty Media Foundation sample with no buffers. +static IMFSample* CreateEmptySample() { + HRESULT hr; + ScopedComPtr sample; + hr = MFCreateSample(sample.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Unable to create an empty sample"; + return NULL; + } + return sample.Detach(); +} + +// Creates a Media Foundation sample with one buffer of length |buffer_length| +// on a |align|-byte boundary. Alignment must be a perfect power of 2 or 0. +// If |align| is 0, then no alignment is specified. +static IMFSample* CreateEmptySampleWithBuffer(int buffer_length, int align) { + CHECK_GT(buffer_length, 0); + ScopedComPtr sample; + sample.Attach(CreateEmptySample()); + if (!sample.get()) + return NULL; + ScopedComPtr buffer; + HRESULT hr; + if (align == 0) { + // Note that MFCreateMemoryBuffer is same as MFCreateAlignedMemoryBuffer + // with the align argument being 0. + hr = MFCreateMemoryBuffer(buffer_length, buffer.Receive()); + } else { + hr = MFCreateAlignedMemoryBuffer(buffer_length, + align - 1, + buffer.Receive()); + } + if (FAILED(hr)) { + LOG(ERROR) << "Unable to create an empty buffer"; + return NULL; + } + hr = sample->AddBuffer(buffer.get()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to add empty buffer to sample"; + return NULL; + } + return sample.Detach(); +} + +// Creates a Media Foundation sample with one buffer containing a copy of the +// given Annex B stream data. +// If duration and sample time are not known, provide 0. +// |min_size| specifies the minimum size of the buffer (might be required by +// the decoder for input). The times here should be given in 100ns units. +// |alignment| specifies the buffer in the sample to be aligned. If no +// alignment is required, provide 0 or 1. +static IMFSample* CreateInputSample(const uint8* stream, int size, + int64 timestamp, int64 duration, + int min_size, int alignment) { + CHECK(stream); + CHECK_GT(size, 0); + ScopedComPtr sample; + sample.Attach(CreateEmptySampleWithBuffer(std::max(min_size, size), + alignment)); + if (!sample.get()) { + LOG(ERROR) << "Failed to create empty buffer for input"; + return NULL; + } + HRESULT hr; + if (duration > 0) { + hr = sample->SetSampleDuration(duration); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set sample duration"; + return NULL; + } + } + if (timestamp > 0) { + hr = sample->SetSampleTime(timestamp); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set sample time"; + return NULL; + } + } + ScopedComPtr buffer; + hr = sample->GetBufferByIndex(0, buffer.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get buffer in sample"; + return NULL; + } + DWORD max_length, current_length; + uint8* destination; + hr = buffer->Lock(&destination, &max_length, ¤t_length); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to lock buffer"; + return NULL; + } + CHECK_EQ(current_length, 0u); + CHECK_GE(static_cast(max_length), size); + memcpy(destination, stream, size); + CHECK(SUCCEEDED(buffer->Unlock())); + hr = buffer->SetCurrentLength(size); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set current length to " << size; + return NULL; + } + LOG(INFO) << __FUNCTION__ << " wrote " << size << " bytes into input sample"; + return sample.Detach(); +} + +const GUID ConvertVideoFrameFormatToGuid(media::VideoFrame::Format format) { + switch (format) { + case media::VideoFrame::NV12: + return MFVideoFormat_NV12; + case media::VideoFrame::YV12: + return MFVideoFormat_YV12; + default: + NOTREACHED() << "Unsupported VideoFrame format"; + return GUID_NULL; + } + NOTREACHED(); + return GUID_NULL; +} + +} // namespace + +namespace media { + +// public methods + +MftH264DecodeEngine::MftH264DecodeEngine(bool use_dxva) + : use_dxva_(use_dxva), + state_(kUninitialized), + event_handler_(NULL) { + memset(&input_stream_info_, 0, sizeof(input_stream_info_)); + memset(&output_stream_info_, 0, sizeof(output_stream_info_)); + memset(&config_, 0, sizeof(config_)); + memset(&info_, 0, sizeof(info_)); +} + +MftH264DecodeEngine::~MftH264DecodeEngine() { +} + +void MftH264DecodeEngine::Initialize( + MessageLoop* message_loop, + VideoDecodeEngine::EventHandler* event_handler, + VideoDecodeContext* context, + const VideoCodecConfig& config) { + DCHECK(!use_dxva_ || context); + if (state_ != kUninitialized) { + LOG(ERROR) << "Initialize: invalid state"; + return; + } + if (!message_loop || !event_handler) { + LOG(ERROR) << "MftH264DecodeEngine::Initialize: parameters cannot be NULL"; + return; + } + context_ = context; + config_ = config; + event_handler_ = event_handler; + info_.provides_buffers = true; + + if (use_dxva_) { + info_.stream_info.surface_format = VideoFrame::NV12; + // TODO(hclam): Need to correct this since this is not really GL texture. + // We should just remove surface_type from stream_info. + info_.stream_info.surface_type = VideoFrame::TYPE_GL_TEXTURE; + } else { + info_.stream_info.surface_format = VideoFrame::YV12; + info_.stream_info.surface_type = VideoFrame::TYPE_SYSTEM_MEMORY; + } + + // codec_info.stream_info_.surface_width_/height_ are initialized + // in InitInternal(). + info_.success = InitInternal(); + if (info_.success) { + state_ = kNormal; + AllocFramesFromContext(); + } else { + LOG(ERROR) << "MftH264DecodeEngine::Initialize failed"; + event_handler_->OnInitializeComplete(info_); + } +} + +void MftH264DecodeEngine::Uninitialize() { + if (state_ == kUninitialized) { + LOG(ERROR) << "Uninitialize: invalid state"; + return; + } + + // TODO(hclam): Call ShutdownComLibraries only after MFT is released. + decode_engine_.Release(); + ShutdownComLibraries(); + state_ = kUninitialized; + event_handler_->OnUninitializeComplete(); +} + +void MftH264DecodeEngine::Flush() { + if (state_ != kNormal) { + LOG(ERROR) << "Flush: invalid state"; + return; + } + state_ = kFlushing; + if (!SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH)) { + LOG(WARNING) << "MftH264DecodeEngine::Flush failed to send message"; + } + state_ = kNormal; + event_handler_->OnFlushComplete(); +} + +void MftH264DecodeEngine::Seek() { + if (state_ != kNormal) { + LOG(ERROR) << "Seek: invalid state"; + return; + } + + // TODO(hclam): Seriously the logic in VideoRendererBase is flawed that we + // have to perform the following hack to get playback going. + for (size_t i = 0; i < Limits::kMaxVideoFrames; ++i) { + event_handler_->ConsumeVideoFrame(output_frames_[0]); + } + + // Seek not implemented. + event_handler_->OnSeekComplete(); +} + +void MftH264DecodeEngine::ConsumeVideoSample(scoped_refptr buffer) { + if (state_ == kUninitialized) { + LOG(ERROR) << "ConsumeVideoSample: invalid state"; + } + ScopedComPtr sample; + if (!buffer->IsEndOfStream()) { + sample.Attach( + CreateInputSample(buffer->GetData(), + buffer->GetDataSize(), + buffer->GetTimestamp().InMicroseconds() * 10, + buffer->GetDuration().InMicroseconds() * 10, + input_stream_info_.cbSize, + input_stream_info_.cbAlignment)); + if (!sample.get()) { + LOG(ERROR) << "Failed to create an input sample"; + } else { + if (FAILED(decode_engine_->ProcessInput(0, sample.get(), 0))) { + event_handler_->OnError(); + } + } + } else { + if (state_ != MftH264DecodeEngine::kEosDrain) { + // End of stream, send drain messages. + if (!SendMFTMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM) || + !SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN)) { + LOG(ERROR) << "Failed to send EOS / drain messages to MFT"; + event_handler_->OnError(); + } else { + state_ = MftH264DecodeEngine::kEosDrain; + } + } + } + DoDecode(); +} + +void MftH264DecodeEngine::ProduceVideoFrame(scoped_refptr frame) { + if (state_ == kUninitialized) { + LOG(ERROR) << "ProduceVideoFrame: invalid state"; + return; + } + event_handler_->ProduceVideoSample(NULL); +} + +// private methods + +// static +bool MftH264DecodeEngine::StartupComLibraries() { + HRESULT hr; + hr = CoInitializeEx(NULL, + COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if (FAILED(hr)) { + LOG(ERROR) << "CoInit fail"; + return false; + } + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + if (FAILED(hr)) { + LOG(ERROR) << "MFStartup fail"; + CoUninitialize(); + return false; + } + return true; +} + +// static +void MftH264DecodeEngine::ShutdownComLibraries() { + HRESULT hr; + hr = MFShutdown(); + if (FAILED(hr)) { + LOG(WARNING) << "Warning: MF failed to shutdown"; + } + CoUninitialize(); +} + +bool MftH264DecodeEngine::EnableDxva() { + IDirect3DDevice9* device = static_cast( + context_->GetDevice()); + ScopedComPtr device_manager; + UINT dev_manager_reset_token = 0; + HRESULT hr = DXVA2CreateDirect3DDeviceManager9(&dev_manager_reset_token, + device_manager.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Couldn't create D3D Device manager"; + return false; + } + + hr = device_manager->ResetDevice(device, dev_manager_reset_token); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to reset device"; + return false; + } + + hr = decode_engine_->ProcessMessage( + MFT_MESSAGE_SET_D3D_MANAGER, + reinterpret_cast(device_manager.get())); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set D3D9 device manager to decoder " + << std::hex << hr; + return false; + } + + return true; +} + +bool MftH264DecodeEngine::InitInternal() { + if (!StartupComLibraries()) + return false; + if (!InitDecodeEngine()) + return false; + if (!GetStreamsInfoAndBufferReqs()) + return false; + return SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING); +} + +bool MftH264DecodeEngine::InitDecodeEngine() { + // TODO(jiesun): use MFEnum to get decoder CLSID. + HRESULT hr = CoCreateInstance(__uuidof(CMSH264DecoderMFT), + NULL, + CLSCTX_INPROC_SERVER, + __uuidof(IMFTransform), + reinterpret_cast( + decode_engine_.Receive())); + if (FAILED(hr) || !decode_engine_.get()) { + LOG(ERROR) << "CoCreateInstance failed " << std::hex << std::showbase << hr; + return false; + } + if (!CheckDecodeEngineDxvaSupport()) + return false; + if (use_dxva_ && !EnableDxva()) + return false; + return SetDecodeEngineMediaTypes(); +} + +void MftH264DecodeEngine::AllocFramesFromContext() { + if (!use_dxva_) + return; + + // TODO(imcheng): Pass in an actual task. (From EventHandler?) + context_->ReleaseAllVideoFrames(); + output_frames_.clear(); + context_->AllocateVideoFrames( + 1, info_.stream_info.surface_width, info_.stream_info.surface_height, + VideoFrame::RGBA, &output_frames_, + NewRunnableMethod(this, &MftH264DecodeEngine::OnAllocFramesDone)); +} + +void MftH264DecodeEngine::OnAllocFramesDone() { + event_handler_->OnInitializeComplete(info_); +} + +bool MftH264DecodeEngine::CheckDecodeEngineDxvaSupport() { + ScopedComPtr attributes; + HRESULT hr = decode_engine_->GetAttributes(attributes.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Unlock: Failed to get attributes, hr = " + << std::hex << std::showbase << hr; + return false; + } + + UINT32 dxva; + hr = attributes->GetUINT32(MF_SA_D3D_AWARE, &dxva); + if (FAILED(hr) || !dxva) { + LOG(ERROR) << "Failed to get DXVA attr or decoder is not DXVA-aware, hr = " + << std::hex << std::showbase << hr + << " this might not be the right decoder."; + return false; + } + return true; +} + +bool MftH264DecodeEngine::SetDecodeEngineMediaTypes() { + if (!SetDecodeEngineInputMediaType()) + return false; + return SetDecodeEngineOutputMediaType( + ConvertVideoFrameFormatToGuid(info_.stream_info.surface_format)); +} + +bool MftH264DecodeEngine::SetDecodeEngineInputMediaType() { + ScopedComPtr media_type; + HRESULT hr = MFCreateMediaType(media_type.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create empty media type object"; + return false; + } + + hr = media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + if (FAILED(hr)) { + LOG(ERROR) << "SetGUID for major type failed"; + return false; + } + + hr = media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); + if (FAILED(hr)) { + LOG(ERROR) << "SetGUID for subtype failed"; + return false; + } + + hr = decode_engine_->SetInputType(0, media_type.get(), 0); // No flags + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set decoder's input type"; + return false; + } + + return true; +} + +bool MftH264DecodeEngine::SetDecodeEngineOutputMediaType(const GUID subtype) { + DWORD i = 0; + IMFMediaType* out_media_type; + bool found = false; + while (SUCCEEDED(decode_engine_->GetOutputAvailableType(0, i, + &out_media_type))) { + GUID out_subtype; + HRESULT hr = out_media_type->GetGUID(MF_MT_SUBTYPE, &out_subtype); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to GetGUID() on GetOutputAvailableType() " << i; + out_media_type->Release(); + continue; + } + if (out_subtype == subtype) { + hr = decode_engine_->SetOutputType(0, out_media_type, 0); // No flags + hr = MFGetAttributeSize(out_media_type, MF_MT_FRAME_SIZE, + reinterpret_cast(&info_.stream_info.surface_width), + reinterpret_cast(&info_.stream_info.surface_height)); + config_.width = info_.stream_info.surface_width; + config_.height = info_.stream_info.surface_height; + if (FAILED(hr)) { + LOG(ERROR) << "Failed to SetOutputType to |subtype| or obtain " + << "width/height " << std::hex << hr; + } else { + out_media_type->Release(); + return true; + } + } + i++; + out_media_type->Release(); + } + return false; +} + +bool MftH264DecodeEngine::SendMFTMessage(MFT_MESSAGE_TYPE msg) { + HRESULT hr = decode_engine_->ProcessMessage(msg, NULL); + return SUCCEEDED(hr); +} + +// Prints out info about the input/output streams, gets the minimum buffer sizes +// for input and output samples. +// The MFT will not allocate buffer for neither input nor output, so we have +// to do it ourselves and make sure they're the correct size. +// Exception is when dxva is enabled, the decoder will allocate output. +bool MftH264DecodeEngine::GetStreamsInfoAndBufferReqs() { + HRESULT hr = decode_engine_->GetInputStreamInfo(0, &input_stream_info_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get input stream info"; + return false; + } + LOG(INFO) << "Input stream info: "; + LOG(INFO) << "Max latency: " << input_stream_info_.hnsMaxLatency; + + // There should be three flags, one for requiring a whole frame be in a + // single sample, one for requiring there be one buffer only in a single + // sample, and one that specifies a fixed sample size. (as in cbSize) + LOG(INFO) << "Flags: " + << std::hex << std::showbase << input_stream_info_.dwFlags; + CHECK_EQ(input_stream_info_.dwFlags, 0x7u); + LOG(INFO) << "Min buffer size: " << input_stream_info_.cbSize; + LOG(INFO) << "Max lookahead: " << input_stream_info_.cbMaxLookahead; + LOG(INFO) << "Alignment: " << input_stream_info_.cbAlignment; + + hr = decode_engine_->GetOutputStreamInfo(0, &output_stream_info_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get output stream info"; + return false; + } + LOG(INFO) << "Output stream info: "; + // The flags here should be the same and mean the same thing, except when + // DXVA is enabled, there is an extra 0x100 flag meaning decoder will + // allocate its own sample. + LOG(INFO) << "Flags: " + << std::hex << std::showbase << output_stream_info_.dwFlags; + CHECK_EQ(output_stream_info_.dwFlags, use_dxva_ ? 0x107u : 0x7u); + LOG(INFO) << "Min buffer size: " << output_stream_info_.cbSize; + LOG(INFO) << "Alignment: " << output_stream_info_.cbAlignment; + + return true; +} + +bool MftH264DecodeEngine::DoDecode() { + if (state_ != kNormal && state_ != kEosDrain) { + LOG(ERROR) << "DoDecode: not in normal or drain state"; + return false; + } + scoped_refptr frame; + ScopedComPtr output_sample; + if (!use_dxva_) { + output_sample.Attach( + CreateEmptySampleWithBuffer(output_stream_info_.cbSize, + output_stream_info_.cbAlignment)); + if (!output_sample.get()) { + LOG(ERROR) << "GetSample: failed to create empty output sample"; + event_handler_->OnError(); + return false; + } + } + MFT_OUTPUT_DATA_BUFFER output_data_buffer; + memset(&output_data_buffer, 0, sizeof(output_data_buffer)); + output_data_buffer.dwStreamID = 0; + output_data_buffer.pSample = output_sample; + + DWORD status; + HRESULT hr = decode_engine_->ProcessOutput(0, // No flags + 1, // # of out streams to pull + &output_data_buffer, + &status); + + IMFCollection* events = output_data_buffer.pEvents; + if (events != NULL) { + LOG(INFO) << "Got events from ProcessOuput, but discarding"; + events->Release(); + } + + if (FAILED(hr)) { + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { + hr = SetDecodeEngineOutputMediaType( + ConvertVideoFrameFormatToGuid(info_.stream_info.surface_format)); + if (SUCCEEDED(hr)) { + // TODO(hclam): Need to fix this case. This happens when we have a + // format change. We have to resume decoding only after we have + // allocated a new set of video frames. + // AllocFramesFromContext(); + // event_handler_->OnFormatChange(info_.stream_info); + event_handler_->ProduceVideoSample(NULL); + return true; + } else { + event_handler_->OnError(); + return false; + } + } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + if (state_ == kEosDrain) { + // No more output from the decoder. Notify EOS and stop playback. + scoped_refptr frame; + VideoFrame::CreateEmptyFrame(&frame); + event_handler_->ConsumeVideoFrame(frame); + state_ = MftH264DecodeEngine::kStopped; + return false; + } + event_handler_->ProduceVideoSample(NULL); + return true; + } else { + LOG(ERROR) << "Unhandled error in DoDecode()"; + state_ = MftH264DecodeEngine::kStopped; + event_handler_->OnError(); + return false; + } + } + + // We succeeded in getting an output sample. + if (use_dxva_) { + // For DXVA we didn't provide the sample, i.e. output_sample was NULL. + output_sample.Attach(output_data_buffer.pSample); + } + if (!output_sample.get()) { + LOG(ERROR) << "ProcessOutput succeeded, but did not get a sample back"; + event_handler_->OnError(); + return true; + } + + int64 timestamp = 0, duration = 0; + if (FAILED(output_sample->GetSampleTime(×tamp)) || + FAILED(output_sample->GetSampleDuration(&duration))) { + LOG(WARNING) << "Failed to get timestamp/duration from output"; + } + + // The duration and timestamps are in 100-ns units, so divide by 10 + // to convert to microseconds. + timestamp /= 10; + duration /= 10; + + // Sanity checks for checking if there is really something in the sample. + DWORD buf_count; + hr = output_sample->GetBufferCount(&buf_count); + if (FAILED(hr) || buf_count != 1) { + LOG(ERROR) << "Failed to get buffer count, or buffer count mismatch"; + return true; + } + + ScopedComPtr output_buffer; + hr = output_sample->GetBufferByIndex(0, output_buffer.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get buffer from sample"; + return true; + } + if (use_dxva_) { + ScopedComPtr surface; + hr = MFGetService(output_buffer, MR_BUFFER_SERVICE, + IID_PPV_ARGS(surface.Receive())); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get surface from buffer"; + return true; + } + // Since we only allocated 1 frame from context. + // TODO(imcheng): Detect error. + output_frames_[0]->SetTimestamp(TimeDelta::FromMicroseconds(timestamp)); + output_frames_[0]->SetDuration(TimeDelta::FromMicroseconds(duration)); + context_->UploadToVideoFrame( + surface.get(), output_frames_[0], + NewRunnableMethod(this, &MftH264DecodeEngine::OnUploadVideoFrameDone, + surface, output_frames_[0])); + return true; + } else { + // TODO(hclam): Remove this branch. + // Not DXVA. + VideoFrame::CreateFrame(info_.stream_info.surface_format, + info_.stream_info.surface_width, + info_.stream_info.surface_height, + TimeDelta::FromMicroseconds(timestamp), + TimeDelta::FromMicroseconds(duration), + &frame); + if (!frame.get()) { + LOG(ERROR) << "Failed to allocate video frame for yuv plane"; + event_handler_->OnError(); + return true; + } + uint8* src_y; + DWORD max_length, current_length; + HRESULT hr = output_buffer->Lock(&src_y, &max_length, ¤t_length); + if (FAILED(hr)) + return true; + uint8* dst_y = static_cast(frame->data(VideoFrame::kYPlane)); + + memcpy(dst_y, src_y, current_length); + CHECK(SUCCEEDED(output_buffer->Unlock())); + event_handler_->ConsumeVideoFrame(frame); + return true; + } +} + +void MftH264DecodeEngine::OnUploadVideoFrameDone( + ScopedComPtr surface, + scoped_refptr frame) { + // After this method is exited the reference to surface is released. + event_handler_->ConsumeVideoFrame(frame); +} + +} // namespace media + +DISABLE_RUNNABLE_METHOD_REFCOUNT(media::MftH264DecodeEngine); diff --git a/media/video/mft_h264_decode_engine.h b/media/video/mft_h264_decode_engine.h new file mode 100644 index 0000000..e13dce9 --- /dev/null +++ b/media/video/mft_h264_decode_engine.h @@ -0,0 +1,102 @@ +// 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. +// +// MFT H.264 decode engine. + +#ifndef MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_H_ +#define MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_H_ + +// TODO(imcheng): Get rid of this header by: +// - forward declaring IMFTransform and its IID as in +// mft_h264_decode_engine_context.h +// - turning the general SendMFTMessage method into specific methods +// (SendFlushMessage, SendDrainMessage, etc.) to avoid having +// MFT_MESSAGE_TYPE in here +#include + +#include "base/gtest_prod_util.h" +#include "base/scoped_comptr_win.h" +#include "media/video/video_decode_engine.h" + +struct IDirect3DSurface9; +extern "C" const GUID IID_IDirect3DSurface9; + +class MessageLoop; + +namespace media { + +class VideoDecodeContext; + +class MftH264DecodeEngine : public media::VideoDecodeEngine { + public: + typedef enum { + kUninitialized, // un-initialized. + kNormal, // normal playing state. + kFlushing, // upon received Flush(), before FlushDone() + kEosDrain, // upon input EOS received. + kStopped, // upon output EOS received. + } State; + + explicit MftH264DecodeEngine(bool use_dxva); + virtual ~MftH264DecodeEngine(); + + // VideoDecodeEngine implementation. + virtual void Initialize(MessageLoop* message_loop, + media::VideoDecodeEngine::EventHandler* event_handler, + VideoDecodeContext* context, + const VideoCodecConfig& config); + virtual void Uninitialize(); + virtual void Flush(); + virtual void Seek(); + virtual void ConsumeVideoSample(scoped_refptr buffer); + virtual void ProduceVideoFrame(scoped_refptr frame); + + bool use_dxva() const { return use_dxva_; } + State state() const { return state_; } + + private: + friend class MftH264DecodeEngineTest; + FRIEND_TEST_ALL_PREFIXES(MftH264DecodeEngineTest, LibraryInit); + + // TODO(jiesun): Find a way to move all these to GpuVideoService.. + static bool StartupComLibraries(); + static void ShutdownComLibraries(); + bool EnableDxva(); + + bool InitInternal(); + bool InitDecodeEngine(); + void AllocFramesFromContext(); + bool CheckDecodeEngineDxvaSupport(); + bool SetDecodeEngineMediaTypes(); + bool SetDecodeEngineInputMediaType(); + bool SetDecodeEngineOutputMediaType(const GUID subtype); + bool SendMFTMessage(MFT_MESSAGE_TYPE msg); + bool GetStreamsInfoAndBufferReqs(); + bool DoDecode(); + void OnAllocFramesDone(); + void OnUploadVideoFrameDone( + ScopedComPtr surface, + scoped_refptr frame); + + bool use_dxva_; + ScopedComPtr decode_engine_; + + MFT_INPUT_STREAM_INFO input_stream_info_; + MFT_OUTPUT_STREAM_INFO output_stream_info_; + + State state_; + + VideoDecodeEngine::EventHandler* event_handler_; + VideoCodecConfig config_; + VideoCodecInfo info_; + + VideoDecodeContext* context_; + std::vector > output_frames_; + + DISALLOW_COPY_AND_ASSIGN(MftH264DecodeEngine); +}; + +} // namespace media + +#endif // MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_H_ diff --git a/media/video/mft_h264_decode_engine_context.cc b/media/video/mft_h264_decode_engine_context.cc deleted file mode 100644 index 1759ced..0000000 --- a/media/video/mft_h264_decode_engine_context.cc +++ /dev/null @@ -1,179 +0,0 @@ -// 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 "media/video/mft_h264_decode_engine_context.h" - -#include -#include - -#include - -#include "base/task.h" -#include "media/base/callback.h" - -#pragma comment(lib, "dxva2.lib") -#pragma comment(lib, "d3d9.lib") - -using base::TimeDelta; - -namespace media { - -static D3DFORMAT VideoFrameToD3DFormat(VideoFrame::Format format) { - switch (format) { - case VideoFrame::RGB555: - return D3DFMT_X1R5G5B5; - case VideoFrame::RGB565: - return D3DFMT_R5G6B5; - case VideoFrame::RGB32: - return D3DFMT_X8R8G8B8; - case VideoFrame::RGBA: - return D3DFMT_A8R8G8B8; - default: - // Note that although there is a corresponding type for VideoFrame::RGB24 - // (D3DFMT_R8G8B8), it is not supported by render targets. - NOTREACHED() << "Unsupported format"; - return D3DFMT_UNKNOWN; - } -} - -static IDirect3DTexture9* GetTexture(scoped_refptr frame) { - return static_cast(frame->d3d_texture(0)); -} - -static void ReleaseTexture(scoped_refptr frame) { - GetTexture(frame)->Release(); -} - -static void ReleaseTextures( - const std::vector >& frames) { - std::for_each(frames.begin(), frames.end(), ReleaseTexture); -} - -MftH264DecodeEngineContext::MftH264DecodeEngineContext(HWND device_window) - : initialized_(false), - device_window_(device_window), - d3d9_(NULL), - device_(NULL) { - DCHECK(device_window); -} - -MftH264DecodeEngineContext::~MftH264DecodeEngineContext() { -} - -// TODO(imcheng): This should set the success variable once the API is -// finalized. -void MftH264DecodeEngineContext::Initialize(Task* task) { - AutoTaskRunner runner(task); - if (initialized_) - return; - d3d9_ = Direct3DCreate9(D3D_SDK_VERSION); - if (!d3d9_) { - LOG(ERROR) << "Direct3DCreate9 failed"; - return; - } - - D3DPRESENT_PARAMETERS present_params = {0}; - present_params.BackBufferWidth = 0; - present_params.BackBufferHeight = 0; - present_params.BackBufferFormat = D3DFMT_UNKNOWN; - present_params.BackBufferCount = 1; - present_params.SwapEffect = D3DSWAPEFFECT_DISCARD; - present_params.hDeviceWindow = device_window_; - present_params.Windowed = TRUE; - present_params.Flags = D3DPRESENTFLAG_VIDEO; - present_params.FullScreen_RefreshRateInHz = 0; - present_params.PresentationInterval = 0; - - HRESULT hr = d3d9_->CreateDevice(D3DADAPTER_DEFAULT, - D3DDEVTYPE_HAL, - device_window_, - (D3DCREATE_HARDWARE_VERTEXPROCESSING | - D3DCREATE_MULTITHREADED), - &present_params, - device_.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "CreateDevice failed " << std::hex << hr; - return; - } - initialized_ = true; -} - -void* MftH264DecodeEngineContext::GetDevice() { - return device_.get(); -} - -void MftH264DecodeEngineContext::AllocateVideoFrames( - int n, size_t width, size_t height, VideoFrame::Format format, - std::vector >* frames, - Task* task) { - DCHECK(initialized_); - DCHECK_GT(n, 0); - DCHECK(frames); - - AutoTaskRunner runner(task); - D3DFORMAT d3d_format = VideoFrameToD3DFormat(format); - std::vector > temp_frames; - temp_frames.reserve(n); - HRESULT hr; - for (int i = 0; i < n; i++) { - IDirect3DTexture9* texture = NULL; - hr = device_->CreateTexture(width, height, 1, D3DUSAGE_RENDERTARGET, - d3d_format, D3DPOOL_DEFAULT, &texture, NULL); - if (FAILED(hr)) { - LOG(ERROR) << "CreateTexture " << i << " failed " << std::hex << hr; - ReleaseTextures(temp_frames); - return; - } - VideoFrame::D3dTexture texture_array[VideoFrame::kMaxPlanes] = - { texture, texture, texture }; - scoped_refptr texture_frame; - VideoFrame::CreateFrameD3dTexture(format, width, height, texture_array, - TimeDelta(), TimeDelta(), &texture_frame); - if (!texture_frame.get()) { - LOG(ERROR) << "CreateFrameD3dTexture " << i << " failed"; - texture->Release(); - ReleaseTextures(temp_frames); - return; - } - temp_frames.push_back(texture_frame); - } - frames->assign(temp_frames.begin(), temp_frames.end()); - managed_frames_.insert(managed_frames_.end(), - temp_frames.begin(), temp_frames.end()); -} - -bool MftH264DecodeEngineContext::UploadToVideoFrame( - void* source, scoped_refptr frame) { - DCHECK(initialized_); - DCHECK(source); - DCHECK(frame.get()); - - IDirect3DSurface9* surface = static_cast(source); - IDirect3DTexture9* texture = GetTexture(frame); - ScopedComPtr top_surface; - HRESULT hr; - hr = texture->GetSurfaceLevel(0, top_surface.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "GetSurfaceLevel failed " << std::hex << hr; - return false; - } - hr = device_->StretchRect(surface, NULL, top_surface.get(), NULL, - D3DTEXF_NONE); - if (FAILED(hr)) { - LOG(ERROR) << "StretchRect failed " << std::hex << hr; - return false; - } - return true; -} - -void MftH264DecodeEngineContext::ReleaseAllVideoFrames() { - ReleaseTextures(managed_frames_); - managed_frames_.clear(); -} - -void MftH264DecodeEngineContext::Destroy(Task* task) { - AutoTaskRunner runner(task); -} - -} // namespace media diff --git a/media/video/mft_h264_decode_engine_context.h b/media/video/mft_h264_decode_engine_context.h deleted file mode 100644 index d33f06c..0000000 --- a/media/video/mft_h264_decode_engine_context.h +++ /dev/null @@ -1,70 +0,0 @@ -// 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. -// -// Video decode context for MftH264DecodeEngine. This context manages -// VideoFrame objects for the DXVA-enabled MFT H.264 decode engine, and -// converts its output (which is IDirect3DSurface9) into IDirect3DTexture9 -// (wrapped in a VideoFrame object), which will be compatible with ANGLE. - -#ifndef MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_CONTEXT_H_ -#define MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_CONTEXT_H_ - -#include - -#include "base/scoped_comptr_win.h" -#include "media/base/video_frame.h" -#include "media/video/video_decode_context.h" - -class Task; - -struct IDirect3D9; -extern "C" const GUID IID_IDirect3D9; -struct IDirect3DDevice9; -extern "C" const GUID IID_IDirect3DDevice9; - -namespace media { - -// TODO(imcheng): Make it implement VideoDecodeContext once the API -// is finalized. -class MftH264DecodeEngineContext { - public: - // Constructs a MftH264DecodeEngineContext with the D3D device attached - // to |device_window|. This device does not own the window, so the caller - // must destroy the window explicitly after the destruction of this object. - explicit MftH264DecodeEngineContext(HWND device_window); - virtual ~MftH264DecodeEngineContext(); - - // TODO(imcheng): Is this a part of the API? - virtual void Initialize(Task* task); - - // Gets the underlying IDirect3DDevice9. - virtual void* GetDevice(); - - // Allocates IDirect3DTexture9 objects wrapped in VideoFrame objects. - virtual void AllocateVideoFrames( - int n, size_t width, size_t height, VideoFrame::Format format, - std::vector >* frames, - Task* task); - - // TODO(imcheng): Make this follow the API once it is finalized. - // Uploads the decoded frame (IDirect3DSurface9) to a VideoFrame allocated - // by AllocateVideoFrames(). - virtual bool UploadToVideoFrame(void* source, - scoped_refptr frame); - virtual void ReleaseAllVideoFrames(); - virtual void Destroy(Task* task); - - bool initialized() const { return initialized_; } - - private: - bool initialized_; - HWND device_window_; - std::vector > managed_frames_; - ScopedComPtr d3d9_; - ScopedComPtr device_; -}; - -} // namespace media - -#endif // MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_CONTEXT_H_ 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 { + public: + virtual ~BaseMftReader() {} + virtual void ReadCallback(scoped_refptr* 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* 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) { + if (reader_.get() && decoder_) { + empty_buffer_callback_count_++; + scoped_refptr input; + reader_->ReadCallback(&input); + decoder_->ConsumeVideoSample(input); + } + } + virtual void ConsumeVideoFrame(scoped_refptr frame) { + fill_buffer_callback_count_++; + current_frame_ = frame; + } + void SetReader(scoped_refptr 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 reader_; + MftH264DecodeEngine* decoder_; + scoped_refptr 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* 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 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 reader) { + handler_->SetReader(reader); + handler_->SetDecodeEngine(engine_.get()); + while (MftH264DecodeEngine::kStopped != engine_->state()) { + scoped_refptr frame; + engine_->ProduceVideoFrame(frame); + } + } + void DecodeValidVideo(const std::string& filename, int num_frames, + bool dxva) { + scoped_refptr 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 handler_; + scoped_ptr engine_; + scoped_ptr 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(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 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 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 -- cgit v1.1