[vlc-devel] [PATCH] Intel Video QuickSync encoder support

Rafaël Carré funman at videolan.org
Fri Jun 7 17:06:14 CEST 2013


Hello,

Le 05/06/2013 15:29, Julien 'Lta' BALLET a écrit :

> +static int Open(vlc_object_t *this)
> +{
> +    encoder_t *enc = (encoder_t *)this;
> +    encoder_sys_t *sys = 0;

I prefer NULL.

> +
> +    mfxStatus sts = MFX_ERR_NONE;
> +    mfxFrameAllocRequest alloc_request;
> +    mfxExtCodingOptionSPSPPS headers;
> +    mfxExtBuffer *extended_params[1] = {(mfxExtBuffer *)&headers};
> +
> +    uint8_t *p_extra;
> +    size_t i_extra;
> +    uint8_t nals[128];
> +
> +    if (enc->fmt_out.i_codec != VLC_CODEC_H264 &&
> +        enc->fmt_out.i_codec != VLC_CODEC_MPGV && !enc->b_force)
> +        return VLC_EGENERIC;
> +
> +    if (!enc->fmt_in.video.i_height || !enc->fmt_in.video.i_width ||
> +        !enc->fmt_in.video.i_frame_rate || !enc->fmt_in.video.i_frame_rate_base) {
> +        msg_Err(enc, "Framerate and picture dimensions must be non-zero");
> +        return VLC_EGENERIC;
> +    }
> +
> +    /* Allocate the memory needed to store the decoder's structure */
> +    sys = calloc(1, sizeof(encoder_sys_t));
> +    if (unlikely(!sys))
> +        return VLC_ENOMEM;
> +
> +    /* Initialize dispatcher, it will loads the actual SW/HW Implementation */
> +    sts = MFXInit(MFX_IMPL_AUTO, 0, &sys->session);
> +
> +    if (sts != MFX_ERR_NONE) {
> +        msg_Err(enc, "Unable to find an Intel Media SDK implementation.");
> +        free(sys);
> +        return VLC_EGENERIC;
> +    }
> +
> +    config_ChainParse(enc, SOUT_CFG_PREFIX, sout_options, enc->p_cfg);
> +
> +    /* Checking if we are on software and are allowing it */
> +    MFXQueryIMPL(sys->session, &sys->impl);
> +    if (!var_InheritBool(enc, SOUT_CFG_PREFIX "software") && (sys->impl & MFX_IMPL_SOFTWARE)) {
> +        msg_Err(enc, "No hardware implementation found and software mode disabled");
> +        free(sys);
> +        return VLC_EGENERIC;
> +    }
> +
> +    if (sys->impl & MFX_IMPL_HARDWARE)
> +        msg_Dbg(enc, "Using Intel QuickSync Video hardware implementation");
> +    else
> +        msg_Dbg(enc, "Using Intel QuickSync Video software implementation");

nitpick: those 2 can be merged by using a %s and (sys->impl &
MFX_IMPL_HARDWARE) ? "hard" : "soft"

> +    /* Vlc module configuration */
> +    enc->p_sys                         = sys;
> +    enc->fmt_in.i_codec                = VLC_CODEC_NV12; // Intel Media SDK requirement
> +    enc->fmt_in.video.i_bits_per_pixel = 12;
> +
> +    /* Input picture format description */
> +    sys->params.mfx.FrameInfo.FrameRateExtN = enc->fmt_in.video.i_frame_rate;
> +    sys->params.mfx.FrameInfo.FrameRateExtD = enc->fmt_in.video.i_frame_rate_base;
> +    sys->params.mfx.FrameInfo.FourCC        = MFX_FOURCC_NV12;
> +    sys->params.mfx.FrameInfo.ChromaFormat  = MFX_CHROMAFORMAT_YUV420;
> +    sys->params.mfx.FrameInfo.Width         = QSV_ALIGN(16, enc->fmt_in.video.i_width);
> +    sys->params.mfx.FrameInfo.Height        = QSV_ALIGN(32, enc->fmt_in.video.i_height);
> +    sys->params.mfx.FrameInfo.CropW         = enc->fmt_in.video.i_width;
> +    sys->params.mfx.FrameInfo.CropH         = enc->fmt_in.video.i_height;
> +    sys->params.mfx.FrameInfo.PicStruct     = MFX_PICSTRUCT_UNKNOWN;
> +
> +    /* Parsing options common to all RC methods and codecs */
> +    sys->params.IOPattern       = MFX_IOPATTERN_IN_SYSTEM_MEMORY;
> +    sys->params.AsyncDepth      = var_InheritInteger(enc, SOUT_CFG_PREFIX "async-depth");
> +    sys->params.mfx.GopPicSize  = var_InheritInteger(enc, SOUT_CFG_PREFIX "gop-size");
> +    sys->params.mfx.GopRefDist  = var_InheritInteger(enc, SOUT_CFG_PREFIX "gop-refdist");
> +    sys->params.mfx.IdrInterval = var_InheritInteger(enc, SOUT_CFG_PREFIX "idr-interval");
> +    sys->params.mfx.NumSlice    = var_InheritInteger(enc, SOUT_CFG_PREFIX "num-slice");
> +    sys->params.mfx.NumRefFrame = var_InheritInteger(enc, SOUT_CFG_PREFIX "num-ref-frame");
> +    sys->params.mfx.TargetUsage = qsv_params_get_value(target_usage_text,
> +        target_usage_list, sizeof(target_usage_list),
> +        var_InheritString(enc, SOUT_CFG_PREFIX "target-usage"));
> +
> +    if (enc->fmt_out.i_codec == VLC_CODEC_H264) {
> +        sys->params.mfx.CodecId = MFX_CODEC_AVC;
> +        sys->params.mfx.CodecProfile = qsv_params_get_value(profile_h264_text,
> +            profile_h264_list, sizeof(profile_h264_list),
> +            var_InheritString(enc, SOUT_CFG_PREFIX "h264-profile"));
> +        sys->params.mfx.CodecLevel = qsv_params_get_value(level_h264_text,
> +            level_h264_list, sizeof(level_h264_list),
> +            var_InheritString(enc, SOUT_CFG_PREFIX "h264-level"));

var_InheritString return value must be freed, and you should check if it
returns NULL

> +        msg_Dbg(enc, "Encoder in H264 mode, with profile %d and level %d",
> +            sys->params.mfx.CodecProfile, sys->params.mfx.CodecLevel);
> +
> +    } else {
> +        sys->params.mfx.CodecId = MFX_CODEC_MPEG2;
> +        sys->params.mfx.CodecProfile = qsv_params_get_value(profile_mpeg2_text,
> +            profile_mpeg2_list, sizeof(profile_mpeg2_list),
> +            var_InheritString(enc, SOUT_CFG_PREFIX "mpeg2-profile"));
> +        sys->params.mfx.CodecLevel = qsv_params_get_value(level_mpeg2_text,
> +            level_mpeg2_list, sizeof(level_mpeg2_list),
> +            var_InheritString(enc, SOUT_CFG_PREFIX "mpeg2-level"));
> +        msg_Dbg(enc, "Encoder in MPEG2 mode, with profile %d and level %d",
> +            sys->params.mfx.CodecProfile, sys->params.mfx.CodecLevel);
> +    }
> +
> +    sys->params.mfx.RateControlMethod = qsv_params_get_value(rc_method_text,
> +        rc_method_list, sizeof(rc_method_list),
> +        var_InheritString(enc, SOUT_CFG_PREFIX "rc-method"));
> +    msg_Dbg(enc, "Encoder using '%s' Rate Control method",
> +        var_InheritString(enc, SOUT_CFG_PREFIX "rc-method"));
> +
> +    if (sys->params.mfx.RateControlMethod == MFX_RATECONTROL_CQP) {
> +        sys->params.mfx.QPI = sys->params.mfx.QPB = sys->params.mfx.QPP =
> +            var_InheritInteger(enc, SOUT_CFG_PREFIX "qp");
> +        sys->params.mfx.QPI = var_InheritInteger(enc, SOUT_CFG_PREFIX "qpi");
> +        sys->params.mfx.QPB = var_InheritInteger(enc, SOUT_CFG_PREFIX "qpb");
> +        sys->params.mfx.QPP = var_InheritInteger(enc, SOUT_CFG_PREFIX "qpp");
> +    } else {
> +        if (!enc->fmt_out.i_bitrate) {
> +            msg_Warn(enc, "No bitrate specified, using default %d",
> +                QSV_BITRATE_DEFAULT);
> +            sys->params.mfx.TargetKbps = QSV_BITRATE_DEFAULT;
> +        } else
> +            sys->params.mfx.TargetKbps = enc->fmt_out.i_bitrate / 1000;
> +
> +        if (sys->params.mfx.RateControlMethod == MFX_RATECONTROL_AVBR) {
> +            sys->params.mfx.Accuracy = var_InheritInteger(enc, SOUT_CFG_PREFIX "accuracy");
> +            sys->params.mfx.Convergence = var_InheritInteger(enc, SOUT_CFG_PREFIX "convergence");
> +        } else if (sys->params.mfx.RateControlMethod == MFX_RATECONTROL_VBR)
> +            sys->params.mfx.MaxKbps = var_InheritInteger(enc, SOUT_CFG_PREFIX "bitrate-max");
> +    }
> +
> +    /* Initializing MFX_Encoder */
> +    sts = MFXVideoENCODE_Init(sys->session, &sys->params);
> +    if (sts == MFX_ERR_NONE)
> +        msg_Dbg(enc, "Successfuly initialized video encoder");
> +    else if (sts < MFX_ERR_NONE) {
> +        msg_Err(enc, "Unable to initialize video encoder error (%d). " \
> +            " Most likely because of provided encoding parameters", sts);
> +        goto error;
> +    } else
> +        msg_Warn(enc, "Video encoder initialization : %d. The stream might be corrupted/invalid", sts);
> +
> +    /* Querying PPS/SPS Headers, BufferSizeInKB, ... */
> +    memset(&headers, 0, sizeof(headers));
> +    memset(&nals, 0, sizeof(nals));
> +    headers.Header.BufferId = MFX_EXTBUFF_CODING_OPTION_SPSPPS;
> +    headers.Header.BufferSz = sizeof(headers);
> +    headers.SPSBufSize      = headers.PPSBufSize = 64;
> +    headers.SPSBuffer       = nals;
> +    headers.PPSBuffer       = nals + 64;
> +    sys->params.ExtParam    = (mfxExtBuffer **)&extended_params;
> +    sys->params.NumExtParam = 1;
> +
> +    MFXVideoENCODE_GetVideoParam(sys->session, &sys->params);
> +    sys->params.NumExtParam = 0;
> +    sys->params.ExtParam = 0;

NULL

> +
> +    i_extra = headers.SPSBufSize + headers.PPSBufSize;
> +    p_extra = malloc(i_extra);
> +
> +    if (unlikely(!p_extra))
> +        goto nomem;
> +
> +    memcpy(p_extra, headers.SPSBuffer, headers.SPSBufSize);
> +    memcpy(p_extra + headers.SPSBufSize, headers.PPSBuffer, headers.PPSBufSize);
> +    enc->fmt_out.p_extra = p_extra;
> +    enc->fmt_out.i_extra = i_extra;
> +
> +    sys->async_depth = sys->params.AsyncDepth;
> +    sys->tasks = calloc(sys->async_depth, sizeof(async_task_t));
> +    if (unlikely(!sys->tasks))
> +        goto nomem;
> +
> +    /* Request number of surface needed and creating frame pool */
> +    if (MFXVideoENCODE_QueryIOSurf(sys->session, &sys->params, &alloc_request)!= MFX_ERR_NONE)
> +        goto error;
> +    if (qsv_frame_pool_Init(&sys->frames, &alloc_request, sys->async_depth) != VLC_SUCCESS)
> +        goto nomem;
> +    msg_Dbg(enc, "Requested %d surfaces for work", alloc_request.NumFrameSuggested);
> +
> +    enc->pf_encode_video = Encode;
> +
> +    return VLC_SUCCESS;
> +
> + error:
> +    Close(this);
> +    return VLC_EGENERIC;
> + nomem:
> +    Close(this);
> +    return VLC_ENOMEM;
> +}
> +
> +static void Close(vlc_object_t *this)
> +{
> +    encoder_t *enc = (encoder_t *)this;
> +    encoder_sys_t *sys = enc->p_sys;
> +
> +    MFXVideoENCODE_Close(sys->session);
> +    MFXClose(sys->session);
> +    if (enc->fmt_out.p_extra) {
> +        free(enc->fmt_out.p_extra);
> +        enc->fmt_out.p_extra = 0;
> +        enc->fmt_out.i_extra = 0;

those 2 lines are not needed.

> +    }
> +    if (sys->frames.size)
> +        qsv_frame_pool_Destroy(&sys->frames);
> +    if (sys->tasks)
> +        free(sys->tasks);
> +    free(sys);
> +}
> +
> +static void qsv_set_block_flags(block_t *block, uint16_t frame_type)
> +{
> +    if ((frame_type & MFX_FRAMETYPE_IDR) || (frame_type & MFX_FRAMETYPE_REF))
> +        block->i_flags = BLOCK_FLAG_TYPE_I;
> +    else if ((frame_type & MFX_FRAMETYPE_P) || (frame_type & MFX_FRAMETYPE_I))

Hmm what is MFX_FRAMETYPE_I ?
It looks weird that it represents a P-frame

> +        block->i_flags = BLOCK_FLAG_TYPE_P;
> +    else if (frame_type & MFX_FRAMETYPE_B)
> +        block->i_flags = BLOCK_FLAG_TYPE_B;
> +    else
> +        block->i_flags = BLOCK_FLAG_TYPE_PB;
> +}
> +
> +/*
> + * Convert the Intel Media SDK's timestamps into VLC's format.
> + */
> +static void qsv_set_block_ts(encoder_t *enc, encoder_sys_t *sys, block_t *block, mfxBitstream *bs)
> +{
> +    if (!bs->TimeStamp)
> +        return;
> +
> +    block->i_pts = qsv_timestamp_to_mtime(bs->TimeStamp) + sys->offset_pts;
> +    block->i_dts = qsv_timestamp_to_mtime(bs->DecodeTimeStamp) + sys->offset_pts;
> +
> +    /* HW encoder (with old driver versions) and some parameters
> +       combinations doesn't set the DecodeTimeStamp field so we warn
> +       the user about it */
> +    if (!bs->DecodeTimeStamp || bs->DecodeTimeStamp > bs->TimeStamp)
> +        msg_Warn(enc, "Encode returning empty DTS or DTS > PTS. Your stream will be invalid. "
> +                 " Please double-check they weren't any warning at encoder initialization "
> +                 " and that you have the last version of Intel's drivers installed.");
> +}
> +
> +
> +/*
> + * The Encode function has 3 encoding phases :
> + *   - Feed : We feed the encoder until it stops to return MFX_MORE_DATA_NEEDED
> + *     and the async_tasks are all in use (honouring the AsyncDepth)
> + *   - Main encoding phase : synchronizing the oldest task each call.
> + *   - Empty : pic = 0, we empty the decoder. Synchronizing the remaining tasks.
> + */
> +static block_t *Encode(encoder_t *this, picture_t *pic)
> +{
> +    encoder_t *enc = (encoder_t *)this;
> +    encoder_sys_t *sys = enc->p_sys;
> +    async_task_t *task = NULL;
> +    block_t *block = NULL;
> +
> +    mfxFrameSurface1 *frame = NULL;
> +    mfxStatus sts;
> +
> +
> +
> +    if (pic) {
> +        /* To avoid qsv -> vlc timestamp conversion overflow, we use timestamp relative
> +           to the first picture received. That way, vlc will overflow before us.
> +           (Thanks to funman for the idea) */
> +        if (!sys->offset_pts) // First frame
> +            sys->offset_pts = pic->date;
> +        pic->date -= sys->offset_pts;
> +
> +        frame = qsv_frame_pool_Get(&sys->frames, pic);
> +        if (!frame) {
> +            msg_Warn(enc, "Unable to find an unlocked surface in the pool");
> +            return NULL;
> +        }
> +
> +        /* Finds an available SyncPoint */
> +        for (size_t i = 0; i < sys->async_depth; i++)
> +            if ((sys->tasks + (i + sys->first_task) % sys->async_depth)->syncp == 0) {
> +                task = sys->tasks + (i + sys->first_task) % sys->async_depth;
> +                break;
> +            }
> +    } else
> +        /* If !pic, we are emptying encoder and tasks, so we force the SyncOperation */
> +        msg_Dbg(enc, "Emptying encoder");
> +
> +
> +
> +    /* There is no available task, we need to synchronize */
> +    if (!task) {
> +        task = sys->tasks + sys->first_task;
> +
> +        /* Synchronize and fill block_t. If the SyncOperation fails we leak :-/ (or we can segfault, ur choice) */
> +        if (MFXVideoCORE_SyncOperation(sys->session, task->syncp, QSV_SYNCPOINT_WAIT) == MFX_ERR_NONE) {
> +            block = task->block;
> +            block->i_buffer = task->bs.DataLength;
> +            block->p_buffer += task->bs.DataOffset;
> +
> +            qsv_set_block_ts(enc, sys, block, &task->bs);
> +            qsv_set_block_flags(block, task->bs.FrameType);
> +
> +            /* msg_Dbg(enc, "block->i_pts = %lld, block->i_dts = %lld", block->i_pts, block->i_dts); */
> +            /* msg_Dbg(enc, "FrameType = %#.4x, TimeStamp (pts) = %lld, DecodeTimeStamp = %lld", */
> +            /*         task->bs.FrameType, task->bs.TimeStamp, task->bs.DecodeTimeStamp); */
> +
> +            /* Copied from x264.c: This isn't really valid for streams with B-frames */
> +            block->i_length = CLOCK_FREQ *
> +                enc->fmt_in.video.i_frame_rate_base /
> +                enc->fmt_in.video.i_frame_rate;
> +
> +            // Buggy DTS (value comes from experiments)
> +            if (task->bs.DecodeTimeStamp < -10000)
> +                block->i_dts = sys->last_dts + block->i_length;
> +            sys->last_dts = block->i_dts;
> +
> +
> +        } else // Only happens on buggy drivers
> +            msg_Err(enc, "SyncOperation failed, outputting garbage data. "
> +                    "Updating your drivers and/or changing the encoding settings might resolve this");
> +
> +        /* Reset the task now it has been synchronized and advances first_task pointer */
> +        task->syncp = 0;
> +        sys->first_task = (sys->first_task + 1) % sys->async_depth;
> +    }
> +
> +
> +
> +    /* Allocate block_t and prepare mfxBitstream for encoder */
> +    if (!(task->block = block_Alloc(sys->params.mfx.BufferSizeInKB * 1000))) {
> +        msg_Err(enc, "Unable to allocate block for encoder output");
> +        return NULL;
> +    }
> +    memset(&task->bs, 0, sizeof(task->bs));
> +    task->bs.MaxLength = sys->params.mfx.BufferSizeInKB * 1000;
> +    task->bs.Data = task->block->p_buffer;
> +
> +    sts = MFXVideoENCODE_EncodeFrameAsync(sys->session, 0, frame, &task->bs, &task->syncp);
> +    while (sts == MFX_WRN_DEVICE_BUSY) {
> +        msg_Info(enc, "Device is busy, let's wait and retry");

BTW, if this message is very frequent it could be rate-limited: smth
like: if (counter++ % 16 == 0) msg_Dbg...

> +        msleep(QSV_BUSYWAIT_TIME);
> +        sts = MFXVideoENCODE_EncodeFrameAsync(sys->session, 0, frame, &task->bs, &task->syncp);
> +    }
> +
> +    // msg_Dbg(enc, "Encode async status: %d, Syncpoint = %tx", sts, (ptrdiff_t)task->syncp);
> +
> +    if (sts == MFX_ERR_MORE_DATA)
> +        if (pic)
> +            msg_Dbg(enc, "Encoder feeding phase, more data is needed.");
> +        else {
> +            msg_Dbg(enc, "Encoder is empty");
> +            return NULL;
> +        }
> +    else if (sts < MFX_ERR_NONE) {
> +        msg_Err(enc, "Encoder not ready or error (%d), trying a reset...", sts);
> +        MFXVideoENCODE_Reset(sys->session, &sys->params);

Shouldn't it return NULL here?

> +    }
> +
> +    return block;
> +}
> +
> -- 1.8.1.2
> 
> 
> 
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> http://mailman.videolan.org/listinfo/vlc-devel
> 




More information about the vlc-devel mailing list