/*
 * Intel MediaSDK QSV encoder utility functions
 *
 * copyright (c) 2013 Yukinori Yamazoe
 * copyright (c) 2015 Anton Khirnov
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg 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.
 *
 * FFmpeg 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 FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <string.h>
#include <sys/types.h>
#include <mfx/mfxvideo.h>

#include "libavutil/common.h"
#include "libavutil/mem.h"
#include "libavutil/log.h"
#include "libavutil/time.h"
#include "libavutil/imgutils.h"
#include "libavcodec/bytestream.h"

#include "avcodec.h"
#include "internal.h"
#include "qsv.h"
#include "qsv_internal.h"
#include "qsvenc.h"

static const struct {
    mfxU16 profile;
    const char *name;
} profile_names[] = {
    { MFX_PROFILE_AVC_BASELINE,                 "baseline"              },
    { MFX_PROFILE_AVC_MAIN,                     "main"                  },
    { MFX_PROFILE_AVC_EXTENDED,                 "extended"              },
    { MFX_PROFILE_AVC_HIGH,                     "high"                  },
#if QSV_VERSION_ATLEAST(1, 15)
    { MFX_PROFILE_AVC_HIGH_422,                 "high 422"              },
#endif
#if QSV_VERSION_ATLEAST(1, 4)
    { MFX_PROFILE_AVC_CONSTRAINED_BASELINE,     "constrained baseline"  },
    { MFX_PROFILE_AVC_CONSTRAINED_HIGH,         "constrained high"      },
    { MFX_PROFILE_AVC_PROGRESSIVE_HIGH,         "progressive high"      },
#endif
    { MFX_PROFILE_MPEG2_SIMPLE,                 "simple"                },
    { MFX_PROFILE_MPEG2_MAIN,                   "main"                  },
    { MFX_PROFILE_MPEG2_HIGH,                   "high"                  },
    { MFX_PROFILE_VC1_SIMPLE,                   "simple"                },
    { MFX_PROFILE_VC1_MAIN,                     "main"                  },
    { MFX_PROFILE_VC1_ADVANCED,                 "advanced"              },
#if QSV_VERSION_ATLEAST(1, 8)
    { MFX_PROFILE_HEVC_MAIN,                    "main"                  },
    { MFX_PROFILE_HEVC_MAIN10,                  "main10"                },
    { MFX_PROFILE_HEVC_MAINSP,                  "mainsp"                },
#endif
};

static const char *print_profile(mfxU16 profile)
{
    int i;
    for (i = 0; i < FF_ARRAY_ELEMS(profile_names); i++)
        if (profile == profile_names[i].profile)
            return profile_names[i].name;
    return "unknown";
}

static const struct {
    mfxU16      rc_mode;
    const char *name;
} rc_names[] = {
    { MFX_RATECONTROL_CBR,     "CBR" },
    { MFX_RATECONTROL_VBR,     "VBR" },
    { MFX_RATECONTROL_CQP,     "CQP" },
    { MFX_RATECONTROL_AVBR,    "AVBR" },
#if QSV_HAVE_LA
    { MFX_RATECONTROL_LA,      "LA" },
#endif
#if QSV_HAVE_ICQ
    { MFX_RATECONTROL_ICQ,     "ICQ" },
    { MFX_RATECONTROL_LA_ICQ,  "LA_ICQ" },
#endif
#if QSV_HAVE_VCM
    { MFX_RATECONTROL_VCM,     "VCM" },
#endif
#if QSV_VERSION_ATLEAST(1, 10)
    { MFX_RATECONTROL_LA_EXT,  "LA_EXT" },
#endif
#if QSV_HAVE_LA_HRD
    { MFX_RATECONTROL_LA_HRD,  "LA_HRD" },
#endif
#if QSV_HAVE_QVBR
    { MFX_RATECONTROL_QVBR,    "QVBR" },
#endif
};

static const char *print_ratecontrol(mfxU16 rc_mode)
{
    int i;
    for (i = 0; i < FF_ARRAY_ELEMS(rc_names); i++)
        if (rc_mode == rc_names[i].rc_mode)
            return rc_names[i].name;
    return "unknown";
}

static const char *print_threestate(mfxU16 val)
{
    if (val == MFX_CODINGOPTION_ON)
        return "ON";
    else if (val == MFX_CODINGOPTION_OFF)
        return "OFF";
    return "unknown";
}

static void dump_video_param(AVCodecContext *avctx, QSVEncContext *q,
                             mfxExtBuffer **coding_opts)
{
    mfxInfoMFX *info = &q->param.mfx;

    mfxExtCodingOption   *co = (mfxExtCodingOption*)coding_opts[0];
#if QSV_HAVE_CO2
    mfxExtCodingOption2 *co2 = (mfxExtCodingOption2*)coding_opts[1];
#endif
#if QSV_HAVE_CO3
    mfxExtCodingOption3 *co3 = (mfxExtCodingOption3*)coding_opts[2];
#endif

    av_log(avctx, AV_LOG_VERBOSE, "profile: %s; level: %"PRIu16"\n",
           print_profile(info->CodecProfile), info->CodecLevel);

    av_log(avctx, AV_LOG_VERBOSE, "GopPicSize: %"PRIu16"; GopRefDist: %"PRIu16"; GopOptFlag: ",
           info->GopPicSize, info->GopRefDist);
    if (info->GopOptFlag & MFX_GOP_CLOSED)
        av_log(avctx, AV_LOG_VERBOSE, "closed ");
    if (info->GopOptFlag & MFX_GOP_STRICT)
        av_log(avctx, AV_LOG_VERBOSE, "strict ");
    av_log(avctx, AV_LOG_VERBOSE, "; IdrInterval: %"PRIu16"\n", info->IdrInterval);

    av_log(avctx, AV_LOG_VERBOSE, "TargetUsage: %"PRIu16"; RateControlMethod: %s\n",
           info->TargetUsage, print_ratecontrol(info->RateControlMethod));

    if (info->RateControlMethod == MFX_RATECONTROL_CBR ||
        info->RateControlMethod == MFX_RATECONTROL_VBR
#if QSV_HAVE_VCM
        || info->RateControlMethod == MFX_RATECONTROL_VCM
#endif
        ) {
        av_log(avctx, AV_LOG_VERBOSE,
               "InitialDelayInKB: %"PRIu16"; TargetKbps: %"PRIu16"; MaxKbps: %"PRIu16"\n",
               info->InitialDelayInKB, info->TargetKbps, info->MaxKbps);
    } else if (info->RateControlMethod == MFX_RATECONTROL_CQP) {
        av_log(avctx, AV_LOG_VERBOSE, "QPI: %"PRIu16"; QPP: %"PRIu16"; QPB: %"PRIu16"\n",
               info->QPI, info->QPP, info->QPB);
    } else if (info->RateControlMethod == MFX_RATECONTROL_AVBR) {
        av_log(avctx, AV_LOG_VERBOSE,
               "TargetKbps: %"PRIu16"; Accuracy: %"PRIu16"; Convergence: %"PRIu16"\n",
               info->TargetKbps, info->Accuracy, info->Convergence);
    }
#if QSV_HAVE_LA
    else if (info->RateControlMethod == MFX_RATECONTROL_LA
#if QSV_HAVE_LA_HRD
             || info->RateControlMethod == MFX_RATECONTROL_LA_HRD
#endif
             ) {
        av_log(avctx, AV_LOG_VERBOSE,
               "TargetKbps: %"PRIu16"; LookAheadDepth: %"PRIu16"\n",
               info->TargetKbps, co2->LookAheadDepth);
    }
#endif
#if QSV_HAVE_ICQ
    else if (info->RateControlMethod == MFX_RATECONTROL_ICQ) {
        av_log(avctx, AV_LOG_VERBOSE, "ICQQuality: %"PRIu16"\n", info->ICQQuality);
    } else if (info->RateControlMethod == MFX_RATECONTROL_LA_ICQ) {
        av_log(avctx, AV_LOG_VERBOSE, "ICQQuality: %"PRIu16"; LookAheadDepth: %"PRIu16"\n",
               info->ICQQuality, co2->LookAheadDepth);
    }
#endif
#if QSV_HAVE_QVBR
    else if (info->RateControlMethod == MFX_RATECONTROL_QVBR) {
        av_log(avctx, AV_LOG_VERBOSE, "QVBRQuality: %"PRIu16"\n",
               co3->QVBRQuality);
    }
#endif

    av_log(avctx, AV_LOG_VERBOSE, "NumSlice: %"PRIu16"; NumRefFrame: %"PRIu16"\n",
           info->NumSlice, info->NumRefFrame);
    av_log(avctx, AV_LOG_VERBOSE, "RateDistortionOpt: %s\n",
           print_threestate(co->RateDistortionOpt));

#if QSV_HAVE_CO2
    av_log(avctx, AV_LOG_VERBOSE,
           "RecoveryPointSEI: %s IntRefType: %"PRIu16"; IntRefCycleSize: %"PRIu16"; IntRefQPDelta: %"PRId16"\n",
           print_threestate(co->RecoveryPointSEI), co2->IntRefType, co2->IntRefCycleSize, co2->IntRefQPDelta);

    av_log(avctx, AV_LOG_VERBOSE, "MaxFrameSize: %"PRIu16"; ", co2->MaxFrameSize);
#if QSV_HAVE_MAX_SLICE_SIZE
    av_log(avctx, AV_LOG_VERBOSE, "MaxSliceSize: %"PRIu16"; ", co2->MaxSliceSize);
#endif
    av_log(avctx, AV_LOG_VERBOSE, "\n");

    av_log(avctx, AV_LOG_VERBOSE,
           "BitrateLimit: %s; MBBRC: %s; ExtBRC: %s\n",
           print_threestate(co2->BitrateLimit), print_threestate(co2->MBBRC),
           print_threestate(co2->ExtBRC));

#if QSV_HAVE_TRELLIS
    av_log(avctx, AV_LOG_VERBOSE, "Trellis: ");
    if (co2->Trellis & MFX_TRELLIS_OFF) {
        av_log(avctx, AV_LOG_VERBOSE, "off");
    } else if (!co2->Trellis) {
        av_log(avctx, AV_LOG_VERBOSE, "auto");
    } else {
        if (co2->Trellis & MFX_TRELLIS_I) av_log(avctx, AV_LOG_VERBOSE, "I");
        if (co2->Trellis & MFX_TRELLIS_P) av_log(avctx, AV_LOG_VERBOSE, "P");
        if (co2->Trellis & MFX_TRELLIS_B) av_log(avctx, AV_LOG_VERBOSE, "B");
    }
    av_log(avctx, AV_LOG_VERBOSE, "\n");
#endif

#if QSV_VERSION_ATLEAST(1, 8)
    av_log(avctx, AV_LOG_VERBOSE,
           "RepeatPPS: %s; NumMbPerSlice: %"PRIu16"; LookAheadDS: ",
           print_threestate(co2->RepeatPPS), co2->NumMbPerSlice);
    switch (co2->LookAheadDS) {
    case MFX_LOOKAHEAD_DS_OFF: av_log(avctx, AV_LOG_VERBOSE, "off");     break;
    case MFX_LOOKAHEAD_DS_2x:  av_log(avctx, AV_LOG_VERBOSE, "2x");      break;
    case MFX_LOOKAHEAD_DS_4x:  av_log(avctx, AV_LOG_VERBOSE, "4x");      break;
    default:                   av_log(avctx, AV_LOG_VERBOSE, "unknown"); break;
    }
    av_log(avctx, AV_LOG_VERBOSE, "\n");

    av_log(avctx, AV_LOG_VERBOSE, "AdaptiveI: %s; AdaptiveB: %s; BRefType: ",
           print_threestate(co2->AdaptiveI), print_threestate(co2->AdaptiveB));
    switch (co2->BRefType) {
    case MFX_B_REF_OFF:     av_log(avctx, AV_LOG_VERBOSE, "off");       break;
    case MFX_B_REF_PYRAMID: av_log(avctx, AV_LOG_VERBOSE, "pyramid");   break;
    default:                av_log(avctx, AV_LOG_VERBOSE, "auto");      break;
    }
    av_log(avctx, AV_LOG_VERBOSE, "\n");
#endif

#if QSV_VERSION_ATLEAST(1, 9)
    av_log(avctx, AV_LOG_VERBOSE,
           "MinQPI: %"PRIu8"; MaxQPI: %"PRIu8"; MinQPP: %"PRIu8"; MaxQPP: %"PRIu8"; MinQPB: %"PRIu8"; MaxQPB: %"PRIu8"\n",
           co2->MinQPI, co2->MaxQPI, co2->MinQPP, co2->MaxQPP, co2->MinQPB, co2->MaxQPB);
#endif
#endif

    if (avctx->codec_id == AV_CODEC_ID_H264) {
        av_log(avctx, AV_LOG_VERBOSE, "Entropy coding: %s; MaxDecFrameBuffering: %"PRIu16"\n",
               co->CAVLC == MFX_CODINGOPTION_ON ? "CAVLC" : "CABAC", co->MaxDecFrameBuffering);
        av_log(avctx, AV_LOG_VERBOSE,
               "NalHrdConformance: %s; SingleSeiNalUnit: %s; VuiVclHrdParameters: %s VuiNalHrdParameters: %s\n",
               print_threestate(co->NalHrdConformance), print_threestate(co->SingleSeiNalUnit),
               print_threestate(co->VuiVclHrdParameters), print_threestate(co->VuiNalHrdParameters));
    }
}

static int select_rc_mode(AVCodecContext *avctx, QSVEncContext *q)
{
    const char *rc_desc;
    mfxU16      rc_mode;

    int want_la     = q->look_ahead;
    int want_qscale = !!(avctx->flags & AV_CODEC_FLAG_QSCALE);
    int want_vcm    = q->vcm;

    if (want_la && !QSV_HAVE_LA) {
        av_log(avctx, AV_LOG_ERROR,
               "Lookahead ratecontrol mode requested, but is not supported by this SDK version\n");
        return AVERROR(ENOSYS);
    }
    if (want_vcm && !QSV_HAVE_VCM) {
        av_log(avctx, AV_LOG_ERROR,
               "VCM ratecontrol mode requested, but is not supported by this SDK version\n");
        return AVERROR(ENOSYS);
    }

    if (want_la + want_qscale + want_vcm > 1) {
        av_log(avctx, AV_LOG_ERROR,
               "More than one of: { constant qscale, lookahead, VCM } requested, "
               "only one of them can be used at a time.\n");
        return AVERROR(EINVAL);
    }

    if (want_qscale) {
        rc_mode = MFX_RATECONTROL_CQP;
        rc_desc = "constant quantization parameter (CQP)";
    }
#if QSV_HAVE_VCM
    else if (want_vcm) {
        rc_mode = MFX_RATECONTROL_VCM;
        rc_desc = "video conferencing mode (VCM)";
    }
#endif
#if QSV_HAVE_LA
    else if (want_la) {
        rc_mode = MFX_RATECONTROL_LA;
        rc_desc = "VBR with lookahead (LA)";

#if QSV_HAVE_ICQ
        if (avctx->global_quality > 0) {
            rc_mode = MFX_RATECONTROL_LA_ICQ;
            rc_desc = "intelligent constant quality with lookahead (LA_ICQ)";
        }
#endif
    }
#endif
#if QSV_HAVE_ICQ
    else if (avctx->global_quality > 0) {
        rc_mode = MFX_RATECONTROL_ICQ;
        rc_desc = "intelligent constant quality (ICQ)";
    }
#endif
    else if (avctx->rc_max_rate == avctx->bit_rate) {
        rc_mode = MFX_RATECONTROL_CBR;
        rc_desc = "constant bitrate (CBR)";
    } else if (!avctx->rc_max_rate) {
        rc_mode = MFX_RATECONTROL_AVBR;
        rc_desc = "average variable bitrate (AVBR)";
    } else {
        rc_mode = MFX_RATECONTROL_VBR;
        rc_desc = "variable bitrate (VBR)";
    }

    q->param.mfx.RateControlMethod = rc_mode;
    av_log(avctx, AV_LOG_VERBOSE, "Using the %s ratecontrol method\n", rc_desc);

    return 0;
}

static int rc_supported(QSVEncContext *q)
{
    mfxVideoParam param_out = { .mfx.CodecId = q->param.mfx.CodecId };
    mfxStatus ret;

    ret = MFXVideoENCODE_Query(q->session, &q->param, &param_out);
    if (ret < 0 ||
        param_out.mfx.RateControlMethod != q->param.mfx.RateControlMethod)
        return 0;
    return 1;
}

static int init_video_param(AVCodecContext *avctx, QSVEncContext *q)
{
    float quant;
    int ret;

    ret = ff_qsv_codec_id_to_mfx(avctx->codec_id);
    if (ret < 0)
        return AVERROR_BUG;
    q->param.mfx.CodecId = ret;

    q->width_align = avctx->codec_id == AV_CODEC_ID_HEVC ? 32 : 16;

    if (avctx->level > 0)
        q->param.mfx.CodecLevel = avctx->level;

    q->param.mfx.CodecProfile       = q->profile;
    q->param.mfx.TargetUsage        = q->preset;
    q->param.mfx.GopPicSize         = FFMAX(0, avctx->gop_size);
    q->param.mfx.GopRefDist         = FFMAX(-1, avctx->max_b_frames) + 1;
    q->param.mfx.GopOptFlag         = avctx->flags & AV_CODEC_FLAG_CLOSED_GOP ?
                                      MFX_GOP_CLOSED : 0;
    q->param.mfx.IdrInterval        = q->idr_interval;
    q->param.mfx.NumSlice           = avctx->slices;
    q->param.mfx.NumRefFrame        = FFMAX(0, avctx->refs);
    q->param.mfx.EncodedOrder       = 0;
    q->param.mfx.BufferSizeInKB     = 0;

    q->param.mfx.FrameInfo.FourCC         = MFX_FOURCC_NV12;
    q->param.mfx.FrameInfo.CropX          = 0;
    q->param.mfx.FrameInfo.CropY          = 0;
    q->param.mfx.FrameInfo.CropW          = avctx->width;
    q->param.mfx.FrameInfo.CropH          = avctx->height;
    q->param.mfx.FrameInfo.AspectRatioW   = avctx->sample_aspect_ratio.num;
    q->param.mfx.FrameInfo.AspectRatioH   = avctx->sample_aspect_ratio.den;
    q->param.mfx.FrameInfo.ChromaFormat   = MFX_CHROMAFORMAT_YUV420;
    q->param.mfx.FrameInfo.BitDepthLuma   = 8;
    q->param.mfx.FrameInfo.BitDepthChroma = 8;
    q->param.mfx.FrameInfo.Width          = FFALIGN(avctx->width, q->width_align);

    if (avctx->flags & AV_CODEC_FLAG_INTERLACED_DCT) {
       /* A true field layout (TFF or BFF) is not important here,
          it will specified later during frame encoding. But it is important
          to specify is frame progressive or not because allowed heigh alignment
          does depend by this.
        */
        q->param.mfx.FrameInfo.PicStruct = MFX_PICSTRUCT_FIELD_TFF;
        q->height_align = 32;
    } else {
        q->param.mfx.FrameInfo.PicStruct = MFX_PICSTRUCT_PROGRESSIVE;
        q->height_align = 16;
    }
   q->param.mfx.FrameInfo.Height    = FFALIGN(avctx->height, q->height_align);

    if (avctx->framerate.den > 0 && avctx->framerate.num > 0) {
        q->param.mfx.FrameInfo.FrameRateExtN = avctx->framerate.num;
        q->param.mfx.FrameInfo.FrameRateExtD = avctx->framerate.den;
    } else {
        q->param.mfx.FrameInfo.FrameRateExtN  = avctx->time_base.den;
        q->param.mfx.FrameInfo.FrameRateExtD  = avctx->time_base.num;
    }

    ret = select_rc_mode(avctx, q);
    if (ret < 0)
        return ret;

    switch (q->param.mfx.RateControlMethod) {
    case MFX_RATECONTROL_CBR:
    case MFX_RATECONTROL_VBR:
#if QSV_HAVE_VCM
    case MFX_RATECONTROL_VCM:
#endif
        q->param.mfx.InitialDelayInKB = avctx->rc_initial_buffer_occupancy / 1000;
        q->param.mfx.TargetKbps       = avctx->bit_rate / 1000;
        q->param.mfx.MaxKbps          = avctx->rc_max_rate / 1000;
        break;
    case MFX_RATECONTROL_CQP:
        quant = avctx->global_quality / FF_QP2LAMBDA;

        q->param.mfx.QPI = av_clip(quant * fabs(avctx->i_quant_factor) + avctx->i_quant_offset, 0, 51);
        q->param.mfx.QPP = av_clip(quant, 0, 51);
        q->param.mfx.QPB = av_clip(quant * fabs(avctx->b_quant_factor) + avctx->b_quant_offset, 0, 51);

        break;
    case MFX_RATECONTROL_AVBR:
        q->param.mfx.TargetKbps  = avctx->bit_rate / 1000;
        q->param.mfx.Convergence = q->avbr_convergence;
        q->param.mfx.Accuracy    = q->avbr_accuracy;
        break;
#if QSV_HAVE_LA
    case MFX_RATECONTROL_LA:
        q->param.mfx.TargetKbps  = avctx->bit_rate / 1000;
        q->extco2.LookAheadDepth = q->look_ahead_depth;
        break;
#if QSV_HAVE_ICQ
    case MFX_RATECONTROL_LA_ICQ:
        q->extco2.LookAheadDepth = q->look_ahead_depth;
    case MFX_RATECONTROL_ICQ:
        q->param.mfx.ICQQuality  = avctx->global_quality;
        break;
#endif
#endif
    }

    // the HEVC encoder plugin currently fails if coding options
    // are provided
    if (avctx->codec_id != AV_CODEC_ID_HEVC) {
        q->extco.Header.BufferId      = MFX_EXTBUFF_CODING_OPTION;
        q->extco.Header.BufferSz      = sizeof(q->extco);
        q->extco.CAVLC                = avctx->coder_type == FF_CODER_TYPE_VLC ?
                                        MFX_CODINGOPTION_ON : MFX_CODINGOPTION_UNKNOWN;

        q->extco.PicTimingSEI         = q->pic_timing_sei ?
                                        MFX_CODINGOPTION_ON : MFX_CODINGOPTION_UNKNOWN;

        if (q->rdo >= 0)
            q->extco.RateDistortionOpt = q->rdo > 0 ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;

        if (avctx->codec_id == AV_CODEC_ID_H264) {
            if (avctx->strict_std_compliance != FF_COMPLIANCE_NORMAL)
                q->extco.NalHrdConformance = avctx->strict_std_compliance > FF_COMPLIANCE_NORMAL ?
                                             MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;

            if (q->single_sei_nal_unit >= 0)
                q->extco.SingleSeiNalUnit = q->single_sei_nal_unit ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;
            if (q->recovery_point_sei >= 0)
                q->extco.RecoveryPointSEI = q->recovery_point_sei ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;
            q->extco.MaxDecFrameBuffering = q->max_dec_frame_buffering;
        }

        q->extparam_internal[q->nb_extparam_internal++] = (mfxExtBuffer *)&q->extco;

#if QSV_HAVE_CO2
        if (avctx->codec_id == AV_CODEC_ID_H264) {
            q->extco2.Header.BufferId     = MFX_EXTBUFF_CODING_OPTION2;
            q->extco2.Header.BufferSz     = sizeof(q->extco2);

            if (q->int_ref_type >= 0)
                q->extco2.IntRefType = q->int_ref_type;
            if (q->int_ref_cycle_size >= 0)
                q->extco2.IntRefCycleSize = q->int_ref_cycle_size;
            if (q->int_ref_qp_delta != INT16_MIN)
                q->extco2.IntRefQPDelta = q->int_ref_qp_delta;

            if (q->bitrate_limit >= 0)
                q->extco2.BitrateLimit = q->bitrate_limit ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;
            if (q->mbbrc >= 0)
                q->extco2.MBBRC = q->mbbrc ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;
            if (q->extbrc >= 0)
                q->extco2.ExtBRC = q->extbrc ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;

            if (q->max_frame_size >= 0)
                q->extco2.MaxFrameSize = q->max_frame_size;
#if QSV_HAVE_MAX_SLICE_SIZE
            if (q->max_slice_size >= 0)
                q->extco2.MaxSliceSize = q->max_slice_size;
#endif

#if QSV_HAVE_TRELLIS
            q->extco2.Trellis = q->trellis;
#endif

#if QSV_HAVE_BREF_TYPE
#if FF_API_PRIVATE_OPT
FF_DISABLE_DEPRECATION_WARNINGS
            if (avctx->b_frame_strategy >= 0)
                q->b_strategy = avctx->b_frame_strategy;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
            if (q->b_strategy >= 0)
                q->extco2.BRefType = q->b_strategy ? MFX_B_REF_PYRAMID : MFX_B_REF_OFF;
            if (q->adaptive_i >= 0)
                q->extco2.AdaptiveI = q->adaptive_i ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;
            if (q->adaptive_b >= 0)
                q->extco2.AdaptiveB = q->adaptive_b ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF;
#endif

            q->extparam_internal[q->nb_extparam_internal++] = (mfxExtBuffer *)&q->extco2;

#if QSV_VERSION_ATLEAST(1,8)
            q->extco2.LookAheadDS           = q->look_ahead_downsampling;
#endif
        }
#endif
    }

    if (!rc_supported(q)) {
        av_log(avctx, AV_LOG_ERROR,
               "Selected ratecontrol mode is not supported by the QSV "
               "runtime. Choose a different mode.\n");
        return AVERROR(ENOSYS);
    }

    return 0;
}

