/* qsv_common.c
 *
 * Copyright (c) 2003-2026 HandBrake Team
 * This file is part of the HandBrake source code.
 * Homepage: <http://handbrake.fr/>.
 * It may be used under the terms of the GNU General Public License v2.
 * For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
 */

#include "handbrake/handbrake.h"
#include "handbrake/project.h"

#if HB_PROJECT_FEATURE_QSV

#include <stdio.h>
#include <string.h>

#include "vpl/mfxvideo.h"
#include "vpl/mfxdispatcher.h"

#include "handbrake/ports.h"
#include "handbrake/common.h"
#include "handbrake/hwaccel.h"
#include "handbrake/hb_dict.h"
#include "handbrake/qsv_common.h"
#include "handbrake/h264_common.h"
#include "handbrake/h265_common.h"
#include "handbrake/av1_common.h"
#include "handbrake/hbffmpeg.h"

#ifndef HB_QSV_PRINT_RET_MSG
#define HB_QSV_PRINT_RET_MSG(ERR)              { fprintf(stderr, "Error code %d,\t%s\t%d\n", ERR, __FUNCTION__, __LINE__); }
#endif

#ifndef HB_QSV_DEBUG_ASSERT
#define HB_QSV_DEBUG_ASSERT(x,y)               { if ((x)) { fprintf(stderr, "\nASSERT: %s\n", y); } }
#endif

#define HB_QSV_CHECK_RET(P, X, ERR)                {if ((X) > (P)) {HB_QSV_PRINT_RET_MSG(ERR); return;}}
#define HB_QSV_CHECK_RESULT(P, X, ERR)             {if ((X) > (P)) {HB_QSV_PRINT_RET_MSG(ERR); return ERR;}}
#define HB_QSV_CHECK_POINTER(P, ERR)               {if (!(P)) {HB_QSV_PRINT_RET_MSG(ERR); return ERR;}}
#define HB_QSV_IGNORE_MFX_STS(P, X)                {if ((X) == (P)) {P = MFX_ERR_NONE;}}

#define HB_QSV_ASYNC_DEPTH_DEFAULT     4

#define HB_QSV_AVC_DECODER_WIDTH_MAX   4096
#define HB_QSV_AVC_DECODER_HEIGHT_MAX  4096

typedef struct hb_qsv_adapter_details
{
    // DirectX index
    int index;
    mfxPlatform platform;
    char *impl_name;
    char *impl_path;
    // QSV info for each codec
    hb_qsv_info_t *hb_qsv_info_avc;
    hb_qsv_info_t *hb_qsv_info_hevc;
    hb_qsv_info_t *hb_qsv_info_av1;
    // API versions
    mfxVersion qsv_software_version;
    mfxVersion qsv_hardware_version;
    // AVC implementations
    hb_qsv_info_t qsv_software_info_avc;
    hb_qsv_info_t qsv_hardware_info_avc;
    // HEVC implementations
    hb_qsv_info_t qsv_software_info_hevc;
    hb_qsv_info_t qsv_hardware_info_hevc;
    // AV1 implementations
    hb_qsv_info_t qsv_hardware_info_av1;
    // Extended device information
    mfxExtendedDeviceId extended_device_id;
} hb_qsv_adapter_details_t;

static hb_list_t *g_qsv_adapters_list         = NULL;
static hb_list_t *g_qsv_adapters_details_list = NULL;
static int g_adapter_index = 0;
static int g_default_adapter_index = 0;
static int qsv_init_done = 0;
static int qsv_init_result = 0;

static void init_adapter_details(hb_qsv_adapter_details_t *adapter_details)
{
    adapter_details->index                                 = 0;
    adapter_details->platform.CodeName                     = MFX_PLATFORM_UNKNOWN;
    adapter_details->platform.MediaAdapterType             = MFX_MEDIA_UNKNOWN;
    // QSV info for each codec
    adapter_details->hb_qsv_info_avc                       = NULL;
    adapter_details->hb_qsv_info_hevc                      = NULL;
    adapter_details->hb_qsv_info_av1                       = NULL;
    // API versions
    adapter_details->qsv_software_version.Version          = 0;
    adapter_details->qsv_hardware_version.Version          = 0;
    // AVC implementations
    adapter_details->qsv_software_info_avc.available       = 0;
    adapter_details->qsv_software_info_avc.codec_id        = MFX_CODEC_AVC;
    adapter_details->qsv_software_info_avc.implementation  = MFX_IMPL_SOFTWARE;

    adapter_details->qsv_hardware_info_avc.available       = 0;
    adapter_details->qsv_hardware_info_avc.codec_id        = MFX_CODEC_AVC;
    adapter_details->qsv_hardware_info_avc.implementation  = MFX_IMPL_HARDWARE_ANY|MFX_IMPL_VIA_ANY;
    // HEVC implementations
    adapter_details->qsv_software_info_hevc.available      = 0;
    adapter_details->qsv_software_info_hevc.codec_id       = MFX_CODEC_HEVC;
    adapter_details->qsv_software_info_hevc.implementation = MFX_IMPL_SOFTWARE;

    adapter_details->qsv_hardware_info_hevc.available      = 0;
    adapter_details->qsv_hardware_info_hevc.codec_id       = MFX_CODEC_HEVC;
    adapter_details->qsv_hardware_info_hevc.implementation = MFX_IMPL_HARDWARE_ANY|MFX_IMPL_VIA_ANY;

    // AV1 implementations
    adapter_details->qsv_hardware_info_av1.available      = 0;
    adapter_details->qsv_hardware_info_av1.codec_id       = MFX_CODEC_AV1;
    adapter_details->qsv_hardware_info_av1.implementation = MFX_IMPL_HARDWARE_ANY|MFX_IMPL_VIA_ANY;
}

// QSV info about adapters
static const char* hb_qsv_get_adapter_type(const hb_qsv_adapter_details_t *details);
static int hb_qsv_make_adapters_list(hb_list_t **qsv_adapters_list, hb_list_t **qsv_adapters_details_list);
static int hb_qsv_collect_adapters_details(hb_list_t *hb_qsv_adapter_details_list);

// QSV-supported profile and level lists (not all exposed to the user)
static hb_triplet_t hb_qsv_h264_profiles[] =
{
    { "Baseline",             "baseline",       MFX_PROFILE_AVC_BASELINE,             },
    { "Main",                 "main",           MFX_PROFILE_AVC_MAIN,                 },
    { "Extended",             "extended",       MFX_PROFILE_AVC_EXTENDED,             },
    { "High",                 "high",           MFX_PROFILE_AVC_HIGH,                 },
    { "High 4:2:2",           "high422",        MFX_PROFILE_AVC_HIGH_422,             },
    { "Constrained Baseline", "baseline|set1",  MFX_PROFILE_AVC_CONSTRAINED_BASELINE, },
    { "Constrained High",     "high|set4|set5", MFX_PROFILE_AVC_CONSTRAINED_HIGH,     },
    { "Progressive High",     "high|set4",      MFX_PROFILE_AVC_PROGRESSIVE_HIGH,     },
    { NULL,                                                                           },
};
static hb_triplet_t hb_qsv_h265_profiles[] =
{
    { "Main",               "main",             MFX_PROFILE_HEVC_MAIN,   },
    { "Main 10",            "main10",           MFX_PROFILE_HEVC_MAIN10, },
    { "Main Still Picture", "mainstillpicture", MFX_PROFILE_HEVC_MAINSP, },
    { NULL,                                                              },
};
static const char * const hb_qsv_h265_profiles_names_10bit[] = {
    "auto",
    "main10",
    NULL,
};
static hb_triplet_t hb_qsv_av1_profiles[] =
{
    { "Main",               "main",             MFX_PROFILE_AV1_MAIN,    },
    { "High",               "high",             MFX_PROFILE_AV1_HIGH,    },
    { "Professional",       "professional",     MFX_PROFILE_AV1_PRO,     },
    { NULL,                                                              },
};
static const char * const hb_qsv_av1_profiles_names[] =
{
    "auto",
    "main",
    NULL,
};
static hb_triplet_t hb_qsv_vpp_scale_modes[] =
{
    { "auto",              "auto",              MFX_SCALING_MODE_DEFAULT,  },
    { "lowpower",          "low_power",         MFX_SCALING_MODE_LOWPOWER, },
    { "hq",                "hq",                MFX_SCALING_MODE_QUALITY,  },
    { "compute",           "compute",           3                          },
    { "vd",                "vd",                4                          },
    { "ve",                "ve",                5                          },
    { NULL,                                                                },
};

static hb_triplet_t hb_qsv_memory_types[] =
{
    { "System memory",         "system",        MFX_IOPATTERN_OUT_SYSTEM_MEMORY, },
    { "Video memory",          "video",         MFX_IOPATTERN_OUT_VIDEO_MEMORY,  },
    { NULL,                                                                      },
};

static hb_triplet_t hb_qsv_hyper_encode_modes[] =
{
    { "Hyper Encode off",      "off",           MFX_HYPERMODE_OFF,      },
    { "Hyper Encode on",       "on",            MFX_HYPERMODE_ON,       },
    { "Hyper Encode adaptive", "adaptive",      MFX_HYPERMODE_ADAPTIVE, },
    { NULL,                                                             },
};

