Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  mft.cpp   Sprache: C

 

/*****************************************************************************
 * 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()
    {
        if(!streamStarted)
            return S_OK;
        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> &&nbsp;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();
}

Messung
CHZG
83889086

¤ Dauer der Verarbeitung: 0.48 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104842
#Domains=499162