static int qsv_retrieve_enc_params(AVCodecContext *avctx, QSVEncContext *q)
{
    AVCPBProperties *cpb_props;

    uint8_t sps_buf[128];
    uint8_t pps_buf[128];

    mfxExtCodingOptionSPSPPS extradata = {
        .Header.BufferId = MFX_EXTBUFF_CODING_OPTION_SPSPPS,
        .Header.BufferSz = sizeof(extradata),
        .SPSBuffer = sps_buf, .SPSBufSize = sizeof(sps_buf),
        .PPSBuffer = pps_buf, .PPSBufSize = sizeof(pps_buf)
    };

    mfxExtCodingOption co = {
        .Header.BufferId = MFX_EXTBUFF_CODING_OPTION,
        .Header.BufferSz = sizeof(co),
    };
#if QSV_HAVE_CO2
    mfxExtCodingOption2 co2 = {
        .Header.BufferId = MFX_EXTBUFF_CODING_OPTION2,
        .Header.BufferSz = sizeof(co2),
    };
#endif
#if QSV_HAVE_CO3
    mfxExtCodingOption3 co3 = {
        .Header.BufferId = MFX_EXTBUFF_CODING_OPTION3,
        .Header.BufferSz = sizeof(co3),
    };
#endif

    mfxExtBuffer *ext_buffers[] = {
        (mfxExtBuffer*)&extradata,
        (mfxExtBuffer*)&co,
#if QSV_HAVE_CO2
        (mfxExtBuffer*)&co2,
#endif
#if QSV_HAVE_CO3
        (mfxExtBuffer*)&co3,
#endif
    };

    int need_pps = avctx->codec_id != AV_CODEC_ID_MPEG2VIDEO;
    int ret;

    q->param.ExtParam    = ext_buffers;
    q->param.NumExtParam = FF_ARRAY_ELEMS(ext_buffers);

    ret = MFXVideoENCODE_GetVideoParam(q->session, &q->param);
    if (ret < 0)
        return ff_qsv_error(ret);

    q->packet_size = q->param.mfx.BufferSizeInKB * 1000;

    if (!extradata.SPSBufSize || (need_pps && !extradata.PPSBufSize)) {
        av_log(avctx, AV_LOG_ERROR, "No extradata returned from libmfx.\n");
        return AVERROR_UNKNOWN;
    }

    avctx->extradata = av_malloc(extradata.SPSBufSize + need_pps * extradata.PPSBufSize +
                                 AV_INPUT_BUFFER_PADDING_SIZE);
    if (!avctx->extradata)
        return AVERROR(ENOMEM);

    memcpy(avctx->extradata,                        sps_buf, extradata.SPSBufSize);
    if (need_pps)
        memcpy(avctx->extradata + extradata.SPSBufSize, pps_buf, extradata.PPSBufSize);
    avctx->extradata_size = extradata.SPSBufSize + need_pps * extradata.PPSBufSize;
    memset(avctx->extradata + avctx->extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    cpb_props = ff_add_cpb_side_data(avctx);
    if (!cpb_props)
        return AVERROR(ENOMEM);
    cpb_props->max_bitrate = avctx->rc_max_rate;
    cpb_props->min_bitrate = avctx->rc_min_rate;
    cpb_props->avg_bitrate = avctx->bit_rate;
    cpb_props->buffer_size = avctx->rc_buffer_size;

    dump_video_param(avctx, q, ext_buffers + 1);

    return 0;
}

static int qsv_init_opaque_alloc(AVCodecContext *avctx, QSVEncContext *q)
{
    AVQSVContext *qsv = avctx->hwaccel_context;
    mfxFrameSurface1 *surfaces;
    int nb_surfaces, i;

    nb_surfaces = qsv->nb_opaque_surfaces + q->req.NumFrameSuggested + q->async_depth;

    q->opaque_alloc_buf = av_buffer_allocz(sizeof(*surfaces) * nb_surfaces);
    if (!q->opaque_alloc_buf)
        return AVERROR(ENOMEM);

    q->opaque_surfaces = av_malloc_array(nb_surfaces, sizeof(*q->opaque_surfaces));
    if (!q->opaque_surfaces)
        return AVERROR(ENOMEM);

    surfaces = (mfxFrameSurface1*)q->opaque_alloc_buf->data;
    for (i = 0; i < nb_surfaces; i++) {
        surfaces[i].Info      = q->req.Info;
        q->opaque_surfaces[i] = surfaces + i;
    }

    q->opaque_alloc.Header.BufferId = MFX_EXTBUFF_OPAQUE_SURFACE_ALLOCATION;
    q->opaque_alloc.Header.BufferSz = sizeof(q->opaque_alloc);
    q->opaque_alloc.In.Surfaces     = q->opaque_surfaces;
    q->opaque_alloc.In.NumSurface   = nb_surfaces;
    q->opaque_alloc.In.Type         = q->req.Type;

    q->extparam_internal[q->nb_extparam_internal++] = (mfxExtBuffer *)&q->opaque_alloc;

    qsv->nb_opaque_surfaces = nb_surfaces;
    qsv->opaque_surfaces    = q->opaque_alloc_buf;
    qsv->opaque_alloc_type  = q->req.Type;

    return 0;
}

int ff_qsv_enc_init(AVCodecContext *avctx, QSVEncContext *q)
{
    int opaque_alloc = 0;
    int ret;

    q->param.IOPattern  = MFX_IOPATTERN_IN_SYSTEM_MEMORY;
    q->param.AsyncDepth = q->async_depth;

    q->async_fifo = av_fifo_alloc((1 + q->async_depth) *
                                  (sizeof(AVPacket) + sizeof(mfxSyncPoint) + sizeof(mfxBitstream*)));
    if (!q->async_fifo)
        return AVERROR(ENOMEM);

    if (avctx->hwaccel_context) {
        AVQSVContext *qsv = avctx->hwaccel_context;

        q->session         = qsv->session;
        q->param.IOPattern = qsv->iopattern;

        opaque_alloc = qsv->opaque_alloc;
    }

    if (!q->session) {
        ret = ff_qsv_init_internal_session(avctx, &q->internal_qs,
                                           q->load_plugins);
        if (ret < 0)
            return ret;

        q->session = q->internal_qs.session;
    }

    ret = init_video_param(avctx, q);
    if (ret < 0)
        return ret;

    ret = MFXVideoENCODE_Query(q->session, &q->param,&q->param);
    if (MFX_WRN_PARTIAL_ACCELERATION==ret) {
        av_log(avctx, AV_LOG_WARNING, "Encoder will work with partial HW acceleration\n");
    } else if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Error %d querying encoder params\n", ret);
        return ff_qsv_error(ret);
    }

    ret = MFXVideoENCODE_QueryIOSurf(q->session, &q->param, &q->req);
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Error querying the encoding parameters\n");
        return ff_qsv_error(ret);
    }

    if (opaque_alloc) {
        ret = qsv_init_opaque_alloc(avctx, q);
        if (ret < 0)
            return ret;
    }

    if (avctx->hwaccel_context) {
        AVQSVContext *qsv = avctx->hwaccel_context;
        int i, j;

        q->extparam = av_mallocz_array(qsv->nb_ext_buffers + q->nb_extparam_internal,
                                       sizeof(*q->extparam));
        if (!q->extparam)
            return AVERROR(ENOMEM);

        q->param.ExtParam = q->extparam;
        for (i = 0; i < qsv->nb_ext_buffers; i++)
            q->param.ExtParam[i] = qsv->ext_buffers[i];
        q->param.NumExtParam = qsv->nb_ext_buffers;

        for (i = 0; i < q->nb_extparam_internal; i++) {
            for (j = 0; j < qsv->nb_ext_buffers; j++) {
                if (qsv->ext_buffers[j]->BufferId == q->extparam_internal[i]->BufferId)
                    break;
            }
            if (j < qsv->nb_ext_buffers)
                continue;

            q->param.ExtParam[q->param.NumExtParam++] = q->extparam_internal[i];
        }
    } else {
        q->param.ExtParam    = q->extparam_internal;
        q->param.NumExtParam = q->nb_extparam_internal;
    }

    ret = MFXVideoENCODE_Init(q->session, &q->param);
    if (MFX_WRN_PARTIAL_ACCELERATION==ret) {
        av_log(avctx, AV_LOG_WARNING, "Encoder will work with partial HW acceleration\n");
    } else if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Error initializing the encoder\n");
        return ff_qsv_error(ret);
    }

    ret = qsv_retrieve_enc_params(avctx, q);
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Error retrieving encoding parameters.\n");
        return ret;
    }

    q->avctx = avctx;

    return 0;
}