static const enum AVPixelFormat hb_qsv_pix_fmts[] =
{
    AV_PIX_FMT_NV12, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat hb_qsv_10bit_pix_fmts[] =
{
    AV_PIX_FMT_P010LE, AV_PIX_FMT_NONE
};

#define MFX_IMPL_VIA_MASK(impl) (0x0f00 & (impl))

// check available Intel Media SDK version against a minimum
#define HB_CHECK_MFX_VERSION(MFX_VERSION, MAJOR, MINOR) \
    ((MFX_VERSION.Major * 1000 + MFX_VERSION.Minor) >= (MAJOR * 1000 + MINOR))

int hb_qsv_get_adapter_index()
{
    return g_adapter_index;
}

static int hb_qsv_set_default_adapter_index(int adapter_index)
{
    g_default_adapter_index = adapter_index;
    return 0;
}

static int hb_qsv_get_default_adapter_index()
{
    return g_default_adapter_index;
}

hb_list_t* hb_qsv_adapters_list()
{
    return g_qsv_adapters_list;
}

static hb_qsv_adapter_details_t* hb_qsv_get_adapters_details_by_index(int adapter_index)
{
    for (int i = 0; i < hb_list_count(g_qsv_adapters_details_list); i++)
    {
        const hb_qsv_adapter_details_t *details = hb_list_item(g_qsv_adapters_details_list, i);
        if (details->index == adapter_index || adapter_index == -1)
        {
            return details;
        }
    }
    return NULL;
}

int hb_qsv_get_adapter_render_node(int adapter_index)
{
    const hb_qsv_adapter_details_t *details = hb_qsv_get_adapters_details_by_index(adapter_index);
    return details->extended_device_id.DRMRenderNodeNum;
}

int hb_qsv_set_adapter_index(int adapter_index)
{
    if (g_adapter_index == adapter_index)
        return 0;

    for (int i = 0; i < hb_list_count(g_qsv_adapters_details_list); i++)
    {
        const hb_qsv_adapter_details_t *details = hb_list_item(g_qsv_adapters_details_list, i);
        if (details && (details->index == adapter_index))
        {
            g_adapter_index = adapter_index;
            return 0;
        }
    }
    hb_error("hb_qsv_set_adapter_index: incorrect qsv device index %d", adapter_index);
    return -1;
}

static int qsv_impl_set_preferred(hb_qsv_adapter_details_t *details, const char *name)
{
    if (name == NULL || details == NULL)
    {
        return -1;
    }
    if (!strcasecmp(name, "software"))
    {
        if (details->qsv_software_info_avc.available)
        {
            details->hb_qsv_info_avc = &details->qsv_software_info_avc;
        }
        if (details->qsv_software_info_hevc.available)
        {
            details->hb_qsv_info_hevc = &details->qsv_software_info_hevc;
        }
        return 0;
    }
    if (!strcasecmp(name, "hardware"))
    {
        if (details->qsv_hardware_info_avc.available)
        {
            details->hb_qsv_info_avc = &details->qsv_hardware_info_avc;
        }
        if (details->qsv_hardware_info_hevc.available)
        {
            details->hb_qsv_info_hevc = &details->qsv_hardware_info_hevc;
        }
        if (details->qsv_hardware_info_av1.available)
        {
            details->hb_qsv_info_av1 = &details->qsv_hardware_info_av1;
        }
        return 0;
    }
    return -1;
}

int hb_qsv_impl_set_preferred(const char *name)
{
    hb_qsv_adapter_details_t* details = hb_qsv_get_adapters_details_by_index(hb_qsv_get_adapter_index());
    return qsv_impl_set_preferred(details, name);
}

int hb_qsv_hardware_generation(int cpu_platform)
{
    switch (cpu_platform)
    {
        case HB_CPU_PLATFORM_INTEL_BNL:
            return QSV_G0;
        case HB_CPU_PLATFORM_INTEL_SNB:
            return QSV_G1;
        case HB_CPU_PLATFORM_INTEL_IVB:
        case HB_CPU_PLATFORM_INTEL_SLM:
        case HB_CPU_PLATFORM_INTEL_CHT:
            return QSV_G2;
        case HB_CPU_PLATFORM_INTEL_HSW:
            return QSV_G3;
        case HB_CPU_PLATFORM_INTEL_BDW:
            return QSV_G4;
        case HB_CPU_PLATFORM_INTEL_SKL:
            return QSV_G5;
        case HB_CPU_PLATFORM_INTEL_KBL:
        case HB_CPU_PLATFORM_INTEL_CML:
            return QSV_G6;
        case HB_CPU_PLATFORM_INTEL_ICL:
            return QSV_G7;
        case HB_CPU_PLATFORM_INTEL_TGL:
        case HB_CPU_PLATFORM_INTEL_ADL:
            return QSV_G8;
        case HB_CPU_PLATFORM_INTEL_DG2:
            return QSV_G9;
        case HB_CPU_PLATFORM_INTEL_LNL:
            return QSV_G10;
        default:
            return QSV_FU;
    }
}

int qsv_map_mfx_platform_codename(int mfx_platform_codename)
{
    int platform = HB_CPU_PLATFORM_UNSPECIFIED;

    switch (mfx_platform_codename)
    {
    case MFX_PLATFORM_SANDYBRIDGE:
        platform = HB_CPU_PLATFORM_INTEL_SNB;
        break;
    case MFX_PLATFORM_IVYBRIDGE:
        platform = HB_CPU_PLATFORM_INTEL_IVB;
        break;
    case MFX_PLATFORM_HASWELL:
        platform = HB_CPU_PLATFORM_INTEL_HSW;
        break;
    case MFX_PLATFORM_BAYTRAIL:
    case MFX_PLATFORM_BROADWELL:
        platform = HB_CPU_PLATFORM_INTEL_BDW;
        break;
    case MFX_PLATFORM_CHERRYTRAIL:
        platform = HB_CPU_PLATFORM_INTEL_CHT;
        break;
    case MFX_PLATFORM_SKYLAKE:
        platform = HB_CPU_PLATFORM_INTEL_SKL;
        break;
    case MFX_PLATFORM_APOLLOLAKE:
    case MFX_PLATFORM_KABYLAKE:
        platform = HB_CPU_PLATFORM_INTEL_KBL;
        break;
#if (MFX_VERSION >= 1025)
    case MFX_PLATFORM_GEMINILAKE:
    case MFX_PLATFORM_COFFEELAKE:
    case MFX_PLATFORM_CANNONLAKE:
        platform = HB_CPU_PLATFORM_INTEL_KBL;
        break;
#endif
#if (MFX_VERSION >= 1027)
    case MFX_PLATFORM_ICELAKE:
        platform = HB_CPU_PLATFORM_INTEL_ICL;
        break;
#endif
    case MFX_PLATFORM_ELKHARTLAKE:
    case MFX_PLATFORM_JASPERLAKE:
    case MFX_PLATFORM_TIGERLAKE:
    case MFX_PLATFORM_ROCKETLAKE:
        platform = HB_CPU_PLATFORM_INTEL_TGL;
        break;
    case MFX_PLATFORM_ALDERLAKE_S:
    case MFX_PLATFORM_ALDERLAKE_P:
        platform = HB_CPU_PLATFORM_INTEL_ADL;
        break;
    case MFX_PLATFORM_ARCTICSOUND_P:
    case MFX_PLATFORM_DG2:
    case MFX_PLATFORM_ALDERLAKE_N:
    case MFX_PLATFORM_KEEMBAY:
    case MFX_PLATFORM_METEORLAKE:
    case MFX_PLATFORM_BATTLEMAGE:
    case MFX_PLATFORM_ARROWLAKE:
        platform = HB_CPU_PLATFORM_INTEL_DG2;
        break;
    case MFX_PLATFORM_LUNARLAKE:
        platform = HB_CPU_PLATFORM_INTEL_LNL;
        break;
    default:
        platform = HB_CPU_PLATFORM_UNSPECIFIED;
    }
    return platform;
}

static const char* hb_qsv_get_adapter_type(const hb_qsv_adapter_details_t *details)
{
    if (details)
    {
        return (details->platform.MediaAdapterType == MFX_MEDIA_INTEGRATED) ? "integrated" :
        (details->platform.MediaAdapterType == MFX_MEDIA_DISCRETE) ? "discrete" : "unknown";
    }
    return NULL;
}

/*
 * Determine whether a given mfxIMPL is hardware-accelerated.
 */
int hb_qsv_implementation_is_hardware(mfxIMPL implementation)
{
    return MFX_IMPL_BASETYPE(implementation) != MFX_IMPL_SOFTWARE;
}

int hb_qsv_info_init()
{
    int result;
    result = hb_qsv_make_adapters_list(&g_qsv_adapters_list, &g_qsv_adapters_details_list);
    if (result != 0)
    {
        hb_error("hb_qsv_info_init: hb_qsv_make_adapters_list failed");
        return result;
    }
    result = hb_qsv_collect_adapters_details(g_qsv_adapters_details_list);
    if (result != 0)
    {
        hb_error("hb_qsv_info_init: hb_qsv_collect_adapters_details failed");
        return result;
    }
    if (hb_list_count(g_qsv_adapters_details_list) == 0)
    {
        hb_deep_log(1, "hb_qsv_info_init: g_qsv_adapters_details_list has no adapters");
        return -1;
    }
    return 0;
}

#if defined(_WIN32) || defined(__MINGW32__)
static void hb_qsv_free_adapters_details(hb_list_t *qsv_adapters_details_list)
{
    for (int i = 0; i < hb_list_count(qsv_adapters_details_list); i++)
    {
        hb_qsv_adapter_details_t *details = hb_list_item(qsv_adapters_details_list, i);
        if (details)
        {
            av_free(details);
        }
    }
}
#endif

void hb_qsv_info_close()
{
    if (g_qsv_adapters_details_list)
    {
#if defined(_WIN32) || defined(__MINGW32__)
        hb_qsv_free_adapters_details(g_qsv_adapters_details_list);
#endif
        hb_list_close(&g_qsv_adapters_details_list);
        g_qsv_adapters_details_list = NULL;
    }
    if (g_qsv_adapters_list)
    {
        hb_list_close(&g_qsv_adapters_list);
        g_qsv_adapters_list = NULL;
    }
}

static int hb_qsv_make_adapters_list(hb_list_t **qsv_adapters_list, hb_list_t **qsv_adapters_details_list)
{
    if (!qsv_adapters_list)
    {
        hb_error("hb_qsv_make_adapters_list: qsv_adapters_list destination pointer is NULL");
        return -1;
    }
    if (*qsv_adapters_list)
    {
        hb_error("hb_qsv_make_adapters_list: qsv_adapters_list is allocated already");
        return -1;
    }

    if (!qsv_adapters_details_list)
    {
        hb_error("hb_qsv_make_adapters_list: qsv_adapters_details_list destination pointer is NULL");
        return -1;
    }

    if (*qsv_adapters_details_list)
    {
        hb_error("hb_qsv_make_adapters_list: qsv_adapter_details_list is allocated already");
        return -1;
    }

    hb_list_t *list = hb_list_init();
    if (list == NULL)
    {
        hb_error("hb_qsv_make_adapters_list: hb_list_init() failed");
        return -1;
    }
    hb_list_t *list2 = hb_list_init();
    if (list == NULL)
    {
        hb_error("hb_qsv_make_adapters_list: hb_list_init() failed");
        return -1;
    }

    mfxLoader loader = MFXLoad();
    if (loader == NULL)
    {
        hb_error("hb_qsv_make_adapters_list: Error - MFXLoad() returned null - no libraries found\n");
        return -1;
    }

    mfxConfig config          = MFXCreateConfig(loader);
    int i                     = 0;
    mfxImplDescription *idesc = NULL;
    int max_generation        = QSV_G0;
    int default_adapter       = 0;
    mfxVariant var            = {};
    var.Version.Version       = MFX_VARIANT_VERSION;
    mfxStatus err             = MFX_ERR_NONE;

    var.Type     = MFX_VARIANT_TYPE_U32;
    var.Data.U32 = MFX_IMPL_TYPE_HARDWARE;
    err = MFXSetConfigFilterProperty(config, (const mfxU8 *)"mfxImplDescription.Impl", var);
    if (err != MFX_ERR_NONE)
        hb_error("hb_qsv_make_adapters_list: MFXSetConfigFilterProperty mfxImplDescription.Impl error=%d", err);

#if defined(_WIN32) || defined(__MINGW32__)
    var.Type     = MFX_VARIANT_TYPE_U32;
    var.Data.U32 = MFX_ACCEL_MODE_VIA_D3D11;
    err = MFXSetConfigFilterProperty(config, (const mfxU8 *)"mfxImplDescription.AccelerationMode", var);
    if (err != MFX_ERR_NONE)
        hb_error("hb_qsv_make_adapters_list: MFXSetConfigFilterProperty mfxImplDescription.AccelerationMode error=%d", err);
#endif

    var.Type     = MFX_VARIANT_TYPE_U32;
    var.Data.U32 = 0x8086;
    MFXSetConfigFilterProperty(config, (const mfxU8 *)"mfxImplDescription.VendorID", var);
    if (err != MFX_ERR_NONE)
        hb_error("hb_qsv_make_adapters_list: MFXSetConfigFilterProperty mfxImplDescription.VendorID error=%d", err);

    while (1)
    {
        err = MFXEnumImplementations(loader,
                                     i,
                                     MFX_IMPLCAPS_IMPLDESCSTRUCTURE,
                                     (mfxHDL *)(&idesc));
        if (err != MFX_ERR_NONE)
        {
            if (err != MFX_ERR_NOT_FOUND)
            {
                hb_error("hb_qsv_make_adapters_list: MFXEnumImplementations returns %d", err);
            }
            break;
        }

        mfxSession session = NULL;
        err = MFXCreateSession(loader, i, &session);
        if (err == MFX_ERR_NONE)
        {
            hb_qsv_adapter_details_t *adapter_details = av_mallocz(sizeof(hb_qsv_adapter_details_t));
            if (!adapter_details)
            {
                hb_error("hb_qsv_make_adapters_list: adapter_details allocation failed");
                return -1;
            }
            int *adapter_index = av_mallocz(sizeof(int));
            if (!adapter_index)
            {
                hb_error("hb_qsv_make_adapters_list: adapter_index allocation failed");
                return -1;
            }
            init_adapter_details(adapter_details);
            // On Linux VendorImplID number of the device starting from 0
            adapter_details->index = idesc->VendorImplID;
            adapter_details->impl_name = strdup( (const char *)idesc->ImplName);
            *adapter_index = idesc->VendorImplID;
            mfxHDL impl_path = NULL;
            err = MFXEnumImplementations(loader, i, MFX_IMPLCAPS_IMPLPATH, &impl_path);
            if (err == MFX_ERR_NONE)
            {
                if (impl_path)
                {
                    adapter_details->impl_path = strdup( (const char *)impl_path);
                    MFXDispReleaseImplDescription(loader, impl_path);
                }
            }
            else
            {
                hb_error("hb_qsv_make_adapters_list: MFXEnumImplementations MFX_IMPLCAPS_IMPLPATH failed impl=%d err=%d", i, err);
            }
            mfxExtendedDeviceId *idescDevice;
            err = MFXEnumImplementations(loader,
                                         i,
                                         MFX_IMPLCAPS_DEVICE_ID_EXTENDED,
                                         (mfxHDL *)(&idescDevice));
            if (err == MFX_ERR_NONE) {
                adapter_details->extended_device_id = *idescDevice;
                MFXDispReleaseImplDescription(loader, idescDevice);
            }
            hb_list_add(list, (void*)adapter_index);
            hb_list_add(list2, (void*)adapter_details);
            // On Linux, the handle to the VA display must be set.
            // This code is essentially a NOP other platforms.
            hb_display_t *display = hb_qsv_display_init(adapter_details->extended_device_id.DRMRenderNodeNum);
            if (display != NULL)
            {
                MFXVideoCORE_SetHandle(session, display->mfxType,
                                    (mfxHDL)display->handle);
            }
            mfxPlatform platform = { 0 };
            err = MFXVideoCORE_QueryPlatform(session, &platform);
            if (MFX_ERR_NONE == err)
            {
                int generation = hb_qsv_hardware_generation(qsv_map_mfx_platform_codename(platform.CodeName));
                // select default QSV adapter
                if (generation > max_generation)
                {
                    max_generation = generation;
                    default_adapter = idesc->VendorImplID;
                }
                adapter_details->platform = platform;
            }
            else
            {
                hb_error("hb_qsv_make_adapters_list: MFXVideoCORE_QueryPlatform failed impl=%d err=%d", i, err);
            }
            MFXClose(session);
            // display must be closed after MFXClose
            hb_display_close(&display);
        }
        else
        {
            hb_error("hb_qsv_make_adapters_list: MFXCreateSession failed impl=%d err=%d", i, err);
        }
        MFXDispReleaseImplDescription(loader, idesc);
        i++;
    }
    MFXUnload(loader);
    *qsv_adapters_list = list;
    *qsv_adapters_details_list = list2;
    hb_qsv_set_default_adapter_index(default_adapter);
    hb_qsv_set_adapter_index(default_adapter);
    return 0;
}

/**
 * Check the actual availability of QSV implementations on the system
 * and collect GPU adapters capabilities.
 *
 * @returns encoder codec mask supported by QSV implementation,
 *      0 if QSV is not available, -1 if HB_PROJECT_FEATURE_QSV is not enabled
 */
int hb_qsv_available()
{
    if (hb_is_hardware_disabled())
    {
        return 0;
    }

    if (qsv_init_done != 0)
    {
        // This method gets called a lot. Don't probe hardware each time.
        return qsv_init_result;
    }

    qsv_init_done = 1;
    int result = hb_qsv_info_init();
    if (result != 0)
    {
        hb_log("qsv: not available on this system");
        qsv_init_result = 0;
        return qsv_init_result;
    }
    hb_log("qsv: is available on this system");

    // Return the codec capabilities for the highest platform generation
    qsv_init_result = ((hb_qsv_video_encoder_is_available(HB_VCODEC_FFMPEG_QSV_H264) ? HB_VCODEC_FFMPEG_QSV_H264 : 0) |
                       (hb_qsv_video_encoder_is_available(HB_VCODEC_FFMPEG_QSV_H265) ? HB_VCODEC_FFMPEG_QSV_H265 : 0) |
                       (hb_qsv_video_encoder_is_available(HB_VCODEC_FFMPEG_QSV_H265_10BIT) ? HB_VCODEC_FFMPEG_QSV_H265_10BIT : 0) |
                       (hb_qsv_video_encoder_is_available(HB_VCODEC_FFMPEG_QSV_AV1) ? HB_VCODEC_FFMPEG_QSV_AV1 : 0) |
                       (hb_qsv_video_encoder_is_available(HB_VCODEC_FFMPEG_QSV_AV1_10BIT) ? HB_VCODEC_FFMPEG_QSV_AV1_10BIT : 0));
    return qsv_init_result;
}

int hb_qsv_hyper_encode_available(int adapter_index)
{
    const hb_qsv_adapter_details_t *details = hb_qsv_get_adapters_details_by_index(adapter_index);

    if (details)
    {
        return (details->hb_qsv_info_avc != NULL && details->hb_qsv_info_avc->capabilities & HB_QSV_CAP_HYPERENCODE) ||
            (details->hb_qsv_info_hevc != NULL && details->hb_qsv_info_hevc->capabilities & HB_QSV_CAP_HYPERENCODE) ||
            (details->hb_qsv_info_av1 != NULL && details->hb_qsv_info_av1->capabilities & HB_QSV_CAP_HYPERENCODE);
    }
    else
    {
        return 0;
    }
}


int hb_qsv_is_ffmpeg_supported_codec(int vcodec)
{
    if (vcodec == HB_VCODEC_FFMPEG_QSV_H264 ||
        vcodec == HB_VCODEC_FFMPEG_QSV_H265 ||
        vcodec == HB_VCODEC_FFMPEG_QSV_H265_10BIT ||
        vcodec == HB_VCODEC_FFMPEG_QSV_AV1 ||
        vcodec == HB_VCODEC_FFMPEG_QSV_AV1_10BIT
        )
    {
        return 1;
    }
    return 0;
}

int hb_qsv_video_encoder_is_available(int encoder)
{
    for (int i = 0; i < hb_list_count(g_qsv_adapters_details_list); i++)
    {
        const hb_qsv_adapter_details_t *details = hb_list_item(g_qsv_adapters_details_list, i);
        if (hb_qsv_adapter_video_encoder_is_available(details->index, encoder))
        {
            return 1;
        }
    }
    return 0;
}

int hb_qsv_adapter_video_encoder_is_available(int adapter_index, int encoder)
{
    const hb_qsv_adapter_details_t *details = hb_qsv_get_adapters_details_by_index(adapter_index);

    if (hb_qsv_hardware_generation(hb_qsv_get_platform(adapter_index)) < QSV_G5)
    {
        return 0;
    }

    if (details)
    {
        switch (encoder)
        {
            case HB_VCODEC_FFMPEG_QSV_H264:
                return details->hb_qsv_info_avc != NULL && details->hb_qsv_info_avc->available;
            case HB_VCODEC_FFMPEG_QSV_H265_10BIT:
                if (hb_qsv_hardware_generation(hb_qsv_get_platform(adapter_index)) < QSV_G6)
                    return 0;
            case HB_VCODEC_FFMPEG_QSV_H265:
                return details->hb_qsv_info_hevc != NULL && details->hb_qsv_info_hevc->available;
            case HB_VCODEC_FFMPEG_QSV_AV1_10BIT:
            case HB_VCODEC_FFMPEG_QSV_AV1:
                return details->hb_qsv_info_av1 != NULL && details->hb_qsv_info_av1->available;
            default:
                return 0;
        }
    }
    else
    {
        return 0;
    }
}

static void init_video_param(mfxVideoParam *videoParam)
{
    if (videoParam == NULL)
    {
        return;
    }

    memset(videoParam, 0, sizeof(mfxVideoParam));
    videoParam->mfx.CodecId                 = MFX_CODEC_AVC;
    videoParam->mfx.CodecLevel              = MFX_LEVEL_UNKNOWN;
    videoParam->mfx.CodecProfile            = MFX_PROFILE_UNKNOWN;
    videoParam->mfx.RateControlMethod       = MFX_RATECONTROL_VBR;
    videoParam->mfx.TargetUsage             = MFX_TARGETUSAGE_BALANCED;
    videoParam->mfx.TargetKbps              = 5000;
    videoParam->mfx.GopOptFlag              = MFX_GOP_CLOSED;
    videoParam->mfx.FrameInfo.FourCC        = MFX_FOURCC_NV12;
    videoParam->mfx.FrameInfo.ChromaFormat  = MFX_CHROMAFORMAT_YUV420;
    videoParam->mfx.FrameInfo.PicStruct     = MFX_PICSTRUCT_PROGRESSIVE;
    videoParam->mfx.FrameInfo.FrameRateExtN = 25;
    videoParam->mfx.FrameInfo.FrameRateExtD = 1;
    videoParam->mfx.FrameInfo.Width         = 1920;
    videoParam->mfx.FrameInfo.CropW         = 1920;
    videoParam->mfx.FrameInfo.AspectRatioW  = 1;
    videoParam->mfx.FrameInfo.Height        = 1088;
    videoParam->mfx.FrameInfo.CropH         = 1080;
    videoParam->mfx.FrameInfo.AspectRatioH  = 1;
    videoParam->AsyncDepth                  = HB_QSV_ASYNC_DEPTH_DEFAULT;
    videoParam->IOPattern                   = MFX_IOPATTERN_IN_SYSTEM_MEMORY;
}

static void init_video_hyperencode_param(mfxVideoParam *videoParam, int codec_id)
{
    if (videoParam == NULL)
    {
        return;
    }

    // Both GPUs must support of the same encoder parameters
    if (codec_id == MFX_CODEC_HEVC)
    {
        videoParam->mfx.IdrInterval = 1;
    }
    else if (codec_id == MFX_CODEC_AVC)
    {
        videoParam->mfx.IdrInterval = 0;
        // Relax ARC Gfx encoding settings to align ADL Gfx capabilities
        videoParam->mfx.GopRefDist = 1;
    }
    videoParam->mfx.GopPicSize = 60;
    videoParam->AsyncDepth = 60;
}

static void init_ext_video_signal_info(mfxExtVideoSignalInfo *extVideoSignalInfo)
{
    if (extVideoSignalInfo == NULL)
    {
        return;
    }

    memset(extVideoSignalInfo, 0, sizeof(mfxExtVideoSignalInfo));
    extVideoSignalInfo->Header.BufferId          = MFX_EXTBUFF_VIDEO_SIGNAL_INFO;
    extVideoSignalInfo->Header.BufferSz          = sizeof(mfxExtVideoSignalInfo);
    extVideoSignalInfo->VideoFormat              = 5; // undefined
    extVideoSignalInfo->VideoFullRange           = 0; // TV range
    extVideoSignalInfo->ColourDescriptionPresent = 0; // don't write to bitstream
    extVideoSignalInfo->ColourPrimaries          = 2; // undefined
    extVideoSignalInfo->TransferCharacteristics  = 2; // undefined
    extVideoSignalInfo->MatrixCoefficients       = 2; // undefined
}

static void init_ext_chroma_loc_info(mfxExtChromaLocInfo *extChromaLocInfo)
{
    if (extChromaLocInfo == NULL)
    {
        return;
    }

    memset(extChromaLocInfo, 0, sizeof(mfxExtChromaLocInfo));
    extChromaLocInfo->Header.BufferId = MFX_EXTBUFF_CHROMA_LOC_INFO;
    extChromaLocInfo->Header.BufferSz = sizeof(mfxExtChromaLocInfo);
}

static void init_ext_mastering_display_colour_volume(mfxExtMasteringDisplayColourVolume *extMasteringDisplayColourVolume)
{
    if (extMasteringDisplayColourVolume == NULL)
    {
        return;
    }

    memset(extMasteringDisplayColourVolume, 0, sizeof(mfxExtMasteringDisplayColourVolume));
    extMasteringDisplayColourVolume->Header.BufferId     = MFX_EXTBUFF_MASTERING_DISPLAY_COLOUR_VOLUME;
    extMasteringDisplayColourVolume->Header.BufferSz     = sizeof(mfxExtMasteringDisplayColourVolume);
    extMasteringDisplayColourVolume->InsertPayloadToggle = MFX_PAYLOAD_OFF;
}

static void init_ext_content_light_level_info(mfxExtContentLightLevelInfo *extContentLightLevelInfo)
{
    if (extContentLightLevelInfo == NULL)
    {
        return;
    }

    memset(extContentLightLevelInfo, 0, sizeof(mfxExtContentLightLevelInfo));
    extContentLightLevelInfo->Header.BufferId     = MFX_EXTBUFF_CONTENT_LIGHT_LEVEL_INFO;
    extContentLightLevelInfo->Header.BufferSz     = sizeof(mfxExtContentLightLevelInfo);
    extContentLightLevelInfo->InsertPayloadToggle = MFX_PAYLOAD_OFF;
}

static void init_ext_hyperencode_option(mfxExtHyperModeParam *extHyperEncodemParam)
{
    if (extHyperEncodemParam == NULL)
    {
        return;
    }

    memset(extHyperEncodemParam, 0, sizeof(mfxExtHyperModeParam));
    extHyperEncodemParam->Header.BufferId = MFX_EXTBUFF_HYPER_MODE_PARAM;
    extHyperEncodemParam->Header.BufferSz = sizeof(mfxExtHyperModeParam);
    extHyperEncodemParam->Mode = MFX_HYPERMODE_OFF;
}

static void init_ext_coding_option(mfxExtCodingOption *extCodingOption)
{
    if (extCodingOption == NULL)
    {
        return;
    }

    memset(extCodingOption, 0, sizeof(mfxExtCodingOption));
    extCodingOption->Header.BufferId = MFX_EXTBUFF_CODING_OPTION;
    extCodingOption->Header.BufferSz = sizeof(mfxExtCodingOption);
    extCodingOption->AUDelimiter     = MFX_CODINGOPTION_OFF;
    extCodingOption->PicTimingSEI    = MFX_CODINGOPTION_OFF;
    extCodingOption->CAVLC           = MFX_CODINGOPTION_OFF;
}

static void init_ext_coding_option2(mfxExtCodingOption2 *extCodingOption2)
{
    if (extCodingOption2 == NULL)
    {
        return;
    }

    memset(extCodingOption2, 0, sizeof(mfxExtCodingOption2));
    extCodingOption2->Header.BufferId = MFX_EXTBUFF_CODING_OPTION2;
    extCodingOption2->Header.BufferSz = sizeof(mfxExtCodingOption2);
    extCodingOption2->MBBRC           = MFX_CODINGOPTION_ON;
    extCodingOption2->ExtBRC          = MFX_CODINGOPTION_ON;
    extCodingOption2->Trellis         = MFX_TRELLIS_I|MFX_TRELLIS_P|MFX_TRELLIS_B;
    extCodingOption2->RepeatPPS       = MFX_CODINGOPTION_ON;
    extCodingOption2->BRefType        = MFX_B_REF_PYRAMID;
    extCodingOption2->AdaptiveI       = MFX_CODINGOPTION_ON;
    extCodingOption2->AdaptiveB       = MFX_CODINGOPTION_ON;
    extCodingOption2->LookAheadDS     = MFX_LOOKAHEAD_DS_4x;
    extCodingOption2->NumMbPerSlice   = 2040; // 1920x1088/4
}

static void init_ext_av1bitstream_option(mfxExtAV1BitstreamParam *extAV1BitstreamParam)
{
    if (extAV1BitstreamParam == NULL)
    {
        return;
    }

    memset(extAV1BitstreamParam, 0, sizeof(mfxExtAV1BitstreamParam));
    extAV1BitstreamParam->Header.BufferId = MFX_EXTBUFF_AV1_BITSTREAM_PARAM;
    extAV1BitstreamParam->Header.BufferSz = sizeof(mfxExtAV1BitstreamParam);
    extAV1BitstreamParam->WriteIVFHeaders = MFX_CODINGOPTION_OFF;
}

static void init_ext_av1screencontent_tools(mfxExtAV1ScreenContentTools *extScreenContentTools)
{
    if (extScreenContentTools == NULL)
    {
        return;
    }

    memset(extScreenContentTools, 0, sizeof(mfxExtAV1ScreenContentTools));
    extScreenContentTools->Header.BufferId = MFX_EXTBUFF_AV1_SCREEN_CONTENT_TOOLS;
    extScreenContentTools->Header.BufferSz = sizeof(mfxExtAV1ScreenContentTools);
    extScreenContentTools->IntraBlockCopy  = MFX_CODINGOPTION_OFF;
    extScreenContentTools->Palette         = MFX_CODINGOPTION_OFF;
}

static int query_capabilities(mfxSession session, int index, mfxVersion version, hb_qsv_info_t *info, int lowpower)
{
    /*
     * MFXVideoENCODE_Query(mfxSession, mfxVideoParam *in, mfxVideoParam *out);
     *
     * Mode 1:
     * - in is NULL
     * - out has the parameters we want to query set to 1
     * - out->mfx.CodecId field has to be set (mandatory)
     * - MFXVideoENCODE_Query should zero out all unsupported parameters
     *
     * Mode 2:
     * - the parameters we want to query are set for in
     * - in ->mfx.CodecId field has to be set (mandatory)
     * - out->mfx.CodecId field has to be set (mandatory)
     * - MFXVideoENCODE_Query should sanitize all unsupported parameters
     */
    mfxStatus     status;
    mfxExtBuffer *videoExtParam[1];
    mfxVideoParam videoParam, inputParam;
    mfxExtCodingOption    extCodingOption;
    mfxExtCodingOption2   extCodingOption2;
    mfxExtVideoSignalInfo extVideoSignalInfo;
    mfxExtChromaLocInfo   extChromaLocInfo;
    mfxExtMasteringDisplayColourVolume  extMasteringDisplayColourVolume;
    mfxExtContentLightLevelInfo         extContentLightLevelInfo;
    mfxExtAV1BitstreamParam extAV1BitstreamParam;
    mfxExtAV1ScreenContentTools extAV1ScreenContentToolsParam;
    mfxExtHyperModeParam extHyperEncodeParam;

    /* Reset capabilities before querying */
    info->capabilities = 0;

    /* Disable lowpower if the encoder is software */
    if (hb_qsv_implementation_is_hardware(info->implementation) == 0)
    {
         lowpower = 0;
    }

    /*
     * First of all, check availability of an encoder for
     * this combination of a codec ID and implementation.
     *
     * Note: can error out rather than sanitizing
     * unsupported codec IDs, so don't log errors.
     */
    if (HB_CHECK_MFX_VERSION(version, HB_QSV_MINVERSION_MAJOR, HB_QSV_MINVERSION_MINOR))
    {
        {
            mfxStatus mfxRes;
            init_video_param(&inputParam);
            inputParam.mfx.CodecId = info->codec_id;
            inputParam.mfx.LowPower = lowpower;
            memset(&videoParam, 0, sizeof(mfxVideoParam));
            videoParam.mfx.CodecId = inputParam.mfx.CodecId;

            mfxRes = MFXVideoENCODE_Query(session, &inputParam, &videoParam);
            if (mfxRes >= MFX_ERR_NONE &&
                videoParam.mfx.CodecId == info->codec_id)
            {
                /*
                 * MFXVideoENCODE_Query might tell you that an HEVC encoder is
                 * available on Haswell hardware, but it'll fail to initialize.
                 * So check encoder availability with MFXVideoENCODE_Init too.
                 */
                if ((status = MFXVideoENCODE_Init(session, &videoParam)) >= MFX_ERR_NONE)
                {
                    info->available = 1;
                }
                else if (info->codec_id == MFX_CODEC_AVC)
                {
                    /*
                     * This should not fail for AVC encoders, so we want to know
                     * about it - however, it may fail for other encoders (ignore)
                     */
                    fprintf(stderr,
                            "query_capabilities: MFXVideoENCODE_Init failed"
                            " (0x%"PRIX32", 0x%"PRIX32", %d)\n",
                            info->codec_id, info->implementation, status);
                }
                MFXVideoENCODE_Close(session);
            }
        }
    }
    if (!info->available)
    {
        /* Don't check capabilities for unavailable encoders */
        return 0;
    }

    {
        /* Implementation-specific features that can't be queried */
        if (info->codec_id == MFX_CODEC_AVC || info->codec_id == MFX_CODEC_HEVC || info->codec_id == MFX_CODEC_AV1)
        {
            if (hb_qsv_implementation_is_hardware(info->implementation))
            {
                if (hb_qsv_hardware_generation(hb_qsv_get_platform(index)) >= QSV_G3)
                {
                    info->capabilities |= HB_QSV_CAP_B_REF_PYRAMID;
                }
                if (info->codec_id == MFX_CODEC_AVC &&
                    (hb_qsv_hardware_generation(hb_qsv_get_platform(index)) >= QSV_G7))
                {
                    info->capabilities |= HB_QSV_CAP_LOWPOWER_ENCODE;
                }
                if (info->codec_id == MFX_CODEC_HEVC &&
                    (hb_qsv_hardware_generation(hb_qsv_get_platform(index)) >= QSV_G7))
                {
                    info->capabilities |= HB_QSV_CAP_LOWPOWER_ENCODE;
                }
                if (info->codec_id == MFX_CODEC_AV1 &&
                    (hb_qsv_hardware_generation(hb_qsv_get_platform(index)) > QSV_G8))
                {
                    info->capabilities |= HB_QSV_CAP_LOWPOWER_ENCODE;
                }
            }
            else
            {
                if (HB_CHECK_MFX_VERSION(version, 1, 6))
                {
                    info->capabilities |= HB_QSV_CAP_B_REF_PYRAMID;
                }
            }
        }

        /* API-specific features that can't be queried */
        if (HB_CHECK_MFX_VERSION(version, 1, 6))
        {
            // API >= 1.6 (mfxBitstream::DecodeTimeStamp, mfxExtCodingOption2)
            info->capabilities |= HB_QSV_CAP_MSDK_API_1_6;
        }

        /*
         * Check availability of optional rate control methods.
         *
         * Mode 2 tends to error out, but mode 1 gives false negatives, which
         * is worse. So use mode 2 and assume an error means it's unsupported.
         *
         * Also assume that LA and ICQ combined imply LA_ICQ is
         * supported, so we don't need to check the latter too.
         */
        if (HB_CHECK_MFX_VERSION(version, 1, 7))
        {
            init_video_param(&inputParam);
            inputParam.mfx.CodecId           = info->codec_id;
            inputParam.mfx.LowPower          = lowpower;
            inputParam.mfx.RateControlMethod = MFX_RATECONTROL_LA;
            inputParam.mfx.TargetKbps        = 5000;

            memset(&videoParam, 0, sizeof(mfxVideoParam));
            videoParam.mfx.CodecId = inputParam.mfx.CodecId;

            if (MFXVideoENCODE_Query(session, &inputParam, &videoParam) >= MFX_ERR_NONE &&
                videoParam.mfx.RateControlMethod == MFX_RATECONTROL_LA)
            {
                info->capabilities |= HB_QSV_CAP_RATECONTROL_LA;

                // also check for LA + interlaced support
                init_video_param(&inputParam);
                inputParam.mfx.CodecId             = info->codec_id;
                inputParam.mfx.LowPower            = lowpower;
                inputParam.mfx.RateControlMethod   = MFX_RATECONTROL_LA;
                inputParam.mfx.FrameInfo.PicStruct = MFX_PICSTRUCT_FIELD_TFF;
                inputParam.mfx.TargetKbps          = 5000;

                memset(&videoParam, 0, sizeof(mfxVideoParam));
                videoParam.mfx.CodecId = inputParam.mfx.CodecId;

                if (MFXVideoENCODE_Query(session, &inputParam, &videoParam) >= MFX_ERR_NONE &&
                    videoParam.mfx.FrameInfo.PicStruct == MFX_PICSTRUCT_FIELD_TFF           &&
                    videoParam.mfx.RateControlMethod   == MFX_RATECONTROL_LA)
                {
                    info->capabilities |= HB_QSV_CAP_RATECONTROL_LAi;
                }
            }
        }
        if (HB_CHECK_MFX_VERSION(version, 1, 8))
        {
            init_video_param(&inputParam);
            inputParam.mfx.CodecId           = info->codec_id;
            inputParam.mfx.LowPower          = lowpower;
            inputParam.mfx.RateControlMethod = MFX_RATECONTROL_ICQ;
            inputParam.mfx.ICQQuality        = 20;

            memset(&videoParam, 0, sizeof(mfxVideoParam));
            videoParam.mfx.CodecId = inputParam.mfx.CodecId;

            if (MFXVideoENCODE_Query(session, &inputParam, &videoParam) >= MFX_ERR_NONE &&
                videoParam.mfx.RateControlMethod == MFX_RATECONTROL_ICQ)
            {
                info->capabilities |= HB_QSV_CAP_RATECONTROL_ICQ;
            }
        }

        /*
         * Determine whether mfxExtVideoSignalInfo is supported.
         */
        if (HB_CHECK_MFX_VERSION(version, 1, 3))
        {
            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            videoParam.mfx.LowPower = lowpower;
            init_ext_video_signal_info(&extVideoSignalInfo);
            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extVideoSignalInfo;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE)
            {
                /* Encoder can be configured via mfxExtVideoSignalInfo */
                info->capabilities |= HB_QSV_CAP_VUI_VSINFO;
            }
            else if (info->codec_id == MFX_CODEC_AVC)
            {
                /*
                 * This should not fail for AVC encoders, so we want to know
                 * about it - however, it may fail for other encoders (ignore)
                 */
                fprintf(stderr,
                        "query_capabilities: mfxExtVideoSignalInfo check"
                        " failed (0x%"PRIX32", 0x%"PRIX32", %d)\n",
                        info->codec_id, info->implementation, status);
            }
        }

        /*
         * Determine whether mfxExtCodingOption is supported.
         */
        if (HB_CHECK_MFX_VERSION(version, 1, 0))
        {
            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            videoParam.mfx.LowPower = lowpower;
            init_ext_coding_option(&extCodingOption);
            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extCodingOption;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE)
            {
                /* Encoder can be configured via mfxExtCodingOption */
                info->capabilities |= HB_QSV_CAP_OPTION1;
            }
            else if (info->codec_id == MFX_CODEC_AVC)
            {
                /*
                 * This should not fail for AVC encoders, so we want to know
                 * about it - however, it may fail for other encoders (ignore)
                 */
                fprintf(stderr,
                        "query_capabilities: mfxExtCodingOption check"
                        " failed (0x%"PRIX32", 0x%"PRIX32", %d)\n",
                        info->codec_id, info->implementation, status);
            }
        }

        /*
         * Determine whether mfxExtCodingOption2 and its fields are supported.
         *
         * Mode 2 suffers from false negatives with some drivers, whereas mode 1
         * suffers from false positives instead. The latter is probably easier
         * and/or safer to sanitize for us, so use mode 1.
         */
        if (HB_CHECK_MFX_VERSION(version, 1, 6))
        {
            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            videoParam.mfx.LowPower = lowpower;
            init_ext_coding_option2(&extCodingOption2);
            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extCodingOption2;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE)
            {
#if 0
                // testing code that could come in handy
                fprintf(stderr, "-------------------\n");
                fprintf(stderr, "MBBRC:         0x%02X\n",     extCodingOption2.MBBRC);
                fprintf(stderr, "ExtBRC:        0x%02X\n",     extCodingOption2.ExtBRC);
                fprintf(stderr, "Trellis:       0x%02X\n",     extCodingOption2.Trellis);
                fprintf(stderr, "RepeatPPS:     0x%02X\n",     extCodingOption2.RepeatPPS);
                fprintf(stderr, "BRefType:      %4"PRIu16"\n", extCodingOption2.BRefType);
                fprintf(stderr, "AdaptiveI:     0x%02X\n",     extCodingOption2.AdaptiveI);
                fprintf(stderr, "AdaptiveB:     0x%02X\n",     extCodingOption2.AdaptiveB);
                fprintf(stderr, "LookAheadDS:   %4"PRIu16"\n", extCodingOption2.LookAheadDS);
                fprintf(stderr, "-------------------\n");
#endif

                /* Encoder can be configured via mfxExtCodingOption2 */
                info->capabilities |= HB_QSV_CAP_OPTION2;

                /*
                 * Sanitize API 1.6 fields:
                 *
                 * - MBBRC  requires G3 hardware (Haswell or equivalent)
                 * - ExtBRC requires G2 hardware (Ivy Bridge or equivalent)
                 */
                if (hb_qsv_implementation_is_hardware(info->implementation) &&
                    hb_qsv_hardware_generation(hb_qsv_get_platform(index)) >= QSV_G3)
                {
                    if (extCodingOption2.MBBRC)
                    {
                        info->capabilities |= HB_QSV_CAP_OPTION2_MBBRC;
                    }
                }
                if (hb_qsv_implementation_is_hardware(info->implementation) &&
                    hb_qsv_hardware_generation(hb_qsv_get_platform(index)) >= QSV_G2)
                {
                    if (extCodingOption2.ExtBRC)
                    {
                        info->capabilities |= HB_QSV_CAP_OPTION2_EXTBRC;
                    }
                }

                /*
                 * Sanitize API 1.7 fields:
                 *
                 * - Trellis requires G3 hardware (Haswell or equivalent)
                 */
                if (HB_CHECK_MFX_VERSION(version, 1, 7))
                {
                    if (hb_qsv_implementation_is_hardware(info->implementation) &&
                        hb_qsv_hardware_generation(hb_qsv_get_platform(index)) >= QSV_G3)
                    {
                        if (extCodingOption2.Trellis)
                        {
                            info->capabilities |= HB_QSV_CAP_OPTION2_TRELLIS;
                        }
                    }
                }

                /*
                 * Sanitize API 1.8 fields:
                 *
                 * - BRefType    requires B-pyramid support
                 * - LookAheadDS requires lookahead support
                 * - AdaptiveI, AdaptiveB, NumMbPerSlice unknown (trust Query)
                 */
                if (HB_CHECK_MFX_VERSION(version, 1, 8))
                {
                    if (extCodingOption2.RepeatPPS)
                    {
                        info->capabilities |= HB_QSV_CAP_OPTION2_REPEATPPS;
                    }
                    if (info->capabilities & HB_QSV_CAP_B_REF_PYRAMID)
                    {
                        if (extCodingOption2.BRefType)
                        {
                            info->capabilities |= HB_QSV_CAP_OPTION2_BREFTYPE;
                        }
                    }
                    if (info->capabilities & HB_QSV_CAP_RATECONTROL_LA)
                    {
                        if (extCodingOption2.LookAheadDS)
                        {
                            info->capabilities |= HB_QSV_CAP_OPTION2_LA_DOWNS;
                        }
                    }
                    if (extCodingOption2.AdaptiveI && extCodingOption2.AdaptiveB)
                    {
                        info->capabilities |= HB_QSV_CAP_OPTION2_IB_ADAPT;
                    }
                    if (extCodingOption2.NumMbPerSlice)
                    {
                        info->capabilities |= HB_QSV_CAP_OPTION2_NMPSLICE;
                    }
                }
            }
            else
            {
                fprintf(stderr,
                        "query_capabilities: mfxExtCodingOption2 check failed (0x%"PRIX32", 0x%"PRIX32", %d)\n",
                        info->codec_id, info->implementation, status);
            }
        }
        if (HB_CHECK_MFX_VERSION(version, 1, 13) && info->codec_id == MFX_CODEC_AVC)
        {
            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            videoParam.mfx.LowPower = lowpower;
            init_ext_chroma_loc_info(&extChromaLocInfo);
            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extChromaLocInfo;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE)
            {
                /* Encoder can be configured via mfxExtChromaLocInfo */
                info->capabilities |= HB_QSV_CAP_VUI_CHROMALOCINFO;
            }
        }

        if (HB_CHECK_MFX_VERSION(version, 1, 19))
        {
            if (hb_qsv_hardware_generation(hb_qsv_get_platform(index)) >= QSV_G7)
            {
                info->capabilities |= HB_QSV_CAP_VPP_SCALING;
            }
        }
        if (HB_CHECK_MFX_VERSION(version, 1, 25) && (info->codec_id == MFX_CODEC_HEVC || info->codec_id == MFX_CODEC_AV1))
        {
            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            videoParam.mfx.LowPower = lowpower;
            init_ext_mastering_display_colour_volume(&extMasteringDisplayColourVolume);
            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extMasteringDisplayColourVolume;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE)
            {
                /* Encoder can be configured via mfxExtMasteringDisplayColourVolume */
                info->capabilities |= HB_QSV_CAP_VUI_MASTERINGINFO;
            }

            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            videoParam.mfx.LowPower = lowpower;
            init_ext_content_light_level_info(&extContentLightLevelInfo);
            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extContentLightLevelInfo;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE)
            {
                /* Encoder can be configured via mfxExtContentLightLevelInfo */
                info->capabilities |= HB_QSV_CAP_VUI_CLLINFO;
            }
        }
        if (HB_CHECK_MFX_VERSION(version, 1, 33))
        {
            if (hb_qsv_hardware_generation(hb_qsv_get_platform(index)) >= QSV_G7)
            {
                info->capabilities |= HB_QSV_CAP_VPP_INTERPOLATION;
            }
        }
        if (lowpower == MFX_CODINGOPTION_ON)
        {
            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            init_video_hyperencode_param(&videoParam, info->codec_id);
            videoParam.mfx.LowPower = lowpower;

            init_ext_hyperencode_option(&extHyperEncodeParam);
            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extHyperEncodeParam;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE && extHyperEncodeParam.Mode == MFX_HYPERMODE_ON)
            {
                info->capabilities |= HB_QSV_CAP_HYPERENCODE;
            }
        }
        if ((lowpower == MFX_CODINGOPTION_ON) && (info->codec_id == MFX_CODEC_AV1))
        {
            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            videoParam.mfx.LowPower = lowpower;

            init_ext_av1bitstream_option(&extAV1BitstreamParam);
            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extAV1BitstreamParam;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE)
            {
                info->capabilities |= HB_QSV_CAP_AV1_BITSTREAM;
            }
        }
        if ((lowpower == MFX_CODINGOPTION_ON) && (info->codec_id == MFX_CODEC_AV1))
        {
            init_video_param(&videoParam);
            videoParam.mfx.CodecId = info->codec_id;
            videoParam.mfx.LowPower = lowpower;
            init_ext_av1screencontent_tools(&extAV1ScreenContentToolsParam);

            videoParam.ExtParam    = videoExtParam;
            videoParam.ExtParam[0] = (mfxExtBuffer*)&extAV1ScreenContentToolsParam;
            videoParam.NumExtParam = 1;

            status = MFXVideoENCODE_Query(session, NULL, &videoParam);
            if (status >= MFX_ERR_NONE && extAV1ScreenContentToolsParam.IntraBlockCopy != 0)
            {
                info->capabilities |= HB_QSV_CAP_AV1_SCREENCONTENT;
            }
        }
    }

    return 0;
}

