You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2650 lines
82 KiB

/*****************************************************************************
* mft.cpp : Media Foundation Transform audio/video decoder
*****************************************************************************
* Copyright (C) 2014 VLC authors and VideoLAN
*
* Author: Felix Abecassis <felix.abecassis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef _MSC_VER // including mfapi with mingw-w64 is not clean for UWP yet
#include <process.h>
#include <winapifamily.h>
#undef WINAPI_FAMILY
#define WINAPI_FAMILY WINAPI_FAMILY_DESKTOP_APP
#endif
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cassert>
#include <vector>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_codec.h>
#include <vlc_aout.h>
extern "C" {
#include "hxxx_helper.h"
}
#include "mft_d3d.h"
#include "mft_d3d11.h"
#include <initguid.h>
#include <mfapi.h>
#include <mftransform.h>
#include <mferror.h>
#include <mfobjects.h>
#include <codecapi.h>
#include <mfidl.h>
#if !defined(CODECAPI_AVDecVideoAcceleration_H264) // MINGW < 8.0.1
DEFINE_CODECAPI_GUID(AVDecVideoAcceleration_H264, "f7db8a2f-4f48-4ee8-ae31-8b6ebe558ae2", 0xf7db8a2f, 0x4f48, 0x4ee8, 0xae, 0x31, 0x8b, 0x6e, 0xbe, 0x55, 0x8a, 0xe2)
DEFINE_CODECAPI_GUID(AVDecVideoAcceleration_VC1, "f7db8a30-4f48-4ee8-ae31-8b6ebe558ae2", 0xf7db8a30, 0x4f48, 0x4ee8, 0xae, 0x31, 0x8b, 0x6e, 0xbe, 0x55, 0x8a, 0xe2)
DEFINE_CODECAPI_GUID(AVDecVideoAcceleration_MPEG2, "f7db8a2e-4f48-4ee8-ae31-8b6ebe558ae2", 0xf7db8a2e, 0x4f48, 0x4ee8, 0xae, 0x31, 0x8b, 0x6e, 0xbe, 0x55, 0x8a, 0xe2)
#define CODECAPI_AVDecVideoAcceleration_H264 DEFINE_CODECAPI_GUIDNAMED(AVDecVideoAcceleration_H264)
#define CODECAPI_AVDecVideoAcceleration_VC1 DEFINE_CODECAPI_GUIDNAMED(AVDecVideoAcceleration_VC1)
#define CODECAPI_AVDecVideoAcceleration_MPEG2 DEFINE_CODECAPI_GUIDNAMED(AVDecVideoAcceleration_MPEG2)
#endif
#include <vlc_codecs.h> // wf_tag_to_fourcc
#include <algorithm>
#include <atomic>
#include <new>
#include <memory>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
static int Open(vlc_object_t *);
static void Close(vlc_object_t *);
static int OpenMFTAudioEncoder(vlc_object_t *);
static int OpenMFTVideoEncoder(vlc_object_t *);
#define MFT_DEBUG_TEXT N_("Extra MFT Debug")
#define MFT_DEBUG_LONGTEXT N_( "Show more MediaFoundation debug info, may be slower to load" )
vlc_module_begin()
set_description(N_("Media Foundation Transform decoder"))
add_shortcut("mft")
set_capability("video decoder", 1)
set_callbacks(Open, Close)
set_subcategory(SUBCAT_INPUT_VCODEC)
add_bool("mft-debug", false, MFT_DEBUG_TEXT, MFT_DEBUG_LONGTEXT)
add_submodule()
add_shortcut("mft")
set_capability("audio decoder", 1)
set_callbacks(Open, Close)
#ifdef ENABLE_SOUT
add_submodule()
add_shortcut("mft")
set_capability("audio encoder", 10) // less than DMO for now
set_callback(OpenMFTAudioEncoder)
add_submodule()
add_shortcut("mft")
set_capability("video encoder", 10) // less than DMO for now
set_callback(OpenMFTVideoEncoder)
#endif
vlc_module_end()
class mft_sys_t : public vlc_mft_ref
{
public:
ComPtr<IMFTransform> mft;
virtual ~mft_sys_t()
{
assert(!streamStarted);
event_generator.Reset();
mft.Reset();
input_type.Reset();
output_sample.Reset();
MFShutdown();
CoUninitialize();
}
virtual bool IsEncoder() const = 0;
/* For asynchronous MFT */
bool is_async = false;
ComPtr<IMFMediaEventGenerator> event_generator;
int pending_input_events = 0;
int pending_output_events = 0;
int pending_drain_events = 0;
HRESULT DequeueMediaEvent(vlc_logger *, bool wait = false);
/* Input stream */
DWORD input_stream_id = 0;
ComPtr<IMFMediaType> input_type;
/* Output stream */
DWORD output_stream_id = 0;
ComPtr<IMFSample> output_sample;
HRESULT AllocateOutputSample(es_format_category_e cat, ComPtr<IMFSample> & result);
HRESULT SetOutputType(vlc_logger *, const GUID & req_subtype, es_format_t & fmt_out);
HRESULT SetInputType(const es_format_t & fmt_in, const MFT_REGISTER_TYPE_INFO &);
virtual void DoRelease() = 0;
void AddRef() final
{
refcount++;
}
bool Release() final
{
if (--refcount == 0)
{
if (output_sample.Get())
output_sample->RemoveAllBuffers();
DoRelease();
delete this;
return true;
}
return false;
}
/// Required for Async MFTs
HRESULT startStream()
{
assert(!streamStarted);
HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, (ULONG_PTR)0);
if (SUCCEEDED(hr))
streamStarted = true;
return hr;
}
/// Used for Async MFTs
HRESULT endStream()
{
assert(streamStarted);
HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, (ULONG_PTR)0);
if (SUCCEEDED(hr))
streamStarted = false;
return hr;
}
HRESULT flushStream()
{
HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
if (SUCCEEDED(hr))
streamStarted = false;
return hr;
}
/// Required for Async MFTs
HRESULT shutdownStream()
{
ComPtr<IMFShutdown> shutdownObj;
HRESULT hr = mft.As(&shutdownObj);
if (FAILED(hr))
return hr;
return shutdownObj->Shutdown();
}
private:
bool streamStarted = false;
std::atomic<size_t> refcount{1};
};
class mft_dec_audio : public mft_sys_t
{
public:
virtual ~mft_dec_audio() = default;
bool IsEncoder() const final { return false; }
protected:
void DoRelease() override
{
}
};
/**
* Generic class to handle Direct3D with MediaFoundation
*/
class vlc_mft_d3d : public mft_sys_t
{
public:
vlc_mft_d3d() = default;
virtual ~vlc_mft_d3d() = default;
std::unique_ptr<MFHW_d3d> hw_d3d;
HRESULT SetD3D(vlc_logger *, vlc_decoder_device &);
protected:
void DoRelease() override
{
if (hw_d3d)
hw_d3d->Release(mft);
}
};
class mft_dec_video : public vlc_mft_d3d
{
public:
mft_dec_video() = default;
virtual ~mft_dec_video() = default;
bool IsEncoder() const final { return false; }
/* H264 only. */
struct hxxx_helper hh = {};
bool b_xps_pushed = false; ///< (for xvcC) parameter sets pushed (SPS/PPS/VPS)
};
class mft_enc_audio : public mft_sys_t
{
protected:
void DoRelease() override
{
}
bool IsEncoder() const final { return true; }
};
class mft_enc_video : public vlc_mft_d3d
{
public:
HRESULT ProcessInputPicture(struct vlc_logger *, picture_t *);
HRESULT ProcessOutput(vlc_logger *, block_t * & output);
bool IsEncoder() const final { return true; }
private:
bool is_first_picture = true;
};
static const int pi_channels_maps[9] =
{
0,
AOUT_CHAN_CENTER,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT,
AOUT_CHAN_CENTER | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_REARLEFT
| AOUT_CHAN_REARRIGHT,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
| AOUT_CHAN_REARCENTER | AOUT_CHAN_MIDDLELEFT
| AOUT_CHAN_MIDDLERIGHT | AOUT_CHAN_LFE,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER | AOUT_CHAN_REARLEFT
| AOUT_CHAN_REARRIGHT | AOUT_CHAN_MIDDLELEFT | AOUT_CHAN_MIDDLERIGHT
| AOUT_CHAN_LFE,
};
/* Possibly missing from mingw headers */
#ifndef MF_E_NO_EVENTS_AVAILABLE
# define MF_E_NO_EVENTS_AVAILABLE _HRESULT_TYPEDEF_(0xC00D3E80L)
#endif
typedef struct
{
vlc_fourcc_t fourcc;
const GUID guid;
} pair_format_guid;
#if defined(__MINGW64_VERSION_MAJOR)
# if __MINGW64_VERSION_MAJOR < 10 && !defined(CODECAPI_AVDecVideoAcceleration_H264)
// 8-bit luminance only
// Older versions of mingw-w64 lack this GUID, but it was added in mingw-w64
// git on 2021-07-11 (during __MINGW64_VERSION_MAJOR 10). Use a local
// redefinition of this GUID with a custom prefix, to let the same code build
// with both older and newer versions of mingw-w64 (and earlier git snapshots
// with __MINGW64_VERSION_MAJOR == 10).
DEFINE_MEDIATYPE_GUID (MFVideoFormat_L8, 50); // D3DFMT_L8
DEFINE_MEDIATYPE_GUID (MFVideoFormat_AV1, FCC('AV01'));
# endif // __MINGW64_VERSION_MAJOR >= 0
#endif // __MINGW64_VERSION_MAJOR
/*
* We need this table since the FOURCC used for GUID is not the same
* as the FOURCC used by VLC, for instance h264 vs H264.
*/
static const pair_format_guid video_codec_table[] =
{
{ VLC_CODEC_H264, MFVideoFormat_H264 },
{ VLC_CODEC_MPGV, MFVideoFormat_MPEG2 },
{ VLC_CODEC_MP2V, MFVideoFormat_MPEG2 },
{ VLC_CODEC_MP1V, MFVideoFormat_MPG1 },
{ VLC_CODEC_MJPG, MFVideoFormat_MJPG },
{ VLC_CODEC_WMV1, MFVideoFormat_WMV1 },
{ VLC_CODEC_WMV2, MFVideoFormat_WMV2 },
{ VLC_CODEC_WMV3, MFVideoFormat_WMV3 },
{ VLC_CODEC_VC1, MFVideoFormat_WVC1 },
{ VLC_CODEC_AV1, MFVideoFormat_AV1 },
{ 0, GUID_NULL }
};
/*
* Table to map MF Transform raw chroma output formats to native VLC FourCC
*/
static const pair_format_guid chroma_format_table[] = {
{ VLC_CODEC_NV12, MFVideoFormat_NV12 },
{ VLC_CODEC_I420, MFVideoFormat_I420 },
{ VLC_CODEC_YV12, MFVideoFormat_YV12 },
{ VLC_CODEC_YV12, MFVideoFormat_IYUV },
{ VLC_CODEC_YUYV, MFVideoFormat_YUY2 },
{ VLC_CODEC_BGRX, MFVideoFormat_RGB32 },
{ VLC_CODEC_BGR24, MFVideoFormat_RGB24 },
{ VLC_CODEC_BGRA, MFVideoFormat_ARGB32 },
{ VLC_CODEC_GREY, MFVideoFormat_L8 },
{ 0, GUID_NULL }
};
/*
* We cannot use the FOURCC code for audio either since the
* WAVE_FORMAT value is used to create the GUID.
*/
static const pair_format_guid audio_codec_table[] =
{
{ VLC_CODEC_MPGA, MFAudioFormat_MPEG },
{ VLC_CODEC_MP3, MFAudioFormat_MP3 },
{ VLC_CODEC_DTS, MFAudioFormat_DTS },
{ VLC_CODEC_MP4A, MFAudioFormat_AAC },
{ VLC_CODEC_WMA2, MFAudioFormat_WMAudioV8 },
{ VLC_CODEC_A52, MFAudioFormat_Dolby_AC3 },
{ 0, GUID_NULL }
};
static HRESULT MFTypeFromCodec(const GUID & type, vlc_fourcc_t codec, MFT_REGISTER_TYPE_INFO & info)
{
const pair_format_guid *table = type == MFMediaType_Video ? video_codec_table : audio_codec_table;
for (int i = 0; table[i].fourcc; ++i)
if (table[i].fourcc == codec)
{
info.guidMajorType = type;
info.guidSubtype = table[i].guid;
return S_OK;
}
return E_INVALIDARG;
}
static vlc_fourcc_t MFFormatToChroma(const GUID & guid)
{
for (int i = 0; chroma_format_table[i].fourcc; ++i)
if (chroma_format_table[i].guid == guid)
return chroma_format_table[i].fourcc;
return 0;
}
static HRESULT MFTypeFromAudio(vlc_fourcc_t audio_format, MFT_REGISTER_TYPE_INFO & info)
{
if (audio_format == VLC_CODEC_F32L)
{
info.guidMajorType = MFMediaType_Audio;
info.guidSubtype = MFAudioFormat_Float;
return S_OK;
}
if (audio_format == VLC_CODEC_S16L)
{
info.guidMajorType = MFMediaType_Audio;
info.guidSubtype = MFAudioFormat_PCM;
return S_OK;
}
return E_INVALIDARG;
}
static vlc_fourcc_t MFFormatToCodec(const pair_format_guid table[], const GUID & guid)
{
for (int i = 0; table[i].fourcc; ++i)
if (table[i].guid == guid)
return table[i].fourcc;
return 0;
}
static HRESULT MFTypeFromChroma(vlc_fourcc_t chroma, vlc_video_context *vctx, MFT_REGISTER_TYPE_INFO & info)
{
if (vctx && vlc_video_context_GetType(vctx) == VLC_VIDEO_CONTEXT_D3D11VA)
{
assert(is_d3d11_opaque(chroma));
auto *dev_sys = GetD3D11ContextPrivate(vctx);
chroma = DxgiFormatFourcc(dev_sys->format);
assert(chroma != 0);
}
for (int i = 0; chroma_format_table[i].fourcc; ++i)
if (chroma_format_table[i].fourcc == chroma)
{
info.guidMajorType = MFMediaType_Video;
info.guidSubtype = chroma_format_table[i].guid;
return S_OK;
}
return E_INVALIDARG;
}
HRESULT mft_sys_t::SetInputType(const es_format_t & fmt_in, const MFT_REGISTER_TYPE_INFO & type)
{
HRESULT hr;
DWORD stream_id = input_stream_id;
ComPtr<IMFMediaType> & result = input_type;
result.Reset();
ComPtr<IMFMediaType> input_media_type;
/* Search a suitable input type for the MFT. */
for (int i = 0;; ++i)
{
hr = mft->GetInputAvailableType(stream_id, i, input_media_type.ReleaseAndGetAddressOf());
if (hr == MF_E_NO_MORE_TYPES)
goto error;
else if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
{
/* The output type must be set before setting the input type for this MFT. */
return hr;
}
else if (FAILED(hr))
goto error;
GUID subtype;
hr = input_media_type->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr))
goto error;
if (subtype == type.guidSubtype)
break;
}
if (fmt_in.i_cat == VIDEO_ES)
{
UINT32 width = fmt_in.video.i_visible_width;
UINT32 height = fmt_in.video.i_visible_height;
hr = MFSetAttributeSize(input_media_type.Get(), MF_MT_FRAME_SIZE, width, height);
if (FAILED(hr))
goto error;
/* Some transforms like to know the frame rate and may reject the input type otherwise. */
UINT32 frame_ratio_num = fmt_in.video.i_frame_rate;
UINT32 frame_ratio_den = fmt_in.video.i_frame_rate_base;
if(frame_ratio_num && frame_ratio_den) {
hr = MFSetAttributeRatio(input_media_type.Get(), MF_MT_FRAME_RATE, frame_ratio_num, frame_ratio_den);
if(FAILED(hr))
goto error;
}
}
else
{
hr = input_media_type->SetUINT32(MF_MT_ORIGINAL_WAVE_FORMAT_TAG, type.guidSubtype.Data1);
if (FAILED(hr))
goto error;
if (fmt_in.audio.i_rate)
{
hr = input_media_type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, fmt_in.audio.i_rate);
if (FAILED(hr))
goto error;
}
if (fmt_in.audio.i_channels)
{
hr = input_media_type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, fmt_in.audio.i_channels);
if (FAILED(hr))
goto error;
}
if (fmt_in.audio.i_bitspersample)
{
hr = input_media_type->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, fmt_in.audio.i_bitspersample);
if (FAILED(hr))
goto error;
}
if (fmt_in.audio.i_blockalign)
{
hr = input_media_type->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, fmt_in.audio.i_blockalign);
if (FAILED(hr))
goto error;
}
if (fmt_in.i_bitrate)
{
hr = input_media_type->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, fmt_in.i_bitrate / 8);
if (FAILED(hr))
goto error;
}
}
if (fmt_in.i_extra > 0)
{
UINT32 blob_size = 0;
hr = input_media_type->GetBlobSize(MF_MT_USER_DATA, &blob_size);
/*
* Do not overwrite existing user data in the input type, this
* can cause the MFT to reject the type.
*/
if (hr == MF_E_ATTRIBUTENOTFOUND)
{
hr = input_media_type->SetBlob(MF_MT_USER_DATA,
static_cast<const UINT8*>(fmt_in.p_extra), fmt_in.i_extra);
if (FAILED(hr))
goto error;
}
}
hr = mft->SetInputType(stream_id, input_media_type.Get(), 0);
if (FAILED(hr))
goto error;
result.Swap(input_media_type);
error:
return hr;
}
HRESULT mft_sys_t::SetOutputType(vlc_logger *logger,
const GUID & req_subtype, es_format_t & fmt_out)
{
HRESULT hr;
ComPtr<IMFMediaType> output_media_type;
/*
* Enumerate available output types. The list is ordered by
* preference thus we will use the first one unless YV12/I420 is
* available for video or float32 for audio.
*/
GUID subtype;
UINT32 best_bitrate = 0;
int best_index = 0;
int output_type_index = -1;
for (int i = 0; output_type_index == -1; ++i)
{
hr = mft->GetOutputAvailableType(output_stream_id, i, output_media_type.ReleaseAndGetAddressOf());
if (hr == MF_E_NO_MORE_TYPES)
{
/*
* It's not an error if we don't find the output type we were
* looking for, in this case we use the first available type.
*/
/* No output format found we prefer, just pick the first one preferred
* by the MFT */
break;
}
else if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
{
/* The input type must be set before setting the output type for this MFT. */
return hr;
}
else if (FAILED(hr))
goto error;
hr = output_media_type->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr))
goto error;
if (req_subtype != GUID_NULL)
{
if (subtype != req_subtype)
continue;
}
if (fmt_out.i_cat == VIDEO_ES)
{
if (IsEncoder())
{
if (MFFormatToCodec(video_codec_table, subtype) != 0)
output_type_index = i;
}
else
{
if(MFFormatToChroma(subtype) != 0)
output_type_index = i;
}
}
else
{
if (req_subtype == GUID_NULL)
{
UINT32 bits_per_sample;
hr = output_media_type->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &bits_per_sample);
if (FAILED(hr))
continue;
if (bits_per_sample == 32 && subtype == MFAudioFormat_Float)
output_type_index = i;
if (bits_per_sample == 16 && subtype == MFAudioFormat_PCM)
output_type_index = i;
continue;
}
UINT32 rate = 0;
hr = output_media_type->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &rate);
if (FAILED(hr) || rate != fmt_out.audio.i_rate)
continue;
UINT32 channels = 0;
hr = output_media_type->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &channels);
if (FAILED(hr) || channels != fmt_out.audio.i_channels)
continue;
UINT32 abps = 0;
hr = output_media_type->GetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, &abps);
if (FAILED(hr))
continue;
if (abps > best_bitrate && (fmt_out.i_bitrate == 0 || (8 * abps) <= fmt_out.i_bitrate))
{
best_bitrate = abps;
best_index = i;
}
}
}
if (output_type_index == -1)
output_type_index = best_index;
hr = mft->GetOutputAvailableType(output_stream_id, output_type_index, output_media_type.ReleaseAndGetAddressOf());
if (FAILED(hr))
goto error;
hr = output_media_type->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr))
goto error;
if (fmt_out.i_cat == VIDEO_ES)
{
if (IsEncoder())
{
fmt_out.i_codec = MFFormatToCodec(video_codec_table, subtype);
}
else
{
/* Transform might offer output in a D3DFMT proprietary FCC */
fmt_out.i_codec = MFFormatToChroma(subtype);
if(!fmt_out.i_codec) {
if (subtype == MFVideoFormat_IYUV)
subtype = MFVideoFormat_I420;
fmt_out.i_codec = vlc_fourcc_GetCodec(VIDEO_ES, subtype.Data1);
}
}
}
if (fmt_out.i_cat == VIDEO_ES && IsEncoder())
{
if (fmt_out.i_bitrate != 0)
fmt_out.i_bitrate = 1'000'000;
output_media_type->SetUINT32(MF_MT_AVG_BITRATE, fmt_out.i_bitrate);
if (fmt_out.video.i_frame_rate && fmt_out.video.i_frame_rate_base)
{
hr = MFSetAttributeRatio(output_media_type.Get(), MF_MT_FRAME_RATE,
fmt_out.video.i_frame_rate,
fmt_out.video.i_frame_rate_base);
}
hr = MFSetAttributeRatio(output_media_type.Get(), MF_MT_FRAME_SIZE,
fmt_out.video.i_visible_width,
fmt_out.video.i_visible_height);
hr = MFSetAttributeRatio(output_media_type.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
hr = output_media_type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
hr = output_media_type->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, 0);
hr = output_media_type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1);
if (fmt_out.i_codec == VLC_CODEC_H264)
{
eAVEncH264VProfile profile;
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
bool isWin81OrGreater = false;
HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll"));
if (likely(hKernel32 != NULL))
isWin81OrGreater = GetProcAddress(hKernel32, "IsProcessCritical") != NULL;
profile = isWin81OrGreater ? eAVEncH264VProfile_High : eAVEncH264VProfile_Main;
#else
profile = eAVEncH264VProfile_High;
#endif
hr = output_media_type->SetUINT32(MF_MT_MPEG2_PROFILE, profile);
//hr = output_media_type->SetUINT32(MF_MT_MPEG2_LEVEL, eAVEncH264VLevel4);
}
}
hr = mft->SetOutputType(output_stream_id, output_media_type.Get(), 0);
if (FAILED(hr))
{
vlc_error(logger, "Failed to set the output. (hr=0x%lX)", hr);
goto error;
}
mft->GetOutputCurrentType(output_stream_id, output_media_type.ReleaseAndGetAddressOf());
if (fmt_out.i_cat == VIDEO_ES)
{
if (fmt_out.i_codec == VLC_CODEC_H264)
{
UINT32 blob_size = 0;
hr = output_media_type->GetBlobSize(MF_MT_MPEG_SEQUENCE_HEADER, &blob_size);
if (SUCCEEDED(hr) && blob_size != 0)
{
fmt_out.i_extra = blob_size;
fmt_out.p_extra = malloc(fmt_out.i_extra);
if (unlikely(fmt_out.p_extra == nullptr))
goto error;
output_media_type->GetBlob(MF_MT_MPEG_SEQUENCE_HEADER,
static_cast<UINT8*>(fmt_out.p_extra), fmt_out.i_extra, nullptr);
}
}
}
else
{
UINT32 bitspersample = 0;
hr = output_media_type->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &bitspersample);
if (SUCCEEDED(hr) && bitspersample)
fmt_out.audio.i_bitspersample = bitspersample;
UINT32 channels = 0;
hr = output_media_type->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &channels);
if (SUCCEEDED(hr) && channels)
fmt_out.audio.i_channels = channels;
UINT32 rate = 0;
hr = output_media_type->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &rate);
if (SUCCEEDED(hr) && rate)
fmt_out.audio.i_rate = rate;
vlc_fourcc_t fourcc;
wf_tag_to_fourcc(subtype.Data1, &fourcc, NULL);
fmt_out.i_codec = vlc_fourcc_GetCodecAudio(fourcc, fmt_out.audio.i_bitspersample);
fmt_out.audio.i_physical_channels = pi_channels_maps[fmt_out.audio.i_channels];
if (fmt_out.i_codec == VLC_CODEC_MP4A)
{
UINT32 blob_size = 0;
hr = output_media_type->GetBlobSize(MF_MT_USER_DATA, &blob_size);
if (SUCCEEDED(hr))
{
size_t aac_info = sizeof(HEAACWAVEINFO) - sizeof(WAVEFORMATEX);
if (blob_size > aac_info) // AudioSpecificConfig after HEAACWAVEINFO
{
fmt_out.i_extra = blob_size - aac_info;
fmt_out.p_extra = malloc(fmt_out.i_extra);
if (unlikely(fmt_out.p_extra == nullptr))
goto error;
std::vector<UINT8> tmp(blob_size);
output_media_type->GetBlob(MF_MT_USER_DATA, tmp.data(), blob_size, nullptr);
memcpy(fmt_out.p_extra, &tmp[aac_info], fmt_out.i_extra);
}
}
}
}
return S_OK;
error:
vlc_error(logger, "Error in SetOutputType()");
return hr;
}
static HRESULT AllocateInputSample(struct vlc_logger *logger, ComPtr<IMFTransform> & mft, DWORD stream_id, ComPtr<IMFSample> & result, DWORD size)
{
HRESULT hr;
result.Reset();
ComPtr<IMFSample> input_sample;
ComPtr<IMFMediaBuffer> input_media_buffer;
DWORD allocation_size;
MFT_INPUT_STREAM_INFO input_info;
hr = mft->GetInputStreamInfo(stream_id, &input_info);
if (FAILED(hr))
goto error;
hr = MFCreateSample(&input_sample);
if (FAILED(hr))
goto error;
allocation_size = std::max<DWORD>(input_info.cbSize, size);
hr = MFCreateMemoryBuffer(allocation_size, &input_media_buffer);
if (FAILED(hr))
goto error;
hr = input_sample->AddBuffer(input_media_buffer.Get());
if (FAILED(hr))
goto error;
result.Swap(input_sample);
return hr;
error:
vlc_error(logger, "Error in AllocateInputSample(). (hr=0x%lX)", hr);
return hr;
}
HRESULT mft_sys_t::AllocateOutputSample(es_format_category_e cat, ComPtr<IMFSample> & result)
{
HRESULT hr;
result.Reset();
ComPtr<IMFSample> output_sample;
MFT_OUTPUT_STREAM_INFO output_info;
ComPtr<IMFMediaBuffer> output_media_buffer;
DWORD allocation_size;
DWORD alignment;
hr = mft->GetOutputStreamInfo(output_stream_id, &output_info);
if (FAILED(hr))
return hr;
if (output_info.dwFlags & (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES))
{
/* The MFT will provide an allocated sample. */
return S_FALSE;
}
if (cat == VIDEO_ES && !IsEncoder())
{
const DWORD expected_flags =
MFT_OUTPUT_STREAM_WHOLE_SAMPLES
| MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER
| MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE;
if ((output_info.dwFlags & expected_flags) != expected_flags)
return E_UNEXPECTED;
}
hr = MFCreateSample(&output_sample);
if (FAILED(hr))
return hr;
allocation_size = output_info.cbSize;
alignment = output_info.cbAlignment;
if (alignment > 0)
hr = MFCreateAlignedMemoryBuffer(allocation_size, alignment - 1, &output_media_buffer);
else
hr = MFCreateMemoryBuffer(allocation_size, &output_media_buffer);
if (FAILED(hr))
return hr;
hr = output_sample->AddBuffer(output_media_buffer.Get());
if (FAILED(hr))
return hr;
result.Swap(output_sample);
return S_OK;
}
HRESULT mft_enc_video::ProcessInputPicture(vlc_logger *logger, picture_t *p_pic)
{
HRESULT hr;
ComPtr<IMFSample> input_sample;
const vlc_chroma_description_t *p_chroma_desc = vlc_fourcc_GetChromaDescription( p_pic->format.i_chroma );
if( !p_chroma_desc )
return E_INVALIDARG;
plane_t dst_planes[PICTURE_PLANE_MAX];
DWORD alloc_size = 0;
for( unsigned i = 0; i < p_chroma_desc->plane_count; i++ )
{
plane_t *p = &dst_planes[i];
p->i_lines =
p->i_visible_lines = p_pic->format.i_visible_height * p_chroma_desc->p[i].h.num / p_chroma_desc->p[i].h.den;
p->i_pitch =
p->i_visible_pitch = p_pic->format.i_visible_width * p_chroma_desc->p[i].w.num / p_chroma_desc->p[i].w.den * p_chroma_desc->pixel_size;
alloc_size += p->i_pitch * p->i_lines;
}
vlc_tick_t ts;
ComPtr<IMFMediaBuffer> input_media_buffer;
UINT64 frame_ratio_num = p_pic->format.i_frame_rate;
UINT64 frame_ratio_den = p_pic->format.i_frame_rate_base;
hr = AllocateInputSample(logger, mft, input_stream_id, input_sample, alloc_size);
if (FAILED(hr))
goto error;
hr = input_sample->GetBufferByIndex(0, &input_media_buffer);
if (FAILED(hr))
goto error;
BYTE *buffer_start;
hr = input_media_buffer->Lock(&buffer_start, NULL, NULL);
if (FAILED(hr))
goto error;
alloc_size = 0;
for( unsigned i = 0; i < p_chroma_desc->plane_count; i++ )
{
plane_t *p = &dst_planes[i];
p->p_pixels = &buffer_start[alloc_size];
plane_CopyPixels(p, &p_pic->p[i]);
alloc_size += p->i_pitch * p->i_lines;
}
hr = input_media_buffer->Unlock();
if (FAILED(hr))
goto error;
hr = input_media_buffer->SetCurrentLength(alloc_size);
if (FAILED(hr))
goto error;
ts = p_pic->date;
/* Convert from microseconds to 100 nanoseconds unit. */
hr = input_sample->SetSampleTime(MSFTIME_FROM_VLC_TICK(ts));
if (FAILED(hr))
goto error;
hr = input_sample->SetSampleDuration(INT64_C(10000000) * frame_ratio_den / frame_ratio_num);
if (is_first_picture)
{
input_sample->SetUINT32(MFSampleExtension_Discontinuity, TRUE);
is_first_picture = false;
}
hr = mft->ProcessInput(input_stream_id, input_sample.Get(), 0);
if (FAILED(hr))
{
vlc_debug(logger, "Failed to process input stream %lu (error 0x%lX)", input_stream_id, hr);
goto error;
}
return hr;
error:
vlc_error(logger, "Error in ProcessInputStream(). (hr=0x%lX)", hr);
return hr;
}
static int ProcessInputStream(struct vlc_logger *logger, ComPtr<IMFTransform> & mft, DWORD stream_id, block_t *p_block)
{
HRESULT hr;
ComPtr<IMFSample> input_sample;
DWORD alloc_size = p_block->i_buffer;
vlc_tick_t ts;
ComPtr<IMFMediaBuffer> input_media_buffer;
hr = AllocateInputSample(logger, mft, stream_id, input_sample, alloc_size);
if (FAILED(hr))
goto error;
hr = input_sample->GetBufferByIndex(0, &input_media_buffer);
if (FAILED(hr))
goto error;
BYTE *buffer_start;
hr = input_media_buffer->Lock(&buffer_start, NULL, NULL);
if (FAILED(hr))
goto error;
memcpy(buffer_start, p_block->p_buffer, alloc_size);
hr = input_media_buffer->Unlock();
if (FAILED(hr))
goto error;
hr = input_media_buffer->SetCurrentLength(alloc_size);
if (FAILED(hr))
goto error;
ts = p_block->i_pts == VLC_TICK_INVALID ? p_block->i_dts : p_block->i_pts;
/* Convert from microseconds to 100 nanoseconds unit. */
hr = input_sample->SetSampleTime(MSFTIME_FROM_VLC_TICK(ts));
if (FAILED(hr))
goto error;
hr = mft->ProcessInput(stream_id, input_sample.Get(), 0);
if (FAILED(hr))
{
vlc_debug(logger, "Failed to process input stream %lu (error 0x%lX)", stream_id, hr);
goto error;
}
return VLC_SUCCESS;
error:
vlc_error(logger, "Error in ProcessInputStream(). (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
/* Copy a packed buffer (no padding) to a picture_t */
static void CopyPackedBufferToPicture(picture_t *p_pic, const uint8_t *p_src)
{
for (int i = 0; i < p_pic->i_planes; ++i)
{
uint8_t *p_dst = p_pic->p[i].p_pixels;
if (p_pic->p[i].i_visible_pitch == p_pic->p[i].i_pitch)
{
/* Plane is packed, only one memcpy is needed. */
uint32_t plane_size = p_pic->p[i].i_pitch * p_pic->p[i].i_visible_lines;
memcpy(p_dst, p_src, plane_size);
p_src += plane_size;
continue;
}
for (int i_line = 0; i_line < p_pic->p[i].i_visible_lines; i_line++)
{
memcpy(p_dst, p_src, p_pic->p[i].i_visible_pitch);
p_src += p_pic->p[i].i_visible_pitch;
p_dst += p_pic->p[i].i_pitch;
}
}
}
static int ProcessOutputStream(decoder_t *p_dec, DWORD stream_id, bool & keep_reading)
{
auto *p_sys = static_cast<mft_sys_t*>(p_dec->p_sys);
HRESULT hr;
DWORD output_status = 0;
MFT_OUTPUT_DATA_BUFFER output_buffer = { stream_id, p_sys->output_sample.Get(), 0, NULL };
hr = p_sys->mft->ProcessOutput(0, 1, &output_buffer, &output_status);
if (output_buffer.pEvents)
output_buffer.pEvents->Release();
keep_reading = false;
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
return VLC_SUCCESS;
if (hr == MF_E_TRANSFORM_STREAM_CHANGE || hr == MF_E_TRANSFORM_TYPE_NOT_SET)
{
// there's an output ready, keep trying
keep_reading = hr == MF_E_TRANSFORM_STREAM_CHANGE;
if (p_dec->fmt_in->i_cat == VIDEO_ES)
video_format_Copy( &p_dec->fmt_out.video, &p_dec->fmt_in->video );
else
p_dec->fmt_out.audio = p_dec->fmt_in->audio;
hr = p_sys->SetOutputType(vlc_object_logger(p_dec), GUID_NULL, p_dec->fmt_out);
if (FAILED(hr))
return VLC_EGENERIC;
/* Reallocate output sample. */
hr = p_sys->AllocateOutputSample(p_dec->fmt_in->i_cat, p_sys->output_sample);
if (FAILED(hr))
{
msg_Err(p_dec, "Error in AllocateOutputSample(). (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/* An error not listed above occurred */
if (FAILED(hr))
{
msg_Dbg(p_dec, "Failed to process output stream %lu (error 0x%lX)", stream_id, hr);
return VLC_EGENERIC;
}
if (output_buffer.pSample == nullptr)
return VLC_SUCCESS;
LONGLONG sample_time;
hr = output_buffer.pSample->GetSampleTime(&sample_time);
if (FAILED(hr))
return VLC_EGENERIC;
/* Convert from 100 nanoseconds unit to vlc ticks. */
vlc_tick_t samp_time = VLC_TICK_FROM_MSFTIME(sample_time);
DWORD output_count = 0;
hr = output_buffer.pSample->GetBufferCount(&output_count);
if (unlikely(FAILED(hr)))
return VLC_EGENERIC;
ComPtr<IMFSample> output_sample = output_buffer.pSample;
for (DWORD buf_index = 0; buf_index < output_count; buf_index++)
{
picture_t *picture = NULL;
ComPtr<IMFMediaBuffer> output_media_buffer;
hr = output_sample->GetBufferByIndex(buf_index, &output_media_buffer);
if (FAILED(hr))
goto error;
if (p_dec->fmt_in->i_cat == VIDEO_ES)
{
auto *vidsys = dynamic_cast<vlc_mft_d3d*>(p_sys);
picture_context_t *pic_ctx = nullptr;
if (vidsys->hw_d3d != nullptr)
{
ComPtr<IMFDXGIBuffer> spDXGIBuffer;
hr = output_media_buffer.As(&spDXGIBuffer);
if (vidsys->hw_d3d->vctx_out == nullptr)
{
hr = vidsys->hw_d3d->SetupVideoContext(vlc_object_logger(p_dec), spDXGIBuffer, p_dec->fmt_out);
if (FAILED(hr))
goto error;
}
pic_ctx = vidsys->hw_d3d->CreatePicContext(vlc_object_logger(p_dec), spDXGIBuffer, p_sys);
if (unlikely(pic_ctx == nullptr))
goto error;
}
if (decoder_UpdateVideoOutput(p_dec, vidsys->hw_d3d ? vidsys->hw_d3d->vctx_out : nullptr))
{
if (pic_ctx)
pic_ctx->destroy(pic_ctx);
return VLC_EGENERIC;
}
picture = decoder_NewPicture(p_dec);
if (!picture)
{
if (pic_ctx)
pic_ctx->destroy(pic_ctx);
return VLC_EGENERIC;
}
UINT32 interlaced = FALSE;
hr = output_sample->GetUINT32(MFSampleExtension_Interlaced, &interlaced);
if (FAILED(hr))
picture->b_progressive = true;
else
picture->b_progressive = !interlaced;
picture->date = samp_time;
if (pic_ctx)
{
picture->context = pic_ctx;
}
else
{
BYTE *buffer_start;
hr = output_media_buffer->Lock(&buffer_start, NULL, NULL);
if (FAILED(hr))
{
picture_Release(picture);
goto error;
}
CopyPackedBufferToPicture(picture, buffer_start);
hr = output_media_buffer->Unlock();
if (FAILED(hr))
{
picture_Release(picture);
goto error;
}
}
decoder_QueueVideo(p_dec, picture);
}
else
{
block_t *aout_buffer = NULL;
if (decoder_UpdateAudioFormat(p_dec))
goto error;
if (p_dec->fmt_out.audio.i_bitspersample == 0 || p_dec->fmt_out.audio.i_channels == 0)
goto error;
DWORD total_length = 0;
hr = output_media_buffer->GetCurrentLength(&total_length);
if (FAILED(hr))
goto error;
int samples = total_length / (p_dec->fmt_out.audio.i_bitspersample * p_dec->fmt_out.audio.i_channels / 8);
aout_buffer = decoder_NewAudioBuffer(p_dec, samples);
if (!aout_buffer)
return VLC_SUCCESS;
if (aout_buffer->i_buffer < total_length)
{
block_Release(aout_buffer);
goto error;
}
aout_buffer->i_pts = samp_time;
BYTE *buffer_start;
hr = output_media_buffer->Lock(&buffer_start, NULL, NULL);
if (FAILED(hr))
{
block_Release(aout_buffer);
goto error;
}
memcpy(aout_buffer->p_buffer, buffer_start, total_length);
hr = output_media_buffer->Unlock();
if (FAILED(hr))
{
block_Release(aout_buffer);
goto error;
}
decoder_QueueAudio(p_dec, aout_buffer);
}
if (p_sys->output_sample.Get())
{
/* Sample is not provided by the MFT: clear its content. */
hr = output_media_buffer->SetCurrentLength(0);
if (FAILED(hr))
goto error;
}
}
if (p_sys->output_sample.Get() == nullptr)
{
/* Sample is provided by the MFT: decrease refcount. */
output_sample->Release();
}
keep_reading = true;
return VLC_SUCCESS;
error:
msg_Err(p_dec, "Error in ProcessOutputStream()");
return VLC_EGENERIC;
}
static void Flush(decoder_t *p_dec)
{
auto *p_sys = static_cast<mft_sys_t*>(p_dec->p_sys);
if (SUCCEEDED(p_sys->flushStream()))
p_sys->startStream();
}
static int DecodeSync(decoder_t *p_dec, block_t *p_block)
{
auto *p_sys = static_cast<mft_sys_t*>(p_dec->p_sys);
if (p_block && p_block->i_flags & (BLOCK_FLAG_CORRUPTED))
{
block_Release(p_block);
return VLCDEC_SUCCESS;
}
if (p_block == NULL)
{
HRESULT hr;
hr = p_sys->mft->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
if (FAILED(hr))
{
msg_Warn(p_dec, "draining failed (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
}
/* Drain the output stream before sending the input packet. */
bool keep_reading;
int err;
do {
err = ProcessOutputStream(p_dec, p_sys->output_stream_id, keep_reading);
} while (err == VLC_SUCCESS && keep_reading);
if (err != VLC_SUCCESS)
goto error;
if (p_block != NULL )
{
if (p_dec->fmt_in->i_codec == VLC_CODEC_H264)
{
/* in-place NAL to annex B conversion. */
auto *vidsys = dynamic_cast<mft_dec_video*>(p_sys);
p_block = hxxx_helper_process_block(&vidsys->hh, p_block);
if (vidsys->hh.i_input_nal_length_size && !vidsys->b_xps_pushed)
{
block_t *p_xps_blocks = hxxx_helper_get_extradata_block(&vidsys->hh);
if (p_xps_blocks)
{
size_t extrasize;
block_ChainProperties(p_xps_blocks, NULL, &extrasize, NULL);
if (ProcessInputStream(vlc_object_logger(p_dec), p_sys->mft, p_sys->input_stream_id, p_xps_blocks))
{
block_ChainRelease(p_xps_blocks);
goto error;
}
vidsys->b_xps_pushed = true;
block_ChainRelease(p_xps_blocks);
}
}
}
if (ProcessInputStream(vlc_object_logger(p_dec), p_sys->mft, p_sys->input_stream_id, p_block))
goto error;
block_Release(p_block);
}
return VLCDEC_SUCCESS;
error:
msg_Err(p_dec, "Error in DecodeSync()");
if (p_block)
block_Release(p_block);
return VLCDEC_SUCCESS;
}
HRESULT mft_sys_t::DequeueMediaEvent(vlc_logger *logger, bool wait)
{
HRESULT hr;
ComPtr<IMFMediaEvent> event;
hr = event_generator->GetEvent(wait ? 0 : MF_EVENT_FLAG_NO_WAIT, &event);
if (FAILED(hr))
return hr;
MediaEventType event_type;
hr = event->GetType(&event_type);
if (FAILED(hr))
return hr;
if (event_type == METransformNeedInput)
pending_input_events += 1;
else if (event_type == METransformHaveOutput)
pending_output_events += 1;
else if (event_type == METransformDrainComplete)
pending_drain_events += 1;
else
vlc_error(logger, "Unsupported asynchronous event %lu.", event_type);
return S_OK;
}
static int DecodeAsync(decoder_t *p_dec, block_t *p_block)
{
auto *p_sys = static_cast<mft_sys_t*>(p_dec->p_sys);
HRESULT hr;
if (!p_block) /* No Drain */
return VLCDEC_SUCCESS;
if (p_block->i_flags & (BLOCK_FLAG_CORRUPTED))
{
block_Release(p_block);
return VLCDEC_SUCCESS;
}
/* Dequeue all pending media events. */
while ((hr = p_sys->DequeueMediaEvent(vlc_object_logger(p_dec))) == S_OK)
continue;
if (hr != MF_E_NO_EVENTS_AVAILABLE && FAILED(hr))
goto error;
/* Drain the output stream of the MFT before sending the input packet. */
if (p_sys->pending_output_events > 0)
{
p_sys->pending_output_events -= 1;
bool keep_reading;
int err;
do {
err = ProcessOutputStream(p_dec, p_sys->output_stream_id, keep_reading);
} while (err == VLC_SUCCESS && keep_reading);
if (err != VLC_SUCCESS)
goto error;
}
/* Poll the MFT and return decoded frames until the input stream is ready. */
while (p_sys->pending_input_events == 0)
{
hr = p_sys->DequeueMediaEvent(vlc_object_logger(p_dec));
if (hr == MF_E_NO_EVENTS_AVAILABLE)
{
/* Sleep for 1 ms to avoid excessive polling. */
Sleep(1);
continue;
}
if (FAILED(hr))
goto error;
if (p_sys->pending_output_events > 0)
{
p_sys->pending_output_events -= 1;
bool keep_reading;
int err;
do {
err = ProcessOutputStream(p_dec, p_sys->output_stream_id, keep_reading);
} while (err == VLC_SUCCESS && keep_reading);
if (err != VLC_SUCCESS)
goto error;
break;
}
}
p_sys->pending_input_events -= 1;
if (ProcessInputStream(vlc_object_logger(p_dec), p_sys->mft, p_sys->input_stream_id, p_block))
goto error;
block_Release(p_block);
return VLCDEC_SUCCESS;
error:
msg_Err(p_dec, "Error in DecodeAsync()");
block_Release(p_block);
return VLCDEC_SUCCESS;
}
static HRESULT EnableHardwareAcceleration(const es_format_t & fmt_in, ComPtr<IMFAttributes> & attributes)
{
HRESULT hr;
switch (fmt_in.i_codec)
{
case VLC_CODEC_H264:
hr = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_H264, TRUE);
break;
case VLC_CODEC_WMV1:
case VLC_CODEC_WMV2:
case VLC_CODEC_WMV3:
case VLC_CODEC_VC1:
hr = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_VC1, TRUE);
break;
case VLC_CODEC_MPGV:
case VLC_CODEC_MP2V:
hr = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_MPEG2, TRUE);
break;
default:
hr = S_OK;
break;
}
return hr;
}
static void DestroyMFT(decoder_t *p_dec);
HRESULT vlc_mft_d3d::SetD3D(vlc_logger *logger, vlc_decoder_device & dec_dev)
{
HRESULT hr;
assert(hw_d3d == nullptr);
if (dec_dev.type == VLC_DECODER_DEVICE_D3D11VA)
{
ComPtr<IMFAttributes> attributes;
hr = mft->GetAttributes(&attributes);
if (SUCCEEDED(hr))
{
UINT32 can_d3d11;
hr = attributes->GetUINT32(MF_SA_D3D11_AWARE, &can_d3d11);
if (SUCCEEDED(hr) && can_d3d11)
hw_d3d = std::make_unique<MFHW_d3d11>();
}
}
if (unlikely(hw_d3d == nullptr))
return E_ABORT;
hr = hw_d3d->SetD3D(logger, dec_dev, mft);
if (FAILED(hr))
hw_d3d.reset();
return hr;
}
static int InitializeMFT(decoder_t *p_dec, const MFT_REGISTER_TYPE_INFO & type)
{
auto *p_sys = static_cast<mft_sys_t*>(p_dec->p_sys);
HRESULT hr;
ComPtr<IMFAttributes> attributes;
hr = p_sys->mft->GetAttributes(&attributes);
if (hr != E_NOTIMPL && FAILED(hr))
goto error;
if (SUCCEEDED(hr))
{
UINT32 is_async = FALSE;
hr = attributes->GetUINT32(MF_TRANSFORM_ASYNC, &is_async);
if (hr != MF_E_ATTRIBUTENOTFOUND && FAILED(hr))
goto error;
p_sys->is_async = is_async;
if (p_sys->is_async)
{
hr = attributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
if (FAILED(hr))
goto error;
hr = p_sys->mft.As(&p_sys->event_generator);
if (FAILED(hr))
goto error;
}
}
DWORD input_streams_count;
DWORD output_streams_count;
hr = p_sys->mft->GetStreamCount(&input_streams_count, &output_streams_count);
if (FAILED(hr))
goto error;
if (input_streams_count != 1 || output_streams_count != 1)
{
msg_Err(p_dec, "MFT decoder should have 1 input stream and 1 output stream.");
goto error;
}
hr = p_sys->mft->GetStreamIDs(1, &p_sys->input_stream_id, 1, &p_sys->output_stream_id);
if (hr == E_NOTIMPL)
{
/*
* This is not an error, it happens if:
* - there is a fixed number of streams.
* AND
* - streams are numbered consecutively from 0 to N-1.
*/
p_sys->input_stream_id = 0;
p_sys->output_stream_id = 0;
}
else if (FAILED(hr))
goto error;
hr = p_sys->SetInputType(*p_dec->fmt_in, type);
if (FAILED(hr))
{
msg_Err(p_dec, "Error in SetInputType(). (hr=0x%lX)", hr);
goto error;
}
if (attributes.Get() && p_dec->fmt_in->i_cat == VIDEO_ES)
{
EnableHardwareAcceleration(*p_dec->fmt_in, attributes);
auto *vidsys = dynamic_cast<vlc_mft_d3d*>(p_sys);
if (p_dec->fmt_in->video.i_width != 0 /*&& vidsys->d3d.CanUseD3D()*/)
{
vlc_decoder_device *dec_dev = decoder_GetDecoderDevice(p_dec);
if (dec_dev != nullptr)
{
hr = vidsys->SetD3D(vlc_object_logger(p_dec), *dec_dev);
if (SUCCEEDED(hr) && dec_dev->type == VLC_DECODER_DEVICE_D3D11VA)
{
IMFAttributes *outputAttr = NULL;
hr = p_sys->mft->GetOutputStreamAttributes(p_sys->output_stream_id, &outputAttr);
if (SUCCEEDED(hr))
{
hr = outputAttr->SetUINT32(MF_SA_D3D11_BINDFLAGS, D3D11_BIND_SHADER_RESOURCE);
}
}
vlc_decoder_device_Release(dec_dev);
}
}
}
if (p_dec->fmt_in->i_cat == VIDEO_ES)
video_format_Copy( &p_dec->fmt_out.video, &p_dec->fmt_in->video );
else
p_dec->fmt_out.audio = p_dec->fmt_in->audio;
hr = p_sys->SetOutputType(vlc_object_logger(p_dec), GUID_NULL, p_dec->fmt_out);
if (FAILED(hr))
goto error;
/*
* The input type was not set by the previous call to
* SetInputType, try again after setting the output type.
*/
if (p_sys->input_type.Get() == nullptr)
{
hr = p_sys->SetInputType(*p_dec->fmt_in, type);
if (FAILED(hr))
{
msg_Err(p_dec, "Error in SetInputType(). (hr=0x%lX)", hr);
goto error;
}
if (p_sys->input_type.Get() == nullptr)
goto error;
}
/* This event is required for asynchronous MFTs, optional otherwise. */
hr = p_sys->startStream();
if (FAILED(hr))
{
msg_Err(p_dec, "Error in startStream(). (hr=0x%lX)", hr);
goto error;
}
if (p_dec->fmt_in->i_codec == VLC_CODEC_H264)
{
auto *vidsys = dynamic_cast<mft_dec_video*>(p_sys);
hxxx_helper_init(&vidsys->hh, VLC_OBJECT(p_dec), p_dec->fmt_in->i_codec, 0, 0);
hxxx_helper_set_extra(&vidsys->hh, p_dec->fmt_in->p_extra, p_dec->fmt_in->i_extra);
}
return VLC_SUCCESS;
error:
msg_Err(p_dec, "Error in InitializeMFT()");
DestroyMFT(p_dec);
return VLC_EGENERIC;
}
static void DestroyMFT(decoder_t *p_dec)
{
auto *p_sys = static_cast<mft_sys_t*>(p_dec->p_sys);
if (p_sys->mft.Get())
{
p_sys->endStream();
if (p_sys->output_sample.Get() == nullptr)
{
// the MFT produces the output and may still have some left, we need to drain them
HRESULT hr;
hr = p_sys->mft->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
if (FAILED(hr))
{
msg_Warn(p_dec, "exit draining failed (hr=0x%lX)", hr);
}
else
{
for (;;)
{
DWORD output_status = 0;
MFT_OUTPUT_DATA_BUFFER output_buffer = { p_sys->output_stream_id, p_sys->output_sample.Get(), 0, NULL };
hr = p_sys->mft->ProcessOutput(0, 1, &output_buffer, &output_status);
if (output_buffer.pEvents)
output_buffer.pEvents->Release();
if (output_buffer.pSample)
{
output_buffer.pSample->Release();
}
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
break;
if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
break;
}
}
}
// make sure don't have any input pending
p_sys->flushStream();
p_sys->shutdownStream();
}
auto *vidsys = dynamic_cast<mft_dec_video*>(p_sys);
if (vidsys && vidsys->hh.p_obj)
hxxx_helper_clean(&vidsys->hh);
}
static int ListTransforms(struct vlc_logger *logger, GUID category, const char *type, bool do_test)
{
HRESULT hr;
UINT32 flags = MFT_ENUM_FLAG_SORTANDFILTER | MFT_ENUM_FLAG_LOCALMFT
| MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT
| MFT_ENUM_FLAG_HARDWARE;
IMFActivate **activate_objects = NULL;
UINT32 count = 0;
hr = MFTEnumEx(category, flags, nullptr, nullptr, &activate_objects, &count);
vlc_debug(logger, "Listing %u %s%s", count, type, (count?"s":""));
if (FAILED(hr))
return VLC_EGENERIC;
for (UINT32 o=0; o<count; o++)
{
WCHAR Name[256];
hr = activate_objects[o]->GetString(MFT_FRIENDLY_NAME_Attribute, Name, ARRAY_SIZE(Name), nullptr);
if (FAILED(hr))
wcscpy(Name,L"<unknown>");
if (do_test)
{
ComPtr<IMFTransform> mft;
hr = activate_objects[o]->ActivateObject(IID_PPV_ARGS(mft.GetAddressOf()));
bool available = SUCCEEDED(hr);
const char *async = "";
if (available)
{
ComPtr<IMFAttributes> attributes;
hr = mft->GetAttributes(&attributes);
if (SUCCEEDED(hr))
{
UINT32 is_async = FALSE;
hr = attributes->GetUINT32(MF_TRANSFORM_ASYNC, &is_async);
if (SUCCEEDED(hr))
async = is_async ? " (async)" : " (sync)";
}
}
vlc_debug(logger, "%s '%ls'%s is%s available", type, Name, async, available?"":" not");
}
else
vlc_debug(logger, "found %s '%ls'", type, Name);
activate_objects[o]->ShutdownObject();
}
return VLC_SUCCESS;
}
static int FindMFT(decoder_t *p_dec)
{
auto *p_sys = static_cast<mft_sys_t*>(p_dec->p_sys);
HRESULT hr;
/* Try to create a MFT using MFTEnumEx. */
GUID category;
MFT_REGISTER_TYPE_INFO input_type;
if (p_dec->fmt_in->i_cat == VIDEO_ES)
{
category = MFT_CATEGORY_VIDEO_DECODER;
hr = MFTypeFromCodec(MFMediaType_Video, p_dec->fmt_in->i_codec, input_type);
if(FAILED(hr)) {
/* Codec is not well known. Construct a MF transform subtype from the fourcc */
input_type.guidSubtype = MFVideoFormat_Base;
input_type.guidSubtype.Data1 = p_dec->fmt_in->i_codec;
}
}
else
{
category = MFT_CATEGORY_AUDIO_DECODER;
hr = MFTypeFromCodec(MFMediaType_Audio, p_dec->fmt_in->i_codec, input_type);
if(FAILED(hr))
// not a codec, maybe a raw format
hr = MFTypeFromAudio(p_dec->fmt_in->i_codec, input_type);
}
if (FAILED(hr))
return VLC_EGENERIC;
const char *dec_str;
if (p_dec->fmt_in->i_cat == VIDEO_ES)
dec_str = "video decoder";
else
dec_str = "audio decoder";
ListTransforms(vlc_object_logger(p_dec), category, dec_str, var_InheritBool(p_dec, "mft-debug"));
UINT32 flags = MFT_ENUM_FLAG_SORTANDFILTER | MFT_ENUM_FLAG_LOCALMFT
| MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT
| MFT_ENUM_FLAG_HARDWARE;
IMFActivate **activate_objects = NULL;
UINT32 activate_objects_count = 0;
hr = MFTEnumEx(category, flags, &input_type, NULL, &activate_objects, &activate_objects_count);
if (FAILED(hr))
return VLC_EGENERIC;
msg_Dbg(p_dec, "Found %d available MFT module(s) for %4.4s", activate_objects_count, (const char*)&p_dec->fmt_in->i_codec);
if (activate_objects_count == 0)
return VLC_EGENERIC;
for (UINT32 i = 0; i < activate_objects_count; ++i)
{
hr = activate_objects[i]->ActivateObject(IID_PPV_ARGS(p_sys->mft.ReleaseAndGetAddressOf()));
activate_objects[i]->Release();
if (FAILED(hr))
continue;
if (InitializeMFT(p_dec, input_type) == VLC_SUCCESS)
{
for (++i; i < activate_objects_count; ++i)
activate_objects[i]->Release();
CoTaskMemFree(activate_objects);
return VLC_SUCCESS;
}
}
CoTaskMemFree(activate_objects);
return VLC_EGENERIC;
}
HRESULT mft_enc_video::ProcessOutput(vlc_logger *logger, block_t * & output)
{
output = nullptr;
HRESULT hr;
while (!is_async || pending_output_events > 0)
{
ComPtr<IMFSample> output_sample;
hr = AllocateOutputSample(VIDEO_ES, output_sample);
if (FAILED(hr))
{
vlc_error(logger, "Error in AllocateOutputSample(). (hr=0x%lX)", hr);
break;
}
DWORD output_status = 0;
MFT_OUTPUT_DATA_BUFFER output_buffer = { output_stream_id, output_sample.Get(), 0, NULL };
hr = mft->ProcessOutput(0, 1, &output_buffer, &output_status);
if (output_buffer.pEvents)
output_buffer.pEvents->Release();
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
break;
pending_output_events--;
if (FAILED(hr))
{
vlc_debug(logger, "Failed to process video output stream %lu (error 0x%lX)", output_stream_id, hr);
return hr;
}
if (output_buffer.pSample == nullptr)
break;
LONGLONG sample_time;
hr = output_buffer.pSample->GetSampleTime(&sample_time);
if (FAILED(hr))
{
vlc_debug(logger, "Failed to get output time. (hr=0x%lX)", hr);
// if (output_sample.Get() == nullptr)
// output_buffer.pSample->Release();
return hr;
}
DWORD output_count = 0;
hr = output_buffer.pSample->GetBufferCount(&output_count);
if (unlikely(FAILED(hr)))
{
vlc_debug(logger, "Failed to get output buffer count. (hr=0x%lX)", hr);
// if (output_sample.Get() == nullptr)
// output_buffer.pSample->Release();
return hr;
}
for (DWORD buf_index = 0; buf_index < output_count; buf_index++)
{
ComPtr<IMFMediaBuffer> output_media_buffer;
hr = output_buffer.pSample->GetBufferByIndex(buf_index, &output_media_buffer);
if (FAILED(hr))
{
vlc_debug(logger, "Failed to get output buffer %lu. (hr=0x%lX)", buf_index, hr);
// if (output_sample.Get() == nullptr)
// output_buffer.pSample->Release();
return hr;
}
DWORD total_length = 0;
hr = output_media_buffer->GetCurrentLength(&total_length);
if (FAILED(hr))
{
vlc_debug(logger, "Failed to get output buffer %lu length. (hr=0x%lX)", buf_index, hr);
// if (output_sample.Get() == nullptr)
// output_buffer.pSample->Release();
return hr;
}
block_t *vout_buffer = block_Alloc(total_length);
if (unlikely(vout_buffer == nullptr))
{
// if (output_sample.Get() == nullptr)
// output_buffer.pSample->Release();
hr = E_OUTOFMEMORY;
return hr;
}
BYTE *buffer_start;
hr = output_media_buffer->Lock(&buffer_start, NULL, NULL);
if (FAILED(hr))
{
vlc_debug(logger, "Failed to lock output buffer %lu. (hr=0x%lX)", buf_index, hr);
// if (output_sample.Get() == nullptr)
// output_buffer.pSample->Release();
block_Release(vout_buffer);
return hr;
}
memcpy(vout_buffer->p_buffer, buffer_start, total_length);
hr = output_media_buffer->Unlock();
if (output_sample.Get() == nullptr)
/* Sample is not provided by the MFT: clear its content. */
output_media_buffer->SetCurrentLength(0);
if (FAILED(hr))
{
vlc_debug(logger, "Failed to unlock output buffer %lu. (hr=0x%lX)", buf_index, hr);
// if (output_sample.Get() == nullptr)
// output_buffer.pSample->Release();
block_Release(vout_buffer);
return hr;
}
// Convert from 100 nanoseconds unit to vlc ticks.
vout_buffer->i_dts = vout_buffer->i_pts = VLC_TICK_FROM_MSFTIME(sample_time);
UINT64 decode_timestamp;
hr = output_buffer.pSample->GetUINT64(MFSampleExtension_DecodeTimestamp, &decode_timestamp);
if (SUCCEEDED(hr))
vout_buffer->i_dts = VLC_TICK_FROM_MSFTIME(decode_timestamp);
UINT32 is_keyframe = FALSE;
hr = output_buffer.pSample->GetUINT32(MFSampleExtension_CleanPoint, &is_keyframe);
if (SUCCEEDED(hr) && is_keyframe)
vout_buffer->i_flags |= BLOCK_FLAG_TYPE_I;
if (output == nullptr)
output = vout_buffer;
else
block_ChainAppend(&output, vout_buffer);
}
}
return S_OK;
}
#ifdef ENABLE_SOUT
static block_t *EncodeVideoAsync(encoder_t *p_enc, picture_t *p_pic)
{
mft_enc_video *p_sys = static_cast<mft_enc_video*>(p_enc->p_sys);
block_t *output = nullptr;
if (p_pic == nullptr)
{
HRESULT hr;
hr = p_sys->mft->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
if (FAILED(hr))
{
msg_Warn(p_enc, "draining failed (hr=0x%lX)", hr);
return nullptr;
}
while (p_sys->pending_drain_events == 0)
{
hr = p_sys->DequeueMediaEvent(vlc_object_logger(p_enc), true);
if (FAILED(hr))
goto error;
while (p_sys->pending_output_events > 0)
{
hr = p_sys->ProcessOutput(vlc_object_logger(p_enc), output);
if (FAILED(hr))
{
goto error;
}
}
}
p_sys->pending_drain_events = 0;
return output;
}
HRESULT hr;
/* Dequeue all pending media events. */
while ((hr = p_sys->DequeueMediaEvent(vlc_object_logger(p_enc))) == S_OK)
continue;
if (hr != MF_E_NO_EVENTS_AVAILABLE && FAILED(hr))
goto error;
/* Drain the output stream of the MFT before sending the input packet. */
while (p_sys->pending_output_events > 0)
{
hr = p_sys->ProcessOutput(vlc_object_logger(p_enc), output);
if (FAILED(hr))
{
break;
}
}
if (p_pic != nullptr)
{
/* Poll the MFT and return decoded frames until the input stream is ready. */
while (p_sys->pending_input_events == 0)
{
hr = p_sys->DequeueMediaEvent(vlc_object_logger(p_enc), true);
if (FAILED(hr))
goto error;
while (p_sys->pending_output_events > 0)
{
hr = p_sys->ProcessOutput(vlc_object_logger(p_enc), output);
if (FAILED(hr))
{
goto error;
}
}
}
p_sys->pending_input_events -= 1;
hr = p_sys->ProcessInputPicture(vlc_object_logger(p_enc), p_pic);
if (FAILED(hr))
goto error;
}
error:
return output;
}
static block_t * EncodeVideo(encoder_t *p_enc, picture_t *p_pic)
{
mft_enc_video *p_sys = static_cast<mft_enc_video*>(p_enc->p_sys);
if (p_pic == nullptr)
{
HRESULT hr;
hr = p_sys->mft->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
if (FAILED(hr))
{
msg_Warn(p_enc, "draining failed (hr=0x%lX)", hr);
return nullptr;
}
}
/* Drain the output stream before sending the input packet. */
block_t *output = nullptr;
HRESULT hr = p_sys->ProcessOutput(vlc_object_logger(p_enc), output);
if (FAILED(hr))
{
if (output)
block_Release(output);
return nullptr;
}
if (p_pic != nullptr)
{
hr = p_sys->ProcessInputPicture(vlc_object_logger(p_enc), p_pic);
if (FAILED(hr))
{
if (output)
block_Release(output);
return nullptr;
}
}
return output;
}
static block_t * EncodeAudio(encoder_t *p_enc, block_t *p_aout_buffer)
{
mft_enc_audio *p_sys = static_cast<mft_enc_audio*>(p_enc->p_sys);
if (p_aout_buffer && p_aout_buffer->i_flags & (BLOCK_FLAG_CORRUPTED))
{
block_Release(p_aout_buffer);
return nullptr;
}
if (p_aout_buffer == nullptr)
{
HRESULT hr;
hr = p_sys->mft->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
if (FAILED(hr))
{
msg_Warn(p_enc, "draining failed (hr=0x%lX)", hr);
return nullptr;
}
}
/* Drain the output stream before sending the input packet. */
block_t *output = nullptr;
bool keep_reading = true;
int err = VLC_SUCCESS;
HRESULT hr;
do {
ComPtr<IMFSample> output_sample;
hr = p_sys->AllocateOutputSample(p_enc->fmt_in.i_cat, output_sample);
if (FAILED(hr))
{
msg_Err(p_enc, "Error in AllocateOutputSample(). (hr=0x%lX)", hr);
break;
}
DWORD output_status = 0;
MFT_OUTPUT_DATA_BUFFER output_buffer = { p_sys->output_stream_id, output_sample.Get(), 0, NULL };
hr = p_sys->mft->ProcessOutput(0, 1, &output_buffer, &output_status);
if (output_buffer.pEvents)
output_buffer.pEvents->Release();
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
break;
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to process audio output stream %lu (error 0x%lX)", p_sys->output_stream_id, hr);
return nullptr;
}
if (output_buffer.pSample == nullptr)
break;
LONGLONG sample_time;
hr = output_buffer.pSample->GetSampleTime(&sample_time);
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to get output time. (hr=0x%lX)", hr);
return nullptr;
}
/* Convert from 100 nanoseconds unit to vlc ticks. */
vlc_tick_t samp_time = VLC_TICK_FROM_MSFTIME(sample_time);
DWORD output_count = 0;
hr = output_buffer.pSample->GetBufferCount(&output_count);
if (unlikely(FAILED(hr)))
{
msg_Dbg(p_enc, "Failed to get output buffer count. (hr=0x%lX)", hr);
return nullptr;
}
for (DWORD buf_index = 0; buf_index < output_count; buf_index++)
{
ComPtr<IMFMediaBuffer> output_media_buffer;
hr = output_sample->GetBufferByIndex(buf_index, &output_media_buffer);
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to get output buffer %lu. (hr=0x%lX)", buf_index, hr);
return nullptr;
}
DWORD total_length = 0;
hr = output_media_buffer->GetCurrentLength(&total_length);
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to get output buffer %lu length. (hr=0x%lX)", buf_index, hr);
return nullptr;
}
block_t *aout_buffer = block_Alloc(total_length);
if (unlikely(aout_buffer == nullptr))
{
err = VLC_ENOMEM;
break;
}
aout_buffer->i_pts = samp_time;
BYTE *buffer_start;
hr = output_media_buffer->Lock(&buffer_start, NULL, NULL);
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to lock output buffer %lu. (hr=0x%lX)", buf_index, hr);
block_Release(aout_buffer);
return nullptr;
}
memcpy(aout_buffer->p_buffer, buffer_start, total_length);
hr = output_media_buffer->Unlock();
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to unlock output buffer %lu. (hr=0x%lX)", buf_index, hr);
block_Release(aout_buffer);
return nullptr;
}
if (output == nullptr)
output = aout_buffer;
else
block_ChainAppend(&output, aout_buffer);
}
} while (err == VLC_SUCCESS && keep_reading);
if (err != VLC_SUCCESS)
{
block_Release(output);
return nullptr;
}
if (p_aout_buffer != nullptr &&
ProcessInputStream(vlc_object_logger(p_enc), p_sys->mft,
p_sys->input_stream_id, p_aout_buffer))
{
block_Release(output);
return nullptr;
}
return output;
}
static void EncoderClose(encoder_t *p_enc)
{
mft_sys_t *p_sys = static_cast<mft_sys_t*>(p_enc->p_sys);
if (p_sys->mft.Get())
{
p_sys->endStream();
// if (p_sys->output_sample.Get() == nullptr)
// {
// // the MFT produces the output and may still have some left, we need to drain them
// HRESULT hr;
// hr = p_sys->mft->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
// if (FAILED(hr))
// {
// msg_Warn(p_enc, "exit draining failed (hr=0x%lX)", hr);
// }
// else
// {
// for (;;)
// {
// DWORD output_status = 0;
// MFT_OUTPUT_DATA_BUFFER output_buffer = { p_sys->output_stream_id, p_sys->output_sample.Get(), 0, NULL };
// hr = p_sys->mft->ProcessOutput(0, 1, &output_buffer, &output_status);
// if (output_buffer.pEvents)
// output_buffer.pEvents->Release();
// if (output_buffer.pSample)
// {
// output_buffer.pSample->Release();
// }
// if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
// break;
// if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
// break;
// }
// }
// }
// make sure don't have any input pending
p_sys->flushStream();
p_sys->shutdownStream();
}
p_sys->Release();
}
static int InitializeEncoder(struct vlc_logger *logger, mft_sys_t &mf_sys)
{
HRESULT hr;
ComPtr<IMFAttributes> attributes;
hr = mf_sys.mft->GetAttributes(&attributes);
if (FAILED(hr) && hr != E_NOTIMPL)
goto error;
if (SUCCEEDED(hr))
{
UINT32 is_async = FALSE;
hr = attributes->GetUINT32(MF_TRANSFORM_ASYNC, &is_async);
if (hr != MF_E_ATTRIBUTENOTFOUND && FAILED(hr))
goto error;
mf_sys.is_async = is_async;
if (mf_sys.is_async)
{
hr = attributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
if (FAILED(hr))
goto error;
hr = mf_sys.mft.As(&mf_sys.event_generator);
if (FAILED(hr))
goto error;
}
}
DWORD input_streams_count;
DWORD output_streams_count;
hr = mf_sys.mft->GetStreamCount(&input_streams_count, &output_streams_count);
if (FAILED(hr))
goto error;
if (input_streams_count != 1 || output_streams_count != 1)
{
vlc_error(logger, "MFT encoder should have 1 input stream and 1 output stream.");
goto error;
}
hr = mf_sys.mft->GetStreamIDs(1, &mf_sys.input_stream_id, 1, &mf_sys.output_stream_id);
if (hr == E_NOTIMPL)
{
/*
* This is not an error, it happens if:
* - there is a fixed number of streams.
* AND
* - streams are numbered consecutively from 0 to N-1.
*/
mf_sys.input_stream_id = 0;
mf_sys.output_stream_id = 0;
}
else if (FAILED(hr))
goto error;
return VLC_SUCCESS;
error:
return VLC_ENOTSUP;
}
static int OpenMFTVideoEncoder(vlc_object_t *p_this)
{
encoder_t *p_enc = (encoder_t*)p_this;
if( FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)) )
return VLC_EINVAL;
HRESULT hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
if (FAILED(hr))
{
CoUninitialize();
return VLC_ENOTSUP;
}
mft_enc_video *p_sys = new (std::nothrow) mft_enc_video();
if (unlikely(p_sys == nullptr))
{
MFShutdown();
CoUninitialize();
return VLC_ENOMEM;
}
p_enc->p_sys = p_sys;
GUID category = MFT_CATEGORY_VIDEO_ENCODER;
MFT_REGISTER_TYPE_INFO input_type, output_type;
IMFActivate **activate_objects = nullptr;
UINT32 activate_objects_count = 0;
UINT32 flags;
bool found = false;
hr = MFTypeFromChroma(p_enc->fmt_in.video.i_chroma, p_enc->vctx_in, input_type);
if (FAILED(hr))
{
msg_Dbg(p_this, "Input chroma %4.4s not supported by MediaFoundation, forcing NV12",
(char*)&p_enc->fmt_in.i_codec);
p_enc->fmt_in.video.i_chroma = VLC_CODEC_NV12;
p_enc->fmt_in.i_codec = p_enc->fmt_in.video.i_chroma;
hr = MFTypeFromChroma(p_enc->fmt_in.video.i_chroma, nullptr, input_type);
assert(SUCCEEDED(hr));
}
hr = MFTypeFromCodec(MFMediaType_Video, p_enc->fmt_out.i_codec, output_type);
if (FAILED(hr))
{
msg_Dbg(p_this, "Output codec %4.4s not supported by MediaFoundation",
(char*)&p_enc->fmt_out.i_codec);
goto error;
}
ListTransforms(vlc_object_logger(p_this), category, "video encoder", var_InheritBool(p_this, "mft-debug"));
flags = MFT_ENUM_FLAG_SORTANDFILTER | MFT_ENUM_FLAG_LOCALMFT
| MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT
| MFT_ENUM_FLAG_HARDWARE;
hr = MFTEnumEx(category, flags, &input_type, &output_type, &activate_objects, &activate_objects_count);
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to enumerate MFT modules for %4.4s", (const char*)&p_enc->fmt_out.i_codec);
goto error;
}
if (activate_objects_count == 0)
{
// try the other raw input format
input_type.guidSubtype = MFVideoFormat_NV12;
hr = MFTEnumEx(category, flags, &input_type, &output_type, &activate_objects, &activate_objects_count);
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to enumerate MFT modules for %4.4s", (const char*)&p_enc->fmt_out.i_codec);
goto error;
}
}
msg_Dbg(p_enc, "Found %d available MFT module(s) for %4.4s to %4.4s", activate_objects_count,
(const char*)&p_enc->fmt_in.i_codec, (const char*)&p_enc->fmt_out.i_codec);
if (activate_objects_count == 0)
goto error;
for (UINT32 i = 0; i < activate_objects_count; ++i)
{
WCHAR Name[256];
hr = activate_objects[i]->GetString(MFT_FRIENDLY_NAME_Attribute, Name, ARRAY_SIZE(Name), nullptr);
if (FAILED(hr))
wcsncpy(Name, L"<unknown>", ARRAY_SIZE(Name));
hr = activate_objects[i]->ActivateObject(IID_PPV_ARGS(p_sys->mft.ReleaseAndGetAddressOf()));
if (FAILED(hr))
{
msg_Warn(p_enc, "Failed to initialize %ls encoder", Name);
continue;
}
if (InitializeEncoder(vlc_object_logger(p_this), *p_sys) == VLC_SUCCESS)
{
msg_Dbg(p_enc, "Using video encoder %ls", Name);
found = true;
break;
}
}
for (UINT32 i = 0; i < activate_objects_count; ++i)
activate_objects[i]->Release();
CoTaskMemFree(activate_objects);
if (!found)
goto error;
p_enc->fmt_in.i_codec = MFFormatToChroma(input_type.guidSubtype);
p_enc->fmt_in.video.i_chroma = p_enc->fmt_in.i_codec;
hr = p_sys->SetOutputType(vlc_object_logger(p_this),
output_type.guidSubtype, p_enc->fmt_out);
if (FAILED(hr))
{
msg_Err(p_enc, "Error in SetOutputType(). (hr=0x%lX)", hr);
goto error;
}
hr = p_sys->SetInputType(p_enc->fmt_in, input_type);
if (FAILED(hr))
{
msg_Err(p_enc, "Error in SetInputType(). (hr=0x%lX)", hr);
goto error;
}
/* This event is required for asynchronous MFTs, optional otherwise. */
hr = p_sys->startStream();
if (FAILED(hr))
{
msg_Err(p_enc, "Error in startStream(). (hr=0x%lX)", hr);
goto error;
}
static const struct vlc_encoder_operations video_ops = []{
struct vlc_encoder_operations cbs{};
cbs.close = EncoderClose;
cbs.encode_video = EncodeVideo;
return cbs;
}();
static const struct vlc_encoder_operations video_async_ops = []{
struct vlc_encoder_operations cbs{};
cbs.close = EncoderClose;
cbs.encode_video = EncodeVideoAsync;
return cbs;
}();
p_enc->ops = p_sys->is_async ? &video_async_ops : &video_ops;
return VLC_SUCCESS;
error:
EncoderClose(p_enc);
return VLC_ENOTSUP;
}
static int OpenMFTAudioEncoder(vlc_object_t *p_this)
{
encoder_t *p_enc = (encoder_t*)p_this;
if( FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)) )
return VLC_EINVAL;
HRESULT hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
if (FAILED(hr))
{
CoUninitialize();
return VLC_ENOTSUP;
}
mft_enc_audio *p_sys = new (std::nothrow) mft_enc_audio();
if (unlikely(p_sys == nullptr))
{
MFShutdown();
CoUninitialize();
return VLC_ENOMEM;
}
p_enc->p_sys = p_sys;
GUID category = MFT_CATEGORY_AUDIO_ENCODER;
MFT_REGISTER_TYPE_INFO input_type, output_type;
IMFActivate **activate_objects = nullptr;
UINT32 activate_objects_count = 0;
UINT32 flags;
bool found = false;
hr = MFTypeFromAudio(p_enc->fmt_in.audio.i_format, input_type);
if (FAILED(hr))
{
msg_Dbg(p_this, "Input codec %4.4s not supported by MediaFoundation",
(char*)&p_enc->fmt_in.i_codec);
goto error;
}
hr = MFTypeFromCodec(MFMediaType_Audio, p_enc->fmt_out.i_codec, output_type);
if (FAILED(hr))
hr = MFTypeFromAudio(p_enc->fmt_out.i_codec, output_type);
if (output_type.guidSubtype == GUID_NULL)
{
msg_Dbg(p_this, "Output codec %4.4s not supported by MediaFoundation",
(char*)&p_enc->fmt_out.i_codec);
goto error;
}
ListTransforms(vlc_object_logger(p_this), category, "audio encoder", var_InheritBool(p_this, "mft-debug"));
flags = MFT_ENUM_FLAG_SORTANDFILTER | MFT_ENUM_FLAG_LOCALMFT
| MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT
| MFT_ENUM_FLAG_HARDWARE;
hr = MFTEnumEx(category, flags, &input_type, &output_type, &activate_objects, &activate_objects_count);
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to enumerate MFT modules for %4.4s", (const char*)&p_enc->fmt_out.i_codec);
goto error;
}
if (activate_objects_count == 0)
{
// try the other raw input format
if (p_enc->fmt_in.audio.i_format == VLC_CODEC_F32L)
p_enc->fmt_in.audio.i_format = VLC_CODEC_S16L;
else
p_enc->fmt_in.audio.i_format = VLC_CODEC_F32L;
MFTypeFromAudio(p_enc->fmt_in.audio.i_format, input_type);
hr = MFTEnumEx(category, flags, &input_type, &output_type, &activate_objects, &activate_objects_count);
if (FAILED(hr))
{
msg_Dbg(p_enc, "Failed to enumerate MFT modules for %4.4s", (const char*)&p_enc->fmt_out.i_codec);
goto error;
}
p_enc->fmt_in.i_codec = p_enc->fmt_in.audio.i_format;
p_enc->fmt_in.audio.i_bitspersample = aout_BitsPerSample(p_enc->fmt_in.i_codec);
msg_Warn(p_enc, "Using %4.4s for audio source", (char*)&p_enc->fmt_in.i_codec);
}
msg_Dbg(p_enc, "Found %d available MFT module(s) for %4.4s to %4.4s", activate_objects_count,
(const char*)&p_enc->fmt_in.i_codec, (const char*)&p_enc->fmt_out.i_codec);
if (activate_objects_count == 0)
goto error;
for (UINT32 i = 0; i < activate_objects_count; ++i)
{
WCHAR Name[256];
hr = activate_objects[i]->GetString(MFT_FRIENDLY_NAME_Attribute, Name, ARRAY_SIZE(Name), nullptr);
if (FAILED(hr))
wcsncpy(Name, L"<unknown>", ARRAY_SIZE(Name));
hr = activate_objects[i]->ActivateObject(IID_PPV_ARGS(p_sys->mft.ReleaseAndGetAddressOf()));
if (FAILED(hr))
continue;
if (InitializeEncoder(vlc_object_logger(p_this), *p_sys) == VLC_SUCCESS)
{
msg_Dbg(p_enc, "Using audio encoder %ls", Name);
found = true;
break;
}
}
for (UINT32 i = 0; i < activate_objects_count; ++i)
activate_objects[i]->Release();
CoTaskMemFree(activate_objects);
if (!found)
goto error;
if (p_sys->SetOutputType(vlc_object_logger(p_this),
output_type.guidSubtype, p_enc->fmt_out))
goto error;
hr = p_sys->SetInputType(p_enc->fmt_in, input_type);
if (FAILED(hr))
{
msg_Err(p_enc, "Error in SetInputType(). (hr=0x%lX)", hr);
goto error;
}
/* This event is required for asynchronous MFTs, optional otherwise. */
hr = p_sys->startStream();
if (FAILED(hr))
{
msg_Err(p_enc, "Error in startStream(). (hr=0x%lX)", hr);
goto error;
}
static const struct vlc_encoder_operations audio_ops = []{
struct vlc_encoder_operations cbs{};
cbs.close = EncoderClose;
cbs.encode_audio = EncodeAudio;
return cbs;
}();
p_enc->ops = &audio_ops;
return VLC_SUCCESS;
error:
EncoderClose(p_enc);
return VLC_ENOTSUP;
}
#endif // !ENABLE_SOUT
static int Open(vlc_object_t *p_this)
{
decoder_t *p_dec = (decoder_t *)p_this;
if( FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)) )
return VLC_EINVAL;
HRESULT hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
if (FAILED(hr))
{
CoUninitialize();
return VLC_ENOTSUP;
}
mft_sys_t *p_sys;
if (p_dec->fmt_in->i_cat == VIDEO_ES)
p_sys = new (std::nothrow) mft_dec_video();
else
p_sys = new (std::nothrow) mft_dec_audio();
if (unlikely(p_sys == nullptr))
{
MFShutdown();
CoUninitialize();
return VLC_ENOMEM;
}
p_dec->p_sys = p_sys;
if (FindMFT(p_dec))
{
msg_Err(p_dec, "Could not find suitable MFT decoder for %4.4s", (char*)&p_dec->fmt_in->i_codec);
goto error;
}
/* Only one output sample is needed, we can allocate one and reuse it. */
hr = p_sys->AllocateOutputSample(p_dec->fmt_in->i_cat, p_sys->output_sample);
if (FAILED(hr))
{
msg_Err(p_dec, "Error in AllocateOutputSample(). (hr=0x%lX)", hr);
goto error;
}
p_dec->pf_decode = p_sys->is_async ? DecodeAsync : DecodeSync;
p_dec->pf_flush = p_sys->is_async ? NULL : Flush;
return VLC_SUCCESS;
error:
Close(p_this);
return VLC_EGENERIC;
}
static void Close(vlc_object_t *p_this)
{
decoder_t *p_dec = (decoder_t *)p_this;
auto *p_sys = static_cast<mft_sys_t*>(p_dec->p_sys);
DestroyMFT(p_dec);
p_sys->Release();
}