static void free_encoder_ctrl_payloads(mfxEncodeCtrl* enc_ctrl)
{
    if (enc_ctrl) {
        int i;
        for (i = 0; i < enc_ctrl->NumPayload && i < QSV_MAX_ENC_PAYLOAD; i++) {
            mfxPayload* pay = enc_ctrl->Payload[i];
            av_free(enc_ctrl->Payload[i]->Data);
            av_free(pay);
        }
        enc_ctrl->NumPayload = 0;
    }
}

static void clear_unused_frames(QSVEncContext *q)
{
    QSVFrame *cur = q->work_frames;
    while (cur) {
        if (cur->surface && !cur->surface->Data.Locked) {
            cur->surface = NULL;
            free_encoder_ctrl_payloads(&cur->enc_ctrl);
            av_frame_unref(cur->frame);
        }
        cur = cur->next;
    }
}

static int get_free_frame(QSVEncContext *q, QSVFrame **f)
{
    QSVFrame *frame, **last;

    clear_unused_frames(q);

    frame = q->work_frames;
    last  = &q->work_frames;
    while (frame) {
        if (!frame->surface) {
            *f = frame;
            return 0;
        }

        last  = &frame->next;
        frame = frame->next;
    }

    frame = av_mallocz(sizeof(*frame));
    if (!frame)
        return AVERROR(ENOMEM);
    frame->frame = av_frame_alloc();
    if (!frame->frame) {
        av_freep(&frame);
        return AVERROR(ENOMEM);
    }
    frame->enc_ctrl.Payload = av_mallocz(sizeof(mfxPayload*) * QSV_MAX_ENC_PAYLOAD);
    if (!frame->enc_ctrl.Payload) {
        av_freep(&frame);
        return AVERROR(ENOMEM);
    }
    *last = frame;

    *f = frame;

    return 0;
}