const char * DRM_INTEL_DRIVER_NAME = "i915";
const char * VA_INTEL_DRIVER_NAMES[] = { "iHD", "i965", NULL};

hb_display_t * hb_qsv_display_init(const uint32_t dri_render_node)
{
    return hb_display_init(DRM_INTEL_DRIVER_NAME, dri_render_node, VA_INTEL_DRIVER_NAMES);
}

#if defined(_WIN32) || defined(__MINGW32__)
mfxIMPL hb_qsv_dx_index_to_impl(int dx_index)
{
    mfxIMPL impl;

    switch (dx_index)
    {
        {
        case 0:
            impl = MFX_IMPL_HARDWARE;
            break;
        case 1:
            impl = MFX_IMPL_HARDWARE2;
            break;
        case 2:
            impl = MFX_IMPL_HARDWARE3;
            break;
        case 3:
            impl = MFX_IMPL_HARDWARE4;
            break;

        default:
            // try searching on all display adapters
            impl = MFX_IMPL_HARDWARE_ANY;
            break;
        }
    }
    return impl;
}
#endif

// Adopted implementation of qsv_create_mfx_session() function for HandBrake
int hb_qsv_create_mfx_session(mfxIMPL implementation,
                              int drmRenderNodeNum,
                              mfxVersion *pver,
                              mfxSession *psession)
{
    mfxStatus sts;
    mfxLoader loader = NULL;
    mfxSession session = NULL;
    mfxConfig cfg;
    mfxVersion ver;
    mfxVariant impl_value;
    uint32_t adapter_idx = 0;
    uint32_t impl_idx = 0;

    // Get adapter from MediaSDK implementation value
    adapter_idx = hb_qsv_impl_get_num(implementation);

    *psession = NULL;
    loader = MFXLoad();

    if (!loader) {
        hb_error("hb_qsv_create_mfx_session: Error creating a MFX loader");
        goto fail;
    }

    /* Create configurations for implementation */
    cfg = MFXCreateConfig(loader);

    if (!cfg) {
        hb_error("hb_qsv_create_mfx_session: Error creating a MFX configuration");
        goto fail;
    }

    impl_value.Type = MFX_VARIANT_TYPE_U32;
    impl_value.Data.U32 = (implementation == MFX_IMPL_SOFTWARE) ?
        MFX_IMPL_TYPE_SOFTWARE : MFX_IMPL_TYPE_HARDWARE;
    sts = MFXSetConfigFilterProperty(cfg,
                                     (const mfxU8 *)"mfxImplDescription.Impl", impl_value);

    if (sts != MFX_ERR_NONE) {
        hb_error("hb_qsv_create_mfx_session: Error adding a MFX configuration "
               "property: %d.", sts);
        goto fail;
    }

    if (MFX_IMPL_VIA_D3D11 == MFX_IMPL_VIA_MASK(implementation))
    {
        impl_value.Type = MFX_VARIANT_TYPE_U32;
        impl_value.Data.U32 = MFX_ACCEL_MODE_VIA_D3D11;
        sts = MFXSetConfigFilterProperty(cfg,
                                        (const mfxU8 *)"mfxImplDescription.AccelerationMode", impl_value);

        if (sts != MFX_ERR_NONE) {
            hb_error("hb_qsv_create_mfx_session: Error adding a MFX configuration"
                "MFX_ACCEL_MODE_VIA_D3D11 property: %d.", sts);
            goto fail;
        }

        if (adapter_idx != -1) {
            impl_value.Type = MFX_VARIANT_TYPE_U32;
            impl_value.Data.U32 = adapter_idx;

            sts = MFXSetConfigFilterProperty(cfg,
                                            (const mfxU8 *)"mfxImplDescription.VendorImplID", impl_value);

            if (sts != MFX_ERR_NONE) {
                hb_error("hb_qsv_create_mfx_session: Error adding a MFX configuration"
                    "VendorImplID property: %d.", sts);
                goto fail;
            }
        }
    }
    else
    {
        impl_value.Type = MFX_VARIANT_TYPE_U32;
        impl_value.Data.U32 = drmRenderNodeNum;
        sts = MFXSetConfigFilterProperty(cfg,
                                        (const mfxU8 *)"mfxExtendedDeviceId.DRMRenderNodeNum", impl_value);

        if (sts != MFX_ERR_NONE) {
            hb_error("hb_qsv_create_mfx_session: Error adding a MFX configuration DRMRenderNodeNum property: %d.", sts);
            goto fail;
        }
    }

    impl_value.Type = MFX_VARIANT_TYPE_U32;
    impl_value.Data.U32 = 0x8086;
    sts = MFXSetConfigFilterProperty(cfg, (const mfxU8 *)"mfxImplDescription.VendorID", impl_value);
    if (sts != MFX_ERR_NONE) {
        hb_error("hb_qsv_create_mfx_session: MFXSetConfigFilterProperty mfxImplDescription.VendorID error=%d", sts);
        goto fail;
    }

    impl_value.Type = MFX_VARIANT_TYPE_U32;
    impl_value.Data.U32 = pver->Version;
    sts = MFXSetConfigFilterProperty(cfg,
                                     (const mfxU8 *)"mfxImplDescription.ApiVersion.Version",
                                     impl_value);

    if (sts != MFX_ERR_NONE) {
        hb_error("hb_qsv_create_mfx_session: Error adding a MFX configuration "
               "property: %d.", sts);
        goto fail;
    }

    while (1) {
        /* Enumerate all implementations */
        mfxImplDescription *impl_desc;

        sts = MFXEnumImplementations(loader, impl_idx,
                                     MFX_IMPLCAPS_IMPLDESCSTRUCTURE,
                                     (mfxHDL *)&impl_desc);

        /* Failed to find an available implementation */
        if (sts == MFX_ERR_NOT_FOUND)
            break;
        else if (sts != MFX_ERR_NONE) {
            impl_idx++;
            continue;
        }

        sts = MFXCreateSession(loader, impl_idx, &session);
        MFXDispReleaseImplDescription(loader, impl_desc);

        if (sts == MFX_ERR_NONE)
            break;

        impl_idx++;
    }

    if (sts != MFX_ERR_NONE) {
        hb_error("hb_qsv_create_mfx_session: Error creating a MFX session: %d.", sts);
        goto fail;
    }

    sts = MFXQueryVersion(session, &ver);

    if (sts != MFX_ERR_NONE) {
        hb_error("hb_qsv_create_mfx_session: Error querying a MFX session: %d.", sts);
        goto fail;
    }

    *psession = session;
    MFXUnload(loader);

    return 0;

fail:
    if (session)
        MFXClose(session);

    MFXUnload(loader);

    return AVERROR_UNKNOWN;
}

