/***************************************************************************** * mft.cpp : Media Foundation Transform audio/video decoder ***************************************************************************** * Copyright (C) 2014 VLC authors and VideoLAN * * Author: Felix Abecassis * * 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 #undef WINAPI_FAMILY #define WINAPI_FAMILY WINAPI_FAMILY_DESKTOP_APP #endif #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include extern "C" { #include "hxxx_helper.h" } #include "mft_d3d.h" #include "mft_d3d11.h" #include #include #include #include #include #include #include #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 // wf_tag_to_fourcc #include #include #include #include #include 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 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 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 input_type; /* Output stream */ DWORD output_stream_id = 0; ComPtr output_sample; HRESULT AllocateOutputSample(es_format_category_e cat, ComPtr & 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 shutdownObj; HRESULT hr = mft.As(&shutdownObj); if (FAILED(hr)) return hr; return shutdownObj->Shutdown(); } private: bool streamStarted = false; std::atomic 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 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 & result = input_type; result.Reset(); ComPtr 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(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 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(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 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 & mft, DWORD stream_id, ComPtr & result, DWORD size) { HRESULT hr; result.Reset(); ComPtr input_sample; ComPtr 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(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 & result) { HRESULT hr; result.Reset(); ComPtr output_sample; MFT_OUTPUT_STREAM_INFO output_info; ComPtr 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 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 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 & mft, DWORD stream_id, block_t *p_block) { HRESULT hr; ComPtr input_sample; DWORD alloc_size = p_block->i_buffer; vlc_tick_t ts; ComPtr 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(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 output_sample = output_buffer.pSample; for (DWORD buf_index = 0; buf_index < output_count; buf_index++) { picture_t *picture = NULL; ComPtr 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(p_sys); picture_context_t *pic_ctx = nullptr; if (vidsys->hw_d3d != nullptr) { ComPtr 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(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(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(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 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(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 & 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 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(); } } 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(p_dec->p_sys); HRESULT hr; ComPtr 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(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(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(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(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; oGetString(MFT_FRIENDLY_NAME_Attribute, Name, ARRAY_SIZE(Name), nullptr); if (FAILED(hr)) wcscpy(Name,L""); if (do_test) { ComPtr mft; hr = activate_objects[o]->ActivateObject(IID_PPV_ARGS(mft.GetAddressOf())); bool available = SUCCEEDED(hr); const char *async = ""; if (available) { ComPtr 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(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 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 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(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(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(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 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 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(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 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)) ) 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"", 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)) ) 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"", 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)) ) 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(p_dec->p_sys); DestroyMFT(p_dec); p_sys->Release(); }