[vlc-devel] [PATCH] [WIP] codec: implementation of Media Foundation Transform video decoding

Felix Abecassis felix.abecassis at gmail.com
Wed Apr 23 17:39:33 CEST 2014


Hi, I'm answering now since the new patch is almost ready.

2014-04-18 11:27 GMT+02:00 Denis Charmet <typx at dinauz.org>:
> Hi,
>
> Le jeudi 17 avril 2014 à 05:21:58, Felix Abecassis a écrit :
>> +vlc_module_begin()
>> +    set_description(N_("Media Foundation Transform decoder"))
>> +    add_shortcut("mft")
>> +    set_capability("decoder", 0)
> why 0?
Bumped to 1 for the next patch.

>> +    set_callbacks(Open, Close)
>> +    set_category(CAT_INPUT)
>> +    set_subcategory(SUBCAT_INPUT_VCODEC)
>> +vlc_module_end()
>> +
>> +typedef struct
>> +{
>> +    HINSTANCE mfplat_dll;
>> +    HRESULT (STDCALL *MFTEnumEx)(GUID guidCategory, UINT32 Flags,
>> +                                 const MFT_REGISTER_TYPE_INFO *pInputType,
>> +                                 const MFT_REGISTER_TYPE_INFO *pOutputType,
>> +                                 IMFActivate ***pppMFTActivate, UINT32 *pcMFTActivate);
>> +    HRESULT (STDCALL *MFCreateMediaType)(IMFMediaType **ppMFType);
>> +    HRESULT (STDCALL *MFCreateSample)(IMFSample **ppIMFSample);
>> +    HRESULT (STDCALL *MFCreateMemoryBuffer)(DWORD cbMaxLength, IMFMediaBuffer **ppBuffer);
>> +    HRESULT (STDCALL *MFCreateAlignedMemoryBuffer)(DWORD cbMaxLength, DWORD fAlignmentFlags, IMFMediaBuffer **ppBuffer);
>> +} MFHandle;
>> +
>> +struct decoder_sys_t
>> +{
>> +    MFHandle mf_handle;
>> +
>> +    IMFTransform *mft;
>> +
>> +    /* For asynchronous MFT */
>> +    bool is_async;
>> +    IMFMediaEventGenerator *event_generator;
>> +    int pending_input_events;
>> +    int pending_output_events;
>> +
>> +    /* Input stream */
>> +    DWORD input_stream_id;
>> +    IMFMediaType *input_type;
>> +
>> +    /* Output stream */
>> +    DWORD output_stream_id;
>> +    IMFSample *output_sample;
>> +    IMFMediaType *output_type;
>> +
>> +    /* H264 only. */
>> +    uint32_t nal_size;
>> +};
>> +
>> +/* Possibly missing from mingw headers */
>> +#ifndef MF_E_TRANSFORM_NEED_MORE_INPUT
>> +# define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72)
>> +#endif
>> +
>> +#ifndef MF_E_TRANSFORM_STREAM_CHANGE
>> +# define MF_E_TRANSFORM_STREAM_CHANGE _HRESULT_TYPEDEF_(0xc00d6d61)
>> +#endif
>> +
>> +#ifndef MF_E_NO_EVENTS_AVAILABLE
>> +# define MF_E_NO_EVENTS_AVAILABLE _HRESULT_TYPEDEF_(0xC00D3E80L)
>> +#endif
>> +
>> +#ifndef MF_EVENT_FLAG_NO_WAIT
>> +# define MF_EVENT_FLAG_NO_WAIT 0x00000001
>> +#endif
>> +
>> +/*
>> + * The MFTransformXXX values might not be defined in mingw headers,
>> + * thus we use our own enum with the VLC prefix.
>> + */
>> +enum
>> +{
>> +    VLC_METransformUnknown = 600,
>> +    VLC_METransformNeedInput,
>> +    VLC_METransformHaveOutput,
>> +    VLC_METransformDrainComplete,
>> +    VLC_METransformMarker,
>> +};
>> +
>> +typedef struct
>> +{
>> +    vlc_fourcc_t fourcc;
>> +    const GUID   *guid;
>> +} pair_format_guid;
>> +
>> +/*
>> + * 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_format_table[] =
>> +{
>> +    { VLC_CODEC_H264, &MFVideoFormat_H264 },
>> +    { 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 },
>> +    { 0, 0 }
> this is not really usefull since you can iterate with a:
> i < sizeof(video_format_table)/sizeof(pair_format_guid)
> but OK...
No since I pass the array to a function, I would need to pass the
length to the function too.

>> +};
>> +
>> +static const GUID *FormatToGUID(const pair_format_guid table[], vlc_fourcc_t fourcc)
>> +{
>> +    for (int i = 0; table[i].fourcc; ++i)
>> +        if (table[i].fourcc == fourcc)
>> +            return table[i].guid;
>> +
>> +    return NULL;
>> +}
>> +
>> +/*
>> + * GUIDs were found using the mediasdk_sys_analyzer tool from Intel
>> + * Media SDK 2014 for Clients.
>> + */
>> +DEFINE_GUID(INTEL_HW_H264_DECODER, 0x45E5CE07, 0x5AC7, 0x4509, 0x94, 0xE9, 0x62, 0xDB, 0x27, 0xCF, 0x8F, 0x96);
>> +DEFINE_GUID(INTEL_HW_MJPEG_DECODER, 0x00C69F81, 0x0524, 0x48C0, 0xA3, 0x53, 0x4D, 0xD9, 0xD5, 0x4F, 0x9A, 0x6E);
>> +DEFINE_GUID(INTEL_HW_VC1_DECODER, 0x059A5BAE, 0x5D7A, 0x4C5E, 0x8F, 0x7A, 0xBF, 0xD5, 0x7D, 0x1D, 0x6A, 0xAA);
>> +DEFINE_GUID(INTEL_HW_MPEG2_DECODER, 0xCD5BA7FF, 0x9071, 0x40E9, 0xA4, 0x62, 0x8D, 0xC5, 0x15, 0x2B, 0x17, 0x76);
>> +
>> +/*
>> + * Low latency mode for Windows 8. Without this option, the decoder
>> + * will fill *all* its internal buffers before returning a
>> + * frame. Because of this behavior, the decoder might return no frame
>> + * for more than 500 ms, making it unusable for playback.
>> + */
>> +DEFINE_GUID(CODECAPI_AVLowLatencyMode, 0x9c27891a, 0xed7a, 0x40e1, 0x88, 0xe8, 0xb2, 0x27, 0x27, 0xa0, 0x24, 0xee);
>> +
>> +
>> +/*
>> + * Additional MFT decoders than might be available on the system but
>> + * cannot be enumerated. Several entries are possible for each format
>> + * but should be sorted by priority.
>> + */
>> +static const pair_format_guid extra_mft_table[] =
>> +{
>> +    { VLC_CODEC_H264, &INTEL_HW_H264_DECODER },
>> +    { VLC_CODEC_MJPG, &INTEL_HW_MJPEG_DECODER },
>> +    { VLC_CODEC_VC1,  &INTEL_HW_VC1_DECODER },
>> +    { VLC_CODEC_MPGV, &INTEL_HW_MPEG2_DECODER },
>> +    { VLC_CODEC_MP2V, &INTEL_HW_MPEG2_DECODER },
>> +    { 0, 0 }
>> +};
>> +
>> +static int SetInputType(decoder_t *p_dec, DWORD stream_id, IMFMediaType **result)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    HRESULT hr;
>> +
>> +    if (!result)
>> +        return VLC_EGENERIC;
> It is your static function and you never call it with a NULL result, you
> don't need that test.
OK. Removed.