static int hb_qsv_collect_adapters_details(hb_list_t *hb_qsv_adapter_details_list)
{
    for (int i = 0; i < hb_list_count(hb_qsv_adapter_details_list); i++)
    {
        hb_qsv_adapter_details_t *details = hb_list_item(hb_qsv_adapter_details_list, i);
        /*
        * First, check for any MSDK version to determine whether one or
        * more implementations are present; then check if we can use them.
        *
        * I've had issues using a NULL version with some combinations of
        * hardware and driver, so use a low version number (1.0) instead.
        */
        mfxSession session;
        mfxVersion version = { .Major = 1, .Minor = 0, };

        // check for software fallback
        if (MFXInit(MFX_IMPL_SOFTWARE, &version, &session) == MFX_ERR_NONE)
        {
            // Media SDK software found, but check that our minimum is supported
            MFXQueryVersion(session, &details->qsv_software_version);
            if (HB_CHECK_MFX_VERSION(details->qsv_software_version,
                                    HB_QSV_MINVERSION_MAJOR,
                                    HB_QSV_MINVERSION_MINOR))
            {
                query_capabilities(session, details->index, details->qsv_software_version, &details->qsv_software_info_avc, MFX_CODINGOPTION_OFF);
                query_capabilities(session, details->index, details->qsv_software_version, &details->qsv_software_info_hevc, MFX_CODINGOPTION_OFF);
                // now that we know which hardware encoders are
                // available, we can set the preferred implementation
                qsv_impl_set_preferred(details, "software");
            }
            MFXClose(session);
        }
        // check for actual hardware support
#if defined(_WIN32) || defined(__MINGW32__)
        mfxIMPL hw_preference = MFX_IMPL_VIA_D3D11;
#else
        mfxIMPL hw_preference = MFX_IMPL_VIA_ANY;
#endif
        do{
#if defined(_WIN32) || defined(__MINGW32__)
            mfxIMPL hw_impl = hb_qsv_dx_index_to_impl(details->index);
#else
            mfxIMPL hw_impl = MFX_IMPL_HARDWARE_ANY;
#endif
            if (hb_qsv_create_mfx_session(hw_impl | hw_preference, details->extended_device_id.DRMRenderNodeNum, &version, &session) == MFX_ERR_NONE)
            {
                // On linux, the handle to the VA display must be set.
                // This code is essentially a NOP other platforms.
                hb_display_t * display = hb_qsv_display_init(details->extended_device_id.DRMRenderNodeNum);
                if (display != NULL)
                {
                    MFXVideoCORE_SetHandle(session, display->mfxType,
                                        (mfxHDL)display->handle);
                }
                // Media SDK hardware found, but check that our minimum is supported
                //
                // Note: this-party hardware (QSV_G0) is unsupported for the time being
                MFXQueryVersion(session, &details->qsv_hardware_version);
                if (hb_qsv_hardware_generation(hb_qsv_get_platform(details->index)) >= QSV_G1 &&
                    HB_CHECK_MFX_VERSION(details->qsv_hardware_version,
                                        HB_QSV_MINVERSION_MAJOR,
                                        HB_QSV_MINVERSION_MINOR))
                {
                    if (hb_qsv_hardware_generation(hb_qsv_get_platform(details->index)) >= QSV_G7)
                    {
                        query_capabilities(session, details->index, details->qsv_hardware_version, &details->qsv_hardware_info_avc, MFX_CODINGOPTION_ON);
                    }
                    if (details->qsv_hardware_info_avc.available == 0)
                    {
                        query_capabilities(session, details->index, details->qsv_hardware_version, &details->qsv_hardware_info_avc, MFX_CODINGOPTION_OFF);
                    }
                    details->qsv_hardware_info_avc.implementation = hw_impl | hw_preference;
                    if (hb_qsv_hardware_generation(hb_qsv_get_platform(details->index)) >= QSV_G7)
                    {
                        query_capabilities(session, details->index, details->qsv_hardware_version, &details->qsv_hardware_info_hevc, MFX_CODINGOPTION_ON);
                    }
                    if (details->qsv_hardware_info_hevc.available == 0)
                    {
                        query_capabilities(session, details->index, details->qsv_hardware_version, &details->qsv_hardware_info_hevc, MFX_CODINGOPTION_OFF);
                    }
                    details->qsv_hardware_info_hevc.implementation = hw_impl | hw_preference;
                    if (hb_qsv_hardware_generation(hb_qsv_get_platform(details->index)) > QSV_G8)
                    {
                        query_capabilities(session, details->index, details->qsv_hardware_version, &details->qsv_hardware_info_av1, MFX_CODINGOPTION_ON);
                        details->qsv_hardware_info_av1.implementation = hw_impl | hw_preference;
                    }
                    // now that we know which hardware encoders are
                    // available, we can set the preferred implementation
                    qsv_impl_set_preferred(details, "hardware");
                }
                MFXClose(session);
                // display must be closed after MFXClose
                hb_display_close(&display);
                hw_preference = 0;
            }
            else
            {
#if defined(_WIN32) || defined(__MINGW32__)
                // Windows only: After D3D11 we will try D3D9
                if (hw_preference == MFX_IMPL_VIA_D3D11)
                    hw_preference = MFX_IMPL_VIA_D3D9;
                else
#endif
                    hw_preference = 0;
            }
        }
        while(hw_preference != 0);
    }
    return 0;
}

static void log_decoder_capabilities(const int log_level, const hb_qsv_adapter_details_t *adapter_details, const char *prefix)
{
    char buffer[128] = "";

    if (hb_qsv_decode_h264_is_supported(adapter_details->index))
    {
        strcat(buffer, " h264");
    }

    if (hb_qsv_decode_h265_10_bit_is_supported(adapter_details->index))
    {
        strcat(buffer, " hevc (8bit: yes, 10bit: yes)");
    }
    else if (hb_qsv_decode_h265_is_supported(adapter_details->index))
    {
        strcat(buffer, " hevc (8bit: yes, 10bit: no)");
    }

    if (hb_qsv_decode_av1_is_supported(adapter_details->index))
    {
        strcat(buffer, " av1 (8bit: yes, 10bit: yes)");
    }

    if (hb_qsv_decode_vvc_is_supported(adapter_details->index))
    {
        strcat(buffer, " vvc (8bit: yes, 10bit: yes)");
    }

    hb_deep_log(log_level, "%s%s", prefix,
                strnlen(buffer, 1) ? buffer : " no decode support");
}