static int submit_frame(QSVEncContext *q, const AVFrame *frame,
                        QSVFrame **new_frame)
{
    QSVFrame *qf;
    int ret;

    ret = get_free_frame(q, &qf);
    if (ret < 0)
        return ret;

    if (frame->format == AV_PIX_FMT_QSV) {
        ret = av_frame_ref(qf->frame, frame);
        if (ret < 0)
            return ret;

        qf->surface = (mfxFrameSurface1*)qf->frame->data[3];
    } else {
        /* make a copy if the input is not padded as libmfx requires */
        if (     frame->height & (q->height_align - 1) ||
            frame->linesize[0] & (q->width_align - 1)) {
            qf->frame->height = FFALIGN(frame->height, q->height_align);
            qf->frame->width  = FFALIGN(frame->width, q->width_align);

            ret = ff_get_buffer(q->avctx, qf->frame, AV_GET_BUFFER_FLAG_REF);
            if (ret < 0)
                return ret;

            qf->frame->height = frame->height;
            qf->frame->width  = frame->width;
            ret = av_frame_copy(qf->frame, frame);
            if (ret < 0) {
                av_frame_unref(qf->frame);
                return ret;
            }
        } else {
            ret = av_frame_ref(qf->frame, frame);
            if (ret < 0)
                return ret;
        }

        qf->surface_internal.Info = q->param.mfx.FrameInfo;

        qf->surface_internal.Info.PicStruct =
            !frame->interlaced_frame ? MFX_PICSTRUCT_PROGRESSIVE :
            frame->top_field_first   ? MFX_PICSTRUCT_FIELD_TFF :
                                       MFX_PICSTRUCT_FIELD_BFF;
        if (frame->repeat_pict == 1)
            qf->surface_internal.Info.PicStruct |= MFX_PICSTRUCT_FIELD_REPEATED;
        else if (frame->repeat_pict == 2)
            qf->surface_internal.Info.PicStruct |= MFX_PICSTRUCT_FRAME_DOUBLING;
        else if (frame->repeat_pict == 4)
            qf->surface_internal.Info.PicStruct |= MFX_PICSTRUCT_FRAME_TRIPLING;

        qf->surface_internal.Data.PitchLow  = qf->frame->linesize[0];
        qf->surface_internal.Data.Y         = qf->frame->data[0];
        qf->surface_internal.Data.UV        = qf->frame->data[1];

        qf->surface = &qf->surface_internal;
    }

    qf->surface->Data.TimeStamp = av_rescale_q(frame->pts, q->avctx->time_base, (AVRational){1, 90000});

    *new_frame = qf;

    return 0;
}