>
>> +    *result = NULL;
>> +
>> +    IMFMediaType *input_media_type = NULL;
>> +    vlc_fourcc_t input_type = vlc_fourcc_GetCodec(p_dec->fmt_in.i_cat, p_dec->fmt_in.i_codec);
>> +
>> +    /* Search a suitable input type for the MFT. */
>> +    int input_type_index = 0;
>> +    for (int i = 0; true; ++i)
>> +    {
>> +        hr = IMFTransform_GetInputAvailableType(p_sys->mft, stream_id, i, &input_media_type);
>> +        if (hr == MF_E_NO_MORE_TYPES)
>> +            break;
>> +        else if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
>> +        {
>> +            /* The output type must be set before setting the input type for this MFT. */
>> +            return VLC_SUCCESS;
>> +        }
>> +        else if (FAILED(hr))
>> +            goto error;
>> +
>> +        GUID subtype;
>> +        hr = IMFMediaType_GetGUID(input_media_type, &MF_MT_SUBTYPE, &subtype);
>> +        if (FAILED(hr))
>> +            goto error;
>> +        vlc_fourcc_t mft_type = vlc_fourcc_GetCodec(p_dec->fmt_in.i_cat, subtype.Data1);
>> +        if (input_type == mft_type)
>> +            input_type_index = i;
> Does this case happen only once or it doesn't matter if input_type_index
> is overriden?
Actually we should exit early since the types enumerated first are
supposed to be "better".
I added a "break".

>> +
>> +        IMFMediaType_Release(input_media_type);
>> +        input_media_type = NULL;
>> +    }
>> +
>> +    hr = IMFTransform_GetInputAvailableType(p_sys->mft, stream_id, input_type_index, &input_media_type);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    UINT64 width = p_dec->fmt_in.video.i_width;
>> +    UINT64 height = p_dec->fmt_in.video.i_height;
>> +    UINT64 frame_size = (width << 32) | height;
>> +    hr = IMFMediaType_SetUINT64(input_media_type, &MF_MT_FRAME_SIZE, frame_size);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    hr = IMFTransform_SetInputType(p_sys->mft, stream_id, input_media_type, 0);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    *result = input_media_type;
>> +
>> +    return VLC_SUCCESS;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in SetInputType()");
>> +    if (input_media_type)
>> +        IMFMediaType_Release(input_media_type);
>> +    return VLC_EGENERIC;
>> +}
>> +
>> +static int SetOutputType(decoder_t *p_dec, DWORD stream_id, IMFMediaType **result)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    HRESULT hr;
>> +
>> +    if (!result)
>> +        return VLC_EGENERIC;
> same remark
OK. Removed.