static void log_encoder_capabilities(const int log_level, const uint64_t caps, const char *prefix)
{
    /*
     * Note: keep the string short, as it may be logged by default.
     */
    char buffer[128] = "";

    if (caps & HB_QSV_CAP_LOWPOWER_ENCODE)
    {
        strcat(buffer, " lowpower");
    }
    /* B-Pyramid, with or without direct control (BRefType) */
    if (caps & HB_QSV_CAP_B_REF_PYRAMID)
    {
        if (caps & HB_QSV_CAP_OPTION2_BREFTYPE)
        {
            strcat(buffer, " breftype");
        }
        else
        {
            strcat(buffer, " bpyramid");
        }
    }
    /* Rate control: ICQ, lookahead (options: interlaced, downsampling) */
    if (caps & HB_QSV_CAP_RATECONTROL_LA)
    {
        if (caps & HB_QSV_CAP_RATECONTROL_ICQ)
        {
            strcat(buffer, " icq+la");
        }
        else
        {
            strcat(buffer, " la");
        }
        if (caps & HB_QSV_CAP_RATECONTROL_LAi)
        {
            strcat(buffer, "+i");
        }
        if (caps & HB_QSV_CAP_OPTION2_LA_DOWNS)
        {
            strcat(buffer, "+downs");
        }
    }
    else if (caps & HB_QSV_CAP_RATECONTROL_ICQ)
    {
        strcat(buffer, " icq");
    }
    if (caps & HB_QSV_CAP_VUI_VSINFO)
    {
        strcat(buffer, " vsinfo");
    }
    if (caps & HB_QSV_CAP_VUI_CHROMALOCINFO)
    {
        strcat(buffer, " chromalocinfo");
    }
    if (caps & HB_QSV_CAP_VUI_MASTERINGINFO)
    {
        strcat(buffer, " masteringinfo");
    }
    if (caps & HB_QSV_CAP_VUI_CLLINFO)
    {
        strcat(buffer, " cllinfo");
    }
    if (caps & HB_QSV_CAP_OPTION1)
    {
        strcat(buffer, " opt1");
    }
    if (caps & HB_QSV_CAP_OPTION2)
    {
        {
            strcat(buffer, " opt2");
        }
        if (caps & HB_QSV_CAP_OPTION2_MBBRC)
        {
            strcat(buffer, "+mbbrc");
        }
        if (caps & HB_QSV_CAP_OPTION2_EXTBRC)
        {
            strcat(buffer, "+extbrc");
        }
        if (caps & HB_QSV_CAP_OPTION2_TRELLIS)
        {
            strcat(buffer, "+trellis");
        }
        if (caps & HB_QSV_CAP_OPTION2_REPEATPPS)
        {
            strcat(buffer, "+repeatpps");
        }
        if (caps & HB_QSV_CAP_OPTION2_IB_ADAPT)
        {
            strcat(buffer, "+ib_adapt");
        }
        if (caps & HB_QSV_CAP_OPTION2_NMPSLICE)
        {
            strcat(buffer, "+nmpslice");
        }
    }
    if (caps & HB_QSV_CAP_AV1_SCREENCONTENT)
    {
        strcat(buffer, " av1screencontent");
    }
    if (caps & HB_QSV_CAP_HYPERENCODE)
    {
        strcat(buffer, " hyperencode");
    }
    if (caps & HB_QSV_CAP_AV1_BITSTREAM)
    {
        strcat(buffer, " av1bitstream");
    }

    hb_deep_log(log_level, "%s%s", prefix,
                strnlen(buffer, 1) ? buffer : " standard feature set");
}

static void hb_qsv_adapter_info_print(const hb_qsv_adapter_details_t *adapter_details)
{
    if (adapter_details->qsv_hardware_version.Version)
    {
        hb_log(" - Intel Media SDK hardware: API %"PRIu16".%"PRIu16" (minimum: %"PRIu16".%"PRIu16")",
                adapter_details->qsv_hardware_version.Major, adapter_details->qsv_hardware_version.Minor,
                HB_QSV_MINVERSION_MAJOR, HB_QSV_MINVERSION_MINOR);
    }

    if (adapter_details->qsv_software_version.Version)
    {
        hb_deep_log(3, " - Intel Media SDK software: API %"PRIu16".%"PRIu16" (minimum: %"PRIu16".%"PRIu16")",
                adapter_details->qsv_software_version.Major, adapter_details->qsv_software_version.Minor,
                HB_QSV_MINVERSION_MAJOR, HB_QSV_MINVERSION_MINOR);
    }

    log_decoder_capabilities(1, adapter_details, " - Decode support: ");

    if (adapter_details->hb_qsv_info_avc != NULL && adapter_details->hb_qsv_info_avc->available)
    {
        hb_log(" - H.264 encoder: yes");
        hb_log("    - preferred implementation: %s %s",
                hb_qsv_impl_get_name(adapter_details->hb_qsv_info_avc->implementation),
                hb_qsv_impl_get_via_name(adapter_details->hb_qsv_info_avc->implementation));
        if (adapter_details->qsv_hardware_info_avc.available)
        {
            log_encoder_capabilities(1, adapter_details->qsv_hardware_info_avc.capabilities,
                                "    - capabilities (hardware): ");
        }
        if (adapter_details->qsv_software_info_avc.available)
        {
            log_encoder_capabilities(3, adapter_details->qsv_software_info_avc.capabilities,
                                "    - capabilities (software): ");
        }
    }
    else
    {
        hb_log(" - H.264 encoder: no");
    }
    if (adapter_details->hb_qsv_info_hevc != NULL && adapter_details->hb_qsv_info_hevc->available)
    {
        hb_log(" - H.265 encoder: yes (8bit: yes, 10bit: %s)", (hb_qsv_hardware_generation(hb_qsv_get_platform(adapter_details->index)) < QSV_G6) ? "no" : "yes" );
        hb_log("    - preferred implementation: %s %s",
                hb_qsv_impl_get_name(adapter_details->hb_qsv_info_hevc->implementation),
                hb_qsv_impl_get_via_name(adapter_details->hb_qsv_info_hevc->implementation));
        if (adapter_details->qsv_hardware_info_hevc.available)
        {
            log_encoder_capabilities(1, adapter_details->qsv_hardware_info_hevc.capabilities,
                                "    - capabilities (hardware): ");
        }
        if (adapter_details->qsv_software_info_hevc.available)
        {
            log_encoder_capabilities(3, adapter_details->qsv_software_info_hevc.capabilities,
                                "    - capabilities (software): ");
        }
    }
    else
    {
        hb_log(" - H.265 encoder: no");
    }
    if (adapter_details->hb_qsv_info_av1 != NULL && adapter_details->hb_qsv_info_av1->available)
    {
        hb_log(" - AV1 encoder: yes (8bit: yes, 10bit: yes)");
        hb_log("    - preferred implementation: %s %s",
                hb_qsv_impl_get_name(adapter_details->hb_qsv_info_av1->implementation),
                hb_qsv_impl_get_via_name(adapter_details->hb_qsv_info_av1->implementation));
        if (adapter_details->qsv_hardware_info_av1.available)
        {
            log_encoder_capabilities(1, adapter_details->qsv_hardware_info_av1.capabilities,
                                "    - capabilities (hardware): ");
        }
    }
    else
    {
        hb_log(" - AV1 encoder: no");
    }
}

void hb_qsv_info_print()
{
    // is QSV available and usable?
    if (hb_qsv_available())
    {
#if defined(_WIN32) || defined(__MINGW32__)
        if (hb_list_count(g_qsv_adapters_details_list))
        {
            char gpu_list_str[256] = "";
            for (int i = 0; i < hb_list_count(g_qsv_adapters_details_list); i++)
            {
                const hb_qsv_adapter_details_t *details = hb_list_item(g_qsv_adapters_details_list, i);
                char value_str[256];
                snprintf(value_str, sizeof(value_str), "%d", details->index);
                if (i > 0)
                    strcat(gpu_list_str, ", ");
                strcat(gpu_list_str, value_str);
            }
            hb_log("Intel Quick Sync Video support: yes, gpu list: %s", gpu_list_str);
        }
        else
#endif
        {
            hb_log("Intel Quick Sync Video support: yes");
        }
        // also print the details about all QSV adapters
        for (int i = 0; i < hb_list_count(g_qsv_adapters_details_list); i++)
        {
            const hb_qsv_adapter_details_t *details = hb_list_item(g_qsv_adapters_details_list, i);
#if defined(_WIN32) || defined(__MINGW32__)
            hb_log("Intel Quick Sync Video %s adapter with index %d", hb_qsv_get_adapter_type(details), details->index);
#else
            hb_log("Intel Quick Sync Video %s adapter with index %d and renderD%d",
                hb_qsv_get_adapter_type(details), details->index, details->extended_device_id.DRMRenderNodeNum);
#endif
            hb_log("Impl %s library path: %s", details->impl_name, details->impl_path);
            hb_qsv_adapter_info_print(details);
        }
    }
    else
    {
        hb_log("Intel Quick Sync Video support: no");
    }
}

hb_qsv_info_t* hb_qsv_encoder_info_get(int adapter_index, int encoder)
{
    const hb_qsv_adapter_details_t *details = hb_qsv_get_adapters_details_by_index(adapter_index);

    if (details)
    {
        switch (encoder)
        {
            case HB_VCODEC_FFMPEG_QSV_H264:
                return details->hb_qsv_info_avc;
            case HB_VCODEC_FFMPEG_QSV_H265_10BIT:
            case HB_VCODEC_FFMPEG_QSV_H265:
                return details->hb_qsv_info_hevc;
            case HB_VCODEC_FFMPEG_QSV_AV1_10BIT:
            case HB_VCODEC_FFMPEG_QSV_AV1:
                return details->hb_qsv_info_av1;
            default:
                return NULL;
        }
    }
    return NULL;
}

const char* hb_qsv_decode_get_codec_name(enum AVCodecID codec_id)
{
    switch (codec_id)
    {
        case AV_CODEC_ID_H264:
            return "h264_qsv";

        case AV_CODEC_ID_HEVC:
            return "hevc_qsv";

        case AV_CODEC_ID_MPEG2VIDEO:
            return "mpeg2_qsv";

        case AV_CODEC_ID_AV1:
            return "av1_qsv";

        case AV_CODEC_ID_VVC:
            return "vvc_qsv";

        default:
            return NULL;
    }
}

int hb_qsv_decode_h264_is_supported(int adapter_index)
{
    return hb_qsv_hardware_generation(hb_qsv_get_platform(adapter_index)) >= QSV_G1;
}

int hb_qsv_decode_h265_is_supported(int adapter_index)
{
    return hb_qsv_hardware_generation(hb_qsv_get_platform(adapter_index)) >= QSV_G5;
}

int hb_qsv_decode_h265_10_bit_is_supported(int adapter_index)
{
    return hb_qsv_hardware_generation(hb_qsv_get_platform(adapter_index)) >= QSV_G6;
}

int hb_qsv_decode_av1_is_supported(int adapter_index)
{
    return hb_qsv_hardware_generation(hb_qsv_get_platform(adapter_index)) >= QSV_G8;
}

int hb_qsv_decode_vvc_is_supported(int adapter_index)
{
    return hb_qsv_hardware_generation(hb_qsv_get_platform(adapter_index)) > QSV_G9;
}

int hb_qsv_decode_is_codec_supported(int adapter_index, int video_codec_param, int pix_fmt, int width, int height)
{
    switch (video_codec_param)
    {
        case AV_CODEC_ID_H264:
            // QSV decode for AVC does not support higher video resolutions
            if (width > HB_QSV_AVC_DECODER_WIDTH_MAX || height > HB_QSV_AVC_DECODER_HEIGHT_MAX)
                return 0;

            if (pix_fmt == AV_PIX_FMT_NV12     ||
                pix_fmt == AV_PIX_FMT_YUV420P  ||
                pix_fmt == AV_PIX_FMT_YUVJ420P)
            {
                return hb_qsv_decode_h264_is_supported(adapter_index);
            }
            break;
        case AV_CODEC_ID_HEVC:
            if (pix_fmt == AV_PIX_FMT_NV12     ||
                pix_fmt == AV_PIX_FMT_YUV420P  ||
                pix_fmt == AV_PIX_FMT_YUVJ420P)
            {
                return hb_qsv_decode_h265_is_supported(adapter_index);
            }
            else if (pix_fmt == AV_PIX_FMT_P010LE ||
                pix_fmt == AV_PIX_FMT_YUV420P10)
            {
                return hb_qsv_decode_h265_10_bit_is_supported(adapter_index);
            }
            break;
        case AV_CODEC_ID_AV1:
            if (pix_fmt == AV_PIX_FMT_NV12     ||
                pix_fmt == AV_PIX_FMT_P010LE   ||
                pix_fmt == AV_PIX_FMT_YUV420P  ||
                pix_fmt == AV_PIX_FMT_YUVJ420P ||
                pix_fmt == AV_PIX_FMT_YUV420P10)
            {
                return hb_qsv_decode_av1_is_supported(adapter_index);
            }
            break;
        case AV_CODEC_ID_VVC:
            if (pix_fmt == AV_PIX_FMT_NV12     ||
                pix_fmt == AV_PIX_FMT_P010LE   ||
                pix_fmt == AV_PIX_FMT_YUV420P  ||
                pix_fmt == AV_PIX_FMT_YUVJ420P ||
                pix_fmt == AV_PIX_FMT_YUV420P10)
            {
                return hb_qsv_decode_vvc_is_supported(adapter_index);
            }
            break;
        default:
            return 0;
    }
    return 0;
}

static int hb_qsv_parse_options(hb_job_t *job)
{
    int err = 0;

    if (job->encoder_options != NULL && *job->encoder_options)
    {
        hb_dict_t *options_list;
        options_list = hb_encopts_to_dict(job->encoder_options, job->vcodec);
        hb_dict_iter_t iter;
        for (iter  = hb_dict_iter_init(options_list);
            iter != HB_DICT_ITER_DONE;
            iter  = hb_dict_iter_next(options_list, iter))
        {
            const char *key = hb_dict_iter_key(iter);
            hb_value_t *value = hb_dict_iter_value(iter);
            if (!strcasecmp(key, "gpu"))
            {
                char *str = hb_value_get_string_xform(value);
                int dx_index = hb_qsv_atoi(str, &err);
                free(str);
                if (!err)
                {
                    hb_log("hb_qsv_parse_options: gpu=%d", dx_index);
                    hb_qsv_param_parse_dx_index(job, dx_index);
                }
            }
            else if (!strcasecmp(key, "async-depth"))
            {
                char *str = hb_value_get_string_xform(value);
                int async_depth = hb_qsv_atoi(str, &err);
                free(str);
                if (!err)
                {
                    job->hw_device_async_depth = async_depth;
                }
            }
            else if (!strcasecmp(key, "memory-type"))
            {
                hb_triplet_t *mode = NULL;
                mode = hb_triplet4key(hb_qsv_memory_types, hb_value_get_string_xform(value));
                if (!mode)
                {
                    err = HB_QSV_PARAM_BAD_VALUE;
                }
                else
                {
                    job->qsv_ctx->memory_type = mode->value;
                }
            }
            else if (!strcasecmp(key, "scalingmode") ||
                     !strcasecmp(key, "vpp-sm"))
            {
                hb_triplet_t *mode = NULL;
                mode = hb_triplet4key(hb_qsv_vpp_scale_modes, hb_value_get_string_xform(value));
                if (!mode)
                {
                    err = HB_QSV_PARAM_BAD_VALUE;
                }
                else
                {
                    job->qsv_ctx->vpp_scale_mode = mode->name;
                }
            }
        }
        hb_dict_free(&options_list);
    }
    return 0;
}

int hb_qsv_setup_job(hb_job_t *job)
{
    if (job->qsv_ctx == NULL)
    {
        return 1;
    }

    // Parse the json parameter
    if (job->hw_device_index > -1)
    {
        hb_qsv_param_parse_dx_index(job, job->hw_device_index);
    }
    else
    {
        job->hw_device_index = hb_qsv_get_default_adapter_index();
    }

    // Parse the advanced options parameter
    hb_qsv_parse_options(job);

    int async_depth_default = hb_qsv_param_default_async_depth();
    if (job->hw_device_async_depth <= 0 || job->hw_device_async_depth > async_depth_default)
    {
        job->hw_device_async_depth = async_depth_default;
    }

    // Make sure QSV Decode is only True if the selected QSV adapter supports decode
    if (job->hw_decode & HB_DECODE_QSV)
    {
        int is_codec_supported = hb_qsv_decode_is_codec_supported(hb_qsv_get_adapter_index(),
            job->title->video_codec_param, job->input_pix_fmt,
            job->title->geometry.width, job->title->geometry.height);

        if (is_codec_supported == 0)
        {
            job->hw_decode &= ~HB_DECODE_QSV;
        }
    }

    return 0;
}

int hb_qsv_get_memory_type(hb_job_t *job)
{
    int qsv_full_path_is_enabled = hb_qsv_full_path_is_enabled(job);

    if (qsv_full_path_is_enabled)
    {
        if (job->qsv_ctx->memory_type == MFX_IOPATTERN_OUT_VIDEO_MEMORY)
            return MFX_IOPATTERN_OUT_VIDEO_MEMORY;
        else if (job->qsv_ctx->memory_type == MFX_IOPATTERN_OUT_SYSTEM_MEMORY)
            return MFX_IOPATTERN_OUT_SYSTEM_MEMORY;
    }

    return qsv_full_path_is_enabled ? MFX_IOPATTERN_OUT_VIDEO_MEMORY : MFX_IOPATTERN_OUT_SYSTEM_MEMORY;
}

