diff options
author | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-08 04:52:15 +0000 |
---|---|---|
committer | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-08 04:52:15 +0000 |
commit | 71e43a27d4c6018a9502753566d228cbf2dc2ca5 (patch) | |
tree | 656e03e34018265e322da21c9487dec3ad41eb56 | |
parent | 463d474661abf3f3ed431eb73457efa7a6946201 (diff) | |
download | chromium_src-71e43a27d4c6018a9502753566d228cbf2dc2ca5.zip chromium_src-71e43a27d4c6018a9502753566d228cbf2dc2ca5.tar.gz chromium_src-71e43a27d4c6018a9502753566d228cbf2dc2ca5.tar.bz2 |
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
21 files changed, 716 insertions, 1715 deletions
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 <d3d9.h> +#endif + void GpuVideoDecoder::OnChannelConnected(int32 peer_pid) { } @@ -105,7 +111,12 @@ void GpuVideoDecoder::ProduceVideoSample(scoped_refptr<Buffer> buffer) { } void GpuVideoDecoder::ConsumeVideoFrame(scoped_refptr<VideoFrame> 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<VideoFrame> 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 <d3d9.h> + +#include "media/base/video_frame.h" +#include "third_party/angle/src/libGLESv2/main.h" + +MftAngleVideoDevice::MftAngleVideoDevice() + : device_(reinterpret_cast<egl::Display*>( + eglGetCurrentDisplay())->getDevice()) { +} + +void* MftAngleVideoDevice::GetDevice() { + return device_; +} + +bool MftAngleVideoDevice::CreateVideoFrameFromGlTextures( + size_t width, size_t height, media::VideoFrame::Format format, + const std::vector<media::VideoFrame::GlTexture>& textures, + scoped_refptr<media::VideoFrame>* 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<media::VideoFrame>& 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<media::VideoFrame> 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<media::VideoFrame::GlTexture>& textures, + scoped_refptr<media::VideoFrame>* frame); + virtual void ReleaseVideoFrame( + const scoped_refptr<media::VideoFrame>& frame); + virtual bool UploadToVideoFrame(void* buffer, + scoped_refptr<media::VideoFrame> frame); + + private: + ScopedComPtr<IDirect3DDevice9, &IID_IDirect3DDevice9> 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 <cstring> - -#include <algorithm> - -#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<DataBuffer>* 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<DataBuffer>* 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 <string> - -#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<DataBuffer>* output) = 0; -}; - -class FFmpegFileReader : public FileReader { - public: - explicit FFmpegFileReader(const std::string& filename); - virtual ~FFmpegFileReader(); - virtual bool Initialize(); - virtual void Read(scoped_refptr<DataBuffer>* 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<DataBuffer>* output); - - std::string filename_; - AVFormatContext* format_context_; - AVCodecContext* codec_context_; - int target_stream_; - scoped_ptr<BitstreamConverter> 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_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 <cstdio> - -#include <string> - -#include <d3d9.h> -#include <dxva2api.h> - -#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<MftH264DecoderHandler> { - 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> buffer) { - if (reader_ && decoder_) { - scoped_refptr<DataBuffer> input; - reader_->Read(&input); - if (!input->IsEndOfStream()) - frames_read_++; - decoder_->ConsumeVideoSample(input); - } - } - virtual void ConsumeVideoFrame(scoped_refptr<VideoFrame> 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<VideoFrame> 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<VideoFrame> 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<uint8*>(frame->data(VideoFrame::kYPlane)); - uint8* frame_u = static_cast<uint8*>(frame->data(VideoFrame::kUPlane)); - uint8* frame_v = static_cast<uint8*>(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<BITMAPINFO*>(&hdr), - DIB_RGB_COLORS, SRCCOPY); - EndPaint(window_, &ps); - return rv != 0; - } - bool RenderD3dSurface(scoped_refptr<VideoFrame> frame) { - ScopedComPtr<IDirect3DSurface9> surface; - IDirect3DDevice9* device = decoder_->device(); - // TODO(hclam): Comment this since this file will be removed later. - // surface.Attach(static_cast<IDirect3DSurface9*>(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<IDirect3DSurface9> 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<VideoFrame> 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<VideoFrame> 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<FFmpegFileReader> 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<MftH264Decoder> mft(new MftH264Decoder(use_dxva, window)); - if (!mft.get()) { - LOG(ERROR) << "Failed to create MFT"; - return -1; - } - - scoped_refptr<MftH264DecoderHandler> 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<WindowObserver> 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<BaseMftReader> { - public: - virtual ~BaseMftReader() {} - virtual void ReadCallback(scoped_refptr<DataBuffer>* input) = 0; -}; - -class FakeMftReader : public BaseMftReader { - public: - FakeMftReader() : frames_remaining_(20) {} - explicit FakeMftReader(int count) : frames_remaining_(count) {} - virtual ~FakeMftReader() {} - - // Provides garbage input to the decoder. - virtual void ReadCallback(scoped_refptr<DataBuffer>* input) { - if (frames_remaining_ > 0) { - int sz = 4096; - uint8* buf = new uint8[sz]; - memset(buf, 42, sz); - *input = new DataBuffer(buf, sz); - (*input)->SetDuration(base::TimeDelta::FromMicroseconds(5000)); - (*input)->SetTimestamp( - base::TimeDelta::FromMicroseconds( - 50000000 - frames_remaining_ * 10000)); - --frames_remaining_; - } else { - // Emulate end of stream on the last "frame". - *input = new DataBuffer(0); - } - } - int frames_remaining() const { return frames_remaining_; } - - private: - int frames_remaining_; -}; - -class FFmpegFileReaderWrapper : public BaseMftReader { - public: - FFmpegFileReaderWrapper() {} - virtual ~FFmpegFileReaderWrapper() {} - bool InitReader(const std::string& filename) { - reader_.reset(new FFmpegFileReader(filename)); - if (!reader_.get() || !reader_->Initialize()) { - reader_.reset(); - return false; - } - return true; - } - virtual void ReadCallback(scoped_refptr<DataBuffer>* input) { - if (reader_.get()) { - reader_->Read(input); - } - } - bool GetWidth(int* width) { - if (!reader_.get()) - return false; - return reader_->GetWidth(width); - } - bool GetHeight(int* height) { - if (!reader_.get()) - return false; - return reader_->GetHeight(height); - } - scoped_ptr<FFmpegFileReader> reader_; -}; - -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> buffer) { - if (reader_.get() && decoder_) { - empty_buffer_callback_count_++; - scoped_refptr<DataBuffer> input; - reader_->ReadCallback(&input); - decoder_->ConsumeVideoSample(input); - } - } - virtual void ConsumeVideoFrame(scoped_refptr<VideoFrame> frame) { - fill_buffer_callback_count_++; - current_frame_ = frame; - } - void SetReader(scoped_refptr<BaseMftReader> reader) { - reader_ = reader; - } - void 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<BaseMftReader> reader_; - MftH264Decoder* decoder_; - scoped_refptr<VideoFrame> 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<MftH264Decoder> 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<MftH264Decoder> 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<MftH264Decoder> 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<MftH264Decoder> 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<MftH264Decoder> 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<MftH264Decoder> 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<MftH264Decoder> 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<MftH264Decoder> 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<MftH264Decoder> decoder(new MftH264Decoder(false, NULL)); - ASSERT_TRUE(decoder.get()); - decoder->Initialize(&loop, &handler, NULL, config); - EXPECT_EQ(MftH264Decoder::kNormal, decoder->state()); - scoped_refptr<Buffer> 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<FakeMftReader> reader(new FakeMftReader(kNumFrames)); - ASSERT_TRUE(reader.get()); - - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 1024; - config.height = 768; - scoped_ptr<MftH264Decoder> 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<VideoFrame> 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<MftH264Decoder> 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<BaseMftReader> reader(new FakeMftReader()); - ASSERT_TRUE(reader.get()); - - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 1024; - config.height = 768; - scoped_ptr<MftH264Decoder> 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<VideoFrame> 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<FFmpegFileReaderWrapper> reader(new FFmpegFileReaderWrapper()); - ASSERT_TRUE(reader.get()); - FilePath path = GetVideoFilePath(filename); - ASSERT_TRUE(file_util::PathExists(path)); - ASSERT_TRUE(reader->InitReader(WideToASCII(path.value()))); - int actual_width; - int actual_height; - ASSERT_TRUE(reader->GetWidth(&actual_width)); - ASSERT_TRUE(reader->GetHeight(&actual_height)); - - MessageLoop loop; - SimpleMftH264DecoderHandler handler; - VideoCodecConfig config; - config.width = 1; - config.height = 1; - HWND hwnd = CreateDrawWindow(config.width, config.height); - ASSERT_TRUE(hwnd); - scoped_ptr<MftH264Decoder> 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<VideoFrame> 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/mf/mft_h264_decoder.cc b/media/video/mft_h264_decode_engine.cc index 2c1970d..ea59ad6 100644 --- a/media/mf/mft_h264_decoder.cc +++ b/media/video/mft_h264_decode_engine.cc @@ -2,7 +2,7 @@ // 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 "media/video/mft_h264_decode_engine.h" #include <d3d9.h> #include <dxva2api.h> @@ -15,6 +15,8 @@ #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") @@ -149,48 +151,42 @@ namespace media { // public methods -MftH264Decoder::MftH264Decoder(bool use_dxva, HWND draw_window) +MftH264DecodeEngine::MftH264DecodeEngine(bool use_dxva) : 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(&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_)); } -MftH264Decoder::~MftH264Decoder() { +MftH264DecodeEngine::~MftH264DecodeEngine() { } -void MftH264Decoder::Initialize( +void MftH264DecodeEngine::Initialize( MessageLoop* message_loop, VideoDecodeEngine::EventHandler* event_handler, VideoDecodeContext* context, const VideoCodecConfig& config) { - LOG(INFO) << "MftH264Decoder::Initialize"; + DCHECK(!use_dxva_ || context); if (state_ != kUninitialized) { LOG(ERROR) << "Initialize: invalid state"; return; } if (!message_loop || !event_handler) { - LOG(ERROR) << "MftH264Decoder::Initialize: parameters cannot be NULL"; + LOG(ERROR) << "MftH264DecodeEngine::Initialize: parameters cannot be NULL"; return; } - + context_ = context; 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; + // 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; @@ -202,58 +198,56 @@ void MftH264Decoder::Initialize( info_.success = InitInternal(); if (info_.success) { state_ = kNormal; - event_handler_->OnInitializeComplete(info_); + AllocFramesFromContext(); } else { - LOG(ERROR) << "MftH264Decoder::Initialize failed"; + LOG(ERROR) << "MftH264DecodeEngine::Initialize failed"; + event_handler_->OnInitializeComplete(info_); } } -void MftH264Decoder::Uninitialize() { - LOG(INFO) << "MftH264Decoder::Uninitialize"; +void MftH264DecodeEngine::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(); + // TODO(hclam): Call ShutdownComLibraries only after MFT is released. + decode_engine_.Release(); ShutdownComLibraries(); state_ = kUninitialized; event_handler_->OnUninitializeComplete(); } -void MftH264Decoder::Flush() { - LOG(INFO) << "MftH264Decoder::Flush"; +void MftH264DecodeEngine::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"; + LOG(WARNING) << "MftH264DecodeEngine::Flush failed to send message"; } state_ = kNormal; event_handler_->OnFlushComplete(); } -void MftH264Decoder::Seek() { +void MftH264DecodeEngine::Seek() { if (state_ != kNormal) { LOG(ERROR) << "Seek: invalid state"; return; } - LOG(INFO) << "MftH264Decoder::Seek"; + + // 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 MftH264Decoder::ConsumeVideoSample(scoped_refptr<Buffer> buffer) { - LOG(INFO) << "MftH264Decoder::ConsumeVideoSample"; +void MftH264DecodeEngine::ConsumeVideoSample(scoped_refptr<Buffer> buffer) { if (state_ == kUninitialized) { LOG(ERROR) << "ConsumeVideoSample: invalid state"; } @@ -269,39 +263,37 @@ void MftH264Decoder::ConsumeVideoSample(scoped_refptr<Buffer> buffer) { if (!sample.get()) { LOG(ERROR) << "Failed to create an input sample"; } else { - if (FAILED(decoder_->ProcessInput(0, sample.get(), 0))) { + if (FAILED(decode_engine_->ProcessInput(0, sample.get(), 0))) { event_handler_->OnError(); } } } else { - if (state_ != MftH264Decoder::kEosDrain) { + 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_ = MftH264Decoder::kEosDrain; + state_ = MftH264DecodeEngine::kEosDrain; } } } DoDecode(); } -void MftH264Decoder::ProduceVideoFrame(scoped_refptr<VideoFrame> frame) { - LOG(INFO) << "MftH264Decoder::ProduceVideoFrame"; +void MftH264DecodeEngine::ProduceVideoFrame(scoped_refptr<VideoFrame> frame) { if (state_ == kUninitialized) { LOG(ERROR) << "ProduceVideoFrame: invalid state"; return; } - scoped_refptr<Buffer> buffer; - event_handler_->ProduceVideoSample(buffer); + event_handler_->ProduceVideoSample(NULL); } // private methods // static -bool MftH264Decoder::StartupComLibraries() { +bool MftH264DecodeEngine::StartupComLibraries() { HRESULT hr; hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); @@ -320,7 +312,7 @@ bool MftH264Decoder::StartupComLibraries() { } // static -void MftH264Decoder::ShutdownComLibraries() { +void MftH264DecodeEngine::ShutdownComLibraries() { HRESULT hr; hr = MFShutdown(); if (FAILED(hr)) { @@ -329,100 +321,85 @@ void MftH264Decoder::ShutdownComLibraries() { 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()); +bool MftH264DecodeEngine::EnableDxva() { + IDirect3DDevice9* device = static_cast<IDirect3DDevice9*>( + context_->GetDevice()); + ScopedComPtr<IDirect3DDeviceManager9> device_manager; + UINT dev_manager_reset_token = 0; + HRESULT hr = DXVA2CreateDirect3DDeviceManager9(&dev_manager_reset_token, + device_manager.Receive()); if (FAILED(hr)) { - LOG(ERROR) << "Failed to create D3D Device"; + LOG(ERROR) << "Couldn't create D3D Device manager"; return false; } - UINT dev_manager_reset_token = 0; - hr = DXVA2CreateDirect3DDeviceManager9(&dev_manager_reset_token, - device_manager_.Receive()); + hr = device_manager->ResetDevice(device, dev_manager_reset_token); if (FAILED(hr)) { - LOG(ERROR) << "Couldn't create D3D Device manager"; + LOG(ERROR) << "Failed to reset device"; return false; } - hr = device_manager_->ResetDevice(device_.get(), - dev_manager_reset_token); + hr = decode_engine_->ProcessMessage( + MFT_MESSAGE_SET_D3D_MANAGER, + reinterpret_cast<ULONG_PTR>(device_manager.get())); if (FAILED(hr)) { - LOG(ERROR) << "Failed to set device to device manager"; + LOG(ERROR) << "Failed to set D3D9 device manager to decoder " + << std::hex << hr; return false; } + return true; } -bool MftH264Decoder::InitInternal() { +bool MftH264DecodeEngine::InitInternal() { if (!StartupComLibraries()) return false; - if (use_dxva_ && !CreateD3DDevManager()) - return false; - if (!InitDecoder()) + if (!InitDecodeEngine()) return false; if (!GetStreamsInfoAndBufferReqs()) return false; return SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING); } -bool MftH264Decoder::InitDecoder() { +bool MftH264DecodeEngine::InitDecodeEngine() { // TODO(jiesun): use MFEnum to get decoder CLSID. HRESULT hr = CoCreateInstance(__uuidof(CMSH264DecoderMFT), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMFTransform), - reinterpret_cast<void**>(decoder_.Receive())); - if (FAILED(hr) || !decoder_.get()) { + reinterpret_cast<void**>( + decode_engine_.Receive())); + if (FAILED(hr) || !decode_engine_.get()) { LOG(ERROR) << "CoCreateInstance failed " << std::hex << std::showbase << hr; return false; } - - if (!CheckDecoderDxvaSupport()) + if (!CheckDecodeEngineDxvaSupport()) + return false; + if (use_dxva_ && !EnableDxva()) return false; + return SetDecodeEngineMediaTypes(); +} - if (use_dxva_) { - hr = decoder_->ProcessMessage( - MFT_MESSAGE_SET_D3D_MANAGER, - reinterpret_cast<ULONG_PTR>(device_manager_.get())); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to set D3D9 device to decoder " << std::hex << hr; - return false; - } - } +void MftH264DecodeEngine::AllocFramesFromContext() { + if (!use_dxva_) + return; - return SetDecoderMediaTypes(); + // 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)); } -bool MftH264Decoder::CheckDecoderDxvaSupport() { +void MftH264DecodeEngine::OnAllocFramesDone() { + event_handler_->OnInitializeComplete(info_); +} + +bool MftH264DecodeEngine::CheckDecodeEngineDxvaSupport() { ScopedComPtr<IMFAttributes> attributes; - HRESULT hr = decoder_->GetAttributes(attributes.Receive()); + HRESULT hr = decode_engine_->GetAttributes(attributes.Receive()); if (FAILED(hr)) { LOG(ERROR) << "Unlock: Failed to get attributes, hr = " << std::hex << std::showbase << hr; @@ -440,14 +417,14 @@ bool MftH264Decoder::CheckDecoderDxvaSupport() { return true; } -bool MftH264Decoder::SetDecoderMediaTypes() { - if (!SetDecoderInputMediaType()) +bool MftH264DecodeEngine::SetDecodeEngineMediaTypes() { + if (!SetDecodeEngineInputMediaType()) return false; - return SetDecoderOutputMediaType(ConvertVideoFrameFormatToGuid( - info_.stream_info.surface_format)); + return SetDecodeEngineOutputMediaType( + ConvertVideoFrameFormatToGuid(info_.stream_info.surface_format)); } -bool MftH264Decoder::SetDecoderInputMediaType() { +bool MftH264DecodeEngine::SetDecodeEngineInputMediaType() { ScopedComPtr<IMFMediaType> media_type; HRESULT hr = MFCreateMediaType(media_type.Receive()); if (FAILED(hr)) { @@ -467,7 +444,7 @@ bool MftH264Decoder::SetDecoderInputMediaType() { return false; } - hr = decoder_->SetInputType(0, media_type.get(), 0); // No flags + 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; @@ -476,11 +453,12 @@ bool MftH264Decoder::SetDecoderInputMediaType() { return true; } -bool MftH264Decoder::SetDecoderOutputMediaType(const GUID subtype) { +bool MftH264DecodeEngine::SetDecodeEngineOutputMediaType(const GUID subtype) { DWORD i = 0; IMFMediaType* out_media_type; bool found = false; - while (SUCCEEDED(decoder_->GetOutputAvailableType(0, i, &out_media_type))) { + 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)) { @@ -489,7 +467,7 @@ bool MftH264Decoder::SetDecoderOutputMediaType(const GUID subtype) { continue; } if (out_subtype == subtype) { - hr = decoder_->SetOutputType(0, out_media_type, 0); // No flags + hr = decode_engine_->SetOutputType(0, out_media_type, 0); // No flags hr = MFGetAttributeSize(out_media_type, MF_MT_FRAME_SIZE, reinterpret_cast<UINT32*>(&info_.stream_info.surface_width), reinterpret_cast<UINT32*>(&info_.stream_info.surface_height)); @@ -509,8 +487,8 @@ bool MftH264Decoder::SetDecoderOutputMediaType(const GUID subtype) { return false; } -bool MftH264Decoder::SendMFTMessage(MFT_MESSAGE_TYPE msg) { - HRESULT hr = decoder_->ProcessMessage(msg, NULL); +bool MftH264DecodeEngine::SendMFTMessage(MFT_MESSAGE_TYPE msg) { + HRESULT hr = decode_engine_->ProcessMessage(msg, NULL); return SUCCEEDED(hr); } @@ -519,8 +497,8 @@ bool MftH264Decoder::SendMFTMessage(MFT_MESSAGE_TYPE msg) { // 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_); +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; @@ -538,7 +516,7 @@ bool MftH264Decoder::GetStreamsInfoAndBufferReqs() { LOG(INFO) << "Max lookahead: " << input_stream_info_.cbMaxLookahead; LOG(INFO) << "Alignment: " << input_stream_info_.cbAlignment; - hr = decoder_->GetOutputStreamInfo(0, &output_stream_info_); + hr = decode_engine_->GetOutputStreamInfo(0, &output_stream_info_); if (FAILED(hr)) { LOG(ERROR) << "Failed to get output stream info"; return false; @@ -556,7 +534,7 @@ bool MftH264Decoder::GetStreamsInfoAndBufferReqs() { return true; } -bool MftH264Decoder::DoDecode() { +bool MftH264DecodeEngine::DoDecode() { if (state_ != kNormal && state_ != kEosDrain) { LOG(ERROR) << "DoDecode: not in normal or drain state"; return false; @@ -579,10 +557,10 @@ bool MftH264Decoder::DoDecode() { 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); + 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) { @@ -592,10 +570,15 @@ bool MftH264Decoder::DoDecode() { if (FAILED(hr)) { if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { - hr = SetDecoderOutputMediaType(ConvertVideoFrameFormatToGuid( - info_.stream_info.surface_format)); + hr = SetDecodeEngineOutputMediaType( + ConvertVideoFrameFormatToGuid(info_.stream_info.surface_format)); if (SUCCEEDED(hr)) { - event_handler_->OnFormatChange(info_.stream_info); + // 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(); @@ -607,13 +590,14 @@ bool MftH264Decoder::DoDecode() { scoped_refptr<VideoFrame> frame; VideoFrame::CreateEmptyFrame(&frame); event_handler_->ConsumeVideoFrame(frame); - state_ = MftH264Decoder::kStopped; + state_ = MftH264DecodeEngine::kStopped; return false; } + event_handler_->ProduceVideoSample(NULL); return true; } else { LOG(ERROR) << "Unhandled error in DoDecode()"; - state_ = MftH264Decoder::kStopped; + state_ = MftH264DecodeEngine::kStopped; event_handler_->OnError(); return false; } @@ -655,27 +639,25 @@ bool MftH264Decoder::DoDecode() { LOG(ERROR) << "Failed to get buffer from sample"; return true; } - - - if (use_dxva_) { - ScopedComPtr<IDirect3DSurface9> surface; + ScopedComPtr<IDirect3DSurface9, &IID_IDirect3DSurface9> 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(); + // 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; - } - - // The reference is now in the VideoFrame. - surface.Detach(); } else { + // TODO(hclam): Remove this branch. // Not DXVA. VideoFrame::CreateFrame(info_.stream_info.surface_format, info_.stream_info.surface_width, @@ -697,10 +679,18 @@ bool MftH264Decoder::DoDecode() { memcpy(dst_y, src_y, current_length); CHECK(SUCCEEDED(output_buffer->Unlock())); + event_handler_->ConsumeVideoFrame(frame); + return true; } - // TODO(jiesun): non-System memory case +} + +void MftH264DecodeEngine::OnUploadVideoFrameDone( + ScopedComPtr<IDirect3DSurface9, &IID_IDirect3DSurface9> surface, + scoped_refptr<media::VideoFrame> frame) { + // After this method is exited the reference to surface is released. event_handler_->ConsumeVideoFrame(frame); - return true; } } // namespace media + +DISABLE_RUNNABLE_METHOD_REFCOUNT(media::MftH264DecodeEngine); diff --git a/media/mf/mft_h264_decoder.h b/media/video/mft_h264_decode_engine.h index 57c9e9f..e13dce9 100644 --- a/media/mf/mft_h264_decoder.h +++ b/media/video/mft_h264_decode_engine.h @@ -2,28 +2,33 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// MFT H.264 decoder. +// MFT H.264 decode engine. -#ifndef MEDIA_MF_MFT_H264_DECODER_H_ -#define MEDIA_MF_MFT_H264_DECODER_H_ +#ifndef MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_H_ +#define MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_H_ -#include "build/build_config.h" // For OS_WIN. - -#if defined(OS_WIN) - -#include <d3d9.h> -#include <dxva2api.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 <mfidl.h> #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 MftH264Decoder : public media::VideoDecodeEngine { +class VideoDecodeContext; + +class MftH264DecodeEngine : public media::VideoDecodeEngine { public: typedef enum { kUninitialized, // un-initialized. @@ -33,10 +38,12 @@ class MftH264Decoder : public media::VideoDecodeEngine { kStopped, // upon output EOS received. } State; - explicit MftH264Decoder(bool use_dxva, HWND draw_window); - ~MftH264Decoder(); + explicit MftH264DecodeEngine(bool use_dxva); + virtual ~MftH264DecodeEngine(); + + // VideoDecodeEngine implementation. virtual void Initialize(MessageLoop* message_loop, - VideoDecodeEngine::EventHandler* event_handler, + media::VideoDecodeEngine::EventHandler* event_handler, VideoDecodeContext* context, const VideoCodecConfig& config); virtual void Uninitialize(); @@ -46,37 +53,34 @@ class MftH264Decoder : public media::VideoDecodeEngine { virtual void ProduceVideoFrame(scoped_refptr<VideoFrame> 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); + 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 CreateD3DDevManager(); + bool EnableDxva(); bool InitInternal(); - bool InitDecoder(); - bool CheckDecoderDxvaSupport(); - bool SetDecoderMediaTypes(); - bool SetDecoderInputMediaType(); - bool SetDecoderOutputMediaType(const GUID subtype); + 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<IDirect3DSurface9, &IID_IDirect3DSurface9> surface, + scoped_refptr<media::VideoFrame> frame); bool use_dxva_; - - ScopedComPtr<IDirect3D9> d3d9_; - ScopedComPtr<IDirect3DDevice9> device_; - ScopedComPtr<IDirect3DDeviceManager9> device_manager_; - HWND draw_window_; - ScopedComPtr<IMFTransform> decoder_; + ScopedComPtr<IMFTransform> decode_engine_; MFT_INPUT_STREAM_INFO input_stream_info_; MFT_OUTPUT_STREAM_INFO output_stream_info_; @@ -87,11 +91,12 @@ class MftH264Decoder : public media::VideoDecodeEngine { VideoCodecConfig config_; VideoCodecInfo info_; - DISALLOW_COPY_AND_ASSIGN(MftH264Decoder); + VideoDecodeContext* context_; + std::vector<scoped_refptr<VideoFrame> > output_frames_; + + DISALLOW_COPY_AND_ASSIGN(MftH264DecodeEngine); }; } // namespace media -#endif // defined(OS_WIN) - -#endif // MEDIA_MF_MFT_H264_DECODER_H_ +#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 <algorithm> -#include <vector> - -#include <d3d9.h> - -#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<VideoFrame> frame) { - return static_cast<IDirect3DTexture9*>(frame->d3d_texture(0)); -} - -static void ReleaseTexture(scoped_refptr<VideoFrame> frame) { - GetTexture(frame)->Release(); -} - -static void ReleaseTextures( - const std::vector<scoped_refptr<VideoFrame> >& 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<scoped_refptr<VideoFrame> >* frames, - Task* task) { - DCHECK(initialized_); - DCHECK_GT(n, 0); - DCHECK(frames); - - AutoTaskRunner runner(task); - D3DFORMAT d3d_format = VideoFrameToD3DFormat(format); - std::vector<scoped_refptr<VideoFrame> > 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<VideoFrame> 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<VideoFrame> frame) { - DCHECK(initialized_); - DCHECK(source); - DCHECK(frame.get()); - - IDirect3DSurface9* surface = static_cast<IDirect3DSurface9*>(source); - IDirect3DTexture9* texture = GetTexture(frame); - ScopedComPtr<IDirect3DSurface9> 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 <vector> - -#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<scoped_refptr<VideoFrame> >* 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<VideoFrame> frame); - virtual void ReleaseAllVideoFrames(); - virtual void Destroy(Task* task); - - bool initialized() const { return initialized_; } - - private: - bool initialized_; - HWND device_window_; - std::vector<scoped_refptr<VideoFrame> > managed_frames_; - ScopedComPtr<IDirect3D9, &IID_IDirect3D9> d3d9_; - ScopedComPtr<IDirect3DDevice9, &IID_IDirect3DDevice9> 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<BaseMftReader> { + public: + virtual ~BaseMftReader() {} + virtual void ReadCallback(scoped_refptr<DataBuffer>* input) = 0; +}; + +class FakeMftReader : public BaseMftReader { + public: + FakeMftReader() : frames_remaining_(20) {} + explicit FakeMftReader(int count) : frames_remaining_(count) {} + virtual ~FakeMftReader() {} + + // Provides garbage input to the decoder. + virtual void ReadCallback(scoped_refptr<DataBuffer>* input) { + if (frames_remaining_ > 0) { + int sz = 4096; + uint8* buf = new uint8[sz]; + memset(buf, 42, sz); + *input = new DataBuffer(buf, sz); + (*input)->SetDuration(base::TimeDelta::FromMicroseconds(5000)); + (*input)->SetTimestamp( + base::TimeDelta::FromMicroseconds( + 50000000 - frames_remaining_ * 10000)); + --frames_remaining_; + } else { + // Emulate end of stream on the last "frame". + *input = new DataBuffer(0); + } + } + int frames_remaining() const { return frames_remaining_; } + + private: + int frames_remaining_; +}; + +class SimpleMftH264DecodeEngineHandler + : public VideoDecodeEngine::EventHandler { + public: + SimpleMftH264DecodeEngineHandler() + : init_count_(0), + uninit_count_(0), + flush_count_(0), + format_change_count_(0), + empty_buffer_callback_count_(0), + fill_buffer_callback_count_(0) { + memset(&info_, 0, sizeof(info_)); + } + virtual ~SimpleMftH264DecodeEngineHandler() {} + virtual void OnInitializeComplete(const VideoCodecInfo& info) { + info_ = info; + init_count_++; + } + virtual void OnUninitializeComplete() { + uninit_count_++; + } + virtual void OnFlushComplete() { + flush_count_++; + } + virtual void OnSeekComplete() {} + virtual void OnError() {} + virtual void OnFormatChange(VideoStreamInfo stream_info) { + format_change_count_++; + info_.stream_info = stream_info; + } + virtual void ProduceVideoSample(scoped_refptr<Buffer> buffer) { + if (reader_.get() && decoder_) { + empty_buffer_callback_count_++; + scoped_refptr<DataBuffer> input; + reader_->ReadCallback(&input); + decoder_->ConsumeVideoSample(input); + } + } + virtual void ConsumeVideoFrame(scoped_refptr<VideoFrame> frame) { + fill_buffer_callback_count_++; + current_frame_ = frame; + } + void SetReader(scoped_refptr<BaseMftReader> reader) { + reader_ = reader; + } + void SetDecodeEngine(MftH264DecodeEngine* decoder) { + decoder_ = decoder; + } + + int init_count_; + int uninit_count_; + int flush_count_; + int format_change_count_; + int empty_buffer_callback_count_; + int fill_buffer_callback_count_; + VideoCodecInfo info_; + scoped_refptr<BaseMftReader> reader_; + MftH264DecodeEngine* decoder_; + scoped_refptr<VideoFrame> current_frame_; +}; + +class FFmpegFileReaderWrapper : public BaseMftReader { + public: + FFmpegFileReaderWrapper() {} + virtual ~FFmpegFileReaderWrapper() {} + bool InitReader(const std::string& filename) { + reader_.reset(new FFmpegFileReader(filename)); + if (!reader_.get() || !reader_->Initialize()) { + reader_.reset(); + return false; + } + return true; + } + virtual void ReadCallback(scoped_refptr<DataBuffer>* input) { + if (reader_.get()) { + reader_->Read(input); + } + } + bool GetWidth(int* width) { + if (!reader_.get()) + return false; + return reader_->GetWidth(width); + } + bool GetHeight(int* height) { + if (!reader_.get()) + return false; + return reader_->GetHeight(height); + } + scoped_ptr<FFmpegFileReader> reader_; +}; + +// Helper functions + +static FilePath GetVideoFilePath(const std::string& file_name) { + FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("media") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII(file_name.c_str()); + return path; +} + +class MftH264DecodeEngineTest : public testing::Test { + protected: + MftH264DecodeEngineTest() + : loop_(), + window_(NULL), + handler_(NULL), + engine_(NULL), + context_(NULL) { + } + virtual ~MftH264DecodeEngineTest() {} + virtual void SetUp() { + handler_.reset(new SimpleMftH264DecodeEngineHandler()); + } + virtual void TearDown() { + if (context_.get()) { + context_->ReleaseAllVideoFrames(); + context_->Destroy(NULL); + } + if (window_) + DestroyWindow(window_); + } + void GetDecodeEngine(bool dxva) { + if (dxva) { + if (!window_) + CreateDrawWindow(); + context_.reset(new MftH264DecodeEngineContext(window_)); + ASSERT_TRUE(context_.get()); + context_->Initialize(NULL); + ASSERT_TRUE(context_->initialized()); + } + engine_.reset(new MftH264DecodeEngine(dxva)); + ASSERT_TRUE(engine_.get()); + } + void InitDecodeEngine(int width, int height) { + VideoCodecConfig config; + config.width = width; + config.height = height; + + // Note that although |config| is passed as reference, |config| is copied + // into the decode engine, so it is okay to make |config| a local variable. + engine_->Initialize(&loop_, handler_.get(), context_.get(), config); + EXPECT_EQ(1, handler_->init_count_); + EXPECT_EQ(MftH264DecodeEngine::kNormal, engine_->state()); + } + void InitDecodeEngine() { + InitDecodeEngine(800, 600); + } + void TestInitAndUninit(bool dxva) { + GetDecodeEngine(dxva); + InitDecodeEngine(); + engine_->Uninitialize(); + } + void DecodeAll(scoped_refptr<BaseMftReader> reader) { + handler_->SetReader(reader); + handler_->SetDecodeEngine(engine_.get()); + while (MftH264DecodeEngine::kStopped != engine_->state()) { + scoped_refptr<VideoFrame> frame; + engine_->ProduceVideoFrame(frame); + } + } + void DecodeValidVideo(const std::string& filename, int num_frames, + bool dxva) { + scoped_refptr<FFmpegFileReaderWrapper> reader( + new FFmpegFileReaderWrapper()); + ASSERT_TRUE(reader.get()); + FilePath path = GetVideoFilePath(filename); + ASSERT_TRUE(file_util::PathExists(path)); + ASSERT_TRUE(reader->InitReader(WideToASCII(path.value()))); + int actual_width; + int actual_height; + ASSERT_TRUE(reader->GetWidth(&actual_width)); + ASSERT_TRUE(reader->GetHeight(&actual_height)); + + VideoCodecConfig config; + CreateDrawWindow(config.width, config.height); + GetDecodeEngine(dxva); + InitDecodeEngine(); + DecodeAll(reader); + + // We expect a format change when decoder receives enough data to determine + // the actual frame width/height. + EXPECT_GT(handler_->format_change_count_, 0); + EXPECT_EQ(actual_width, handler_->info_.stream_info.surface_width); + EXPECT_EQ(actual_height, handler_->info_.stream_info.surface_height); + EXPECT_GE(handler_->empty_buffer_callback_count_, num_frames); + EXPECT_EQ(num_frames, handler_->fill_buffer_callback_count_ - 1); + engine_->Uninitialize(); + } + void ExpectDefaultDimensionsOnInput(int width, int height) { + GetDecodeEngine(false); + InitDecodeEngine(width, height); + EXPECT_EQ(kDecoderMaxWidth, handler_->info_.stream_info.surface_width); + EXPECT_EQ(kDecoderMaxHeight, handler_->info_.stream_info.surface_height); + engine_->Uninitialize(); + } + + scoped_ptr<SimpleMftH264DecodeEngineHandler> handler_; + scoped_ptr<MftH264DecodeEngine> engine_; + scoped_ptr<MftH264DecodeEngineContext> context_; + + private: + void CreateDrawWindow(int width, int height) { + static const wchar_t kClassName[] = L"Test"; + static const wchar_t kWindowTitle[] = L"MFT Unittest Draw Window"; + WNDCLASS window_class = {0}; + window_class.lpszClassName = kClassName; + window_class.hInstance = NULL; + window_class.hbrBackground = 0; + window_class.lpfnWndProc = DefWindowProc; + window_class.hCursor = 0; + RegisterClass(&window_class); + window_ = CreateWindow(kClassName, + kWindowTitle, + (WS_OVERLAPPEDWINDOW | WS_VISIBLE) & + ~(WS_MAXIMIZEBOX | WS_THICKFRAME), + 100, 100, width, height, + NULL, NULL, NULL, NULL); + ASSERT_TRUE(window_); + } + void CreateDrawWindow() { + CreateDrawWindow(800, 600); + } + + MessageLoop loop_; + HWND window_; +}; + +// A simple test case for init/deinit of MF/COM libraries. +TEST_F(MftH264DecodeEngineTest, LibraryInit) { + EXPECT_TRUE(MftH264DecodeEngine::StartupComLibraries()); + MftH264DecodeEngine::ShutdownComLibraries(); +} + +TEST_F(MftH264DecodeEngineTest, DecoderUninitializedAtFirst) { + GetDecodeEngine(true); + EXPECT_EQ(MftH264DecodeEngine::kUninitialized, engine_->state()); +} + +TEST_F(MftH264DecodeEngineTest, DecoderInitMissingArgs) { + VideoCodecConfig config; + GetDecodeEngine(false); + engine_->Initialize(NULL, NULL, NULL, config); + EXPECT_EQ(MftH264DecodeEngine::kUninitialized, engine_->state()); +} + +TEST_F(MftH264DecodeEngineTest, DecoderInitNoDxva) { + TestInitAndUninit(false); +} + +TEST_F(MftH264DecodeEngineTest, DecoderInitDxva) { + TestInitAndUninit(true); +} + +TEST_F(MftH264DecodeEngineTest, DecoderUninit) { + TestInitAndUninit(false); + EXPECT_EQ(1, handler_->uninit_count_); + EXPECT_EQ(MftH264DecodeEngine::kUninitialized, engine_->state()); +} + +TEST_F(MftH264DecodeEngineTest, UninitBeforeInit) { + GetDecodeEngine(false); + engine_->Uninitialize(); + EXPECT_EQ(0, handler_->uninit_count_); +} + +TEST_F(MftH264DecodeEngineTest, InitWithNegativeDimensions) { + ExpectDefaultDimensionsOnInput(-123, -456); +} + +TEST_F(MftH264DecodeEngineTest, InitWithTooHighDimensions) { + ExpectDefaultDimensionsOnInput(kDecoderMaxWidth + 1, kDecoderMaxHeight + 1); +} + +TEST_F(MftH264DecodeEngineTest, DrainOnEmptyBuffer) { + GetDecodeEngine(false); + InitDecodeEngine(); + + // Decoder should switch to drain mode because of this NULL buffer, and then + // switch to kStopped when it says it needs more input during drain mode. + scoped_refptr<Buffer> buffer(new DataBuffer(0)); + engine_->ConsumeVideoSample(buffer); + EXPECT_EQ(MftH264DecodeEngine::kStopped, engine_->state()); + + // Should have called back with one empty frame. + EXPECT_EQ(1, handler_->fill_buffer_callback_count_); + ASSERT_TRUE(handler_->current_frame_.get()); + EXPECT_EQ(VideoFrame::EMPTY, handler_->current_frame_->format()); + engine_->Uninitialize(); +} + +TEST_F(MftH264DecodeEngineTest, NoOutputOnGarbageInput) { + // 100 samples of garbage. + const int kNumFrames = 100; + scoped_refptr<FakeMftReader> reader(new FakeMftReader(kNumFrames)); + ASSERT_TRUE(reader.get()); + + GetDecodeEngine(false); + InitDecodeEngine(); + DecodeAll(reader); + + // Output callback should only be invoked once - the empty frame to indicate + // end of stream. + EXPECT_EQ(1, handler_->fill_buffer_callback_count_); + ASSERT_TRUE(handler_->current_frame_.get()); + EXPECT_EQ(VideoFrame::EMPTY, handler_->current_frame_->format()); + + // One extra count because of the end of stream NULL sample. + EXPECT_EQ(kNumFrames, handler_->empty_buffer_callback_count_ - 1); + engine_->Uninitialize(); +} + +TEST_F(MftH264DecodeEngineTest, FlushAtStart) { + GetDecodeEngine(false); + InitDecodeEngine(); + engine_->Flush(); + + // Flush should succeed even if input/output are empty. + EXPECT_EQ(1, handler_->flush_count_); + engine_->Uninitialize(); +} + +TEST_F(MftH264DecodeEngineTest, NoFlushAtStopped) { + scoped_refptr<BaseMftReader> reader(new FakeMftReader()); + ASSERT_TRUE(reader.get()); + + GetDecodeEngine(false); + InitDecodeEngine(); + DecodeAll(reader); + + EXPECT_EQ(0, handler_->flush_count_); + int old_flush_count = handler_->flush_count_; + engine_->Flush(); + EXPECT_EQ(old_flush_count, handler_->flush_count_); + engine_->Uninitialize(); +} + +TEST_F(MftH264DecodeEngineTest, DecodeValidVideoDxva) { + DecodeValidVideo("bear.1280x720.mp4", 82, true); +} + +TEST_F(MftH264DecodeEngineTest, DecodeValidVideoNoDxva) { + DecodeValidVideo("bear.1280x720.mp4", 82, false); +} + +} // namespace media |