>> +    *result = NULL;
>> +
>> +    IMFMediaType *output_media_type = NULL;
>> +
>> +    /*
>> +     * Enumerate available output types. The list is ordered by
>> +     * preference thus we will use the first one unless YV12/I420 is
>> +     * available.
>> +     */
>> +    int output_type_index = 0;
>> +    for (int i = 0; true; ++i)
>> +    {
>> +        hr = IMFTransform_GetOutputAvailableType(p_sys->mft, stream_id, i, &output_media_type);
>> +        if (hr == MF_E_NO_MORE_TYPES)
>> +            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 VLC_SUCCESS;
>> +        }
>> +        else if (FAILED(hr))
>> +            goto error;
>> +
>> +        GUID subtype;
>> +        hr = IMFMediaType_GetGUID(output_media_type, &MF_MT_SUBTYPE, &subtype);
>> +        if (FAILED(hr))
>> +            goto error;
>> +
>> +        if (IsEqualGUID(&subtype, &MFVideoFormat_YV12) || IsEqualGUID(&subtype, &MFVideoFormat_I420))
>> +            output_type_index = i;
>> +
>> +        IMFMediaType_Release(output_media_type);
>> +        output_media_type = NULL;
>> +    }
>> +
>> +    hr = IMFTransform_GetOutputAvailableType(p_sys->mft, stream_id, output_type_index, &output_media_type);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    hr = IMFTransform_SetOutputType(p_sys->mft, stream_id, output_media_type, 0);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    GUID subtype;
>> +    hr = IMFMediaType_GetGUID(output_media_type, &MF_MT_SUBTYPE, &subtype);
>> +    if (FAILED(hr))
>> +        goto error;
>> +    p_dec->fmt_out.i_codec = vlc_fourcc_GetCodec(p_dec->fmt_in.i_cat, subtype.Data1);
>> +
>> +    *result = output_media_type;
>> +
>> +    return VLC_SUCCESS;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in SetOutputType()");
>> +    if (output_media_type)
>> +        IMFMediaType_Release(output_media_type);
>> +    return VLC_EGENERIC;
>> +}
>> +
>> +static int AllocateInputSample(decoder_t *p_dec, DWORD stream_id, IMFSample** result, DWORD size)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    MFHandle *mf = &p_sys->mf_handle;
>> +    HRESULT hr;
>> +
>> +    if (!result)
>> +        return VLC_EGENERIC;
>> +    *result = NULL;
>> +
>> +    IMFSample *input_sample = NULL;
>> +
>> +    MFT_INPUT_STREAM_INFO input_info;
>> +    hr = IMFTransform_GetInputStreamInfo(p_sys->mft, stream_id, &input_info);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    hr = mf->MFCreateSample(&input_sample);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    IMFMediaBuffer *input_media_buffer = NULL;
>> +    DWORD allocation_size = __MAX(input_info.cbSize, size);
>> +    hr = mf->MFCreateMemoryBuffer(allocation_size, &input_media_buffer);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    hr = IMFSample_AddBuffer(input_sample, input_media_buffer);
>> +    if (FAILED(hr))
> leak input_media_buffer?
Indeed.

>> +        goto error;
>> +
>> +    *result = input_sample;
>> +
>> +    return VLC_SUCCESS;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in AllocateInputSample()");
>> +    if (input_sample)
>> +        IMFSample_Release(input_sample);
>> +    return VLC_EGENERIC;
>> +}
>> +
>> +static int AllocateOutputSample(decoder_t *p_dec, DWORD stream_id, IMFSample **result)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    MFHandle *mf = &p_sys->mf_handle;
>> +    HRESULT hr;
>> +
>> +    if (!result)
>> +        return VLC_EGENERIC;
>> +    *result = NULL;
>> +
>> +    IMFSample *output_sample = NULL;
>> +
>> +    MFT_OUTPUT_STREAM_INFO output_info;
>> +    hr = IMFTransform_GetOutputStreamInfo(p_sys->mft, stream_id, &output_info);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    if (output_info.dwFlags & (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES))
>> +    {
>> +        /* The MFT will provide an allocated sample. */
>> +        return VLC_SUCCESS;
>> +    }
>> +    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)
>> +        goto error;
>> +
>> +    hr = mf->MFCreateSample(&output_sample);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    IMFMediaBuffer *output_media_buffer = NULL;
>> +    DWORD allocation_size = output_info.cbSize;
>> +    DWORD alignment = output_info.cbAlignment;
>> +    if (alignment > 0)
>> +        hr = mf->MFCreateAlignedMemoryBuffer(allocation_size, alignment - 1, &output_media_buffer);
>> +    else
>> +        hr = mf->MFCreateMemoryBuffer(allocation_size, &output_media_buffer);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    hr = IMFSample_AddBuffer(output_sample, output_media_buffer);
>> +    if (FAILED(hr))
> leak?
Indeed. Fixed, thanks.