static int are_filters_supported(hb_list_t *filters)
{
#if defined(_WIN32) || defined(__MINGW32__)
    int num_sw_filters = 0;
    for (int i = 0; i < hb_list_count(filters); i++)
    {
        hb_filter_object_t *filter = hb_list_item(filters, i);
        switch (filter->id)
        {
            // pixel format conversion is done via VPP filter
            case HB_FILTER_FORMAT:
            // cropping and scaling always done via VPP filter
            case HB_FILTER_CROP_SCALE:
            case HB_FILTER_ROTATE:
            case HB_FILTER_AVFILTER:
                break;
            case HB_FILTER_VFR:
            {
                // Mode 0 doesn't require access to the frame data
                int mode = hb_dict_get_int(filter->settings, "mode");
                if (mode == 0)
                {
                    break;
                }
            }
            default:
                // count only filters with access to frame data
                num_sw_filters++;
                break;
        }
    }
    return num_sw_filters == 0;
#else // other OS
    return 0;
#endif
}

int hb_qsv_full_path_is_enabled(hb_job_t *job)
{
    int qsv_full_path_is_enabled = 0;
    if (!job || !job->qsv_ctx)
    {
        return 0;
    }
#if defined(_WIN32) || defined(__MINGW32__)
    hb_qsv_info_t *info = hb_qsv_encoder_info_get(hb_qsv_get_adapter_index(), job->vcodec);

    qsv_full_path_is_enabled = (job->hw_decode & HB_DECODE_QSV &&
        info && hb_qsv_implementation_is_hardware(info->implementation) &&
        job->qsv_ctx && are_filters_supported(job->list_filter));
#endif
    return qsv_full_path_is_enabled;
}

int hb_qsv_atoindex(const char* const *arr, const char *str, int *err)
{
    int i;
    for (i = 0; arr[i] != NULL; i++)
    {
        if (!strcasecmp(arr[i], str))
        {
            break;
        }
    }
    *err = (arr[i] == NULL);
    return i;
}

// adapted from libx264
int hb_qsv_atobool(const char *str, int *err)
{
    if (!strcasecmp(str,    "1") ||
        !strcasecmp(str,  "yes") ||
        !strcasecmp(str, "true"))
    {
        return 1;
    }
    if (!strcasecmp(str,     "0") ||
        !strcasecmp(str,    "no") ||
        !strcasecmp(str, "false"))
    {
        return 0;
    }
    *err = 1;
    return 0;
}

// adapted from libx264
int hb_qsv_atoi(const char *str, int *err)
{
    char *end;
    int v = strtol(str, &end, 0);
    if (end == str || end[0] != '\0')
    {
        *err = 1;
    }
    return v;
}

// adapted from libx264
float hb_qsv_atof(const char *str, int *err)
{
    char *end;
    float v = strtod(str, &end);
    if (end == str || end[0] != '\0')
    {
        *err = 1;
    }
    return v;
}

static void add_qsv_param(AVDictionary** av_opts, const char *key, const char *value)
{
    // qsv_params=KEY=VALUE:NEXTKEY=NEXTVALUE
    char c[100] = {0};

    if(av_dict_get(*av_opts, "qsv_params", NULL, 0) != NULL)
    {
        strcat(c, ":"); // add separator if already exists
    }

    strcat(c, key);
    strcat(c, "=");
    strcat(c, value);

    av_dict_set(av_opts, "qsv_params", c, AV_DICT_APPEND);
}

static void add_qsv_param_u32(AVDictionary** av_opts, const char *key, mfxU32 value)
{
    if(value == 0) return; // Default == 0; skip

    int len = snprintf(NULL, 0, "%"PRIu32"", value); // u16 == u32
    char c[100] = {0};
    snprintf(c, len + 1, "%"PRIu32"", value);

    add_qsv_param(av_opts, key, c);
}

int hb_qsv_select_ffmpeg_options(qsv_data_t * qsv_data, hb_job_t *job, AVDictionary **av_opts)
{
#define MFX_STRUCT_TO_AV_OPTS(MEMBER_NAME) add_qsv_param_u32(av_opts, #MEMBER_NAME, param->videoParam->mfx.MEMBER_NAME);

    hb_qsv_info_t *qsv_info = qsv_data->qsv_info;
    hb_qsv_param_t *param = &qsv_data->param;

    int hw_generation = hb_qsv_hardware_generation(hb_qsv_get_platform(hb_qsv_get_adapter_index()));
    // sanitize ICQ
    // workaround for MediaSDK platforms below TGL to disable ICQ if incorrectly detected
    if (!(qsv_info->capabilities & HB_QSV_CAP_RATECONTROL_ICQ) ||
        ((param->videoParam->mfx.LowPower == MFX_CODINGOPTION_ON) && (hw_generation < QSV_G8)))
    {
        // ICQ not supported
        param->rc.icq = 0;
    }
    else
    {
        param->rc.icq = param->rc.icq && job->vquality > HB_INVALID_VIDEO_QUALITY;
    }

    // sanitize lookahead
    if (!(qsv_info->capabilities & HB_QSV_CAP_RATECONTROL_LA))
    {
        // lookahead not supported
        param->rc.lookahead = 0;
    }
    else if ((param->rc.lookahead)                                      &&
             (qsv_info->capabilities & HB_QSV_CAP_RATECONTROL_LAi) == 0 &&
             (param->videoParam->mfx.FrameInfo.PicStruct != MFX_PICSTRUCT_PROGRESSIVE))
    {
        // lookahead enabled but we can't use it
        hb_log("encqsvInit: LookAhead not used (LookAhead is progressive-only)");
        param->rc.lookahead = 0;
    }
    else
    {
        param->rc.lookahead = param->rc.lookahead && (param->rc.icq || job->vquality <= HB_INVALID_VIDEO_QUALITY);
    }

    if (job->qsv_ctx != NULL)
    {
        job->qsv_ctx->la_is_enabled = param->rc.lookahead ? 1 : 0;
    }

    // libmfx BRC parameters are 16 bits thus maybe overflow, then BRCParamMultiplier is needed
    // Comparison vbitrate in Kbps (kilobit) with vbv_max_bitrate, vbv_buffer_size, vbv_buffer_init in KB (kilobyte)
    int brc_param_multiplier = (FFMAX(FFMAX3(job->vbitrate, param->rc.vbv_max_bitrate, param->rc.vbv_buffer_size),
                            param->rc.vbv_buffer_init) + 0x10000) / 0x10000;
    // set VBV here (this will be overridden for CQP and ignored for LA)
    // only set BufferSizeInKB, InitialDelayInKB and MaxKbps if we have
    // them - otherwise Media SDK will pick values for us automatically
    if (param->rc.vbv_buffer_size > 0)
    {
        if (param->rc.vbv_buffer_init > 1.0)
        {
            param->videoParam->mfx.InitialDelayInKB = (param->rc.vbv_buffer_init / 8) / brc_param_multiplier;
            MFX_STRUCT_TO_AV_OPTS(InitialDelayInKB)
        }
        else if (param->rc.vbv_buffer_init > 0.0)
        {
            param->videoParam->mfx.InitialDelayInKB = (param->rc.vbv_buffer_size *
                                                          param->rc.vbv_buffer_init / 8) / brc_param_multiplier;
            MFX_STRUCT_TO_AV_OPTS(InitialDelayInKB)
        }
        param->videoParam->mfx.BufferSizeInKB       = (param->rc.vbv_buffer_size / 8) / brc_param_multiplier;
        MFX_STRUCT_TO_AV_OPTS(BufferSizeInKB)
        param->videoParam->mfx.BRCParamMultiplier   = brc_param_multiplier;
    }
    if (param->rc.vbv_max_bitrate > 0)
    {
        param->videoParam->mfx.MaxKbps              = param->rc.vbv_max_bitrate / brc_param_multiplier;
        MFX_STRUCT_TO_AV_OPTS(MaxKbps)
        param->videoParam->mfx.BRCParamMultiplier   = brc_param_multiplier;
    }

    // set rate control parameters
    if (job->vquality > HB_INVALID_VIDEO_QUALITY)
    {
        unsigned int upper_limit = 51;

        if (param->rc.icq)
        {
            // introduced in API 1.8
            if (param->rc.lookahead)
            {
                param->videoParam->mfx.RateControlMethod = MFX_RATECONTROL_LA_ICQ;
            }
            else
            {
                param->videoParam->mfx.RateControlMethod = MFX_RATECONTROL_ICQ;
            }
            param->videoParam->mfx.ICQQuality = HB_QSV_CLIP3(1, upper_limit, job->vquality);
            MFX_STRUCT_TO_AV_OPTS(ICQQuality)
        }
        else
        {
            // introduced in API 1.1
            // HEVC 10b has QP range as [-12;51]
            // with shift +12 needed to be in QSV's U16 range
            if (param->videoParam->mfx.CodecProfile == MFX_PROFILE_HEVC_MAIN10)
            {
                upper_limit = 63;
            }
            if (param->videoParam->mfx.CodecId == MFX_CODEC_AV1)
            {
                upper_limit = 255;
            }

            param->videoParam->mfx.RateControlMethod = MFX_RATECONTROL_CQP;
            param->videoParam->mfx.QPI = HB_QSV_CLIP3(0, upper_limit, job->vquality + param->rc.cqp_offsets[0]);
            param->videoParam->mfx.QPP = HB_QSV_CLIP3(0, upper_limit, job->vquality + param->rc.cqp_offsets[1]);
            param->videoParam->mfx.QPB = HB_QSV_CLIP3(0, upper_limit, job->vquality + param->rc.cqp_offsets[2]);

            MFX_STRUCT_TO_AV_OPTS(QPI)
            MFX_STRUCT_TO_AV_OPTS(QPP)
            MFX_STRUCT_TO_AV_OPTS(QPB)

            // CQP + ExtBRC can cause bad output
            param->codingOption2.ExtBRC = MFX_CODINGOPTION_OFF;
            av_dict_set(av_opts, "extbrc", "0", 0); // MFX_CODINGOPTION_OFF
        }
    }
    else if (job->vbitrate > 0)
    {
        if (param->rc.lookahead)
        {
            // introduced in API 1.7
            param->videoParam->mfx.RateControlMethod  = MFX_RATECONTROL_LA;
            param->videoParam->mfx.TargetKbps         = job->vbitrate / brc_param_multiplier;
            MFX_STRUCT_TO_AV_OPTS(TargetKbps)
            param->videoParam->mfx.BRCParamMultiplier = brc_param_multiplier;
            // ignored, but some drivers will change AsyncDepth because of it
            param->codingOption2.ExtBRC = MFX_CODINGOPTION_OFF;
            av_dict_set(av_opts, "extbrc", "0", 0); // MFX_CODINGOPTION_OFF
        }
        else
        {
            // introduced in API 1.0
            if (job->vbitrate == param->rc.vbv_max_bitrate)
            {
                param->videoParam->mfx.RateControlMethod = MFX_RATECONTROL_CBR;
            }
            else
            {
                param->videoParam->mfx.RateControlMethod = MFX_RATECONTROL_VBR;
            }
            param->videoParam->mfx.TargetKbps            = job->vbitrate / brc_param_multiplier;
            MFX_STRUCT_TO_AV_OPTS(TargetKbps)
            param->videoParam->mfx.BRCParamMultiplier    = brc_param_multiplier;
        }
    }
    else
    {
        hb_error("encqsvInit: invalid rate control (%f, %d)",
                 job->vquality, job->vbitrate);
        return -1;
    }

    MFX_STRUCT_TO_AV_OPTS(RateControlMethod);

    // if VBV is enabled but ignored, log it
    if (param->rc.vbv_max_bitrate > 0 || param->rc.vbv_buffer_size > 0)
    {
        switch (param->videoParam->mfx.RateControlMethod)
        {
            case MFX_RATECONTROL_LA:
            case MFX_RATECONTROL_LA_ICQ:
                hb_log("encqsvInit: LookAhead enabled, ignoring VBV");
                break;
            case MFX_RATECONTROL_ICQ:
                hb_log("encqsvInit: ICQ rate control, ignoring VBV");
                break;
            default:
                break;
        }
    }

    // set the GOP structure
    if (param->gop.gop_ref_dist < 0)
    {
        if ((hw_generation >= QSV_G8) &&
            (param->videoParam->mfx.CodecId == MFX_CODEC_HEVC ||
            param->videoParam->mfx.CodecId == MFX_CODEC_AV1))
        {
            param->gop.gop_ref_dist = 8;
        }
        else
        {
            param->gop.gop_ref_dist = 4;
        }
    }
    param->videoParam->mfx.GopRefDist = param->gop.gop_ref_dist;

    // set the keyframe interval
    if (param->gop.gop_pic_size < 0)
    {
        double rate = (double)job->orig_vrate.num / job->orig_vrate.den + 0.5;
        // set the keyframe interval based on the framerate
        param->gop.gop_pic_size = (int)(FFMIN(rate * 2, 120));
    }
    param->videoParam->mfx.GopPicSize = param->gop.gop_pic_size;

    // set the Hyper Encode structure
    if (param->hyperEncodeParam->value != MFX_HYPERMODE_OFF)
    {
        if (param->videoParam->mfx.CodecId == MFX_CODEC_HEVC)
        {
            param->videoParam->mfx.IdrInterval = 1;
        }
        else if (param->videoParam->mfx.CodecId == MFX_CODEC_AVC)
        {
            param->videoParam->mfx.IdrInterval = 0;
        }

        MFX_STRUCT_TO_AV_OPTS(IdrInterval)
        // sanitize some of the encoding parameters
        param->videoParam->mfx.GopPicSize = (int)(FFMIN(param->gop.gop_pic_size, 60));
        param->videoParam->AsyncDepth = (int)(FFMAX(param->videoParam->AsyncDepth, 60));
        av_dict_set_int(av_opts, "async_depth", param->videoParam->AsyncDepth, 0);

        char hyperencode[16];
        snprintf(hyperencode, sizeof(hyperencode), "%s", qsv_data->param.hyperEncodeParam->key);
        av_dict_set(av_opts, "dual_gfx", hyperencode, 0);
        hb_log("encavcodec: Hyper Encoding mode: %s", hyperencode);
    }
    MFX_STRUCT_TO_AV_OPTS(GopPicSize)

    // sanitize some settings that affect memory consumption
    if (!qsv_data->is_sys_mem)
    {
        // limit these to avoid running out of resources (causes hang)
        param->videoParam->mfx.GopRefDist   = FFMIN(param->videoParam->mfx.GopRefDist,
                                                       param->rc.lookahead ? 8 : 16);
        param->codingOption2.LookAheadDepth = FFMIN(param->codingOption2.LookAheadDepth,
                                                       param->rc.lookahead ? (48 - param->videoParam->mfx.GopRefDist -
                                                                                 3 * !param->videoParam->mfx.GopRefDist) : 0);
    }
    else
    {
        // encode-only is a bit less sensitive to memory issues
        param->videoParam->mfx.GopRefDist   = FFMIN(param->videoParam->mfx.GopRefDist, 16);
        param->codingOption2.LookAheadDepth = FFMIN(param->codingOption2.LookAheadDepth,
                                                       param->rc.lookahead ? 100 : 0);
    }
    MFX_STRUCT_TO_AV_OPTS(GopRefDist)

    if (param->rc.lookahead)
    {
        // LookAheadDepth 10 will cause a hang with some driver versions
        param->codingOption2.LookAheadDepth = FFMAX(param->codingOption2.LookAheadDepth, 11);
    }
    av_dict_set_int(av_opts, "look_ahead_depth", param->codingOption2.LookAheadDepth, 0);

    if(qsv_data->qsv_info->capabilities & HB_QSV_CAP_LOWPOWER_ENCODE)
    {
        char low_power[7];
        snprintf(low_power, sizeof(low_power), "%d", qsv_data->param.low_power);
        av_dict_set(av_opts, "low_power", low_power, 0);
        if(qsv_data->param.low_power)
            hb_log("encavcodec: using Low Power mode");
    }

    if((qsv_data->qsv_info->capabilities & HB_QSV_CAP_AV1_SCREENCONTENT) &&
        qsv_data->param.av1ScreenContentToolsParam.IntraBlockCopy)
    {
        char intrabc[7];
        snprintf(intrabc, sizeof(intrabc), "%d", qsv_data->param.av1ScreenContentToolsParam.IntraBlockCopy);
        av_dict_set(av_opts, "intrabc", intrabc, 0);
        hb_log("encavcodec: ScreenContentCoding is enabled IBC %s",
            qsv_data->param.av1ScreenContentToolsParam.IntraBlockCopy ? "on" : "off");
    }

    if((qsv_data->qsv_info->capabilities & HB_QSV_CAP_AV1_SCREENCONTENT) &&
        qsv_data->param.av1ScreenContentToolsParam.Palette)
    {
        char palette_mode[7];
        snprintf(palette_mode, sizeof(palette_mode), "%d", qsv_data->param.av1ScreenContentToolsParam.Palette);
        av_dict_set(av_opts, "palette_mode", palette_mode, 0);
        hb_log("encavcodec: ScreenContentCoding is enabled Palette %s",
            qsv_data->param.av1ScreenContentToolsParam.Palette ? "on" : "off");
    }

  // Transcoding Info
  MFX_STRUCT_TO_AV_OPTS(BRCParamMultiplier)
  // scenecut
  MFX_STRUCT_TO_AV_OPTS(GopOptFlag)

#undef MFX_STRUCT_TO_AV_OPTS
  return 0;
}