static void print_interlace_msg(AVCodecContext *avctx, QSVEncContext *q)
{
    if (q->param.mfx.CodecId == MFX_CODEC_AVC) {
        if (q->param.mfx.CodecProfile == MFX_PROFILE_AVC_BASELINE ||
            q->param.mfx.CodecLevel < MFX_LEVEL_AVC_21 ||
            q->param.mfx.CodecLevel > MFX_LEVEL_AVC_41)
            av_log(avctx, AV_LOG_WARNING,
                   "Interlaced coding is supported"
                   " at Main/High Profile Level 2.1-4.1\n");
    }
}

static int encode_frame(AVCodecContext *avctx, QSVEncContext *q,
                        const AVFrame *frame)
{
    AVPacket new_pkt = { 0 };
    mfxBitstream *bs;

    mfxFrameSurface1 *surf = NULL;
    mfxSyncPoint sync      = NULL;
    QSVFrame *qsv_frame = NULL;
    mfxEncodeCtrl* enc_ctrl = NULL;
    int ret;

    if (frame) {
        ret = submit_frame(q, frame, &qsv_frame);
        if (ret < 0) {
            av_log(avctx, AV_LOG_ERROR, "Error submitting the frame for encoding.\n");
            return ret;
        }
    }
    if (qsv_frame) {
        surf = qsv_frame->surface;
        enc_ctrl = &qsv_frame->enc_ctrl;
    }

    ret = av_new_packet(&new_pkt, q->packet_size);
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Error allocating the output packet\n");
        return ret;
    }

    bs = av_mallocz(sizeof(*bs));
    if (!bs) {
        av_packet_unref(&new_pkt);
        return AVERROR(ENOMEM);
    }
    bs->Data      = new_pkt.data;
    bs->MaxLength = new_pkt.size;

    if (q->set_encode_ctrl_cb) {
        q->set_encode_ctrl_cb(avctx, frame, &qsv_frame->enc_ctrl);
    }

    do {
        ret = MFXVideoENCODE_EncodeFrameAsync(q->session, enc_ctrl, surf, bs, &sync);
        if (ret == MFX_WRN_DEVICE_BUSY) {
            av_usleep(500);
            continue;
        }
        break;
    } while ( 1 );

    if (ret < 0) {
        av_packet_unref(&new_pkt);
        av_freep(&bs);
        if (ret == MFX_ERR_MORE_DATA)
            return 0;
        av_log(avctx, AV_LOG_ERROR, "EncodeFrameAsync returned %d\n", ret);
        return ff_qsv_error(ret);
    }

    if (ret == MFX_WRN_INCOMPATIBLE_VIDEO_PARAM) {
        if (frame->interlaced_frame)
            print_interlace_msg(avctx, q);
        else
            av_log(avctx, AV_LOG_WARNING,
                   "EncodeFrameAsync returned 'incompatible param' code\n");
    }
    if (sync) {
        av_fifo_generic_write(q->async_fifo, &new_pkt, sizeof(new_pkt), NULL);
        av_fifo_generic_write(q->async_fifo, &sync,    sizeof(sync),    NULL);
        av_fifo_generic_write(q->async_fifo, &bs,      sizeof(bs),    NULL);
    } else {
        av_packet_unref(&new_pkt);
        av_freep(&bs);
    }

    return 0;
}