>> +        goto error;
>> +
>> +    *result = output_sample;
>> +
>> +    return VLC_SUCCESS;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in AllocateOutputSample()");
>> +    if (output_sample)
>> +        IMFSample_Release(output_sample);
>> +    return VLC_EGENERIC;
>> +}
>> +
>> +static int ProcessInputStream(decoder_t *p_dec, DWORD stream_id, block_t *p_block)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    HRESULT hr;
>> +    IMFSample *input_sample = NULL;
>> +
>> +    if (AllocateInputSample(p_dec, stream_id, &input_sample, p_block->i_buffer))
>> +        goto error;
>> +
>> +    IMFMediaBuffer *input_media_buffer = NULL;
>> +    hr = IMFSample_GetBufferByIndex(input_sample, stream_id, &input_media_buffer);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    BYTE *buffer_start;
>> +    hr = IMFMediaBuffer_Lock(input_media_buffer, &buffer_start, NULL, NULL);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    memcpy(buffer_start, p_block->p_buffer, p_block->i_buffer);
>> +
>> +    if (p_dec->fmt_in.i_codec == VLC_CODEC_H264)
>> +    {
>> +        /* in-place NAL to annex B conversion. */
>> +        struct H264ConvertState convert_state = { 0, 0 };
>> +        convert_h264_to_annexb(buffer_start, p_block->i_buffer, p_sys->nal_size, &convert_state);
>> +    }
>> +
>> +    hr = IMFMediaBuffer_Unlock(input_media_buffer);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    hr = IMFMediaBuffer_SetCurrentLength(input_media_buffer, p_block->i_buffer);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    LONGLONG ts = p_block->i_pts;
>> +    if (!ts && p_block->i_dts)
>> +        ts = p_block->i_dts;
>> +
>> +    /* Convert from microseconds to 100 nanoseconds unit. */
>> +    hr = IMFSample_SetSampleTime(input_sample, ts * 10);
> Ideally you should use CLOCK_FREQ since it may change one day :)
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    hr = IMFTransform_ProcessInput(p_sys->mft, stream_id, input_sample, 0);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    IMFSample_Release(input_sample);
>> +
>> +    return VLC_SUCCESS;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in ProcessInputStream()");
>> +    if (input_sample)
>> +        IMFSample_Release(input_sample);
>> +    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, picture_t **result)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    HRESULT hr;
>> +    picture_t *picture = NULL;
>> +
>> +    if (!result)
>> +        return VLC_EGENERIC;
>> +    *result = NULL;
>> +
>> +    DWORD output_status = 0;
>> +    MFT_OUTPUT_DATA_BUFFER output_buffer = { stream_id, p_sys->output_sample, 0, NULL };
>> +    hr = IMFTransform_ProcessOutput(p_sys->mft, 0, 1, &output_buffer, &output_status);
>> +    /* Use the returned sample since it can be provided by the MFT. */
>> +    IMFSample *output_sample = output_buffer.pSample;
>> +
>> +    if (hr == S_OK)
>> +    {
>> +        if (!output_sample)
>> +            return VLC_SUCCESS;
>> +        picture = decoder_NewPicture(p_dec);
>> +        if (!picture)
>> +            return VLC_SUCCESS;
>> +
>> +        UINT32 interlaced = false;
>> +        hr = IMFSample_GetUINT32(output_sample, &MFSampleExtension_Interlaced, &interlaced);
>> +        picture->b_progressive = !interlaced;
>> +
>> +        IMFMediaBuffer *output_media_buffer = NULL;
>> +        hr = IMFSample_GetBufferByIndex(output_sample, 0, &output_media_buffer);
>> +
>> +        BYTE *buffer_start;
>> +        hr = IMFMediaBuffer_Lock(output_media_buffer, &buffer_start, NULL, NULL);
>> +        if (FAILED(hr))
>> +            goto error;
>> +
>> +        CopyPackedBufferToPicture(picture, buffer_start);
>> +
>> +        LONGLONG sample_time;
>> +        hr = IMFSample_GetSampleTime(output_sample, &sample_time);
>> +        if (FAILED(hr))
> unlock buffer?
Apparently we can get the timestamp while the buffer is locked, but
indeed it's better to lower the distance between lock/unlock.

>> +            goto error;
>> +        /* Convert from 100 nanoseconds unit to microseconds. */
>> +        picture->date = sample_time / 10;
>> +
>> +        hr = IMFMediaBuffer_Unlock(output_media_buffer);
>> +        if (FAILED(hr))
>> +            goto error;
>> +
>> +        if (p_sys->output_sample)
>> +        {
>> +            /* Sample is not provided by the MFT: clear its content. */
>> +            hr = IMFMediaBuffer_SetCurrentLength(output_media_buffer, 0);
>> +            if (FAILED(hr))
>> +                goto error;
>> +        }
>> +        else
>> +        {
>> +            /* Sample is provided by the MFT: decrease refcount. */
>> +            IMFSample_Release(output_sample);
>> +        }
>> +    }
>> +    else if (hr == MF_E_TRANSFORM_STREAM_CHANGE || hr == MF_E_TRANSFORM_TYPE_NOT_SET)
>> +    {
>> +        if (p_sys->output_type)
>> +            IMFMediaType_Release(p_sys->output_type);
>> +        if (SetOutputType(p_dec, p_sys->output_stream_id, &p_sys->output_type))
>> +            goto error;
>> +    }
>> +    else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
>> +    {
>> +        return VLC_SUCCESS;
>> +    }
>> +    else /* An error not listed above occurred */
>> +    {
>> +        msg_Err(p_dec, "Unexpected error in IMFTransform::ProcessOutput");
>> +        goto error;
>> +    }
>> +
>> +    *result = picture;
>> +
>> +    return VLC_SUCCESS;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in ProcessOutputStream()");
>> +    if (picture)
>> +        picture_Release(picture);
>> +    return VLC_EGENERIC;
>> +}
>> +
>> +static picture_t *DecodeSync(decoder_t *p_dec, block_t **pp_block)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    HRESULT hr;
>> +
>> +    if (!pp_block || !*pp_block)
>> +        return NULL;
>> +
>> +    block_t *p_block = *pp_block;
>> +    if (p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY | BLOCK_FLAG_CORRUPTED))
>> +    {
>> +        block_Release(p_block);
>> +
>> +        hr = IMFTransform_ProcessMessage(p_sys->mft, MFT_MESSAGE_COMMAND_FLUSH, 0);
>> +        if (FAILED(hr))
>> +            msg_Err(p_dec, "COMMAND_FLUSH: error.");
>> +
>> +        return NULL;
>> +    }
>> +
>> +    /* Drain the output stream before sending the input packet. */
>> +    picture_t *picture = NULL;
>> +    if (ProcessOutputStream(p_dec, p_sys->output_stream_id, &picture))
>> +        goto error;
>> +    if (picture)
>> +        return picture;
> why returning the picture here and not after ProcessInputStream?
If we have several pictures waiting in the decoder, with this code we
dequeue all the pictures before trying to send the input packet.
With your option, we will only get one picture, with some decoders
sending the input packet might fail if there are still some pictures
in the MFT.