int hb_qsv_apply_encoder_options(qsv_data_t * qsv_data, hb_job_t *job, AVDictionary **av_opts)
{
    if (qsv_data == NULL)
    {
        hb_error("hb_qsv_apply_encoder_options: invalid pointer qsv_data=%p", qsv_data);
        return -1;
    }

    qsv_data->qsv_info = hb_qsv_encoder_info_get(hb_qsv_get_adapter_index(), job->vcodec);

    if (qsv_data->qsv_info == NULL)
    {
        hb_error("hb_qsv_apply_encoder_options: invalid pointer qsv_data->qsv_info=%p", qsv_data->qsv_info);
        return -1;
    }

    mfxVideoParam videoParam = {0};
    qsv_data->param.videoParam = &videoParam;
    qsv_data->is_sys_mem = (hb_qsv_get_memory_type(job) == MFX_IOPATTERN_OUT_SYSTEM_MEMORY);

    int ret = hb_qsv_param_default(&qsv_data->param, qsv_data->qsv_info);
    if (ret)
    {
        return ret;
    }

    if (job->encoder_options != NULL && *job->encoder_options)
    {
        hb_dict_t *options_list;
        options_list = hb_encopts_to_dict(job->encoder_options, job->vcodec);

        hb_dict_iter_t iter;
        for (iter  = hb_dict_iter_init(options_list);
             iter != HB_DICT_ITER_DONE;
             iter  = hb_dict_iter_next(options_list, iter))
        {
            const char *key = hb_dict_iter_key(iter);
            hb_value_t *value = hb_dict_iter_value(iter);
            char *str = hb_value_get_string_xform(value);

            switch (hb_qsv_param_parse(av_opts, &qsv_data->param, qsv_data->qsv_info, job, key, str))
            {
                case HB_QSV_PARAM_OK:
                    break;

                case HB_QSV_PARAM_BAD_NAME:
                    hb_log("qsv_encavcodecInit: hb_qsv_param_parse: bad key %s", key);
                    break;
                case HB_QSV_PARAM_BAD_VALUE:
                    hb_log("qsv_encavcodecInit: hb_qsv_param_parse: bad value %s for key %s",
                           str, key);
                    break;
                case HB_QSV_PARAM_UNSUPPORTED:
                    hb_log("qsv_encavcodecInit: hb_qsv_param_parse: unsupported option %s",
                           key);
                    break;

                case HB_QSV_PARAM_ERROR:
                default:
                    hb_log("qsv_encavcodecInit: hb_qsv_param_parse: unknown error");
                    break;
            }
            free(str);
        }
        hb_dict_free(&options_list);
    }

    ret = hb_qsv_select_ffmpeg_options(qsv_data, job, av_opts);
    if(ret)
    {
        hb_log("encavcodec: parsing ffmpeg options failed");
        return ret;
    }

    hb_log("encavcodec: using%s%s path",
           hb_qsv_full_path_is_enabled(job) ? " full QSV" : " encode-only",
           hb_qsv_get_memory_type(job) == MFX_IOPATTERN_OUT_VIDEO_MEMORY ? " via video memory" : " via system memory");

    return 0;
}

