diff options
author | imcheng@chromium.org <imcheng@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-06 23:14:36 +0000 |
---|---|---|
committer | imcheng@chromium.org <imcheng@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-06 23:14:36 +0000 |
commit | 9c17999887073e5d0c58bd9e27ce56fd378a0e01 (patch) | |
tree | 55a82a7a64c0c8af96cd077164d5e000ddfb16ca /media | |
parent | a5c493b9b38dcb17d5e621e06a9c9bb9e317c2f9 (diff) | |
download | chromium_src-9c17999887073e5d0c58bd9e27ce56fd378a0e01.zip chromium_src-9c17999887073e5d0c58bd9e27ce56fd378a0e01.tar.gz chromium_src-9c17999887073e5d0c58bd9e27ce56fd378a0e01.tar.bz2 |
Moved files from media/media_foundation to media/mf. Cleaned up the code a little bit.
BUG=none
TEST=coming
Review URL: http://codereview.chromium.org/3072030
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@55310 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/media.gyp | 10 | ||||
-rw-r--r-- | media/mf/README.chromium (renamed from media/media_foundation/README.chromium) | 15 | ||||
-rw-r--r-- | media/mf/file_reader_util.cc (renamed from media/media_foundation/file_reader_util.cc) | 32 | ||||
-rw-r--r-- | media/mf/file_reader_util.h (renamed from media/media_foundation/file_reader_util.h) | 17 | ||||
-rw-r--r-- | media/mf/main.cc (renamed from media/media_foundation/main.cc) | 170 | ||||
-rw-r--r-- | media/mf/mft_h264_decoder.cc (renamed from media/media_foundation/h264mft.cc) | 449 | ||||
-rw-r--r-- | media/mf/mft_h264_decoder.h (renamed from media/media_foundation/h264mft.h) | 61 |
7 files changed, 344 insertions, 410 deletions
diff --git a/media/media.gyp b/media/media.gyp index a80fbd2..bb35b5e 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -428,11 +428,11 @@ '..', ], 'sources': [ - 'media_foundation/main.cc', - 'media_foundation/h264mft.cc', - 'media_foundation/h264mft.h', - 'media_foundation/file_reader_util.cc', - 'media_foundation/mf_file_reader_util.h', + 'mf/mft_h264_decoder.cc', + 'mf/mft_h264_decoder.h', + 'mf/file_reader_util.cc', + 'mf/file_reader_util.h', + 'mf/main.cc', ], 'msvs_settings': { 'VCLinkerTool': { diff --git a/media/media_foundation/README.chromium b/media/mf/README.chromium index 2847e97..cdd17a5 100644 --- a/media/media_foundation/README.chromium +++ b/media/mf/README.chromium @@ -1,26 +1,25 @@ 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
-NV12 video frames as output, contained in a buffer object (if DXVA is not
+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 as a state machine, and the steps taken in each state.
+H.264 decoder using callbacks.
Requirements: Windows 7
-Note1: This tool currently does decoding only. There is no visible output
-besides the log entry containing state of the decoder at each input/output
-step.
+Note: Rendering coming in next patch
-Note2: There is a mysterious 1-off decoded frame count when DXVA is enabled.
+Note1: On some video files, there is a mysterious 1-off decoded frame count
+when DXVA is enabled.
-Note3: This tool requires the ffmpeg library to have the H.264 codec and Annex
+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/
-Note4: A single H264Mft instance is only for 1 H.264 video stream only.
+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/media_foundation/file_reader_util.cc b/media/mf/file_reader_util.cc index d7c9944..c4a9873 100644 --- a/media/media_foundation/file_reader_util.cc +++ b/media/mf/file_reader_util.cc @@ -5,7 +5,9 @@ // Borrowed from media/tools/omx_test/file_reader_util.cc. // Added some functionalities related to timestamps on packets. -#include "media/media_foundation/file_reader_util.h" +#include "media/mf/file_reader_util.h" + +#include <cstring> #include <algorithm> @@ -13,7 +15,6 @@ #include "base/logging.h" #include "media/ffmpeg/ffmpeg_common.h" #include "media/filters/bitstream_converter.h" -#include "media/media_foundation/h264mft.h" namespace media { @@ -92,11 +93,11 @@ bool FFmpegFileReader::Initialize() { } void FFmpegFileReader::Read(uint8** output, int* size) { - Read(output, size, NULL, NULL); + Read2(output, size, NULL, NULL); } -void FFmpegFileReader::Read(uint8** output, int* size, int* duration, - int64* sample_time) { +void FFmpegFileReader::Read2(uint8** output, int* size, int64* timestamp, + int64* duration) { if (!format_context_ || !codec_context_ || target_stream_ == -1) { *size = 0; *output = NULL; @@ -129,15 +130,15 @@ void FFmpegFileReader::Read(uint8** output, int* size, int* duration, LOG(WARNING) << "Packet duration not known"; } // This is in AVCodecContext::time_base units - *duration = packet.duration; + *duration = ConvertFFmpegTimeBaseTo100Ns(packet.duration); } - if (sample_time) { + if (timestamp) { if (packet.pts == AV_NOPTS_VALUE) { LOG(ERROR) << "Packet presentation time not known"; - *sample_time = 0L; + *timestamp = 0L; } else { // This is in AVCodecContext::time_base units - *sample_time = packet.pts; + *timestamp = ConvertFFmpegTimeBaseTo100Ns(packet.pts); } } found = true; @@ -146,12 +147,13 @@ void FFmpegFileReader::Read(uint8** output, int* size, int* duration, } } -bool FFmpegFileReader::GetFrameRate(int* num, int *denom) const { +bool FFmpegFileReader::GetFrameRate(int* num, int* denom) const { if (!codec_context_) return false; - *num = codec_context_->time_base.num; - *denom = codec_context_->time_base.den; - if (denom == 0) { + + *denom = codec_context_->time_base.num; + *num = codec_context_->time_base.den; + if (*denom == 0) { *num = 0; return false; } @@ -188,8 +190,10 @@ int64 FFmpegFileReader::ConvertFFmpegTimeBaseTo100Ns( // 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 10,000 to convert it into units compatible with MF. + // 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 * 10000 * codec_context_->time_base.num / + return time_base_unit * 20000 * codec_context_->time_base.num / codec_context_->time_base.den; } diff --git a/media/media_foundation/file_reader_util.h b/media/mf/file_reader_util.h index 15cf643..a1426e0 100644 --- a/media/media_foundation/file_reader_util.h +++ b/media/mf/file_reader_util.h @@ -1,13 +1,13 @@ -// 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. +// 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_MEDIA_FOUNDATION_FILE_READER_UTIL_H_ -#define MEDIA_MEDIA_FOUNDATION_FILE_READER_UTIL_H_ +#ifndef MEDIA_MF_FILE_READER_UTIL_H_ +#define MEDIA_MF_FILE_READER_UTIL_H_ #include <string> @@ -43,8 +43,9 @@ class FFmpegFileReader : public FileReader { virtual void Read(uint8** output, int* size); // Reads a video packet, converts it into Annex B stream, and allocates a - // buffer to |*output| and copies the contents into it. - void Read(uint8** output, int* size, int* duration, int64* sample_time); + // buffer to |*output| and copies the contents into it. Timestamp and + // duration are given in 100-ns units. + void Read2(uint8** output, int* size, int64* timestamp, int64* duration); bool GetFrameRate(int* num, int* denom) const; bool GetWidth(int* width) const; bool GetHeight(int* height) const; @@ -65,4 +66,4 @@ class FFmpegFileReader : public FileReader { } // namespace media -#endif // MEDIA_MEDIA_FOUNDATION_FILE_READER_UTIL_H_ +#endif // MEDIA_MF_FILE_READER_UTIL_H_ diff --git a/media/media_foundation/main.cc b/media/mf/main.cc index fbb1bdc2..50dd9ac 100644 --- a/media/media_foundation/main.cc +++ b/media/mf/main.cc @@ -1,8 +1,12 @@ -// 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. +// 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 H264Mft. +// Demonstrates the use of MftH264Decoder. + +#include <cstdio> + +#include <string> #include <d3d9.h> #include <dxva2api.h> @@ -15,22 +19,25 @@ #include "base/scoped_ptr.h" #include "base/time.h" #include "media/base/media.h" +#include "media/base/video_frame.h" #include "media/ffmpeg/ffmpeg_common.h" #include "media/ffmpeg/file_protocol.h" -#include "media/media_foundation/file_reader_util.h" -#include "media/media_foundation/h264mft.h" +#include "media/mf/file_reader_util.h" +#include "media/mf/mft_h264_decoder.h" +using base::Time; +using base::TimeDelta; using media::FFmpegFileReader; -using media::H264Mft; +using media::MftH264Decoder; using media::VideoFrame; namespace { void usage() { static char* usage_msg = - "Usage: h264mft [--enable-dxva] --input-file=FILE\n" + "Usage: mft_h264_decoder [--enable-dxva] --input-file=FILE\n" "enable-dxva: Enables hardware accelerated decoding\n" - "To display this message: h264mft --help"; + "To display this message: mft_h264_decoder --help"; fprintf(stderr, "%s\n", usage_msg); } @@ -68,11 +75,9 @@ void ShutdownComLibraries() { CoUninitialize(); } -IDirect3DDeviceManager9* CreateD3DDevManager(HWND video_window, - int width, - int height, - IDirect3D9** direct3d, - IDirect3DDevice9** device) { +static IDirect3DDeviceManager9* CreateD3DDevManager(HWND video_window, + IDirect3D9** direct3d, + IDirect3DDevice9** device) { CHECK(video_window != NULL); CHECK(direct3d != NULL); CHECK(device != NULL); @@ -86,8 +91,10 @@ IDirect3DDeviceManager9* CreateD3DDevManager(HWND video_window, } D3DPRESENT_PARAMETERS present_params = {0}; - present_params.BackBufferWidth = width; - present_params.BackBufferHeight = height; + // Once we know the dimensions, we need to reset using + // AdjustD3DDeviceBackBufferDimensions(). + present_params.BackBufferWidth = 0; + present_params.BackBufferHeight = 0; present_params.BackBufferFormat = D3DFMT_UNKNOWN; present_params.BackBufferCount = 1; present_params.SwapEffect = D3DSWAPEFFECT_DISCARD; @@ -103,7 +110,7 @@ IDirect3DDeviceManager9* CreateD3DDevManager(HWND video_window, // (Is it even needed for just video decoding?) HRESULT hr = d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, - video_window, + NULL, D3DCREATE_HARDWARE_VERTEXPROCESSING, &present_params, temp_device.Receive()); @@ -128,104 +135,49 @@ IDirect3DDeviceManager9* CreateD3DDevManager(HWND video_window, return dev_manager.Detach(); } -// Example usage of how to get a decoded frame from the decoder. -bool GetDecodedSample(FFmpegFileReader* reader, H264Mft* decoder, - scoped_refptr<VideoFrame>* decoded_frame) { - // Keep feeding the MFT with inputs until it spits out an output. - for (;;) { - // First check if there is output. - H264Mft::DecoderOutputState state = decoder->GetOutput(decoded_frame); - if (state == H264Mft::kOutputOk) { - LOG(INFO) << "Got an output from decoder"; - return true; - } else if (state == H264Mft::kResetOutputStreamFailed) { - LOG(ERROR) << "Reset output stream failed, quitting"; - return false; - } else if (state == H264Mft::kResetOutputStreamOk) { - LOG(INFO) << "Reset output stream, try to get output again"; - continue; - } else if (state == H264Mft::kNeedMoreInput) { - LOG(INFO) << "Need more input"; - uint8* input_stream_dummy; - int size; - int duration; - int64 timestamp; - reader->Read(&input_stream_dummy, &size, &duration, ×tamp); - scoped_array<uint8> input_stream(input_stream_dummy); - if (input_stream.get() == NULL) { - LOG(INFO) << "No more input, sending drain message to decoder"; - if (!decoder->SendDrainMessage()) { - LOG(ERROR) << "Failed to send drain message, quitting"; - return false; - } else { - continue; // Try reading the rest of the drained outputs. - } - } else { - // We read an input stream, we can feed it into the decoder. - if (!decoder->SendInput(input_stream.get(), size, - reader->ConvertFFmpegTimeBaseTo100Ns(timestamp), - reader->ConvertFFmpegTimeBaseTo100Ns(duration))) { - LOG(ERROR) << "Failed to send input, dropping frame..."; - } - continue; // Try reading the output after attempting to send an input. - } - } else if (state == H264Mft::kNoMoreOutput) { - LOG(INFO) << "Decoder has processed everything, quitting"; - return false; - } else if (state == H264Mft::kUnspecifiedError) { - LOG(ERROR) << "Unknown error, quitting"; - return false; - } else if (state == H264Mft::kNoMemory) { - LOG(ERROR) << "Not enough memory for sample, quitting"; - return false; - } else if (state == H264Mft::kOutputSampleError) { - LOG(ERROR) << "Inconsistent sample, dropping..."; - continue; - } else { - NOTREACHED(); - } - } // for (;;) - NOTREACHED(); -} - static void ReleaseOutputBuffer(VideoFrame* frame) { - if (frame->type() == VideoFrame::TYPE_MFBUFFER || + if (frame != NULL && + frame->type() == VideoFrame::TYPE_MFBUFFER || frame->type() == VideoFrame::TYPE_DIRECT3DSURFACE) { static_cast<IMFMediaBuffer*>(frame->private_buffer())->Release(); - } else { - return; } } -int Run(bool use_dxva, const std::string& input_file) { - scoped_ptr<FFmpegFileReader> reader(new FFmpegFileReader(input_file)); - if (reader.get() == NULL) { - LOG(ERROR) << "Failed to create reader"; - return -1; +class FakeRenderer { + public: + FakeRenderer() {} + ~FakeRenderer() {} + void ProcessFrame(scoped_refptr<VideoFrame> frame) { + ReleaseOutputBuffer(frame.get()); } - if (!reader->Initialize()) { - LOG(ERROR) << "Failed to initialize reader"; +}; + +static int Run(bool use_dxva, const std::string& input_file) { + // If we are not rendering, we need a window anyway to create a D3D device, + // so we will just use the desktop window. (?) + HWND window = GetDesktopWindow(); + scoped_ptr<FFmpegFileReader> reader(new FFmpegFileReader(input_file)); + if (reader.get() == NULL || !reader->Initialize()) { + LOG(ERROR) << "Failed to create/initialize reader"; return -1; } - int frame_rate_num = 0, frame_rate_denom = 0; - if (!reader->GetFrameRate(&frame_rate_num, &frame_rate_denom)) { - LOG(WARNING) << "Failed to get frame rate from reader"; - } int width = 0, height = 0; if (!reader->GetWidth(&width) || !reader->GetHeight(&height)) { LOG(WARNING) << "Failed to get width/height from reader"; } int aspect_ratio_num = 0, aspect_ratio_denom = 0; if (!reader->GetAspectRatio(&aspect_ratio_num, &aspect_ratio_denom)) { - LOG(WARNING) << "Failed to get aspect ratio from reader"; + LOG(WARNING) << "Failed to get aspect ratio"; + } + int frame_rate_num = 0, frame_rate_denom = 0; + if (!reader->GetFrameRate(&frame_rate_num, &frame_rate_denom)) { + LOG(WARNING) << "Failed to get frame rate"; } ScopedComPtr<IDirect3D9> d3d9; ScopedComPtr<IDirect3DDevice9> device; ScopedComPtr<IDirect3DDeviceManager9> dev_manager; if (use_dxva) { - dev_manager.Attach(CreateD3DDevManager(GetDesktopWindow(), - width, - height, + dev_manager.Attach(CreateD3DDevManager(window, d3d9.Receive(), device.Receive())); if (dev_manager.get() == NULL) { @@ -233,26 +185,29 @@ int Run(bool use_dxva, const std::string& input_file) { return -1; } } - scoped_ptr<H264Mft> mft(new H264Mft(use_dxva)); - if (mft.get() == NULL) { - LOG(ERROR) << "Failed to create MFT"; + scoped_ptr<MftH264Decoder> mft(new MftH264Decoder(use_dxva)); + scoped_ptr<FakeRenderer> renderer(new FakeRenderer()); + + if (mft.get() == NULL || renderer.get() == NULL) { + LOG(ERROR) << "Failed to create fake renderer / MFT"; return -1; } - if (!mft->Init(dev_manager, frame_rate_num, frame_rate_denom, width, height, - aspect_ratio_num, aspect_ratio_denom)) { + if (!mft->Init(dev_manager, + frame_rate_num, frame_rate_denom, + width, height, + aspect_ratio_num, aspect_ratio_denom, + NewCallback(reader.get(), &FFmpegFileReader::Read2), + NewCallback(renderer.get(), &FakeRenderer::ProcessFrame))) { LOG(ERROR) << "Failed to initialize mft"; return -1; } - base::TimeDelta decode_time; + Time decode_start(Time::Now()); while (true) { - // Do nothing with the sample except to let it go out of scope - scoped_refptr<VideoFrame> decoded_frame; - base::Time decode_start(base::Time::Now()); - if (!GetDecodedSample(reader.get(), mft.get(), &decoded_frame)) + if (MftH264Decoder::kOutputOk != mft->GetOutput()) break; - decode_time += base::Time::Now() - decode_start; - ReleaseOutputBuffer(decoded_frame.get()); } + TimeDelta decode_time = Time::Now() - decode_start; + printf("All done, frames read: %d, frames decoded: %d\n", mft->frames_read(), mft->frames_decoded()); printf("Took %lldms\n", decode_time.InMilliseconds()); @@ -268,7 +223,6 @@ int main(int argc, char** argv) { usage(); return -1; } - const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); if (cmd_line.HasSwitch("help")) { usage(); diff --git a/media/media_foundation/h264mft.cc b/media/mf/mft_h264_decoder.cc index bca600f..3ff88f6 100644 --- a/media/media_foundation/h264mft.cc +++ b/media/mf/mft_h264_decoder.cc @@ -1,30 +1,24 @@ -// 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. +// 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/media_foundation/h264mft.h" +#include "media/mf/mft_h264_decoder.h" #include <algorithm> #include <string> -#include <d3d9.h> -#include <evr.h> -#include <initguid.h> #include <mfapi.h> #include <mferror.h> -#include <mfidl.h> -#include <shlwapi.h> #include <wmcodecdsp.h> +#include "base/callback.h" #include "base/logging.h" #include "base/scoped_comptr_win.h" #include "media/base/video_frame.h" -#include "media/media_foundation/file_reader_util.h" #pragma comment(lib, "dxva2.lib") #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "mfuuid.lib") -#pragma comment(lib, "evr.lib") #pragma comment(lib, "mfplat.lib") namespace media { @@ -121,7 +115,7 @@ static IMFSample* CreateInputSample(uint8* stream, int size, LOG(ERROR) << "Failed to lock buffer"; return NULL; } - CHECK_EQ(static_cast<int>(current_length), 0); + CHECK_EQ(current_length, 0u); CHECK_GE(static_cast<int>(max_length), size); memcpy(destination, stream, size); CHECK(SUCCEEDED(buffer->Unlock())); @@ -136,8 +130,10 @@ static IMFSample* CreateInputSample(uint8* stream, int size, // Public methods -H264Mft::H264Mft(bool use_dxva) - : decoder_(NULL), +MftH264Decoder::MftH264Decoder(bool use_dxva) + : read_input_callback_(NULL), + output_avail_callback_(NULL), + decoder_(NULL), initialized_(false), use_dxva_(use_dxva), drain_message_sent_(false), @@ -147,18 +143,25 @@ H264Mft::H264Mft(bool use_dxva) frames_decoded_(0), width_(0), height_(0), - stride_(0) { + stride_(0), + output_format_(use_dxva ? MFVideoFormat_NV12 : MFVideoFormat_YV12) { } -H264Mft::~H264Mft() { +MftH264Decoder::~MftH264Decoder() { } -bool H264Mft::Init(IDirect3DDeviceManager9* dev_manager, - int frame_rate_num, int frame_rate_denom, - int width, int height, - int aspect_num, int aspect_denom) { +bool MftH264Decoder::Init(IDirect3DDeviceManager9* dev_manager, + int frame_rate_num, int frame_rate_denom, + int width, int height, + int aspect_num, int aspect_denom, + ReadInputCallback* read_input_cb, + OutputReadyCallback* output_avail_cb) { + CHECK(read_input_cb != NULL); + CHECK(output_avail_cb != NULL); if (initialized_) return true; + read_input_callback_.reset(read_input_cb); + output_avail_callback_.reset(output_avail_cb); if (!InitDecoder(dev_manager, frame_rate_num, frame_rate_denom, width, height, aspect_num, aspect_denom)) return false; @@ -170,8 +173,8 @@ bool H264Mft::Init(IDirect3DDeviceManager9* dev_manager, return true; } -bool H264Mft::SendInput(uint8* data, int size, int64 timestamp, - int64 duration) { +bool MftH264Decoder::SendInput(uint8* data, int size, int64 timestamp, + int64 duration) { CHECK(initialized_); CHECK(data != NULL); CHECK_GT(size, 0); @@ -205,10 +208,8 @@ static const char* const ProcessOutputStatusToCString(HRESULT hr) { return "unhandled error from ProcessOutput"; } -H264Mft::DecoderOutputState H264Mft::GetOutput( - scoped_refptr<VideoFrame>* decoded_frame) { +MftH264Decoder::DecoderOutputState MftH264Decoder::GetOutput() { CHECK(initialized_); - CHECK(decoded_frame != NULL); ScopedComPtr<IMFSample> output_sample; if (!use_dxva_) { @@ -220,154 +221,140 @@ H264Mft::DecoderOutputState H264Mft::GetOutput( } } MFT_OUTPUT_DATA_BUFFER output_data_buffer; - output_data_buffer.dwStreamID = 0; - output_data_buffer.pSample = output_sample; - output_data_buffer.dwStatus = 0; - output_data_buffer.pEvents = NULL; - DWORD status; HRESULT hr; - hr = decoder_->ProcessOutput(0, // No flags - 1, // # of out streams to pull from - &output_data_buffer, - &status); - - // TODO(imcheng): Handle the events, if any. (No event is returned most of - // the time.) - IMFCollection* events = output_data_buffer.pEvents; - if (events != NULL) { - LOG(INFO) << "Got events from ProcessOuput, but discarding"; - events->Release(); - } - if (FAILED(hr)) { - LOG(INFO) << "ProcessOutput failed with status " << std::hex << hr - << ", meaning..." << ProcessOutputStatusToCString(hr); - if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { - if (!SetDecoderOutputMediaType(MFVideoFormat_NV12)) { - LOG(ERROR) << "Failed to reset output type"; - return kResetOutputStreamFailed; - } else { - LOG(INFO) << "Reset output type done"; - return kResetOutputStreamOk; - } - } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { - // At this point we have either read everything from file or we can - // still feed the decoder input. If we have read everything then we - // should've sent a drain message to the MFT. If the drain message is - // sent but it doesn't give out anymore output then we know the decoder - // has processed everything. - if (drain_message_sent_) { - LOG(INFO) << "Drain message was already sent + no output => done"; - return kNoMoreOutput; + DWORD status; + for (;;) { + output_data_buffer.dwStreamID = 0; + output_data_buffer.pSample = output_sample; + output_data_buffer.dwStatus = 0; + output_data_buffer.pEvents = NULL; + hr = decoder_->ProcessOutput(0, // No flags + 1, // # of out streams to pull from + &output_data_buffer, + &status); + IMFCollection* events = output_data_buffer.pEvents; + if (events != NULL) { + LOG(INFO) << "Got events from ProcessOuput, but discarding"; + events->Release(); + } + if (FAILED(hr)) { + LOG(INFO) << "ProcessOutput failed with status " << std::hex << hr + << ", meaning..." << ProcessOutputStatusToCString(hr); + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { + if (!SetDecoderOutputMediaType(output_format_)) { + LOG(ERROR) << "Failed to reset output type"; + return kResetOutputStreamFailed; + } else { + LOG(INFO) << "Reset output type done"; + continue; + } + } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + // If we have read everything then we should've sent a drain message + // to the MFT. If the drain message is sent but it doesn't give out + // anymore output then we know the decoder has processed everything. + if (drain_message_sent_) { + LOG(INFO) << "Drain message was already sent + no output => done"; + return kNoMoreOutput; + } else { + if (!ReadAndProcessInput()) { + LOG(INFO) << "Failed to read/process input. Sending drain message"; + if (!SendDrainMessage()) { + LOG(ERROR) << "Failed to send drain message"; + return kNoMoreOutput; + } + } + continue; + } } else { - return kNeedMoreInput; + return kUnspecifiedError; } } else { - return kUnspecifiedError; - } - } else { - // A decoded sample was successfully obtained. - LOG(INFO) << "Got a decoded sample from decoder"; - if (use_dxva_) { - // If dxva is enabled, we did not provide a sample to ProcessOutput, - // i.e. output_sample is NULL. - output_sample.Attach(output_data_buffer.pSample); - if (output_sample.get() == NULL) { - LOG(ERROR) << "Output sample using DXVA is NULL - ProcessOutput did " - << "not provide it!"; + // A decoded sample was successfully obtained. + LOG(INFO) << "Got a decoded sample from decoder"; + if (use_dxva_) { + // If dxva is enabled, we did not provide a sample to ProcessOutput, + // i.e. output_sample is NULL. + output_sample.Attach(output_data_buffer.pSample); + if (output_sample.get() == NULL) { + LOG(ERROR) << "Output sample using DXVA is NULL - ProcessOutput did " + << "not provide it!"; + return kOutputSampleError; + } + } + int64 timestamp, duration; + hr = output_sample->GetSampleTime(×tamp); + hr = output_sample->GetSampleDuration(&duration); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get sample duration or timestamp " + << std::hex << hr; return kOutputSampleError; } - } - int64 timestamp, duration; - hr = output_sample->GetSampleTime(×tamp); - hr = output_sample->GetSampleDuration(&duration); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get sample duration or timestamp " - << std::hex << hr; - return kOutputSampleError; - } - - // 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)) { - LOG(ERROR) << "Failed to get buff count, hr = " << std::hex << hr; - return kOutputSampleError; - } - if (buf_count == 0) { - LOG(ERROR) << "buf_count is 0, dropping sample"; - return kOutputSampleError; - } - ScopedComPtr<IMFMediaBuffer> out_buffer; - hr = output_sample->GetBufferByIndex(0, out_buffer.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get decoded output buffer"; - return kOutputSampleError; - } - // To obtain the data, the caller should call the Lock() method instead - // of using the data field. - // In NV12, there are only 2 planes - the Y plane, and the interleaved UV - // plane. Both have the same strides. - uint8* null_data[2] = { NULL, NULL }; - int32 strides[2] = { stride_, stride_ }; - VideoFrame::CreateFrameExternal( - use_dxva_ ? VideoFrame::TYPE_DIRECT3DSURFACE : - VideoFrame::TYPE_MFBUFFER, - VideoFrame::NV12, - width_, - height_, - 2, - null_data, - strides, - base::TimeDelta::FromMicroseconds(timestamp), - base::TimeDelta::FromMicroseconds(duration), - out_buffer.Detach(), - decoded_frame); - CHECK(decoded_frame->get() != NULL); - frames_decoded_++; - return kOutputOk; - } -} + // The duration and timestamps are in 100-ns units, so divide by 10 + // to convert to microseconds. + timestamp /= 10; + duration /= 10; -bool H264Mft::SendDrainMessage() { - CHECK(initialized_); - if (drain_message_sent_) { - LOG(ERROR) << "Drain message was already sent before!"; - return false; - } + // Sanity checks for checking if there is really something in the sample. + DWORD buf_count; + hr = output_sample->GetBufferCount(&buf_count); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get buff count, hr = " << std::hex << hr; + return kOutputSampleError; + } + if (buf_count == 0) { + LOG(ERROR) << "buf_count is 0, dropping sample"; + return kOutputSampleError; + } + ScopedComPtr<IMFMediaBuffer> out_buffer; + hr = output_sample->GetBufferByIndex(0, out_buffer.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get decoded output buffer"; + return kOutputSampleError; + } - // Send the drain message with no parameters. - HRESULT hr = decoder_->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to send the drain message to decoder"; - return false; + // To obtain the data, the caller should call the Lock() method instead + // of using the data field. + // In NV12, there are only 2 planes - the Y plane, and the interleaved UV + // plane. Both have the same strides. + uint8* null_data[2] = { NULL, NULL }; + int32 strides[2] = { stride_, output_format_ == MFVideoFormat_NV12 ? + stride_ : + stride_ / 2 }; + scoped_refptr<VideoFrame> decoded_frame; + VideoFrame::CreateFrameExternal( + use_dxva_ ? VideoFrame::TYPE_DIRECT3DSURFACE : + VideoFrame::TYPE_MFBUFFER, + output_format_ == MFVideoFormat_NV12 ? VideoFrame::NV12 + : VideoFrame::YV12, + width_, + height_, + 2, + null_data, + strides, + base::TimeDelta::FromMicroseconds(timestamp), + base::TimeDelta::FromMicroseconds(duration), + out_buffer.Detach(), + &decoded_frame); + CHECK(decoded_frame.get() != NULL); + frames_decoded_++; + output_avail_callback_->Run(decoded_frame); + return kOutputOk; + } } - drain_message_sent_ = true; - return true; } // Private methods -bool H264Mft::InitDecoder(IDirect3DDeviceManager9* dev_manager, - int frame_rate_num, int frame_rate_denom, - int width, int height, - int aspect_num, int aspect_denom) { +bool MftH264Decoder::InitDecoder(IDirect3DDeviceManager9* dev_manager, + int frame_rate_num, int frame_rate_denom, + int width, int height, + int aspect_num, int aspect_denom) { decoder_.Attach(GetH264Decoder()); if (!decoder_.get()) return false; - if (!CheckDecoderProperties()) + if (use_dxva_ && !SetDecoderD3d9Manager(dev_manager)) return false; - if (use_dxva_) { - if (!CheckDecoderDxvaSupport()) - return false; - if (!SetDecoderD3d9Manager(dev_manager)) - return false; - } if (!SetDecoderMediaTypes(frame_rate_num, frame_rate_denom, width, height, aspect_num, aspect_denom)) { @@ -376,58 +363,8 @@ bool H264Mft::InitDecoder(IDirect3DDeviceManager9* dev_manager, return true; } -bool H264Mft::CheckDecoderProperties() { - DCHECK(decoder_.get()); - DWORD in_stream_count; - DWORD out_stream_count; - HRESULT hr; - hr = decoder_->GetStreamCount(&in_stream_count, &out_stream_count); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get stream count"; - return false; - } else { - LOG(INFO) << "Input stream count: " << in_stream_count << ", " - << "Output stream count: " << out_stream_count; - bool mismatch = false; - if (in_stream_count != 1) { - LOG(ERROR) << "Input stream count mismatch!"; - mismatch = true; - } - if (out_stream_count != 1) { - LOG(ERROR) << "Output stream count mismatch!"; - mismatch = true; - } - return !mismatch; - } -} - -bool H264Mft::CheckDecoderDxvaSupport() { - HRESULT hr; - ScopedComPtr<IMFAttributes> attributes; - hr = decoder_->GetAttributes(attributes.Receive()); - if (FAILED(hr)) { - LOG(ERROR) << "Unlock: Failed to get attributes, hr = " - << std::hex << std::showbase << hr; - return false; - } - UINT32 dxva; - hr = attributes->GetUINT32(MF_SA_D3D_AWARE, &dxva); - if (FAILED(hr)) { - LOG(ERROR) << "Failed to get DXVA attr, hr = " - << std::hex << std::showbase << hr - << "this might not be the right decoder."; - return false; - } - LOG(INFO) << "Support dxva? " << dxva; - if (!dxva) { - LOG(ERROR) << "Decoder does not support DXVA - this might not be the " - << "right decoder."; - return false; - } - return true; -} - -bool H264Mft::SetDecoderD3d9Manager(IDirect3DDeviceManager9* dev_manager) { +bool MftH264Decoder::SetDecoderD3d9Manager( + IDirect3DDeviceManager9* dev_manager) { DCHECK(use_dxva_) << "SetDecoderD3d9Manager should only be called if DXVA is " << "enabled"; CHECK(dev_manager != NULL); @@ -441,56 +378,59 @@ bool H264Mft::SetDecoderD3d9Manager(IDirect3DDeviceManager9* dev_manager) { return true; } -bool H264Mft::SetDecoderMediaTypes(int frame_rate_num, int frame_rate_denom, - int width, int height, - int aspect_num, int aspect_denom) { +bool MftH264Decoder::SetDecoderMediaTypes(int frame_rate_num, + int frame_rate_denom, + int width, int height, + int aspect_num, int aspect_denom) { DCHECK(decoder_.get()); if (!SetDecoderInputMediaType(frame_rate_num, frame_rate_denom, width, height, aspect_num, aspect_denom)) return false; - if (!SetDecoderOutputMediaType(MFVideoFormat_NV12)) { + if (!SetDecoderOutputMediaType(output_format_)) { return false; } return true; } -bool H264Mft::SetDecoderInputMediaType(int frame_rate_num, int frame_rate_denom, - int width, int height, - int aspect_num, int aspect_denom) { +bool MftH264Decoder::SetDecoderInputMediaType(int frame_rate_num, + int frame_rate_denom, + int width, int height, + int aspect_num, + int aspect_denom) { ScopedComPtr<IMFMediaType> media_type; HRESULT hr; hr = MFCreateMediaType(media_type.Receive()); if (FAILED(hr)) { LOG(ERROR) << "Failed to create empty media type object"; - return NULL; + return false; } hr = media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); if (FAILED(hr)) { LOG(ERROR) << "SetGUID for major type failed"; - return NULL; + return false; } hr = media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); if (FAILED(hr)) { LOG(ERROR) << "SetGUID for subtype failed"; - return NULL; + return false; } // Provide additional info to the decoder to avoid a format change during // streaming. - if (frame_rate_num == 0 || frame_rate_denom == 0) { + if (frame_rate_num > 0 && frame_rate_denom > 0) { hr = MFSetAttributeRatio(media_type.get(), MF_MT_FRAME_RATE, frame_rate_num, frame_rate_denom); if (FAILED(hr)) { LOG(ERROR) << "Failed to set frame rate"; - return NULL; + return false; } } - if (width == 0 || height == 0) { + if (width > 0 && height > 0) { hr = MFSetAttributeSize(media_type.get(), MF_MT_FRAME_SIZE, width, height); if (FAILED(hr)) { LOG(ERROR) << "Failed to set frame size"; - return NULL; + return false; } } @@ -500,14 +440,14 @@ bool H264Mft::SetDecoderInputMediaType(int frame_rate_num, int frame_rate_denom, MFVideoInterlace_MixedInterlaceOrProgressive); if (FAILED(hr)) { LOG(ERROR) << "Failed to set interlace mode"; - return NULL; + return false; } - if (aspect_num == 0 || aspect_denom == 0) { + if (aspect_num > 0 && aspect_denom > 0) { hr = MFSetAttributeRatio(media_type.get(), MF_MT_PIXEL_ASPECT_RATIO, aspect_num, aspect_denom); if (FAILED(hr)) { LOG(ERROR) << "Failed to get aspect ratio"; - return NULL; + return false; } } hr = decoder_->SetInputType(0, media_type.get(), 0); // No flags @@ -518,7 +458,7 @@ bool H264Mft::SetDecoderInputMediaType(int frame_rate_num, int frame_rate_denom, return true; } -bool H264Mft::SetDecoderOutputMediaType(const GUID subtype) { +bool MftH264Decoder::SetDecoderOutputMediaType(const GUID subtype) { DWORD i = 0; IMFMediaType* out_media_type; bool found = false; @@ -538,10 +478,9 @@ bool H264Mft::SetDecoderOutputMediaType(const GUID subtype) { hr = MFGetAttributeSize(out_media_type, MF_MT_FRAME_SIZE, reinterpret_cast<UINT32*>(&width_), reinterpret_cast<UINT32*>(&height_)); - hr = MFGetStrideForBitmapInfoHeader( - MFVideoFormat_NV12.Data1, - width_, - reinterpret_cast<LONG*>(&stride_)); + hr = MFGetStrideForBitmapInfoHeader(output_format_.Data1, + width_, + reinterpret_cast<LONG*>(&stride_)); if (FAILED(hr)) { LOG(ERROR) << "Failed to SetOutputType to |subtype| or obtain " << "width/height/stride " << std::hex << hr; @@ -555,13 +494,13 @@ bool H264Mft::SetDecoderOutputMediaType(const GUID subtype) { out_media_type->Release(); } if (!found) { - LOG(ERROR) << "NV12 was not found in GetOutputAvailableType()"; + LOG(ERROR) << "|subtype| was not found in GetOutputAvailableType()"; return false; } return true; } -bool H264Mft::SendStartMessage() { +bool MftH264Decoder::SendStartMessage() { HRESULT hr; hr = decoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL); if (FAILED(hr)) { @@ -579,7 +518,7 @@ bool H264Mft::SendStartMessage() { // 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 H264Mft::GetStreamsInfoAndBufferReqs() { +bool MftH264Decoder::GetStreamsInfoAndBufferReqs() { DCHECK(decoder_.get()); HRESULT hr; MFT_INPUT_STREAM_INFO input_stream_info; @@ -596,13 +535,11 @@ bool H264Mft::GetStreamsInfoAndBufferReqs() { // 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(static_cast<int>(input_stream_info.dwFlags), 0x7); + 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; - if (input_stream_info.cbAlignment > 0) { - LOG(WARNING) << "Warning: Decoder requires input to be aligned"; - } + CHECK_EQ(input_stream_info.cbAlignment, 0u); in_buffer_size_ = input_stream_info.cbSize; MFT_OUTPUT_STREAM_INFO output_stream_info; @@ -618,16 +555,46 @@ bool H264Mft::GetStreamsInfoAndBufferReqs() { // allocate its own sample. LOG(INFO) << "Flags: " << std::hex << std::showbase << output_stream_info.dwFlags; - CHECK_EQ(static_cast<int>(output_stream_info.dwFlags), - use_dxva_ ? 0x107 : 0x7); + 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; - if (output_stream_info.cbAlignment > 0) { - LOG(WARNING) << "Warning: Decoder requires output to be aligned"; - } + CHECK_EQ(output_stream_info.cbAlignment, 0u); out_buffer_size_ = output_stream_info.cbSize; return true; } +bool MftH264Decoder::ReadAndProcessInput() { + uint8* input_stream_dummy; + int size; + int64 duration; + int64 timestamp; + read_input_callback_->Run(&input_stream_dummy, &size, ×tamp, &duration); + scoped_array<uint8> input_stream(input_stream_dummy); + if (input_stream.get() == NULL) { + LOG(INFO) << "No more input"; + return false; + } else { + // We read an input stream, we can feed it into the decoder. + return SendInput(input_stream.get(), size, timestamp, duration); + } +} + +bool MftH264Decoder::SendDrainMessage() { + CHECK(initialized_); + if (drain_message_sent_) { + LOG(ERROR) << "Drain message was already sent before!"; + return false; + } + + // Send the drain message with no parameters. + HRESULT hr = decoder_->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to send the drain message to decoder"; + return false; + } + drain_message_sent_ = true; + return true; +} + } // namespace media diff --git a/media/media_foundation/h264mft.h b/media/mf/mft_h264_decoder.h index 3e60a23..3a57d3e 100644 --- a/media/media_foundation/h264mft.h +++ b/media/mf/mft_h264_decoder.h @@ -1,56 +1,62 @@ -// 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. +// 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. // // Decodes H.264 Annex B streams using the Media Foundation H.264 decoder as // a standalone Media Foundation Transform (MFT). -// Note: A single H264Mft instance is only for 1 H.264 video stream only. +// Note: A single MftH264Decoder 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. -#ifndef MEDIA_MEDIA_FOUNDATION_H264MFT_H_ -#define MEDIA_MEDIA_FOUNDATION_H264MFT_H_ +#ifndef MEDIA_MF_MFT_H264_DECODER_H_ +#define MEDIA_MF_MFT_H264_DECODER_H_ #include <string> #include <mfidl.h> #include "base/basictypes.h" +#include "base/callback.h" #include "base/scoped_ptr.h" #include "base/scoped_comptr_win.h" -#include "media/base/video_frame.h" struct IDirect3DDeviceManager9; -struct IMFSample; struct IMFTransform; namespace media { +class VideoFrame; + // A decoder that takes samples of Annex B streams then outputs decoded frames. -class H264Mft { +class MftH264Decoder { public: + typedef Callback4<uint8**, int*, int64*, int64*>::Type ReadInputCallback; + typedef Callback1<scoped_refptr<VideoFrame> >::Type OutputReadyCallback; enum DecoderOutputState { kOutputOk = 0, kResetOutputStreamFailed, - kResetOutputStreamOk, - kNeedMoreInput, kNoMoreOutput, kUnspecifiedError, kNoMemory, kOutputSampleError }; - explicit H264Mft(bool use_dxva); - ~H264Mft(); + explicit MftH264Decoder(bool use_dxva); + ~MftH264Decoder(); // Initializes the decoder. |dev_manager| is not required if the decoder does // not use DXVA. // If the other arguments are not known, leave them as 0. They can be // provided to the decoder to try to avoid an initial output format change, // but it is not necessary to have them. + // The object takes ownership of the callbacks. However, the caller must + // make sure the objects associated with the callbacks outlives the time + // when GetOutput() will be called. bool Init(IDirect3DDeviceManager9* dev_manager, int frame_rate_num, int frame_rate_denom, int width, int height, - int aspect_num, int aspect_denom); + int aspect_num, int aspect_denom, + ReadInputCallback* read_input_cb, + OutputReadyCallback* output_avail_cb); // Sends an Annex B stream to the decoder. The times here should be given // in 100ns units. This creates a IMFSample, copies the stream over to the @@ -58,14 +64,10 @@ class H264Mft { // Returns: true if the sample was sent successfully. bool SendInput(uint8* data, int size, int64 timestamp, int64 duration); - // Tries to get an output sample from the decoder. - // Returns: status of the decoder, and if successful, a decoded sample. - DecoderOutputState GetOutput(scoped_refptr<VideoFrame>* decoded_frame); - - // Sends a drain message to the decoder to indicate no more input will be - // sent. SendInput() should not be called after calling this method. - // Returns: true if the drain message was sent successfully. - bool SendDrainMessage(); + // Tries to get an output sample from the decoder, and if successful, calls + // the callback with the sample. + // Returns: status of the decoder. + DecoderOutputState GetOutput(); bool initialized() const { return initialized_; } bool use_dxva() const { return use_dxva_; } @@ -82,8 +84,6 @@ class H264Mft { int frame_rate_num, int frame_rate_denom, int width, int height, int aspect_num, int aspect_denom); - bool CheckDecoderProperties(); - bool CheckDecoderDxvaSupport(); bool SetDecoderD3d9Manager(IDirect3DDeviceManager9* dev_manager); bool SetDecoderMediaTypes(int frame_rate_num, int frame_rate_denom, int width, int height, @@ -94,7 +94,15 @@ class H264Mft { bool SetDecoderOutputMediaType(const GUID subtype); bool SendStartMessage(); bool GetStreamsInfoAndBufferReqs(); + bool ReadAndProcessInput(); + + // Sends a drain message to the decoder to indicate no more input will be + // sent. SendInput() should not be called after calling this method. + // Returns: true if the drain message was sent successfully. + bool SendDrainMessage(); + scoped_ptr<ReadInputCallback> read_input_callback_; + scoped_ptr<OutputReadyCallback> output_avail_callback_; ScopedComPtr<IMFTransform> decoder_; bool initialized_; bool use_dxva_; @@ -108,10 +116,11 @@ class H264Mft { int width_; int height_; int stride_; + const GUID output_format_; - DISALLOW_COPY_AND_ASSIGN(H264Mft); + DISALLOW_COPY_AND_ASSIGN(MftH264Decoder); }; } // namespace media -#endif // MEDIA_MF_H264MFT_H_ +#endif // MEDIA_MF_MFT_H264_DECODER_H_ |