>> +
>> +    if (ProcessInputStream(p_dec, p_sys->input_stream_id, p_block))
>> +        goto error;
>> +
>> +    block_Release(p_block);
>> +    *pp_block = NULL;
>> +
>> +    return NULL;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in DecodeSync()");
>> +    if (p_block)
>> +        block_Release(p_block);
>> +    return NULL;
>> +}
>> +
>> +static HRESULT DequeueMediaEvent(decoder_t *p_dec)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    HRESULT hr;
>> +
>> +    IMFMediaEvent *event = NULL;
>> +    hr = IMFMediaEventGenerator_GetEvent(p_sys->event_generator, MF_EVENT_FLAG_NO_WAIT, &event);
>> +    if (FAILED(hr))
>> +        return hr;
>> +    MediaEventType event_type;
>> +    hr = IMFMediaEvent_GetType(event, &event_type);
>> +    IMFMediaEvent_Release(event);
>> +    if (FAILED(hr))
>> +        return hr;
>> +
>> +    if (event_type == VLC_METransformNeedInput)
>> +        p_sys->pending_input_events += 1;
>> +    else if (event_type == VLC_METransformHaveOutput)
>> +        p_sys->pending_output_events += 1;
>> +    else
>> +        msg_Err(p_dec, "UNKNOWN EVENT");
>> +
>> +    return S_OK;
>> +}
>> +
>> +static picture_t *DecodeAsync(decoder_t *p_dec, block_t **pp_block)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    HRESULT hr;
>> +
>> +    if (!pp_block || !*pp_block)
>> +        return NULL;
>> +
>> +    block_t *p_block = *pp_block;
>> +    if (p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY | BLOCK_FLAG_CORRUPTED))
>> +    {
>> +        block_Release(p_block);
>> +
>> +        /*
>> +         * The following code should be used according to the MSDN
>> +         * documentation, however it currently causes the Intel MFT
>> +         * decoder to freeze after a seek.
>> +        */
>> +#if 0
>> +        p_sys->pending_input_events = p_sys->pending_output_events = 0;
>> +
>> +        hr = IMFTransform_ProcessMessage(p_sys->mft, MFT_MESSAGE_COMMAND_FLUSH, 0);
>> +        if (FAILED(hr))
>> +            msg_Err(p_dec, "COMMAND_FLUSH: error.");
>> +
>> +         hr = IMFTransform_ProcessMessage(p_sys->mft, MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
>> +        if (FAILED(hr))
>> +            msg_Err(p_dec, "NOTIFY_START_OF_STREAM: error.");
>> +#endif
> Is it dead code or for future use?
It was meant for future use, but apparently all decoders I tested are
working correctly without having to signal a discontinuity. So I
removed this code for now.

>> +
>> +        return NULL;
>> +    }
>> +
>> +    /* Dequeue all pending media events. */
>> +    while ((hr = DequeueMediaEvent(p_dec)) == S_OK)
>> +        continue;
> useless continue
This is intended, I think this option is ugly:
while (...)
   ;
And this one is dangerous IMO:
while (...);

>
>> +    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;
>> +        picture_t *picture = NULL;
>> +        if (ProcessOutputStream(p_dec, p_sys->output_stream_id, &picture))
>> +            goto error;
>> +        return picture;
>> +    }
>> +
>> +    /* Poll the MFT and return decoded frames until the input stream is ready. */
>> +    while (p_sys->pending_input_events == 0)
>> +    {
>> +        hr = DequeueMediaEvent(p_dec);
>> +        if (hr == MF_E_NO_EVENTS_AVAILABLE)
>> +        {
>> +            /* Sleep for 1 ms to avoid excessive polling. */
>> +            Sleep(1);
> maybe DequeueMediaEvent should take a wait param, it would avoid the use
> of a Sleep after a poll.
I don't really see how that would be better. But maybe I misunderstood you.

>> +            continue;
>> +        }
>> +        if (FAILED(hr))
>> +            goto error;
>> +
>> +        if (p_sys->pending_output_events > 0)
>> +        {
>> +            p_sys->pending_output_events -= 1;
>> +            picture_t *picture = NULL;
>> +            if (ProcessOutputStream(p_dec, p_sys->output_stream_id, &picture))
>> +                goto error;
>> +            return picture;
>> +        }
>> +    }
>> +
>> +    p_sys->pending_input_events -= 1;
>> +    if (ProcessInputStream(p_dec, p_sys->input_stream_id, p_block))
>> +        goto error;
>> +
>> +    block_Release(p_block);
>> +    *pp_block = NULL;
>> +
>> +    return NULL;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in DecodeAsync()");
>> +    block_Release(p_block);
>> +    return NULL;
>> +}
>> +
>> +static void DestroyMFT(decoder_t *p_dec);
>> +
>> +static int InitializeMFT(decoder_t *p_dec)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    HRESULT hr;
>> +
>> +    IMFAttributes *attributes;
>> +    IMFTransform_GetAttributes(p_sys->mft, &attributes);
>> +    UINT32 is_async = false;
>> +    hr = IMFAttributes_GetUINT32(attributes, &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 = IMFAttributes_SetUINT32(attributes, &MF_TRANSFORM_ASYNC_UNLOCK, true);
>> +        if (FAILED(hr))
>> +            goto error;
>> +        hr = IMFTransform_QueryInterface(p_sys->mft, &IID_IMFMediaEventGenerator, (void**)&p_sys->event_generator);
>> +        if (FAILED(hr))
>> +            goto error;
>> +    }
>> +
>> +    DWORD input_streams_count;
>> +    DWORD output_streams_count;
>> +    hr = IMFTransform_GetStreamCount(p_sys->mft, &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 = IMFTransform_GetStreamIDs(p_sys->mft, 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 O to N-1.
>> +         */
>> +        p_sys->input_stream_id = 0;
>> +        p_sys->output_stream_id = 0;
>> +    }
>> +    else if (FAILED(hr))
>> +        goto error;
>> +
>> +    if (SetInputType(p_dec, p_sys->input_stream_id, &p_sys->input_type))
>> +        goto error;
>> +
>> +    if (SetOutputType(p_dec, p_sys->output_stream_id, &p_sys->output_type))
>> +        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)
>> +        if (SetInputType(p_dec, p_sys->input_stream_id, &p_sys->input_type) || !p_sys->input_type)
>> +            goto error;
>> +
>> +    /* This call can be a no-op for some MFT decoders, but it can potentially reduce starting time. */
>> +    hr = IMFTransform_ProcessMessage(p_sys->mft, MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, (ULONG_PTR)0);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    /* This event is required for asynchronous MFTs, optional otherwise. */
>> +    hr = IMFTransform_ProcessMessage(p_sys->mft, MFT_MESSAGE_NOTIFY_START_OF_STREAM, (ULONG_PTR)0);
>> +    if (FAILED(hr))
>> +        goto error;
>> +
>> +    if (p_dec->fmt_in.i_codec == VLC_CODEC_H264)
>> +    {
>> +        /* It's not an error if the following call fails. */
>> +        IMFAttributes_SetUINT32(attributes, &CODECAPI_AVLowLatencyMode, true);
>> +
>> +        if (p_dec->fmt_in.i_extra)
>> +        {
>> +            int buf_size = p_dec->fmt_in.i_extra + 20;
>> +            uint32_t size = p_dec->fmt_in.i_extra;
>> +            uint8_t *buf = malloc(buf_size);
>> +            if (((uint8_t*)p_dec->fmt_in.p_extra)[0] == 1)
>> +            {
>> +                convert_sps_pps(p_dec, p_dec->fmt_in.p_extra, p_dec->fmt_in.i_extra,
>> +                                buf, buf_size,
>> +                                &size, &p_sys->nal_size);
>> +            }
>> +            free(buf);
>> +        }
>> +    }
> Doesn't the packetizer already outputs annexb frames?
Apparently, no.

