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 /media/video | |
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
Diffstat (limited to 'media/video')
-rw-r--r-- | media/video/mft_h264_decode_engine.cc | 696 | ||||
-rw-r--r-- | media/video/mft_h264_decode_engine.h | 102 | ||||
-rw-r--r-- | media/video/mft_h264_decode_engine_context.cc | 179 | ||||
-rw-r--r-- | media/video/mft_h264_decode_engine_context.h | 70 | ||||
-rw-r--r-- | media/video/mft_h264_decode_engine_unittest.cc | 410 |
5 files changed, 1208 insertions, 249 deletions
diff --git a/media/video/mft_h264_decode_engine.cc b/media/video/mft_h264_decode_engine.cc new file mode 100644 index 0000000..ea59ad6 --- /dev/null +++ b/media/video/mft_h264_decode_engine.cc @@ -0,0 +1,696 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/video/mft_h264_decode_engine.h" + +#include <d3d9.h> +#include <dxva2api.h> +#include <initguid.h> +#include <mfapi.h> +// Placed after mfapi.h to avoid linking strmiids.lib for MR_BUFFER_SERVICE. +#include <evr.h> +#include <mferror.h> +#include <wmcodecdsp.h> + +#include "base/time.h" +#include "base/message_loop.h" +#include "media/base/limits.h" +#include "media/video/video_decode_context.h" + +#pragma comment(lib, "dxva2.lib") +#pragma comment(lib, "d3d9.lib") +#pragma comment(lib, "mf.lib") +#pragma comment(lib, "mfplat.lib") + +using base::TimeDelta; + +namespace { + +// Creates an empty Media Foundation sample with no buffers. +static IMFSample* CreateEmptySample() { + HRESULT hr; + ScopedComPtr<IMFSample> sample; + hr = MFCreateSample(sample.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Unable to create an empty sample"; + return NULL; + } + return sample.Detach(); +} + +// Creates a Media Foundation sample with one buffer of length |buffer_length| +// on a |align|-byte boundary. Alignment must be a perfect power of 2 or 0. +// If |align| is 0, then no alignment is specified. +static IMFSample* CreateEmptySampleWithBuffer(int buffer_length, int align) { + CHECK_GT(buffer_length, 0); + ScopedComPtr<IMFSample> sample; + sample.Attach(CreateEmptySample()); + if (!sample.get()) + return NULL; + ScopedComPtr<IMFMediaBuffer> buffer; + HRESULT hr; + if (align == 0) { + // Note that MFCreateMemoryBuffer is same as MFCreateAlignedMemoryBuffer + // with the align argument being 0. + hr = MFCreateMemoryBuffer(buffer_length, buffer.Receive()); + } else { + hr = MFCreateAlignedMemoryBuffer(buffer_length, + align - 1, + buffer.Receive()); + } + if (FAILED(hr)) { + LOG(ERROR) << "Unable to create an empty buffer"; + return NULL; + } + hr = sample->AddBuffer(buffer.get()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to add empty buffer to sample"; + return NULL; + } + return sample.Detach(); +} + +// Creates a Media Foundation sample with one buffer containing a copy of the +// given Annex B stream data. +// If duration and sample time are not known, provide 0. +// |min_size| specifies the minimum size of the buffer (might be required by +// the decoder for input). The times here should be given in 100ns units. +// |alignment| specifies the buffer in the sample to be aligned. If no +// alignment is required, provide 0 or 1. +static IMFSample* CreateInputSample(const uint8* stream, int size, + int64 timestamp, int64 duration, + int min_size, int alignment) { + CHECK(stream); + CHECK_GT(size, 0); + ScopedComPtr<IMFSample> sample; + sample.Attach(CreateEmptySampleWithBuffer(std::max(min_size, size), + alignment)); + if (!sample.get()) { + LOG(ERROR) << "Failed to create empty buffer for input"; + return NULL; + } + HRESULT hr; + if (duration > 0) { + hr = sample->SetSampleDuration(duration); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set sample duration"; + return NULL; + } + } + if (timestamp > 0) { + hr = sample->SetSampleTime(timestamp); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set sample time"; + return NULL; + } + } + ScopedComPtr<IMFMediaBuffer> buffer; + hr = sample->GetBufferByIndex(0, buffer.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get buffer in sample"; + return NULL; + } + DWORD max_length, current_length; + uint8* destination; + hr = buffer->Lock(&destination, &max_length, ¤t_length); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to lock buffer"; + return NULL; + } + CHECK_EQ(current_length, 0u); + CHECK_GE(static_cast<int>(max_length), size); + memcpy(destination, stream, size); + CHECK(SUCCEEDED(buffer->Unlock())); + hr = buffer->SetCurrentLength(size); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set current length to " << size; + return NULL; + } + LOG(INFO) << __FUNCTION__ << " wrote " << size << " bytes into input sample"; + return sample.Detach(); +} + +const GUID ConvertVideoFrameFormatToGuid(media::VideoFrame::Format format) { + switch (format) { + case media::VideoFrame::NV12: + return MFVideoFormat_NV12; + case media::VideoFrame::YV12: + return MFVideoFormat_YV12; + default: + NOTREACHED() << "Unsupported VideoFrame format"; + return GUID_NULL; + } + NOTREACHED(); + return GUID_NULL; +} + +} // namespace + +namespace media { + +// public methods + +MftH264DecodeEngine::MftH264DecodeEngine(bool use_dxva) + : use_dxva_(use_dxva), + state_(kUninitialized), + event_handler_(NULL) { + memset(&input_stream_info_, 0, sizeof(input_stream_info_)); + memset(&output_stream_info_, 0, sizeof(output_stream_info_)); + memset(&config_, 0, sizeof(config_)); + memset(&info_, 0, sizeof(info_)); +} + +MftH264DecodeEngine::~MftH264DecodeEngine() { +} + +void MftH264DecodeEngine::Initialize( + MessageLoop* message_loop, + VideoDecodeEngine::EventHandler* event_handler, + VideoDecodeContext* context, + const VideoCodecConfig& config) { + DCHECK(!use_dxva_ || context); + if (state_ != kUninitialized) { + LOG(ERROR) << "Initialize: invalid state"; + return; + } + if (!message_loop || !event_handler) { + LOG(ERROR) << "MftH264DecodeEngine::Initialize: parameters cannot be NULL"; + return; + } + context_ = context; + config_ = config; + event_handler_ = event_handler; + info_.provides_buffers = true; + + if (use_dxva_) { + info_.stream_info.surface_format = VideoFrame::NV12; + // TODO(hclam): Need to correct this since this is not really GL texture. + // We should just remove surface_type from stream_info. + info_.stream_info.surface_type = VideoFrame::TYPE_GL_TEXTURE; + } else { + info_.stream_info.surface_format = VideoFrame::YV12; + info_.stream_info.surface_type = VideoFrame::TYPE_SYSTEM_MEMORY; + } + + // codec_info.stream_info_.surface_width_/height_ are initialized + // in InitInternal(). + info_.success = InitInternal(); + if (info_.success) { + state_ = kNormal; + AllocFramesFromContext(); + } else { + LOG(ERROR) << "MftH264DecodeEngine::Initialize failed"; + event_handler_->OnInitializeComplete(info_); + } +} + +void MftH264DecodeEngine::Uninitialize() { + if (state_ == kUninitialized) { + LOG(ERROR) << "Uninitialize: invalid state"; + return; + } + + // TODO(hclam): Call ShutdownComLibraries only after MFT is released. + decode_engine_.Release(); + ShutdownComLibraries(); + state_ = kUninitialized; + event_handler_->OnUninitializeComplete(); +} + +void MftH264DecodeEngine::Flush() { + if (state_ != kNormal) { + LOG(ERROR) << "Flush: invalid state"; + return; + } + state_ = kFlushing; + if (!SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH)) { + LOG(WARNING) << "MftH264DecodeEngine::Flush failed to send message"; + } + state_ = kNormal; + event_handler_->OnFlushComplete(); +} + +void MftH264DecodeEngine::Seek() { + if (state_ != kNormal) { + LOG(ERROR) << "Seek: invalid state"; + return; + } + + // TODO(hclam): Seriously the logic in VideoRendererBase is flawed that we + // have to perform the following hack to get playback going. + for (size_t i = 0; i < Limits::kMaxVideoFrames; ++i) { + event_handler_->ConsumeVideoFrame(output_frames_[0]); + } + + // Seek not implemented. + event_handler_->OnSeekComplete(); +} + +void MftH264DecodeEngine::ConsumeVideoSample(scoped_refptr<Buffer> buffer) { + if (state_ == kUninitialized) { + LOG(ERROR) << "ConsumeVideoSample: invalid state"; + } + ScopedComPtr<IMFSample> sample; + if (!buffer->IsEndOfStream()) { + sample.Attach( + CreateInputSample(buffer->GetData(), + buffer->GetDataSize(), + buffer->GetTimestamp().InMicroseconds() * 10, + buffer->GetDuration().InMicroseconds() * 10, + input_stream_info_.cbSize, + input_stream_info_.cbAlignment)); + if (!sample.get()) { + LOG(ERROR) << "Failed to create an input sample"; + } else { + if (FAILED(decode_engine_->ProcessInput(0, sample.get(), 0))) { + event_handler_->OnError(); + } + } + } else { + if (state_ != MftH264DecodeEngine::kEosDrain) { + // End of stream, send drain messages. + if (!SendMFTMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM) || + !SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN)) { + LOG(ERROR) << "Failed to send EOS / drain messages to MFT"; + event_handler_->OnError(); + } else { + state_ = MftH264DecodeEngine::kEosDrain; + } + } + } + DoDecode(); +} + +void MftH264DecodeEngine::ProduceVideoFrame(scoped_refptr<VideoFrame> frame) { + if (state_ == kUninitialized) { + LOG(ERROR) << "ProduceVideoFrame: invalid state"; + return; + } + event_handler_->ProduceVideoSample(NULL); +} + +// private methods + +// static +bool MftH264DecodeEngine::StartupComLibraries() { + HRESULT hr; + hr = CoInitializeEx(NULL, + COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if (FAILED(hr)) { + LOG(ERROR) << "CoInit fail"; + return false; + } + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + if (FAILED(hr)) { + LOG(ERROR) << "MFStartup fail"; + CoUninitialize(); + return false; + } + return true; +} + +// static +void MftH264DecodeEngine::ShutdownComLibraries() { + HRESULT hr; + hr = MFShutdown(); + if (FAILED(hr)) { + LOG(WARNING) << "Warning: MF failed to shutdown"; + } + CoUninitialize(); +} + +bool MftH264DecodeEngine::EnableDxva() { + IDirect3DDevice9* device = static_cast<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) << "Couldn't create D3D Device manager"; + return false; + } + + hr = device_manager->ResetDevice(device, dev_manager_reset_token); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to reset device"; + return false; + } + + hr = decode_engine_->ProcessMessage( + MFT_MESSAGE_SET_D3D_MANAGER, + reinterpret_cast<ULONG_PTR>(device_manager.get())); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set D3D9 device manager to decoder " + << std::hex << hr; + return false; + } + + return true; +} + +bool MftH264DecodeEngine::InitInternal() { + if (!StartupComLibraries()) + return false; + if (!InitDecodeEngine()) + return false; + if (!GetStreamsInfoAndBufferReqs()) + return false; + return SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING); +} + +bool MftH264DecodeEngine::InitDecodeEngine() { + // TODO(jiesun): use MFEnum to get decoder CLSID. + HRESULT hr = CoCreateInstance(__uuidof(CMSH264DecoderMFT), + NULL, + CLSCTX_INPROC_SERVER, + __uuidof(IMFTransform), + reinterpret_cast<void**>( + decode_engine_.Receive())); + if (FAILED(hr) || !decode_engine_.get()) { + LOG(ERROR) << "CoCreateInstance failed " << std::hex << std::showbase << hr; + return false; + } + if (!CheckDecodeEngineDxvaSupport()) + return false; + if (use_dxva_ && !EnableDxva()) + return false; + return SetDecodeEngineMediaTypes(); +} + +void MftH264DecodeEngine::AllocFramesFromContext() { + if (!use_dxva_) + return; + + // TODO(imcheng): Pass in an actual task. (From EventHandler?) + context_->ReleaseAllVideoFrames(); + output_frames_.clear(); + context_->AllocateVideoFrames( + 1, info_.stream_info.surface_width, info_.stream_info.surface_height, + VideoFrame::RGBA, &output_frames_, + NewRunnableMethod(this, &MftH264DecodeEngine::OnAllocFramesDone)); +} + +void MftH264DecodeEngine::OnAllocFramesDone() { + event_handler_->OnInitializeComplete(info_); +} + +bool MftH264DecodeEngine::CheckDecodeEngineDxvaSupport() { + ScopedComPtr<IMFAttributes> attributes; + HRESULT hr = decode_engine_->GetAttributes(attributes.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Unlock: Failed to get attributes, hr = " + << std::hex << std::showbase << hr; + return false; + } + + UINT32 dxva; + hr = attributes->GetUINT32(MF_SA_D3D_AWARE, &dxva); + if (FAILED(hr) || !dxva) { + LOG(ERROR) << "Failed to get DXVA attr or decoder is not DXVA-aware, hr = " + << std::hex << std::showbase << hr + << " this might not be the right decoder."; + return false; + } + return true; +} + +bool MftH264DecodeEngine::SetDecodeEngineMediaTypes() { + if (!SetDecodeEngineInputMediaType()) + return false; + return SetDecodeEngineOutputMediaType( + ConvertVideoFrameFormatToGuid(info_.stream_info.surface_format)); +} + +bool MftH264DecodeEngine::SetDecodeEngineInputMediaType() { + ScopedComPtr<IMFMediaType> media_type; + HRESULT hr = MFCreateMediaType(media_type.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create empty media type object"; + return false; + } + + hr = media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + if (FAILED(hr)) { + LOG(ERROR) << "SetGUID for major type failed"; + return false; + } + + hr = media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); + if (FAILED(hr)) { + LOG(ERROR) << "SetGUID for subtype failed"; + return false; + } + + hr = decode_engine_->SetInputType(0, media_type.get(), 0); // No flags + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set decoder's input type"; + return false; + } + + return true; +} + +bool MftH264DecodeEngine::SetDecodeEngineOutputMediaType(const GUID subtype) { + DWORD i = 0; + IMFMediaType* out_media_type; + bool found = false; + while (SUCCEEDED(decode_engine_->GetOutputAvailableType(0, i, + &out_media_type))) { + GUID out_subtype; + HRESULT hr = out_media_type->GetGUID(MF_MT_SUBTYPE, &out_subtype); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to GetGUID() on GetOutputAvailableType() " << i; + out_media_type->Release(); + continue; + } + if (out_subtype == subtype) { + hr = decode_engine_->SetOutputType(0, out_media_type, 0); // No flags + hr = MFGetAttributeSize(out_media_type, MF_MT_FRAME_SIZE, + reinterpret_cast<UINT32*>(&info_.stream_info.surface_width), + reinterpret_cast<UINT32*>(&info_.stream_info.surface_height)); + config_.width = info_.stream_info.surface_width; + config_.height = info_.stream_info.surface_height; + if (FAILED(hr)) { + LOG(ERROR) << "Failed to SetOutputType to |subtype| or obtain " + << "width/height " << std::hex << hr; + } else { + out_media_type->Release(); + return true; + } + } + i++; + out_media_type->Release(); + } + return false; +} + +bool MftH264DecodeEngine::SendMFTMessage(MFT_MESSAGE_TYPE msg) { + HRESULT hr = decode_engine_->ProcessMessage(msg, NULL); + return SUCCEEDED(hr); +} + +// Prints out info about the input/output streams, gets the minimum buffer sizes +// for input and output samples. +// The MFT will not allocate buffer for neither input nor output, so we have +// to do it ourselves and make sure they're the correct size. +// Exception is when dxva is enabled, the decoder will allocate output. +bool MftH264DecodeEngine::GetStreamsInfoAndBufferReqs() { + HRESULT hr = decode_engine_->GetInputStreamInfo(0, &input_stream_info_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get input stream info"; + return false; + } + LOG(INFO) << "Input stream info: "; + LOG(INFO) << "Max latency: " << input_stream_info_.hnsMaxLatency; + + // There should be three flags, one for requiring a whole frame be in a + // single sample, one for requiring there be one buffer only in a single + // sample, and one that specifies a fixed sample size. (as in cbSize) + LOG(INFO) << "Flags: " + << std::hex << std::showbase << input_stream_info_.dwFlags; + CHECK_EQ(input_stream_info_.dwFlags, 0x7u); + LOG(INFO) << "Min buffer size: " << input_stream_info_.cbSize; + LOG(INFO) << "Max lookahead: " << input_stream_info_.cbMaxLookahead; + LOG(INFO) << "Alignment: " << input_stream_info_.cbAlignment; + + hr = decode_engine_->GetOutputStreamInfo(0, &output_stream_info_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get output stream info"; + return false; + } + LOG(INFO) << "Output stream info: "; + // The flags here should be the same and mean the same thing, except when + // DXVA is enabled, there is an extra 0x100 flag meaning decoder will + // allocate its own sample. + LOG(INFO) << "Flags: " + << std::hex << std::showbase << output_stream_info_.dwFlags; + CHECK_EQ(output_stream_info_.dwFlags, use_dxva_ ? 0x107u : 0x7u); + LOG(INFO) << "Min buffer size: " << output_stream_info_.cbSize; + LOG(INFO) << "Alignment: " << output_stream_info_.cbAlignment; + + return true; +} + +bool MftH264DecodeEngine::DoDecode() { + if (state_ != kNormal && state_ != kEosDrain) { + LOG(ERROR) << "DoDecode: not in normal or drain state"; + return false; + } + scoped_refptr<VideoFrame> frame; + ScopedComPtr<IMFSample> output_sample; + if (!use_dxva_) { + output_sample.Attach( + CreateEmptySampleWithBuffer(output_stream_info_.cbSize, + output_stream_info_.cbAlignment)); + if (!output_sample.get()) { + LOG(ERROR) << "GetSample: failed to create empty output sample"; + event_handler_->OnError(); + return false; + } + } + MFT_OUTPUT_DATA_BUFFER output_data_buffer; + memset(&output_data_buffer, 0, sizeof(output_data_buffer)); + output_data_buffer.dwStreamID = 0; + output_data_buffer.pSample = output_sample; + + DWORD status; + HRESULT hr = decode_engine_->ProcessOutput(0, // No flags + 1, // # of out streams to pull + &output_data_buffer, + &status); + + IMFCollection* events = output_data_buffer.pEvents; + if (events != NULL) { + LOG(INFO) << "Got events from ProcessOuput, but discarding"; + events->Release(); + } + + if (FAILED(hr)) { + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { + hr = SetDecodeEngineOutputMediaType( + ConvertVideoFrameFormatToGuid(info_.stream_info.surface_format)); + if (SUCCEEDED(hr)) { + // TODO(hclam): Need to fix this case. This happens when we have a + // format change. We have to resume decoding only after we have + // allocated a new set of video frames. + // AllocFramesFromContext(); + // event_handler_->OnFormatChange(info_.stream_info); + event_handler_->ProduceVideoSample(NULL); + return true; + } else { + event_handler_->OnError(); + return false; + } + } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + if (state_ == kEosDrain) { + // No more output from the decoder. Notify EOS and stop playback. + scoped_refptr<VideoFrame> frame; + VideoFrame::CreateEmptyFrame(&frame); + event_handler_->ConsumeVideoFrame(frame); + state_ = MftH264DecodeEngine::kStopped; + return false; + } + event_handler_->ProduceVideoSample(NULL); + return true; + } else { + LOG(ERROR) << "Unhandled error in DoDecode()"; + state_ = MftH264DecodeEngine::kStopped; + event_handler_->OnError(); + return false; + } + } + + // We succeeded in getting an output sample. + if (use_dxva_) { + // For DXVA we didn't provide the sample, i.e. output_sample was NULL. + output_sample.Attach(output_data_buffer.pSample); + } + if (!output_sample.get()) { + LOG(ERROR) << "ProcessOutput succeeded, but did not get a sample back"; + event_handler_->OnError(); + return true; + } + + int64 timestamp = 0, duration = 0; + if (FAILED(output_sample->GetSampleTime(×tamp)) || + FAILED(output_sample->GetSampleDuration(&duration))) { + LOG(WARNING) << "Failed to get timestamp/duration from output"; + } + + // The duration and timestamps are in 100-ns units, so divide by 10 + // to convert to microseconds. + timestamp /= 10; + duration /= 10; + + // Sanity checks for checking if there is really something in the sample. + DWORD buf_count; + hr = output_sample->GetBufferCount(&buf_count); + if (FAILED(hr) || buf_count != 1) { + LOG(ERROR) << "Failed to get buffer count, or buffer count mismatch"; + return true; + } + + ScopedComPtr<IMFMediaBuffer> output_buffer; + hr = output_sample->GetBufferByIndex(0, output_buffer.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get buffer from sample"; + return true; + } + if (use_dxva_) { + ScopedComPtr<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; + } + // Since we only allocated 1 frame from context. + // TODO(imcheng): Detect error. + output_frames_[0]->SetTimestamp(TimeDelta::FromMicroseconds(timestamp)); + output_frames_[0]->SetDuration(TimeDelta::FromMicroseconds(duration)); + context_->UploadToVideoFrame( + surface.get(), output_frames_[0], + NewRunnableMethod(this, &MftH264DecodeEngine::OnUploadVideoFrameDone, + surface, output_frames_[0])); + return true; + } else { + // TODO(hclam): Remove this branch. + // Not DXVA. + VideoFrame::CreateFrame(info_.stream_info.surface_format, + info_.stream_info.surface_width, + info_.stream_info.surface_height, + TimeDelta::FromMicroseconds(timestamp), + TimeDelta::FromMicroseconds(duration), + &frame); + if (!frame.get()) { + LOG(ERROR) << "Failed to allocate video frame for yuv plane"; + event_handler_->OnError(); + return true; + } + uint8* src_y; + DWORD max_length, current_length; + HRESULT hr = output_buffer->Lock(&src_y, &max_length, ¤t_length); + if (FAILED(hr)) + return true; + uint8* dst_y = static_cast<uint8*>(frame->data(VideoFrame::kYPlane)); + + memcpy(dst_y, src_y, current_length); + CHECK(SUCCEEDED(output_buffer->Unlock())); + event_handler_->ConsumeVideoFrame(frame); + return true; + } +} + +void MftH264DecodeEngine::OnUploadVideoFrameDone( + ScopedComPtr<IDirect3DSurface9, &IID_IDirect3DSurface9> surface, + scoped_refptr<media::VideoFrame> frame) { + // After this method is exited the reference to surface is released. + event_handler_->ConsumeVideoFrame(frame); +} + +} // namespace media + +DISABLE_RUNNABLE_METHOD_REFCOUNT(media::MftH264DecodeEngine); diff --git a/media/video/mft_h264_decode_engine.h b/media/video/mft_h264_decode_engine.h new file mode 100644 index 0000000..e13dce9 --- /dev/null +++ b/media/video/mft_h264_decode_engine.h @@ -0,0 +1,102 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// MFT H.264 decode engine. + +#ifndef MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_H_ +#define MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_H_ + +// TODO(imcheng): Get rid of this header by: +// - forward declaring IMFTransform and its IID as in +// mft_h264_decode_engine_context.h +// - turning the general SendMFTMessage method into specific methods +// (SendFlushMessage, SendDrainMessage, etc.) to avoid having +// MFT_MESSAGE_TYPE in here +#include <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 VideoDecodeContext; + +class MftH264DecodeEngine : public media::VideoDecodeEngine { + public: + typedef enum { + kUninitialized, // un-initialized. + kNormal, // normal playing state. + kFlushing, // upon received Flush(), before FlushDone() + kEosDrain, // upon input EOS received. + kStopped, // upon output EOS received. + } State; + + explicit MftH264DecodeEngine(bool use_dxva); + virtual ~MftH264DecodeEngine(); + + // VideoDecodeEngine implementation. + virtual void Initialize(MessageLoop* message_loop, + media::VideoDecodeEngine::EventHandler* event_handler, + VideoDecodeContext* context, + const VideoCodecConfig& config); + virtual void Uninitialize(); + virtual void Flush(); + virtual void Seek(); + virtual void ConsumeVideoSample(scoped_refptr<Buffer> buffer); + virtual void ProduceVideoFrame(scoped_refptr<VideoFrame> frame); + + bool use_dxva() const { return use_dxva_; } + State state() const { return state_; } + + private: + friend class MftH264DecodeEngineTest; + FRIEND_TEST_ALL_PREFIXES(MftH264DecodeEngineTest, LibraryInit); + + // TODO(jiesun): Find a way to move all these to GpuVideoService.. + static bool StartupComLibraries(); + static void ShutdownComLibraries(); + bool EnableDxva(); + + bool InitInternal(); + bool InitDecodeEngine(); + void AllocFramesFromContext(); + bool CheckDecodeEngineDxvaSupport(); + bool SetDecodeEngineMediaTypes(); + bool SetDecodeEngineInputMediaType(); + bool SetDecodeEngineOutputMediaType(const GUID subtype); + bool SendMFTMessage(MFT_MESSAGE_TYPE msg); + bool GetStreamsInfoAndBufferReqs(); + bool DoDecode(); + void OnAllocFramesDone(); + void OnUploadVideoFrameDone( + ScopedComPtr<IDirect3DSurface9, &IID_IDirect3DSurface9> surface, + scoped_refptr<media::VideoFrame> frame); + + bool use_dxva_; + ScopedComPtr<IMFTransform> decode_engine_; + + MFT_INPUT_STREAM_INFO input_stream_info_; + MFT_OUTPUT_STREAM_INFO output_stream_info_; + + State state_; + + VideoDecodeEngine::EventHandler* event_handler_; + VideoCodecConfig config_; + VideoCodecInfo info_; + + VideoDecodeContext* context_; + std::vector<scoped_refptr<VideoFrame> > output_frames_; + + DISALLOW_COPY_AND_ASSIGN(MftH264DecodeEngine); +}; + +} // namespace media + +#endif // MEDIA_VIDEO_MFT_H264_DECODE_ENGINE_H_ diff --git a/media/video/mft_h264_decode_engine_context.cc b/media/video/mft_h264_decode_engine_context.cc deleted file mode 100644 index 1759ced..0000000 --- a/media/video/mft_h264_decode_engine_context.cc +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/video/mft_h264_decode_engine_context.h" - -#include <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 |