int hb_qsv_param_parse(AVDictionary** av_opts, hb_qsv_param_t *param, hb_qsv_info_t *info, hb_job_t *job,
                       const char *key, const char *value)
{
    float fvalue;
    int ivalue, error = 0;
    if (param == NULL || info == NULL)
    {
        return HB_QSV_PARAM_ERROR;
    }
    if (value == NULL || value[0] == '\0')
    {
        value = "true";
    }
    else if (value[0] == '=')
    {
        value++;
    }
    if (key == NULL || key[0] == '\0')
    {
        return HB_QSV_PARAM_BAD_NAME;
    }
    else if (!strncasecmp(key, "no-", 3))
    {
        key  += 3;
        value = hb_qsv_atobool(value, &error) ? "false" : "true";
        if (error)
        {
            return HB_QSV_PARAM_BAD_VALUE;
        }
    }
    if (!strcasecmp(key, "target-usage") ||
        !strcasecmp(key, "tu"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            int target_usage = HB_QSV_CLIP3(MFX_TARGETUSAGE_1, MFX_TARGETUSAGE_7, ivalue);
            switch (target_usage)
            {
                case MFX_TARGETUSAGE_1:
                    av_dict_set(av_opts, "preset", "veryslow", 0);
                    break;
                case MFX_TARGETUSAGE_2:
                    av_dict_set(av_opts, "preset", "slower", 0);
                    break;
                case MFX_TARGETUSAGE_3:
                    av_dict_set(av_opts, "preset", "slow", 0);
                    break;
                case MFX_TARGETUSAGE_4:
                    av_dict_set(av_opts, "preset", "medium", 0);
                    break;
                case MFX_TARGETUSAGE_5:
                    av_dict_set(av_opts, "preset", "fast", 0);
                    break;
                case MFX_TARGETUSAGE_6:
                    av_dict_set(av_opts, "preset", "faster", 0);
                    break;
                case MFX_TARGETUSAGE_7:
                    av_dict_set(av_opts, "preset", "veryfast", 0);
                    break;
                default:
                    break;
            }
        }
    }
    else if (!strcasecmp(key, "num-ref-frame") ||
             !strcasecmp(key, "ref"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            av_dict_set_int(av_opts, "refs", HB_QSV_CLIP3(0, 16, ivalue), 0);
        }
    }
    else if (!strcasecmp(key, "gop-ref-dist"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            av_dict_set_int(av_opts, "bf", HB_QSV_CLIP3(-1, 32, ivalue), 0);
        }
    }
    else if (!strcasecmp(key, "gop-pic-size") ||
             !strcasecmp(key, "keyint"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            av_dict_set_int(av_opts, "g", HB_QSV_CLIP3(-1, UINT16_MAX, ivalue), 0);
        }
    }
    else if (!strcasecmp(key, "b-pyramid"))
    {
        if (info->capabilities & HB_QSV_CAP_B_REF_PYRAMID)
        {
            ivalue = hb_qsv_atoi(value, &error);
            if (!error)
            {
                av_dict_set_int(av_opts, "b_strategy", HB_QSV_CLIP3(-1, 1, ivalue), 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "scenecut"))
    {
        ivalue = hb_qsv_atobool(value, &error);
        if (!error)
        {
            if (!ivalue)
            {
                param->videoParam->mfx.GopOptFlag |= MFX_GOP_STRICT;
            }
            else
            {
                param->videoParam->mfx.GopOptFlag &= ~MFX_GOP_STRICT;
            }
        }
    }
    else if (!strcasecmp(key, "adaptive-i") ||
             !strcasecmp(key, "i-adapt"))
    {
        if (info->capabilities & HB_QSV_CAP_OPTION2_IB_ADAPT)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                av_dict_set_int(av_opts, "adaptive_i", ivalue, 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "adaptive-b") ||
             !strcasecmp(key, "b-adapt"))
    {
        if (info->capabilities & HB_QSV_CAP_OPTION2_IB_ADAPT)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                av_dict_set_int(av_opts, "adaptive_b", ivalue, 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "force-cqp"))
    {
        ivalue = hb_qsv_atobool(value, &error);
        if (!error)
        {
            param->rc.icq = !ivalue;
        }
    }
    else if (!strcasecmp(key, "cqp-offset-i"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            param->rc.cqp_offsets[0] = HB_QSV_CLIP3(INT16_MIN, INT16_MAX, ivalue);
        }
    }
    else if (!strcasecmp(key, "cqp-offset-p"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            param->rc.cqp_offsets[1] = HB_QSV_CLIP3(INT16_MIN, INT16_MAX, ivalue);
        }
    }
    else if (!strcasecmp(key, "cqp-offset-b"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            param->rc.cqp_offsets[2] = HB_QSV_CLIP3(INT16_MIN, INT16_MAX, ivalue);
        }
    }
    else if (!strcasecmp(key, "vbv-init"))
    {
        fvalue = hb_qsv_atof(value, &error);
        if (!error)
        {
            param->rc.vbv_buffer_init = HB_QSV_CLIP3(0, INT32_MAX, fvalue);
        }
    }
    else if (!strcasecmp(key, "vbv-bufsize"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            param->rc.vbv_buffer_size = HB_QSV_CLIP3(0, INT32_MAX, ivalue);
        }
    }
    else if (!strcasecmp(key, "vbv-maxrate"))
    {
        ivalue = hb_qsv_atoi(value, &error);
        if (!error)
        {
            param->rc.vbv_max_bitrate = HB_QSV_CLIP3(0, INT32_MAX, ivalue);
        }
    }
    else if (!strcasecmp(key, "cavlc") || !strcasecmp(key, "cabac"))
    {
        if (info->capabilities & HB_QSV_CAP_OPTION1)
        {
            switch (info->codec_id)
            {
                case MFX_CODEC_AVC:
                    ivalue = hb_qsv_atobool(value, &error);
                    break;
                default:
                    return HB_QSV_PARAM_UNSUPPORTED;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
        if (!error)
        {
            if (!strcasecmp(key, "cabac"))
            {
                ivalue = !ivalue;
            }
            av_dict_set_int(av_opts, "cavlc", ivalue, 0);
        }
    }
    else if (!strcasecmp(key, "colorprim"))
    {
        if (info->capabilities & HB_QSV_CAP_VUI_VSINFO)
        {
            switch (info->codec_id)
            {
                case MFX_CODEC_AVC:
                    ivalue = hb_qsv_atoindex(hb_h264_colorprim_names, value, &error);
                    break;
                case MFX_CODEC_HEVC:
                    ivalue = hb_qsv_atoindex(hb_h265_colorprim_names, value, &error);
                    break;
                default:
                    return HB_QSV_PARAM_UNSUPPORTED;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
        if (!error)
        {
            av_dict_set_int(av_opts, "color_primaries", ivalue, 0);
        }
    }
    else if (!strcasecmp(key, "transfer"))
    {
        if (info->capabilities & HB_QSV_CAP_VUI_VSINFO)
        {
            switch (info->codec_id)
            {
                case MFX_CODEC_AVC:
                    ivalue = hb_qsv_atoindex(hb_h264_transfer_names, value, &error);
                    break;
                case MFX_CODEC_HEVC:
                    ivalue = hb_qsv_atoindex(hb_h265_transfer_names, value, &error);
                    break;
                default:
                    return HB_QSV_PARAM_UNSUPPORTED;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
        if (!error)
        {
            av_dict_set_int(av_opts, "color_trc", ivalue, 0);
        }
    }
    else if (!strcasecmp(key, "colormatrix"))
    {
        if (info->capabilities & HB_QSV_CAP_VUI_VSINFO)
        {
            switch (info->codec_id)
            {
                case MFX_CODEC_AVC:
                    ivalue = hb_qsv_atoindex(hb_h264_colmatrix_names, value, &error);
                    break;
                case MFX_CODEC_HEVC:
                    ivalue = hb_qsv_atoindex(hb_h265_colmatrix_names, value, &error);
                    break;
                default:
                    return HB_QSV_PARAM_UNSUPPORTED;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
        if (!error)
        {
            av_dict_set_int(av_opts, "colorspace", ivalue, 0);
        }
    }
    else if (!strcasecmp(key, "tff") ||
             !strcasecmp(key, "interlaced"))
    {
        switch (info->codec_id)
        {
            case MFX_CODEC_AVC:
                ivalue = hb_qsv_atobool(value, &error);
                break;
            default:
                return HB_QSV_PARAM_UNSUPPORTED;
        }
        if (!error)
        {
            ivalue = ivalue ? MFX_PICSTRUCT_FIELD_TFF : MFX_PICSTRUCT_PROGRESSIVE;
            av_dict_set_int(av_opts, "flags", ivalue, AV_DICT_APPEND);
        }
    }
    else if (!strcasecmp(key, "bff"))
    {
        switch (info->codec_id)
        {
            case MFX_CODEC_AVC:
                ivalue = hb_qsv_atobool(value, &error);
                break;
            default:
                return HB_QSV_PARAM_UNSUPPORTED;
        }
        if (!error)
        {
            ivalue = ivalue ? MFX_PICSTRUCT_FIELD_BFF : MFX_PICSTRUCT_PROGRESSIVE;
            av_dict_set_int(av_opts, "flags", ivalue, AV_DICT_APPEND);
        }
    }
    else if (!strcasecmp(key, "mbbrc"))
    {
        if (info->capabilities & HB_QSV_CAP_OPTION2_MBBRC)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                av_dict_set_int(av_opts, "mbbrc", ivalue, 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "extbrc"))
    {
        if (info->capabilities & HB_QSV_CAP_OPTION2_EXTBRC)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                av_dict_set_int(av_opts, "extbrc", ivalue, 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "lookahead") ||
             !strcasecmp(key, "la"))
    {
        if (info->capabilities & HB_QSV_CAP_RATECONTROL_LA)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                param->rc.lookahead = ivalue;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "lookahead-depth") ||
             !strcasecmp(key, "la-depth"))
    {
        if (info->capabilities & HB_QSV_CAP_RATECONTROL_LA)
        {
            ivalue = hb_qsv_atoi(value, &error);
            if (!error)
            {
                av_dict_set_int(av_opts, "look_ahead_depth", HB_QSV_CLIP3(10, 100, ivalue), 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "lookahead-ds") ||
             !strcasecmp(key, "la-ds"))
    {
        if (info->capabilities & HB_QSV_CAP_OPTION2_LA_DOWNS)
        {
            ivalue = hb_qsv_atoi(value, &error);
            if (!error)
            {
                ivalue = HB_QSV_CLIP3(MFX_LOOKAHEAD_DS_UNKNOWN, MFX_LOOKAHEAD_DS_4x, ivalue);
                av_dict_set_int(av_opts, "look_ahead_downsampling", ivalue, 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "trellis"))
    {
        if (info->capabilities & HB_QSV_CAP_OPTION2_TRELLIS)
        {
            ivalue = hb_qsv_atoi(value, &error);
            if (!error)
            {
                av_dict_set_int(av_opts, "trellis", ivalue, 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "repeatpps"))
    {
        if (info->capabilities & HB_QSV_CAP_OPTION2_REPEATPPS)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                av_dict_set_int(av_opts, "repeat_pps", ivalue, 0);
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "lowpower"))
    {
        if (info->capabilities & HB_QSV_CAP_LOWPOWER_ENCODE)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                param->low_power = ivalue;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "gpu"))
    {
        // Already parsed in QSV initialization
    }
    else if (!strcasecmp(key, "memory-type"))
    {
        // Check if was parsed already in decoder initialization
        if (job->qsv_ctx && !job->qsv_ctx->memory_type)
        {
            hb_triplet_t* mode = NULL;
            mode = hb_triplet4key(hb_qsv_memory_types, value);
            if (!mode)
                error = HB_QSV_PARAM_BAD_VALUE;
            else
                job->qsv_ctx->memory_type = mode->value;
        }
    }
    else if (!strcasecmp(key, "scalingmode") ||
             !strcasecmp(key, "vpp-sm"))
    {
        // Already parsed it in decoder but need to check support
        if (info->capabilities & HB_QSV_CAP_VPP_SCALING)
        {
            hb_triplet_t *mode = NULL;
            mode = hb_triplet4key(hb_qsv_vpp_scale_modes, value);
            if (!mode)
            {
                error = HB_QSV_PARAM_BAD_VALUE;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "hyperencode"))
    {
        hb_triplet_t *mode = NULL;
        if (info->capabilities & HB_QSV_CAP_HYPERENCODE)
        {
            mode = hb_triplet4key(hb_qsv_hyper_encode_modes, value);
            if (!mode)
            {
                error = HB_QSV_PARAM_BAD_VALUE;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
        if (mode)
        {
            param->hyperEncodeParam = mode;
        }
    }
    else if (!strcasecmp(key, "palette"))
    {
        if (info->capabilities & HB_QSV_CAP_AV1_SCREENCONTENT)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                param->av1ScreenContentToolsParam.Palette = ivalue;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "intrabc"))
    {
        if (info->capabilities & HB_QSV_CAP_AV1_SCREENCONTENT)
        {
            ivalue = hb_qsv_atobool(value, &error);
            if (!error)
            {
                param->av1ScreenContentToolsParam.IntraBlockCopy = ivalue;
            }
        }
        else
        {
            return HB_QSV_PARAM_UNSUPPORTED;
        }
    }
    else if (!strcasecmp(key, "async-depth"))
    {
        int async_depth = hb_qsv_atoi(value, &error);
        if (!error)
        {
            av_dict_set_int(av_opts, "async_depth", async_depth, 0);
            param->videoParam->AsyncDepth = async_depth;
        }
    }
    else
    {
        /*
         * TODO:
         * - slice count (num-slice/slices, num-mb-per-slice/slice-max-mbs)
         * - open-gop
         * - fake-interlaced (mfxExtCodingOption.FramePicture???)
         * - intra-refresh
         */
        return HB_QSV_PARAM_BAD_NAME;
    }
    return error ? HB_QSV_PARAM_BAD_VALUE : HB_QSV_PARAM_OK;
}

int hb_qsv_profile_parse(hb_qsv_param_t *param, hb_qsv_info_t *info, const char *profile_key, const int codec)
{
    hb_triplet_t *profile = NULL;
    if (profile_key != NULL && *profile_key && strcasecmp(profile_key, "auto"))
    {
        switch (param->videoParam->mfx.CodecId)
        {
            case MFX_CODEC_AVC:
                profile = hb_triplet4key(hb_qsv_h264_profiles, profile_key);
                break;

            case MFX_CODEC_HEVC:
                profile = hb_triplet4key(hb_qsv_h265_profiles, profile_key);

                /* HEVC10 supported starting from KBL/G6 */
                if (profile->value == MFX_PROFILE_HEVC_MAIN10 &&
                    hb_qsv_hardware_generation(hb_qsv_get_platform(hb_qsv_get_adapter_index())) < QSV_G6)
                {
                    hb_log("qsv: HEVC Main10 is not supported on this platform");
                    profile = NULL;
                }
                break;

            case MFX_CODEC_AV1:
                profile = hb_triplet4key(hb_qsv_av1_profiles, profile_key);

                if (hb_qsv_hardware_generation(hb_qsv_get_platform(hb_qsv_get_adapter_index())) <= QSV_G8)
                {
                    hb_log("qsv: AV1 is not supported on this platform");
                    profile = NULL;
                }
                break;

            default:
                break;
        }
        if (profile == NULL)
        {
            return -1;
        }
        param->videoParam->mfx.CodecProfile = profile->value;
    }
    /* HEVC 10 bits defaults to Main 10 */
    else if (((profile_key != NULL && !strcasecmp(profile_key, "auto")) || profile_key == NULL) &&
              codec == HB_VCODEC_FFMPEG_QSV_H265_10BIT &&
              param->videoParam->mfx.CodecId == MFX_CODEC_HEVC &&
              hb_qsv_hardware_generation(hb_qsv_get_platform(hb_qsv_get_adapter_index())) >= QSV_G6)
    {
         profile = &hb_qsv_h265_profiles[1];
         param->videoParam->mfx.CodecProfile = profile->value;
    }
    /* AV1 10 bits defaults to Main */
    else if (((profile_key != NULL && !strcasecmp(profile_key, "auto")) || profile_key == NULL) &&
              codec == HB_VCODEC_FFMPEG_QSV_AV1_10BIT &&
              param->videoParam->mfx.CodecId == MFX_CODEC_AV1 &&
              hb_qsv_hardware_generation(hb_qsv_get_platform(hb_qsv_get_adapter_index())) > QSV_G8)
    {
        profile = &hb_qsv_av1_profiles[0];
        param->videoParam->mfx.CodecProfile = profile->value;
    }
    return 0;
}

const char* const* hb_qsv_preset_get_names()
{
    if (hb_qsv_hardware_generation(hb_qsv_get_platform(hb_qsv_get_adapter_index())) >= QSV_G3)
    {
        return hb_qsv_preset_names2;
    }
    else
    {
        return hb_qsv_preset_names1;
    }
}

const char* const* hb_qsv_profile_get_names(int encoder)
{
    switch (encoder)
    {
        case HB_VCODEC_FFMPEG_QSV_H264:
            return hb_h264_profile_names_8bit;
        case HB_VCODEC_FFMPEG_QSV_H265_8BIT:
            return hb_h265_profile_names_8bit;
        case HB_VCODEC_FFMPEG_QSV_H265_10BIT:
            return hb_qsv_h265_profiles_names_10bit;
        case HB_VCODEC_FFMPEG_QSV_AV1_10BIT:
        case HB_VCODEC_FFMPEG_QSV_AV1:
            return hb_qsv_av1_profiles_names;
        default:
            return NULL;
    }
}

const char* const* hb_qsv_level_get_names(int encoder)
{
    switch (encoder)
    {
        case HB_VCODEC_FFMPEG_QSV_H264:
            return hb_qsv_h264_level_names;
        case HB_VCODEC_FFMPEG_QSV_H265_10BIT:
        case HB_VCODEC_FFMPEG_QSV_H265:
            return hb_qsv_h265_level_names;
        case HB_VCODEC_FFMPEG_QSV_AV1_10BIT:
        case HB_VCODEC_FFMPEG_QSV_AV1:
            return hb_qsv_av1_level_names;
        default:
            return NULL;
    }
}

const int* hb_qsv_get_pix_fmts(int encoder)
{
    switch (encoder)
    {
    case HB_VCODEC_FFMPEG_QSV_H264:
    case HB_VCODEC_FFMPEG_QSV_H265:
    case HB_VCODEC_FFMPEG_QSV_AV1:
        return hb_qsv_pix_fmts;
    case HB_VCODEC_FFMPEG_QSV_H265_10BIT:
    case HB_VCODEC_FFMPEG_QSV_AV1_10BIT:
        return hb_qsv_10bit_pix_fmts;

    default:
        return hb_qsv_pix_fmts;
    }
}

const char* hb_qsv_video_quality_get_name(uint32_t codec)
{
    uint64_t caps = 0;
    const hb_qsv_adapter_details_t *details = hb_qsv_get_adapters_details_by_index(hb_qsv_get_adapter_index());
    if (details)
    {
        switch (codec)
        {
            case HB_VCODEC_FFMPEG_QSV_H264:
                if (details->hb_qsv_info_avc != NULL) caps = details->hb_qsv_info_avc->capabilities;
                break;

            case HB_VCODEC_FFMPEG_QSV_H265_10BIT:
            case HB_VCODEC_FFMPEG_QSV_H265:
                if (details->hb_qsv_info_hevc != NULL) caps = details->hb_qsv_info_hevc->capabilities;
                break;

            case HB_VCODEC_FFMPEG_QSV_AV1_10BIT:
            case HB_VCODEC_FFMPEG_QSV_AV1:
                if (details->hb_qsv_info_av1 != NULL) caps = details->hb_qsv_info_av1->capabilities;
                break;

            default:
                break;
        }
    }
    return (caps & HB_QSV_CAP_RATECONTROL_ICQ) ? "ICQ" : "QP";
}

void hb_qsv_video_quality_get_limits(uint32_t codec, float *low, float *high,
                                     float *granularity, int *direction)
{
    uint64_t caps = 0;
    const hb_qsv_adapter_details_t *details = hb_qsv_get_adapters_details_by_index(hb_qsv_get_adapter_index());
    if (details)
    {
        switch (codec)
        {
            case HB_VCODEC_FFMPEG_QSV_H265_10BIT:
            case HB_VCODEC_FFMPEG_QSV_H265:
                if (details->hb_qsv_info_hevc != NULL) caps = details->hb_qsv_info_hevc->capabilities;
                *direction   = 1;
                *granularity = 1.;
                *low         = (caps & HB_QSV_CAP_RATECONTROL_ICQ) ? 1. : 0.;
                *high        = 51.;
                break;

            case HB_VCODEC_FFMPEG_QSV_AV1_10BIT:
            case HB_VCODEC_FFMPEG_QSV_AV1:
                if (details->hb_qsv_info_av1 != NULL) caps = details->hb_qsv_info_av1->capabilities;
                *direction   = 1;
                *granularity = 1.;
                *low         = (caps & HB_QSV_CAP_RATECONTROL_ICQ) ? 1. : 0.;
                *high        = 51.;
                break;

            case HB_VCODEC_FFMPEG_QSV_H264:
            default:
                if (details->hb_qsv_info_avc != NULL) caps = details->hb_qsv_info_avc->capabilities;
                *direction   = 1;
                *granularity = 1.;
                *low         = (caps & HB_QSV_CAP_RATECONTROL_ICQ) ? 1. : 0.;
                *high        = 51.;
                break;
        }
    }
}

const char * hb_map_qsv_preset_name(const char * preset)
{
    if (preset == NULL)
    {
        return "medium";
    }

    if (strcmp(preset, "speed") == 0) {
      return "veryfast";
    } else if (strcmp(preset, "balanced") == 0) {
      return "medium";
    } else if (strcmp(preset, "quality") == 0) {
      return "veryslow";
    }

    return "medium";
}

int hb_qsv_param_default_async_depth()
{
    return hb_qsv_hardware_generation(hb_qsv_get_platform(hb_qsv_get_adapter_index())) >= QSV_G7 ? 6 : HB_QSV_ASYNC_DEPTH_DEFAULT;
}

int hb_qsv_param_default(hb_qsv_param_t *param, hb_qsv_info_t *info)
{
    if (param != NULL && info != NULL)
    {
        // introduced in API 1.6
        memset(&param->codingOption2, 0, sizeof(mfxExtCodingOption2));
        param->codingOption2.Header.BufferId = MFX_EXTBUFF_CODING_OPTION2;
        param->codingOption2.Header.BufferSz = sizeof(mfxExtCodingOption2);
        param->codingOption2.IntRefType      = 0;
        param->codingOption2.IntRefCycleSize = 2;
        param->codingOption2.IntRefQPDelta   = 0;
        param->codingOption2.MaxFrameSize    = 0;
        param->codingOption2.BitrateLimit    = MFX_CODINGOPTION_ON;
        param->codingOption2.MBBRC           = MFX_CODINGOPTION_OFF;
        param->codingOption2.ExtBRC          = MFX_CODINGOPTION_OFF;
        // introduced in API 1.7
        param->codingOption2.LookAheadDepth  = 40;
        param->codingOption2.Trellis         = MFX_TRELLIS_OFF;
        // introduced in API 1.8
        param->codingOption2.RepeatPPS       = MFX_CODINGOPTION_OFF;
        param->codingOption2.BRefType        = MFX_B_REF_UNKNOWN; // controlled via gop.b_pyramid
        param->codingOption2.AdaptiveI       = MFX_CODINGOPTION_OFF;
        param->codingOption2.AdaptiveB       = MFX_CODINGOPTION_OFF;
        param->codingOption2.LookAheadDS     = MFX_LOOKAHEAD_DS_OFF;
        param->codingOption2.NumMbPerSlice   = 0;
        // introduced in API 2.5
        param->hyperEncodeParam              = hb_triplet4key(hb_qsv_hyper_encode_modes, "off");

        // introduced in API 2.11
        memset(&param->av1ScreenContentToolsParam, 0, sizeof(mfxExtAV1ScreenContentTools));
        param->av1ScreenContentToolsParam.Header.BufferId = MFX_EXTBUFF_AV1_SCREEN_CONTENT_TOOLS;
        param->av1ScreenContentToolsParam.Header.BufferSz = sizeof(mfxExtAV1ScreenContentTools);
        param->av1ScreenContentToolsParam.IntraBlockCopy  = 0;
        param->av1ScreenContentToolsParam.Palette         = 0;

        // GOP & rate control
        param->gop.b_pyramid          =  1; // enabled by default (if supported)
        param->gop.gop_pic_size       = -1; // set automatically
        param->gop.gop_ref_dist       = -1; // set automatically
        param->gop.int_ref_cycle_size = -1; // set automatically
        param->rc.icq                 =  1; // enabled by default (if supported)
        param->rc.lookahead           =  0; // disabled by default
        param->rc.cqp_offsets[0]      =  0;
        param->rc.cqp_offsets[1]      =  2;
        param->rc.cqp_offsets[2]      =  4;
        param->rc.vbv_max_bitrate     =  0; // set automatically
        param->rc.vbv_buffer_size     =  0; // set automatically
        param->rc.vbv_buffer_init     = .0; // set automatically

        param->low_power              = 0;

#if defined(_WIN32) || defined(__MINGW32__)
        if (info->capabilities & HB_QSV_CAP_LOWPOWER_ENCODE)
        {
            param->low_power          = 1;
        }
#endif
    }
    else
    {
        hb_error("hb_qsv_param_default: invalid pointer(s)");
        return -1;
    }
    return 0;
}

hb_triplet_t* hb_triplet4value(hb_triplet_t *triplets, const int value)
{
    for (int i = 0; triplets[i].name != NULL; i++)
    {
        if (triplets[i].value == value)
        {
            return &triplets[i];
        }
    }
    return NULL;
}

hb_triplet_t* hb_triplet4name(hb_triplet_t *triplets, const char *name)
{
    for (int i = 0; triplets[i].name != NULL; i++)
    {
        if (!strcasecmp(triplets[i].name, name))
        {
            return &triplets[i];
        }
    }
    return NULL;
}

hb_triplet_t* hb_triplet4key(hb_triplet_t *triplets, const char *key)
{
    for (int i = 0; triplets[i].name != NULL; i++)
    {
        if (!strcasecmp(triplets[i].key, key))
        {
            return &triplets[i];
        }
    }
    return NULL;
}

const char* hb_qsv_codec_name(uint32_t codec_id)
{
    switch (codec_id)
    {
        case MFX_CODEC_AVC:
            return "H.264/AVC";

        case MFX_CODEC_HEVC:
            return "H.265/HEVC";

        case MFX_CODEC_AV1:
            return "AV1";

        default:
            return NULL;
    }
}

const char* hb_qsv_profile_name(uint32_t codec_id, uint16_t profile_id)
{
    hb_triplet_t *profile = NULL;
    switch (codec_id)
    {
        case MFX_CODEC_AVC:
            profile = hb_triplet4value(hb_qsv_h264_profiles, profile_id);
            break;

        case MFX_CODEC_HEVC:
            profile = hb_triplet4value(hb_qsv_h265_profiles, profile_id);
            break;

        case MFX_CODEC_AV1:
            profile = hb_triplet4value(hb_qsv_av1_profiles, profile_id);
            break;

        default:
            break;
    }
    return profile != NULL ? profile->name : NULL;
}

const char* hb_qsv_impl_get_name(int impl)
{
    switch (MFX_IMPL_BASETYPE(impl))
    {
        case MFX_IMPL_SOFTWARE:
            return "software";

        case MFX_IMPL_HARDWARE:
            return "hardware (1)";
        case MFX_IMPL_HARDWARE2:
            return "hardware (2)";
        case MFX_IMPL_HARDWARE3:
            return "hardware (3)";
        case MFX_IMPL_HARDWARE4:
            return "hardware (4)";
        case MFX_IMPL_HARDWARE_ANY:
            return "hardware (any)";

        case MFX_IMPL_AUTO:
            return "automatic";
        case MFX_IMPL_AUTO_ANY:
            return "automatic (any)";

        default:
            return NULL;
    }
}

int hb_qsv_impl_get_num(int impl)
{
    switch (MFX_IMPL_BASETYPE(impl))
    {
        case MFX_IMPL_HARDWARE:
            return 0;
        case MFX_IMPL_HARDWARE2:
            return 1;
        case MFX_IMPL_HARDWARE3:
            return 2;
        case MFX_IMPL_HARDWARE4:
            return 3;
        case MFX_IMPL_SOFTWARE:
        case MFX_IMPL_AUTO:
        case MFX_IMPL_AUTO_ANY:
        case MFX_IMPL_HARDWARE_ANY:
        default:
            return -1;
    }
}

const char* hb_qsv_impl_get_via_name(int impl)
{
    if      ((impl & 0xF00) == MFX_IMPL_VIA_VAAPI)
        return "via VAAPI";
    else if ((impl & 0xF00) == MFX_IMPL_VIA_D3D11)
        return "via D3D11";
    else if ((impl & 0xF00) == MFX_IMPL_VIA_D3D9)
        return "via D3D9";
    else if ((impl & 0xF00) == MFX_IMPL_VIA_ANY)
        return "via ANY";
    else return NULL;
}

int hb_qsv_get_platform(int adapter_index)
{
    for (int i = 0; i < hb_list_count(g_qsv_adapters_details_list); i++)
    {
        const hb_qsv_adapter_details_t *details = hb_list_item(g_qsv_adapters_details_list, i);
        // find DirectX adapter with given index in list of QSV adapters
        if (details && (details->index == adapter_index))
        {
            return qsv_map_mfx_platform_codename(details->platform.CodeName);
        }
    }
    return HB_CPU_PLATFORM_UNSPECIFIED;
}

int hb_qsv_param_parse_dx_index(hb_job_t *job, const int dx_index)
{
    for (int i = 0; i < hb_list_count(g_qsv_adapters_details_list); i++)
    {
        const hb_qsv_adapter_details_t *details = hb_list_item(g_qsv_adapters_details_list, i);
        // find DirectX adapter with given index in list of QSV adapters
        if (details && (details->index == dx_index))
        {
            job->hw_device_index = details->index;
            hb_log("qsv: %s qsv adapter with index %u has been selected", hb_qsv_get_adapter_type(details), details->index);
            hb_qsv_set_adapter_index(details->index);
            return 0;
        }
    }
    job->hw_device_index = hb_qsv_get_adapter_index();
    hb_log("qsv: default qsv adapter has been selected");
    return -1;
}

hb_qsv_context_t * hb_qsv_context_init()
{
    if (!hb_qsv_available())
    {
        return 0;
    }
  
    hb_qsv_context_t *ctx = av_mallocz(sizeof(hb_qsv_context_t));
    if (!ctx)
    {
        hb_error("hb_qsv_context_init: qsv ctx alloc failed");
        return NULL;
    }
    return ctx;
}

hb_qsv_context_t * hb_qsv_context_dup(const hb_qsv_context_t *src)
{
    if (src == NULL)
    {
        return NULL;
    }

    hb_qsv_context_t *ctx = hb_qsv_context_init();
    if (ctx)
    {
        memcpy(ctx, src, sizeof(hb_qsv_context_t));
    }
    return ctx;
}

void hb_qsv_context_close(hb_qsv_context_t **_ctx)
{
    hb_qsv_context_t *ctx = *_ctx;
    if (ctx == NULL)
    {
        return;
    }

    av_freep(_ctx);

    // restore adapter index after user preferences
    g_adapter_index = hb_qsv_get_default_adapter_index();
}

static void * find_decoder(int codec_param)
{
    const char *codec_name = hb_qsv_decode_get_codec_name(codec_param);
    return (void *)avcodec_find_decoder_by_name(codec_name);
}

static const int qsv_encoders[] =
{
    HB_VCODEC_FFMPEG_QSV_H264,
    HB_VCODEC_FFMPEG_QSV_H265,
    HB_VCODEC_FFMPEG_QSV_H265_10BIT,
    HB_VCODEC_FFMPEG_QSV_AV1,
    HB_VCODEC_FFMPEG_QSV_AV1_10BIT,
    HB_VCODEC_INVALID
};

hb_hwaccel_t hb_hwaccel_qsv =
{
    .id           = HB_DECODE_QSV,
    .name         = "qsv",
    .encoders     = qsv_encoders,
    .type         = AV_HWDEVICE_TYPE_QSV,
    .hw_pix_fmt   = AV_PIX_FMT_QSV,
    .can_filter   = are_filters_supported,
    .find_decoder = find_decoder,
    .caps         = HB_HWACCEL_CAP_ROTATE | HB_HWACCEL_CAP_COLOR_RANGE
};

#else // HB_PROJECT_FEATURE_QSV

int hb_qsv_available()
{
    return -1;
}

#endif // HB_PROJECT_FEATURE_QSV