int ff_qsv_encode(AVCodecContext *avctx, QSVEncContext *q,
                  AVPacket *pkt, const AVFrame *frame, int *got_packet)
{
    int ret;

    ret = encode_frame(avctx, q, frame);
    if (ret < 0)
        return ret;

    if (!av_fifo_space(q->async_fifo) ||
        (!frame && av_fifo_size(q->async_fifo))) {
        AVPacket new_pkt;
        mfxBitstream *bs;
        mfxSyncPoint sync;

        av_fifo_generic_read(q->async_fifo, &new_pkt, sizeof(new_pkt), NULL);
        av_fifo_generic_read(q->async_fifo, &sync,    sizeof(sync),    NULL);
        av_fifo_generic_read(q->async_fifo, &bs,      sizeof(bs),      NULL);

        do {
            ret = MFXVideoCORE_SyncOperation(q->session, sync, 1000);
        } while (ret == MFX_WRN_IN_EXECUTION);

        new_pkt.dts  = av_rescale_q(bs->DecodeTimeStamp, (AVRational){1, 90000}, avctx->time_base);
        new_pkt.pts  = av_rescale_q(bs->TimeStamp,       (AVRational){1, 90000}, avctx->time_base);
        new_pkt.size = bs->DataLength;

        if (bs->FrameType & MFX_FRAMETYPE_IDR ||
            bs->FrameType & MFX_FRAMETYPE_xIDR)
            new_pkt.flags |= AV_PKT_FLAG_KEY;

#if FF_API_CODED_FRAME
FF_DISABLE_DEPRECATION_WARNINGS
        if (bs->FrameType & MFX_FRAMETYPE_I || bs->FrameType & MFX_FRAMETYPE_xI)
            avctx->coded_frame->pict_type = AV_PICTURE_TYPE_I;
        else if (bs->FrameType & MFX_FRAMETYPE_P || bs->FrameType & MFX_FRAMETYPE_xP)
            avctx->coded_frame->pict_type = AV_PICTURE_TYPE_P;
        else if (bs->FrameType & MFX_FRAMETYPE_B || bs->FrameType & MFX_FRAMETYPE_xB)
            avctx->coded_frame->pict_type = AV_PICTURE_TYPE_B;
FF_ENABLE_DEPRECATION_WARNINGS
#endif

        av_freep(&bs);

        if (pkt->data) {
            if (pkt->size < new_pkt.size) {
                av_log(avctx, AV_LOG_ERROR, "Submitted buffer not large enough: %d < %d\n",
                       pkt->size, new_pkt.size);
                av_packet_unref(&new_pkt);
                return AVERROR(EINVAL);
            }

            memcpy(pkt->data, new_pkt.data, new_pkt.size);
            pkt->size = new_pkt.size;

            ret = av_packet_copy_props(pkt, &new_pkt);
            av_packet_unref(&new_pkt);
            if (ret < 0)
                return ret;
        } else
            *pkt = new_pkt;

        *got_packet = 1;
    }

    return 0;
}