>> +    return VLC_SUCCESS;
>> +
>> +error:
>> +    msg_Err(p_dec, "Error in InitializeMFT()");
>> +    DestroyMFT(p_dec);
>> +    return VLC_EGENERIC;
>> +}
>> +
>> +static void DestroyMFT(decoder_t *p_dec)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +
>> +    if (p_sys->event_generator)
>> +        IMFMediaEventGenerator_Release(p_sys->event_generator);
>> +    if (p_sys->input_type)
>> +        IMFMediaType_Release(p_sys->input_type);
>> +    if (p_sys->output_sample)
>> +        IMFSample_Release(p_sys->output_sample);
>> +    if (p_sys->output_type)
>> +        IMFMediaType_Release(p_sys->output_type);
>> +    if (p_sys->mft)
>> +        IMFTransform_Release(p_sys->mft);
>> +
>> +    p_sys->event_generator = NULL;
>> +    p_sys->input_type = NULL;
>> +    p_sys->output_sample = NULL;
>> +    p_sys->output_type = NULL;
>> +    p_sys->mft = NULL;
>> +}
>> +
>> +static int FindMFT(decoder_t *p_dec)
>> +{
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    MFHandle *mf = &p_sys->mf_handle;
>> +    HRESULT hr;
>> +
>> +    /*
>> +     * Try to initialize a MFT decoder using the list of extra MFT
>> +     * (MFTs that are not available through MFTEnumEx)
>> +     */
>> +    for (int i = 0; extra_mft_table[i].fourcc; ++i)
>> +    {
>> +        if (extra_mft_table[i].fourcc == p_dec->fmt_in.i_codec)
>> +        {
>> +            const GUID *guid = extra_mft_table[i].guid;
>> +            hr = CoCreateInstance(guid, NULL, CLSCTX_INPROC, &IID_IMFTransform, (void**)&p_sys->mft);
>> +            if (FAILED(hr))
>> +                continue;
>> +            if (InitializeMFT(p_dec) == VLC_SUCCESS)
>> +                return VLC_SUCCESS;
>> +
>> +            IMFTransform_Release(p_sys->mft);
>> +            p_sys->mft = NULL;
>> +        }
>> +    }
>> +
>> +    /* Try to create a MFT using MFTEnumEx. */
>> +    GUID category = MFT_CATEGORY_VIDEO_DECODER;
>> +    UINT32 flags = MFT_ENUM_FLAG_SORTANDFILTER | MFT_ENUM_FLAG_LOCALMFT
>> +                 | MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT
>> +                 | MFT_ENUM_FLAG_HARDWARE;
>> +    GUID major_type = MFMediaType_Video;
>> +    const GUID *subtype = FormatToGUID(video_format_table, p_dec->fmt_in.i_codec);
>> +    if (!subtype)
>> +    {
>> +        msg_Err(p_dec, "FOURCC not supported.");
>> +        return VLC_EGENERIC;
>> +    }
>> +    MFT_REGISTER_TYPE_INFO input_type = { major_type, *subtype };
>> +    IMFActivate **activate_objects = NULL;
>> +    UINT32 activate_objects_count = 0;
>> +    hr = mf->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)", activate_objects_count);
>> +    if (activate_objects_count == 0)
>> +        return VLC_EGENERIC;
>> +
>> +    for (UINT32 i = 0; i < activate_objects_count; ++i)
>> +    {
>> +        hr = IMFActivate_ActivateObject(activate_objects[i], &IID_IMFTransform, (void**)&p_sys->mft);
>> +        IMFActivate_Release(activate_objects[i]);
>> +        if (FAILED(hr))
>> +            continue;
>> +
>> +        if (InitializeMFT(p_dec) == VLC_SUCCESS)
>> +        {
>> +            CoTaskMemFree(activate_objects);
>> +            return VLC_SUCCESS;
>> +        }
>> +    }
>> +    CoTaskMemFree(activate_objects);
>> +
>> +    return VLC_EGENERIC;
>> +}
>> +
>> +static int LoadMFTLibrary(MFHandle *mf)
>> +{
>> +    mf->mfplat_dll = LoadLibrary(TEXT("mfplat.dll"));
>> +    if (!mf->mfplat_dll)
>> +        return VLC_EGENERIC;
>> +
>> +    mf->MFTEnumEx = (void*)GetProcAddress(mf->mfplat_dll, "MFTEnumEx");
>> +    mf->MFCreateMediaType = (void*)GetProcAddress(mf->mfplat_dll, "MFCreateMediaType");
>> +    mf->MFCreateSample = (void*)GetProcAddress(mf->mfplat_dll, "MFCreateSample");
>> +    mf->MFCreateMemoryBuffer = (void*)GetProcAddress(mf->mfplat_dll, "MFCreateMemoryBuffer");
>> +    mf->MFCreateAlignedMemoryBuffer = (void*)GetProcAddress(mf->mfplat_dll, "MFCreateAlignedMemoryBuffer");
>> +    if (!mf->MFTEnumEx || !mf->MFCreateMediaType || !mf->MFCreateSample
>> +        || !mf->MFCreateMemoryBuffer || !mf->MFCreateAlignedMemoryBuffer)
>> +        return VLC_EGENERIC;
>> +
>> +    return VLC_SUCCESS;
>> +}
>> +
>> +int Open(vlc_object_t *p_this)
>> +{
>> +    decoder_t *p_dec = (decoder_t *)p_this;
>> +    decoder_sys_t *p_sys;
>> +
>> +    /* Audio is not yet supported. */
>> +    if (p_dec->fmt_in.i_cat != VIDEO_ES)
>> +        return VLC_EGENERIC;
>> +
>> +    p_sys = p_dec->p_sys = calloc(1, sizeof(*p_sys));
>> +    if (!p_sys)
>> +        return VLC_ENOMEM;
>> +
>> +    if (LoadMFTLibrary(&p_sys->mf_handle))
>> +    {
>> +        msg_Err(p_dec, "Failed to load MFT library.");
>> +        goto error;
>> +    }
>> +
>> +    if (FindMFT(p_dec))
>> +    {
>> +        msg_Err(p_dec, "Could not find suitable MFT decoder");
>> +        goto error;
>> +    }
>> +
>> +    /* Only one output sample is needed, we can allocate one and reuse it. */
>> +    if (AllocateOutputSample(p_dec, 0, &p_sys->output_sample))
>> +    {
>> +        msg_Err(p_dec, "Failed to allocated output sample.");
>> +        goto error;
>> +    }
>> +
>> +    if (p_sys->is_async)
>> +        p_dec->pf_decode_video = DecodeAsync;
>> +    else
>> +        p_dec->pf_decode_video = DecodeSync;
>> +
>> +    p_dec->fmt_out.i_cat = p_dec->fmt_in.i_cat;
>> +    p_dec->fmt_out.video = p_dec->fmt_in.video;
>> +    p_dec->fmt_out.audio = p_dec->fmt_in.audio;
>> +    p_dec->b_need_packetized = true;
>> +
>> +    return VLC_SUCCESS;
>> +
>> +error:
>> +    Close(p_this);
>> +    return VLC_EGENERIC;
>> +}
>> +
>> +void Close(vlc_object_t *p_this)
>> +{
>> +    decoder_t *p_dec = (decoder_t *)p_this;
>> +    decoder_sys_t *p_sys = p_dec->p_sys;
>> +    MFHandle *mf = &p_sys->mf_handle;
>> +
>> +    DestroyMFT(p_dec);
>> +
>> +    if (mf->mfplat_dll)
>> +        FreeLibrary(mf->mfplat_dll);
>> +
>> +    free(p_sys);
>> +}
>> --
>> 1.7.10.4
>>
>> _______________________________________________
>> vlc-devel mailing list
>> To unsubscribe or modify your subscription options:
>> https://mailman.videolan.org/listinfo/vlc-devel
>
> Regards,
> --
> Denis Charmet - TypX
> Le mauvais esprit est un art de vivre
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel

Thanks for the review!

-- 
Félix Abecassis
http://felix.abecassis.me



More information about the vlc-devel mailing list