int ff_qsv_enc_close(AVCodecContext *avctx, QSVEncContext *q)
{
    QSVFrame *cur;

    if (q->session)
        MFXVideoENCODE_Close(q->session);
    q->session = NULL;

    ff_qsv_close_internal_session(&q->internal_qs);

    cur = q->work_frames;
    while (cur) {
        q->work_frames = cur->next;
        av_frame_free(&cur->frame);
        av_free(cur->enc_ctrl.Payload);
        av_freep(&cur);
        cur = q->work_frames;
    }

    while (q->async_fifo && av_fifo_size(q->async_fifo)) {
        AVPacket pkt;
        mfxSyncPoint sync;
        mfxBitstream *bs;

        av_fifo_generic_read(q->async_fifo, &pkt,  sizeof(pkt),  NULL);
        av_fifo_generic_read(q->async_fifo, &sync, sizeof(sync), NULL);
        av_fifo_generic_read(q->async_fifo, &bs,   sizeof(bs),   NULL);

        av_freep(&bs);
        av_packet_unref(&pkt);
    }
    av_fifo_free(q->async_fifo);
    q->async_fifo = NULL;

    av_freep(&q->opaque_surfaces);
    av_buffer_unref(&q->opaque_alloc_buf);

    av_freep(&q->extparam);

    return 0;
}
