Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Steven Zhou <steven.zhou@netint.ca>
To: "ffmpeg-devel@ffmpeg.org" <ffmpeg-devel@ffmpeg.org>
Subject: [FFmpeg-devel] [PATCH 2/3] libavcodec: add NETINT Quadra HW decoders & encoders
Date: Wed, 2 Jul 2025 08:11:07 +0000
Message-ID: <YT2PR01MB4701C5ADD8964E3325DC7E80E340A@YT2PR01MB4701.CANPRD01.PROD.OUTLOOK.COM> (raw)

Add NETINT Quadra hardware video decoder and encoder codecs
h264_ni_quadra_dec, h265_ni_quadra_dec, jpeg_ni_quadra_dec,
vp9_ni_quadra_dec, h264_ni_quadra_enc, h265_ni_quadra_enc,
jpeg_ni_quadra_enc, and av1_ni_quadra_enc.

More information:
https://netint.com/products/quadra-t1a-video-processing-unit/
https://docs.netint.com/vpu/quadra/

Signed-off-by: Steven Zhou <steven.zhou@netint.ca>
---
 configure               |    8 +
 libavcodec/Makefile     |    9 +
 libavcodec/allcodecs.c  |    8 +
 libavcodec/nicodec.c    | 1392 ++++++++++++++++++
 libavcodec/nicodec.h    |  215 +++
 libavcodec/nidec.c      |  539 +++++++
 libavcodec/nidec.h      |   86 ++
 libavcodec/nidec_h264.c |   73 +
 libavcodec/nidec_hevc.c |   73 +
 libavcodec/nidec_jpeg.c |   68 +
 libavcodec/nidec_vp9.c  |   72 +
 libavcodec/nienc.c      | 3009 +++++++++++++++++++++++++++++++++++++++
 libavcodec/nienc.h      |  114 ++
 libavcodec/nienc_av1.c  |   51 +
 libavcodec/nienc_h264.c |   52 +
 libavcodec/nienc_hevc.c |   52 +
 libavcodec/nienc_jpeg.c |   48 +
 17 files changed, 5869 insertions(+)
 create mode 100644 libavcodec/nicodec.c
 create mode 100644 libavcodec/nicodec.h
 create mode 100644 libavcodec/nidec.c
 create mode 100644 libavcodec/nidec.h
 create mode 100644 libavcodec/nidec_h264.c
 create mode 100644 libavcodec/nidec_hevc.c
 create mode 100644 libavcodec/nidec_jpeg.c
 create mode 100644 libavcodec/nidec_vp9.c
 create mode 100644 libavcodec/nienc.c
 create mode 100644 libavcodec/nienc.h
 create mode 100644 libavcodec/nienc_av1.c
 create mode 100644 libavcodec/nienc_h264.c
 create mode 100644 libavcodec/nienc_hevc.c
 create mode 100644 libavcodec/nienc_jpeg.c

diff --git a/configure b/configure
index ca15d675d4..0a2dda84c9 100755
--- a/configure
+++ b/configure
@@ -3643,6 +3643,14 @@ libx264_encoder_select="atsc_a53 golomb"
 libx264rgb_encoder_deps="libx264"
 libx264rgb_encoder_select="libx264_encoder"
 libx265_encoder_deps="libx265"
+h264_ni_quadra_decoder_deps="ni_quadra"
+h265_ni_quadra_decoder_deps="ni_quadra"
+jpeg_ni_quadra_decoder_deps="ni_quadra"
+vp9_ni_quadra_decoder_deps="ni_quadra"
+av1_ni_quadra_encoder_deps="ni_quadra"
+h264_ni_quadra_encoder_deps="ni_quadra"
+h265_ni_quadra_encoder_deps="ni_quadra"
+jpeg_ni_quadra_encoder_deps="ni_quadra"
 libx265_encoder_select="atsc_a53 dovi_rpuenc"
 libxavs_encoder_deps="libxavs"
 libxavs2_encoder_deps="libxavs2"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 7f963e864d..0072f1d562 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -247,6 +247,7 @@ OBJS-$(CONFIG_APNG_ENCODER)            += png.o pngenc.o
 OBJS-$(CONFIG_APV_DECODER)             += apv_decode.o apv_entropy.o apv_dsp.o
 OBJS-$(CONFIG_ARBC_DECODER)            += arbc.o
 OBJS-$(CONFIG_ARGO_DECODER)            += argo.o
+OBJS-$(CONFIG_AV1_NI_QUADRA_ENCODER)   += nienc_av1.o nicodec.o nienc.o
 OBJS-$(CONFIG_SSA_DECODER)             += assdec.o ass.o
 OBJS-$(CONFIG_SSA_ENCODER)             += assenc.o ass.o
 OBJS-$(CONFIG_ASS_DECODER)             += assdec.o ass.o
@@ -427,6 +428,8 @@ OBJS-$(CONFIG_H264_MEDIACODEC_DECODER) += mediacodecdec.o
 OBJS-$(CONFIG_H264_MEDIACODEC_ENCODER) += mediacodecenc.o
 OBJS-$(CONFIG_H264_MF_ENCODER)         += mfenc.o mf_utils.o
 OBJS-$(CONFIG_H264_MMAL_DECODER)       += mmaldec.o
+OBJS-$(CONFIG_H264_NI_QUADRA_DECODER)  += nidec_h264.o nicodec.o nidec.o
+OBJS-$(CONFIG_H264_NI_QUADRA_ENCODER)  += nienc_h264.o nicodec.o nienc.o
 OBJS-$(CONFIG_H264_NVENC_ENCODER)      += nvenc_h264.o nvenc.o
 OBJS-$(CONFIG_H264_OMX_ENCODER)        += omx.o
 OBJS-$(CONFIG_H264_QSV_DECODER)        += qsvdec.o
@@ -455,6 +458,8 @@ OBJS-$(CONFIG_HEVC_D3D12VA_ENCODER)    += d3d12va_encode_hevc.o h265_profile_lev
 OBJS-$(CONFIG_HEVC_MEDIACODEC_DECODER) += mediacodecdec.o
 OBJS-$(CONFIG_HEVC_MEDIACODEC_ENCODER) += mediacodecenc.o
 OBJS-$(CONFIG_HEVC_MF_ENCODER)         += mfenc.o mf_utils.o
+OBJS-$(CONFIG_H265_NI_QUADRA_DECODER)  += nidec_hevc.o nicodec.o nidec.o
+OBJS-$(CONFIG_H265_NI_QUADRA_ENCODER)  += nienc_hevc.o nicodec.o nienc.o
 OBJS-$(CONFIG_HEVC_NVENC_ENCODER)      += nvenc_hevc.o nvenc.o
 OBJS-$(CONFIG_HEVC_QSV_DECODER)        += qsvdec.o
 OBJS-$(CONFIG_HEVC_QSV_ENCODER)        += qsvenc_hevc.o hevc/ps_enc.o
@@ -495,6 +500,8 @@ OBJS-$(CONFIG_JPEG2000_DECODER)        += jpeg2000dec.o jpeg2000.o jpeg2000dsp.o
                                           jpeg2000dwt.o mqcdec.o mqc.o jpeg2000htdec.o
 OBJS-$(CONFIG_JPEGLS_DECODER)          += jpeglsdec.o jpegls.o
 OBJS-$(CONFIG_JPEGLS_ENCODER)          += jpeglsenc.o jpegls.o
+OBJS-$(CONFIG_JPEG_NI_QUADRA_DECODER)  += nidec_jpeg.o nicodec.o nidec.o
+OBJS-$(CONFIG_JPEG_NI_QUADRA_ENCODER)  += nienc_jpeg.o nicodec.o nidec.o
 OBJS-$(CONFIG_JV_DECODER)              += jvdec.o
 OBJS-$(CONFIG_KGV1_DECODER)            += kgv1dec.o
 OBJS-$(CONFIG_KMVC_DECODER)            += kmvc.o
@@ -806,6 +813,7 @@ OBJS-$(CONFIG_VP9_AMF_DECODER)         += amfdec.o
 OBJS-$(CONFIG_VP9_CUVID_DECODER)       += cuviddec.o
 OBJS-$(CONFIG_VP9_MEDIACODEC_DECODER)  += mediacodecdec.o
 OBJS-$(CONFIG_VP9_MEDIACODEC_ENCODER)  += mediacodecenc.o
+OBJS-$(CONFIG_VP9_NI_QUADRA_DECODER)   += nidec_vp9.o nicodec.o nidec.o
 OBJS-$(CONFIG_VP9_RKMPP_DECODER)       += rkmppdec.o
 OBJS-$(CONFIG_VP9_VAAPI_ENCODER)       += vaapi_encode_vp9.o
 OBJS-$(CONFIG_VP9_QSV_ENCODER)         += qsvenc_vp9.o
@@ -1311,6 +1319,7 @@ SKIPHEADERS-$(CONFIG_LIBVPX)           += libvpx.h
 SKIPHEADERS-$(CONFIG_LIBWEBP_ENCODER)  += libwebpenc_common.h
 SKIPHEADERS-$(CONFIG_MEDIACODEC)       += mediacodecdec_common.h mediacodec_surface.h mediacodec_wrapper.h mediacodec_sw_buffer.h
 SKIPHEADERS-$(CONFIG_MEDIAFOUNDATION)  += mf_utils.h
+SKIPHEADERS-$(CONFIG_NI_QUADRA)        += nidec.h nicodec.h nienc.h ni_hevc_rbsp.h
 SKIPHEADERS-$(CONFIG_NVDEC)            += nvdec.h
 SKIPHEADERS-$(CONFIG_NVENC)            += nvenc.h
 SKIPHEADERS-$(CONFIG_QSV)              += qsv.h qsv_internal.h
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 9087b16895..91a78ebbc0 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -841,6 +841,14 @@ extern const FFCodec ff_amrwb_mediacodec_decoder;
 extern const FFCodec ff_h263_v4l2m2m_encoder;
 extern const FFCodec ff_libaom_av1_decoder;
 /* hwaccel hooks only, so prefer external decoders */
+extern const FFCodec ff_h264_ni_quadra_decoder;
+extern const FFCodec ff_h265_ni_quadra_decoder;
+extern const FFCodec ff_vp9_ni_quadra_decoder;
+extern const FFCodec ff_jpeg_ni_quadra_decoder;
+extern const FFCodec ff_h264_ni_quadra_encoder;
+extern const FFCodec ff_h265_ni_quadra_encoder;
+extern const FFCodec ff_av1_ni_quadra_encoder;
+extern const FFCodec ff_jpeg_ni_quadra_encoder;
 extern const FFCodec ff_av1_decoder;
 extern const FFCodec ff_av1_cuvid_decoder;
 extern const FFCodec ff_av1_mediacodec_decoder;
diff --git a/libavcodec/nicodec.c b/libavcodec/nicodec.c
new file mode 100644
index 0000000000..af2baea74d
--- /dev/null
+++ b/libavcodec/nicodec.c
@@ -0,0 +1,1392 @@
+/*
+ * XCoder Codec Lib Wrapper
+ * Copyright (c) 2018 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * XCoder codec lib wrapper.
+ */
+
+#include "nicodec.h"
+#include "get_bits.h"
+#include "internal.h"
+#include "libavcodec/h264.h"
+#include "libavcodec/h264_sei.h"
+#include "libavcodec/hevc/hevc.h"
+#include "libavcodec/hevc/sei.h"
+
+#include "libavutil/eval.h"
+#include "libavutil/hdr_dynamic_metadata.h"
+#include "libavutil/hwcontext.h"
+#include "libavutil/hwcontext_ni_quad.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mastering_display_metadata.h"
+#include "libavutil/mem.h"
+#include "libavutil/pixdesc.h"
+#include "nidec.h"
+
+#include <math.h>
+#include <ni_av_codec.h>
+#include <ni_rsrc_api.h>
+#include <ni_bitstream.h>
+
+#define NAL_264(X) ((X) & (0x1F))
+#define NAL_265(X) (((X)&0x7E) >> 1)
+#define MAX_HEADERS_SIZE 1000
+
+static const char *const var_names[] = {
+    "in_w", "iw",   ///< width  of the input video
+    "in_h", "ih",   ///< height of the input video
+    "out_w", "ow",  ///< width  of the cropped video
+    "out_h", "oh",  ///< height of the cropped video
+    "x",
+    "y",
+    NULL
+};
+
+enum var_name {
+    VAR_IN_W, VAR_IW,
+    VAR_IN_H, VAR_IH,
+    VAR_OUT_W, VAR_OW,
+    VAR_OUT_H, VAR_OH,
+    VAR_X,
+    VAR_Y,
+    VAR_VARS_NB
+};
+
+static inline void ni_align_free(void *opaque, uint8_t *data)
+{
+    ni_buf_t *buf = (ni_buf_t *)opaque;
+    if (buf) {
+        ni_decoder_frame_buffer_pool_return_buf(buf, (ni_buf_pool_t *)buf->pool);
+    }
+}
+
+static inline void ni_frame_free(void *opaque, uint8_t *data)
+{
+    if (data) {
+        int ret;
+        int num_buffers = opaque ? *((int*)opaque) : 1;
+        for (int i = 0; i < num_buffers; i++) {
+            niFrameSurface1_t* p_data3 = (niFrameSurface1_t*)(data + i * sizeof(niFrameSurface1_t));
+            if (p_data3->ui16FrameIdx != 0) {
+                av_log(NULL, AV_LOG_DEBUG, "Recycle trace ui16FrameIdx = [%d] DevHandle %d\n", p_data3->ui16FrameIdx, p_data3->device_handle);
+                ret = ni_hwframe_buffer_recycle(p_data3, p_data3->device_handle);
+                if (ret != NI_RETCODE_SUCCESS) {
+                    av_log(NULL, AV_LOG_ERROR, "ERROR Failed to recycle trace ui16frameidx = [%d] DevHandle %d\n", p_data3->ui16FrameIdx, p_data3->device_handle);
+                }
+            }
+        }
+        ni_aligned_free(data);
+        (void)data;  // suppress cppcheck
+    }
+}
+
+static inline void __ni_free(void *opaque, uint8_t *data)
+{
+    free(data); // Free data allocated by libxcoder
+}
+
+static enum AVPixelFormat ni_supported_pixel_formats[] =
+{
+    AV_PIX_FMT_YUV420P, //0
+    AV_PIX_FMT_YUV420P10LE,
+    AV_PIX_FMT_NV12,
+    AV_PIX_FMT_P010LE,
+    AV_PIX_FMT_NONE, //convert RGB to unused
+    AV_PIX_FMT_NONE,
+    AV_PIX_FMT_NONE,
+    AV_PIX_FMT_NONE,
+    AV_PIX_FMT_NONE, //8
+    AV_PIX_FMT_NONE,
+    AV_PIX_FMT_NONE,
+    AV_PIX_FMT_NONE,
+    AV_PIX_FMT_NONE, //12
+    AV_PIX_FMT_NI_QUAD_8_TILE_4X4,
+    AV_PIX_FMT_NI_QUAD_10_TILE_4X4,
+    AV_PIX_FMT_NONE, //15
+};
+
+static inline int ni_pix_fmt_2_ff_pix_fmt(ni_pix_fmt_t pix_fmt)
+{
+    return ni_supported_pixel_formats[pix_fmt];
+}
+
+int parse_symbolic_decoder_param(XCoderDecContext *s) {
+    ni_decoder_input_params_t *pdec_param = &s->api_param.dec_input_params;
+    int i, ret;
+    double res;
+    double var_values[VAR_VARS_NB];
+
+    if (pdec_param == NULL) {
+        return AVERROR_INVALIDDATA;
+    }
+
+    for (i = 0; i < NI_MAX_NUM_OF_DECODER_OUTPUTS; i++) {
+        /*Set output width and height*/
+        var_values[VAR_IN_W] = var_values[VAR_IW] = pdec_param->crop_whxy[i][0];
+        var_values[VAR_IN_H] = var_values[VAR_IH] = pdec_param->crop_whxy[i][1];
+        var_values[VAR_OUT_W] = var_values[VAR_OW] = pdec_param->crop_whxy[i][0];
+        var_values[VAR_OUT_H] = var_values[VAR_OH] = pdec_param->crop_whxy[i][1];
+        if (pdec_param->cr_expr[i][0][0] && pdec_param->cr_expr[i][1][0]) {
+            if (av_expr_parse_and_eval(&res, pdec_param->cr_expr[i][0], var_names,
+                                     var_values, NULL, NULL, NULL, NULL, NULL, 0,
+                                     s) < 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            var_values[VAR_OUT_W] = var_values[VAR_OW] = (double)floor(res);
+            if (av_expr_parse_and_eval(&res, pdec_param->cr_expr[i][1], var_names,
+                                     var_values, NULL, NULL, NULL, NULL, NULL, 0,
+                                     s) < 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            var_values[VAR_OUT_H] = var_values[VAR_OH] = (double)floor(res);
+            /* evaluate again ow as it may depend on oh */
+            ret = av_expr_parse_and_eval(&res, pdec_param->cr_expr[i][0], var_names,
+                                       var_values, NULL, NULL, NULL, NULL, NULL,
+                                       0, s);
+            if (ret < 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            var_values[VAR_OUT_W] = var_values[VAR_OW] = (double)floor(res);
+            pdec_param->crop_whxy[i][0] = (int)var_values[VAR_OUT_W];
+            pdec_param->crop_whxy[i][1] = (int)var_values[VAR_OUT_H];
+        }
+        /*Set output crop offset X,Y*/
+        if (pdec_param->cr_expr[i][2][0]) {
+            ret = av_expr_parse_and_eval(&res, pdec_param->cr_expr[i][2], var_names,
+                                       var_values, NULL, NULL, NULL, NULL, NULL,
+                                       0, s);
+            if (ret < 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            var_values[VAR_X] = res;
+            pdec_param->crop_whxy[i][2] = floor(var_values[VAR_X]);
+        }
+        if (pdec_param->cr_expr[i][3][0]) {
+            ret = av_expr_parse_and_eval(&res, pdec_param->cr_expr[i][3], var_names,
+                                       var_values, NULL, NULL, NULL, NULL, NULL,
+                                       0, s);
+            if (ret < 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            var_values[VAR_Y] = res;
+            pdec_param->crop_whxy[i][3] = floor(var_values[VAR_Y]);
+        }
+        /*Set output Scale*/
+        /*Reset OW and OH to next lower even number*/
+        var_values[VAR_OUT_W] = var_values[VAR_OW] =
+            (double)(pdec_param->crop_whxy[i][0] -
+                    (pdec_param->crop_whxy[i][0] % 2));
+        var_values[VAR_OUT_H] = var_values[VAR_OH] =
+            (double)(pdec_param->crop_whxy[i][1] -
+                    (pdec_param->crop_whxy[i][1] % 2));
+        if (pdec_param->sc_expr[i][0][0] && pdec_param->sc_expr[i][1][0]) {
+            if (av_expr_parse_and_eval(&res, pdec_param->sc_expr[i][0], var_names,
+                                     var_values, NULL, NULL, NULL, NULL, NULL, 0,
+                                     s) < 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            pdec_param->scale_wh[i][0] = ceil(res);
+            ret = av_expr_parse_and_eval(&res, pdec_param->sc_expr[i][1], var_names,
+                                       var_values, NULL, NULL, NULL, NULL, NULL,
+                                       0, s);
+            if (ret < 0) {
+                return AVERROR_INVALIDDATA;
+            }
+            pdec_param->scale_wh[i][1] = ceil(res);
+        }
+    }
+    return 0;
+}
+
+int ff_xcoder_dec_init(AVCodecContext *avctx, XCoderDecContext *s) {
+    int ret = 0;
+    ni_xcoder_params_t *p_param = &s->api_param;
+
+    s->api_ctx.hw_id = s->dev_dec_idx;
+    s->api_ctx.decoder_low_delay = 0;
+    ff_xcoder_strncpy(s->api_ctx.blk_dev_name, s->dev_blk_name,
+                      NI_MAX_DEVICE_NAME_LEN);
+    ff_xcoder_strncpy(s->api_ctx.dev_xcoder_name, s->dev_xcoder,
+                      MAX_CHAR_IN_DEVICE_NAME);
+
+    ret = ni_device_session_open(&s->api_ctx, NI_DEVICE_TYPE_DECODER);
+    if (ret != 0) {
+        av_log(avctx, AV_LOG_ERROR, "Failed to open decoder (status = %d), "
+               "resource unavailable\n", ret);
+        ret = AVERROR_EXTERNAL;
+        ff_xcoder_dec_close(avctx, s);
+    } else {
+        s->dev_xcoder_name = s->api_ctx.dev_xcoder_name;
+        s->blk_xcoder_name = s->api_ctx.blk_xcoder_name;
+        s->dev_dec_idx = s->api_ctx.hw_id;
+        av_log(avctx, AV_LOG_VERBOSE,
+               "XCoder %s.%d (inst: %d) opened successfully\n",
+               s->dev_xcoder_name, s->dev_dec_idx, s->api_ctx.session_id);
+
+        if (p_param->dec_input_params.hwframes) {
+            if (!avctx->hw_device_ctx) {
+                char buf[64] = {0};
+                av_log(avctx, AV_LOG_DEBUG,
+                       "nicodec.c:ff_xcoder_dec_init() hwdevice_ctx_create\n");
+                snprintf(buf, sizeof(buf), "%d", s->dev_dec_idx);
+                ret = av_hwdevice_ctx_create(&avctx->hw_device_ctx, AV_HWDEVICE_TYPE_NI_QUADRA,
+                                           buf, NULL, 0); // create with null device
+                if (ret < 0) {
+                    av_log(NULL, AV_LOG_ERROR, "Error creating a NI HW device\n");
+                    return ret;
+                }
+            }
+            if (!avctx->hw_frames_ctx) {
+                avctx->hw_frames_ctx = av_hwframe_ctx_alloc(avctx->hw_device_ctx);
+
+                if (!avctx->hw_frames_ctx) {
+                    ret = AVERROR(ENOMEM);
+                    return ret;
+                }
+            }
+            s->frames = (AVHWFramesContext *)avctx->hw_frames_ctx->data;
+
+            s->frames->format = AV_PIX_FMT_NI_QUAD;
+            s->frames->width = avctx->width;
+            s->frames->height = avctx->height;
+
+            s->frames->sw_format = avctx->sw_pix_fmt;
+            // Decoder has its own dedicated pool
+            s->frames->initial_pool_size = -1;
+
+            ret = av_hwframe_ctx_init(avctx->hw_frames_ctx);
+
+            avctx->pix_fmt = AV_PIX_FMT_NI_QUAD;
+            s->api_ctx.hw_action = NI_CODEC_HW_ENABLE;
+        } else {
+            // reassign in case above conditions alter value
+            avctx->pix_fmt = avctx->sw_pix_fmt;
+            s->api_ctx.hw_action = NI_CODEC_HW_NONE;
+        }
+    }
+
+    return ret;
+}
+
+int ff_xcoder_dec_close(AVCodecContext *avctx, XCoderDecContext *s) {
+    ni_session_context_t *p_ctx = &s->api_ctx;
+
+    if (p_ctx) {
+        // dec params in union with enc params struct
+        ni_retcode_t ret;
+        ni_xcoder_params_t *p_param = &s->api_param;
+        int suspended = 0;
+
+        ret = ni_device_session_close(p_ctx, s->eos, NI_DEVICE_TYPE_DECODER);
+        if (NI_RETCODE_SUCCESS != ret) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Failed to close Decode Session (status = %d)\n", ret);
+        }
+        ni_device_session_context_clear(p_ctx);
+
+        if (p_param->dec_input_params.hwframes) {
+            av_log(avctx, AV_LOG_VERBOSE,
+                   "File BLK handle %d close suspended to frames Uninit\n",
+                   p_ctx->blk_io_handle); // suspended_device_handle
+            if (avctx->hw_frames_ctx) {
+                AVHWFramesContext *ctx =
+                    (AVHWFramesContext *)avctx->hw_frames_ctx->data;
+                if (ctx) {
+                    AVNIFramesContext *dst_ctx = (AVNIFramesContext*) ctx->hwctx;
+                    if (dst_ctx) {
+                        dst_ctx->suspended_device_handle = p_ctx->blk_io_handle;
+                        suspended = 1;
+                    }
+                }
+            }
+        }
+
+        if (suspended) {
+#ifdef __linux__
+            ni_device_close(p_ctx->device_handle);
+#endif
+        } else {
+#ifdef _WIN32
+            ni_device_close(p_ctx->device_handle);
+#elif __linux__
+            ni_device_close(p_ctx->device_handle);
+            ni_device_close(p_ctx->blk_io_handle);
+#endif
+        }
+        p_ctx->device_handle = NI_INVALID_DEVICE_HANDLE;
+        p_ctx->blk_io_handle = NI_INVALID_DEVICE_HANDLE;
+        ni_packet_t *xpkt = &(s->api_pkt.data.packet);
+        ni_packet_buffer_free(xpkt);
+    }
+
+    return 0;
+}
+
+// return 1 if need to prepend saved header to pkt data, 0 otherwise
+int ff_xcoder_add_headers(AVCodecContext *avctx, AVPacket *pkt,
+                          uint8_t *extradata, int extradata_size) {
+    XCoderDecContext *s = avctx->priv_data;
+    int ret = 0;
+    int vps_num, sps_num, pps_num;
+
+    // check key frame packet only
+    if (!(pkt->flags & AV_PKT_FLAG_KEY) || !pkt->data || !extradata ||
+        !extradata_size) {
+        return ret;
+    }
+
+    if (s->extradata_size == extradata_size &&
+        memcmp(s->extradata, extradata, extradata_size) == 0) {
+        av_log(avctx, AV_LOG_TRACE, "%s extradata unchanged.\n", __FUNCTION__);
+        return ret;
+    }
+
+    if (AV_CODEC_ID_H264 != avctx->codec_id &&
+        AV_CODEC_ID_HEVC != avctx->codec_id) {
+        av_log(avctx, AV_LOG_DEBUG, "%s not AVC/HEVC codec: %d, skip!\n",
+              __FUNCTION__, avctx->codec_id);
+        return ret;
+    }
+
+    // extradata (headers) non-existing or changed: save/update it in the
+    // session storage
+    av_freep(&s->extradata);
+    s->extradata_size      = 0;
+    s->got_first_key_frame = 0;
+    s->extradata           = av_malloc(extradata_size);
+    if (!s->extradata) {
+        av_log(avctx, AV_LOG_ERROR, "%s memory allocation failed !\n",
+               __FUNCTION__);
+        return ret;
+    }
+
+    memcpy(s->extradata, extradata, extradata_size);
+    s->extradata_size = extradata_size;
+    // prepend header by default (assuming no header found in the pkt itself)
+    ret = 1;
+    // and we've got the first key frame of this stream
+    s->got_first_key_frame = 1;
+    vps_num = sps_num = pps_num = 0;
+
+    if (s->api_param.dec_input_params.skip_extra_headers &&
+        (s->extradata_size > 0) &&
+        s->extradata) {
+        const uint8_t *ptr = s->extradata;
+        const uint8_t *end = s->extradata + s->extradata_size;
+        uint32_t stc;
+        uint8_t nalu_type;
+
+        while (ptr < end) {
+            stc = -1;
+            ptr = avpriv_find_start_code(ptr, end, &stc);
+            if (ptr == end) {
+                break;
+            }
+
+            if (AV_CODEC_ID_H264 == avctx->codec_id) {
+                nalu_type = stc & 0x1f;
+
+                if (H264_NAL_SPS == nalu_type) {
+                    sps_num++;
+                } else if(H264_NAL_PPS == nalu_type) {
+                    pps_num++;
+                }
+
+                if (sps_num > H264_MAX_SPS_COUNT ||
+                    pps_num > H264_MAX_PPS_COUNT) {
+                    ret = 0;
+                    av_log(avctx, AV_LOG_WARNING, "Drop extradata because of repeated SPS/PPS\n");
+                    break;
+                }
+            } else if (AV_CODEC_ID_HEVC == avctx->codec_id) {
+                nalu_type = (stc >> 1) & 0x3F;
+
+                if (HEVC_NAL_VPS == nalu_type) {
+                    vps_num++;
+                } else if (HEVC_NAL_SPS == nalu_type) {
+                    sps_num++;
+                } else if (HEVC_NAL_PPS == nalu_type) {
+                    pps_num++;
+                }
+
+                if (vps_num > HEVC_MAX_VPS_COUNT ||
+                    sps_num > HEVC_MAX_SPS_COUNT ||
+                    pps_num > HEVC_MAX_PPS_COUNT) {
+                    ret = 0;
+                    av_log(avctx, AV_LOG_WARNING, "Drop extradata because of repeated VPS/SPS/PPS\n");
+                    break;
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+int ff_xcoder_dec_send(AVCodecContext *avctx, XCoderDecContext *s, AVPacket *pkt) {
+    /* call ni_decoder_session_write to send compressed video packet to the decoder
+       instance */
+    int need_draining = 0;
+    size_t size;
+    ni_packet_t *xpkt = &(s->api_pkt.data.packet);
+    int ret;
+    int sent;
+    int send_size = 0;
+    int new_packet = 0;
+    int extra_prev_size = 0;
+    int svct_skip_packet = s->svct_skip_next_packet;
+    OpaqueData *opaque_data;
+
+    size = pkt->size;
+
+    if (s->flushing) {
+        av_log(avctx, AV_LOG_ERROR, "Decoder is flushing and cannot accept new "
+               "buffer until all output buffers have been released\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    if (pkt->size == 0) {
+        need_draining = 1;
+    }
+
+    if (s->draining && s->eos) {
+        av_log(avctx, AV_LOG_VERBOSE, "Decoder is draining, eos\n");
+        return AVERROR_EOF;
+    }
+
+    if (xpkt->data_len == 0) {
+        AVBSFContext *bsf = avctx->internal->bsf;
+        uint8_t *extradata = bsf ? bsf->par_out->extradata : avctx->extradata;
+        int extradata_size = bsf ? bsf->par_out->extradata_size : avctx->extradata_size;
+
+        memset(xpkt, 0, sizeof(ni_packet_t));
+        xpkt->pts = pkt->pts;
+        xpkt->dts = pkt->dts;
+        xpkt->flags = pkt->flags;
+        xpkt->video_width = avctx->width;
+        xpkt->video_height = avctx->height;
+        xpkt->p_data = NULL;
+        xpkt->data_len = pkt->size;
+        xpkt->pkt_pos = pkt->pos;
+
+        if (pkt->flags & AV_PKT_FLAG_KEY && extradata_size > 0 &&
+            ff_xcoder_add_headers(avctx, pkt, extradata, extradata_size)) {
+            if (extradata_size > s->api_ctx.max_nvme_io_size * 2) {
+                av_log(avctx, AV_LOG_ERROR,
+                       "ff_xcoder_dec_send extradata_size %d "
+                       "exceeding max size supported: %d\n",
+                       extradata_size, s->api_ctx.max_nvme_io_size * 2);
+            } else {
+                av_log(avctx, AV_LOG_VERBOSE,
+                       "ff_xcoder_dec_send extradata_size %d "
+                       "copied to pkt start.\n",
+                       s->extradata_size);
+
+                s->api_ctx.prev_size = s->extradata_size;
+                memcpy(s->api_ctx.p_leftover, s->extradata, s->extradata_size);
+            }
+        }
+
+        s->svct_skip_next_packet = 0;
+        // If there was lone custom sei in the last packet and the firmware would
+        // fail to recoginze it. So passthrough the custom sei here.
+        if (s->lone_sei_pkt.size > 0) {
+            // No need to check the return value here because the lone_sei_pkt was
+            // parsed before. Here it is only to extract the SEI data.
+            ni_dec_packet_parse(&s->api_ctx, &s->api_param, s->lone_sei_pkt.data,
+                                s->lone_sei_pkt.size, xpkt, s->low_delay,
+                                s->api_ctx.codec_format, s->pkt_nal_bitmap, -1,
+                                &s->svct_skip_next_packet, &s->is_lone_sei_pkt);
+        }
+
+        ret = ni_dec_packet_parse(&s->api_ctx, &s->api_param, pkt->data,
+                                  pkt->size, xpkt, s->low_delay,
+                                  s->api_ctx.codec_format, s->pkt_nal_bitmap,
+                                  -1, &s->svct_skip_next_packet,
+                                  &s->is_lone_sei_pkt);
+        if (ret < 0) {
+            goto fail;
+        }
+
+        if (svct_skip_packet) {
+            av_log(avctx, AV_LOG_TRACE, "ff_xcoder_dec_send packet: pts:%" PRIi64 ","
+                   " size:%d\n", pkt->pts, pkt->size);
+            xpkt->data_len = 0;
+            return pkt->size;
+        }
+
+        // If the current packet is a lone SEI, save it to be sent with the next
+        // packet. And also check if getting the first packet containing key frame
+        // in decoder low delay mode.
+        if (s->is_lone_sei_pkt) {
+            av_packet_ref(&s->lone_sei_pkt, pkt);
+            xpkt->data_len = 0;
+            ni_memfree(xpkt->p_custom_sei_set);
+            if (s->low_delay && s->got_first_key_frame &&
+                !(s->pkt_nal_bitmap & NI_GENERATE_ALL_NAL_HEADER_BIT)) {
+                // Packets before the IDR is sent cannot be decoded. So
+                // set packet num to zero here.
+                s->api_ctx.decoder_low_delay = s->low_delay;
+                s->api_ctx.pkt_num = 0;
+                s->pkt_nal_bitmap |= NI_GENERATE_ALL_NAL_HEADER_BIT;
+                av_log(avctx, AV_LOG_TRACE,
+                       "ff_xcoder_dec_send got first IDR in decoder low delay "
+                       "mode, "
+                       "delay time %dms, pkt_nal_bitmap %d\n",
+                       s->low_delay, s->pkt_nal_bitmap);
+            }
+            av_log(avctx, AV_LOG_TRACE, "ff_xcoder_dec_send pkt lone SEI, saved, "
+                   "and return %d\n", pkt->size);
+            return pkt->size;
+        }
+
+        // Send the previous saved lone SEI packet to the decoder
+        if (s->lone_sei_pkt.size > 0) {
+            av_log(avctx, AV_LOG_TRACE, "ff_xcoder_dec_send copy over lone SEI "
+                   "data size: %d\n", s->lone_sei_pkt.size);
+            memcpy(s->api_ctx.p_leftover + s->api_ctx.prev_size,
+                   s->lone_sei_pkt.data, s->lone_sei_pkt.size);
+            s->api_ctx.prev_size += s->lone_sei_pkt.size;
+            av_packet_unref(&s->lone_sei_pkt);
+        }
+
+        if (pkt->size + s->api_ctx.prev_size > 0) {
+            ni_packet_buffer_alloc(xpkt, (pkt->size + s->api_ctx.prev_size));
+            if (!xpkt->p_data) {
+                ret = AVERROR(ENOMEM);
+                goto fail;
+            }
+        }
+        new_packet = 1;
+    } else {
+        send_size = xpkt->data_len;
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "ff_xcoder_dec_send: pkt->size=%d pkt->buf=%p\n", pkt->size, pkt->buf);
+
+    if (s->started == 0) {
+        xpkt->start_of_stream = 1;
+        s->started = 1;
+    }
+
+    if (need_draining && !s->draining) {
+        av_log(avctx, AV_LOG_VERBOSE, "Sending End Of Stream signal\n");
+        xpkt->end_of_stream = 1;
+        xpkt->data_len = 0;
+
+        av_log(avctx, AV_LOG_TRACE, "ni_packet_copy before: size=%d, s->prev_size=%d, send_size=%d (end of stream)\n", pkt->size, s->api_ctx.prev_size, send_size);
+        if (new_packet) {
+            extra_prev_size = s->api_ctx.prev_size;
+            send_size = ni_packet_copy(xpkt->p_data, pkt->data, pkt->size, s->api_ctx.p_leftover, &s->api_ctx.prev_size);
+            // increment offset of data sent to decoder and save it
+            xpkt->pos = (long long)s->offset;
+            s->offset += pkt->size + extra_prev_size;
+        }
+        av_log(avctx, AV_LOG_TRACE, "ni_packet_copy after: size=%d, s->prev_size=%d, send_size=%d, xpkt->data_len=%d (end of stream)\n", pkt->size, s->api_ctx.prev_size, send_size, xpkt->data_len);
+
+        if (send_size < 0) {
+            av_log(avctx, AV_LOG_ERROR, "Failed to copy pkt (status = %d)\n",
+                   send_size);
+            ret = AVERROR_EXTERNAL;
+            goto fail;
+        }
+        xpkt->data_len += extra_prev_size;
+
+        sent = 0;
+        if (xpkt->data_len > 0) {
+            sent = ni_device_session_write(&(s->api_ctx), &(s->api_pkt), NI_DEVICE_TYPE_DECODER);
+        }
+        if (sent < 0) {
+            av_log(avctx, AV_LOG_ERROR, "Failed to send eos signal (status = %d)\n",
+                   sent);
+            if (NI_RETCODE_ERROR_VPU_RECOVERY == sent) {
+                ret = xcoder_decode_reset(avctx);
+                if (0 == ret) {
+                    ret = AVERROR(EAGAIN);
+                }
+            } else {
+                ret = AVERROR(EIO);
+            }
+            goto fail;
+        }
+        av_log(avctx, AV_LOG_VERBOSE, "Queued eos (status = %d) ts=%llu\n",
+               sent, xpkt->pts);
+        s->draining = 1;
+
+        ni_device_session_flush(&(s->api_ctx), NI_DEVICE_TYPE_DECODER);
+    } else {
+        av_log(avctx, AV_LOG_TRACE, "ni_packet_copy before: size=%d, s->prev_size=%d, send_size=%d\n", pkt->size, s->api_ctx.prev_size, send_size);
+        if (new_packet) {
+            extra_prev_size = s->api_ctx.prev_size;
+            send_size = ni_packet_copy(xpkt->p_data, pkt->data, pkt->size, s->api_ctx.p_leftover, &s->api_ctx.prev_size);
+            // increment offset of data sent to decoder and save it
+            xpkt->pos = (long long)s->offset;
+            s->offset += pkt->size + extra_prev_size;
+        }
+        av_log(avctx, AV_LOG_TRACE, "ni_packet_copy after: size=%d, s->prev_size=%d, send_size=%d, xpkt->data_len=%d\n", pkt->size, s->api_ctx.prev_size, send_size, xpkt->data_len);
+
+        if (send_size < 0) {
+            av_log(avctx, AV_LOG_ERROR, "Failed to copy pkt (status = %d)\n", send_size);
+            ret = AVERROR_EXTERNAL;
+            goto fail;
+        }
+        xpkt->data_len += extra_prev_size;
+
+        sent = 0;
+        if (xpkt->data_len > 0) {
+            sent = ni_device_session_write(&s->api_ctx, &(s->api_pkt), NI_DEVICE_TYPE_DECODER);
+            av_log(avctx, AV_LOG_VERBOSE, "ff_xcoder_dec_send pts=%" PRIi64 ", dts=%" PRIi64 ", pos=%" PRIi64 ", sent=%d\n", pkt->pts, pkt->dts, pkt->pos, sent);
+        }
+        if (sent < 0) {
+            av_log(avctx, AV_LOG_ERROR, "Failed to send compressed pkt (status = %d)\n", sent);
+            if (NI_RETCODE_ERROR_VPU_RECOVERY == sent) {
+                ret = xcoder_decode_reset(avctx);
+                if (0 == ret) {
+                    ret = AVERROR(EAGAIN);
+                }
+            } else {
+                ret = AVERROR(EIO);
+            }
+            goto fail;
+        } else if (sent == 0) {
+            av_log(avctx, AV_LOG_VERBOSE, "Queued input buffer size=0\n");
+        } else if (sent < size) { /* partial sent; keep trying */
+            av_log(avctx, AV_LOG_VERBOSE, "Queued input buffer size=%d\n", sent);
+        }
+    }
+
+    if (xpkt->data_len == 0) {
+        /* if this packet is done sending, free any sei buffer. */
+        ni_memfree(xpkt->p_custom_sei_set);
+
+        /* save the opaque pointers from input packet to be copied to corresponding frame later */
+        if (avctx->flags & AV_CODEC_FLAG_COPY_OPAQUE) {
+            opaque_data = &s->opaque_data_array[s->opaque_data_pos];
+            opaque_data->pkt_pos = pkt->pos;
+            opaque_data->opaque = pkt->opaque;
+            av_buffer_replace(&opaque_data->opaque_ref, pkt->opaque_ref);
+            s->opaque_data_pos = (s->opaque_data_pos + 1) % s->opaque_data_nb;
+        }
+    }
+
+    if (sent != 0) {
+        //keep the current pkt to resend next time
+        ni_packet_buffer_free(xpkt);
+        return sent;
+    } else {
+        if (s->draining) {
+            av_log(avctx, AV_LOG_WARNING, "%s draining, sent == 0, return 0!\n", __func__);
+            return 0;
+        } else {
+            av_log(avctx, AV_LOG_VERBOSE, "%s NOT draining, sent == 0, return EAGAIN !\n", __func__);
+            return AVERROR(EAGAIN);
+        }
+    }
+
+fail:
+    ni_packet_buffer_free(xpkt);
+    ni_memfree(xpkt->p_custom_sei_set);
+    s->draining = 1;
+    s->eos = 1;
+
+    return ret;
+}
+
+int retrieve_frame(AVCodecContext *avctx, AVFrame *data, int *got_frame,
+                   ni_frame_t *xfme) {
+    XCoderDecContext *s = avctx->priv_data;
+    ni_xcoder_params_t *p_param =
+        &s->api_param; // dec params in union with enc params struct
+    int num_extra_outputs = (p_param->dec_input_params.enable_out1 > 0) + (p_param->dec_input_params.enable_out2 > 0);
+    uint32_t buf_size = xfme->data_len[0] + xfme->data_len[1] +
+                        xfme->data_len[2] + xfme->data_len[3];
+    uint8_t *buf = xfme->p_data[0];
+    uint8_t *buf1, *buf2;
+    bool is_hw;
+    int frame_planar;
+    int stride = 0;
+    int res = 0;
+    AVHWFramesContext *ctx     = NULL;
+    AVNIFramesContext *dst_ctx = NULL;
+    AVFrame *frame = data;
+    ni_aux_data_t *aux_data = NULL;
+    AVFrameSideData *av_side_data = NULL;
+    ni_session_data_io_t session_io_data1;
+    ni_session_data_io_t session_io_data2;
+    ni_session_data_io_t *p_session_data1 = &session_io_data1;
+    ni_session_data_io_t *p_session_data2 = &session_io_data2;
+    niFrameSurface1_t *p_data3;
+    niFrameSurface1_t *p_data3_1;
+    niFrameSurface1_t *p_data3_2;
+    OpaqueData *opaque_data;
+    int i;
+
+    av_log(avctx, AV_LOG_TRACE,
+           "retrieve_frame: buf %p data_len [%d %d %d %d] buf_size %u\n", buf,
+           xfme->data_len[0], xfme->data_len[1], xfme->data_len[2],
+           xfme->data_len[3], buf_size);
+
+    memset(p_session_data1, 0, sizeof(ni_session_data_io_t));
+    memset(p_session_data2, 0, sizeof(ni_session_data_io_t));
+
+    switch (avctx->sw_pix_fmt) {
+    case AV_PIX_FMT_NV12:
+    case AV_PIX_FMT_P010LE:
+        frame_planar = NI_PIXEL_PLANAR_FORMAT_SEMIPLANAR;
+        break;
+    case AV_PIX_FMT_NI_QUAD_8_TILE_4X4:
+    case AV_PIX_FMT_NI_QUAD_10_TILE_4X4:
+        frame_planar = NI_PIXEL_PLANAR_FORMAT_TILED4X4;
+        break;
+    default:
+        frame_planar = NI_PIXEL_PLANAR_FORMAT_PLANAR;
+        break;
+    }
+
+    if (num_extra_outputs) {
+        ni_frame_buffer_alloc(&(p_session_data1->data.frame), 1,
+                             1, // width height does not matter//codec id does
+                                // not matter//no metadata
+                             1, 0, 1, 1, frame_planar);
+        buf1 = p_session_data1->data.frame.p_data[0];
+        if (num_extra_outputs > 1) {
+            ni_frame_buffer_alloc(&(p_session_data2->data.frame), 1,
+                                 1, // width height does not matter
+                                 1, 0, 1, 1, frame_planar);
+            buf2 = p_session_data2->data.frame.p_data[0];
+        }
+    }
+
+    is_hw = xfme->data_len[3] > 0;
+    if (is_hw) {
+        if (frame->hw_frames_ctx) {
+            ctx = (AVHWFramesContext*)frame->hw_frames_ctx->data;
+            dst_ctx = (AVNIFramesContext*) ctx->hwctx;
+        }
+
+        // Note, the real first frame could be dropped due to AV_PKT_FLAG_DISCARD
+        if ((dst_ctx != NULL) &&
+            (dst_ctx->api_ctx.device_handle != s->api_ctx.device_handle)) {
+            if (frame->hw_frames_ctx) {
+                av_log(avctx, AV_LOG_VERBOSE,
+                       "First frame, set hw_frame_context to copy decode sessions "
+                       "threads\n");
+                res = ni_device_session_copy(&s->api_ctx, &dst_ctx->api_ctx);
+                if (NI_RETCODE_SUCCESS != res) {
+                    return res;
+                }
+                av_log(avctx, AV_LOG_VERBOSE,
+                       "retrieve_frame: blk_io_handle %d device_handle %d\n",
+                       s->api_ctx.blk_io_handle, s->api_ctx.device_handle);
+            }
+        }
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "decoding %" PRId64 " frame ...\n", s->api_ctx.frame_num);
+
+    if (avctx->width <= 0) {
+        av_log(avctx, AV_LOG_ERROR, "width is not set\n");
+        return AVERROR_INVALIDDATA;
+    }
+    if (avctx->height <= 0) {
+        av_log(avctx, AV_LOG_ERROR, "height is not set\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    stride = s->api_ctx.active_video_width;
+
+    av_log(avctx, AV_LOG_VERBOSE, "XFRAME SIZE: %d, STRIDE: %d\n", buf_size, stride);
+
+    if (!is_hw && (stride == 0 || buf_size < stride * avctx->height)) {
+        av_log(avctx, AV_LOG_ERROR, "Packet too small (%d)\n", buf_size);
+        return AVERROR_INVALIDDATA;
+    }
+
+    frame->flags &= ~AV_FRAME_FLAG_KEY;
+    if(xfme->ni_pict_type & 0x10) //key frame marker
+        frame->flags |= AV_FRAME_FLAG_KEY;
+    switch (xfme->ni_pict_type & 0xF) {
+    case DECODER_PIC_TYPE_IDR:
+        frame->flags |= AV_FRAME_FLAG_KEY;
+    case PIC_TYPE_I:
+        frame->pict_type = AV_PICTURE_TYPE_I;
+        if(s->api_param.dec_input_params.enable_follow_iframe) {
+            frame->flags |= AV_FRAME_FLAG_KEY;
+        }
+        break;
+    case PIC_TYPE_P:
+        frame->pict_type = AV_PICTURE_TYPE_P;
+        break;
+    case PIC_TYPE_B:
+        frame->pict_type = AV_PICTURE_TYPE_B;
+        break;
+    default:
+        frame->pict_type = AV_PICTURE_TYPE_NONE;
+    }
+
+    if (AV_CODEC_ID_MJPEG == avctx->codec_id) {
+        frame->flags |= AV_FRAME_FLAG_KEY;
+    }
+
+    // lowdelay mode should close when frame is B frame
+    if (frame->pict_type == AV_PICTURE_TYPE_B &&
+        s->api_ctx.enable_low_delay_check &&
+        s->low_delay) {
+        av_log(avctx, AV_LOG_WARNING,
+               "Warning: session %d decoder lowDelay mode "
+               "is cancelled due to B frames with "
+               "enable_low_delay_check, frame_num  %" PRId64 "\n",
+               s->api_ctx.session_id, s->api_ctx.frame_num);
+        s->low_delay = 0;
+    }
+    res = ff_decode_frame_props(avctx, frame);
+    if (res < 0)
+        return res;
+
+    frame->duration = avctx->internal->last_pkt_props->duration;
+
+    if ((res = av_image_check_size(xfme->video_width, xfme->video_height, 0, avctx)) < 0)
+        return res;
+
+    if (is_hw) {
+        frame->buf[0] = av_buffer_create(buf, buf_size, ni_frame_free, NULL, 0);
+        if (num_extra_outputs) {
+            frame->buf[1] =
+                av_buffer_create(buf1, (int)(buf_size / 3), ni_frame_free, NULL, 0);
+            buf1 = frame->buf[1]->data;
+            memcpy(buf1, buf + sizeof(niFrameSurface1_t),
+                   sizeof(niFrameSurface1_t)); // copy hwdesc to new buffer
+            if (num_extra_outputs > 1) {
+                frame->buf[2] = av_buffer_create(buf2, (int)(buf_size / 3),
+                                               ni_frame_free, NULL, 0);
+                buf2 = frame->buf[2]->data;
+                memcpy(buf2, buf + 2 * sizeof(niFrameSurface1_t),
+                       sizeof(niFrameSurface1_t));
+            }
+        }
+    } else {
+        frame->buf[0] = av_buffer_create(buf, buf_size, ni_align_free, xfme->dec_buf, 0);
+    }
+    av_log(avctx, AV_LOG_TRACE,
+           "retrieve_frame: is_hw %d frame->buf[0] %p buf %p buf_size %u "
+           "num_extra_outputs %d pkt_duration %ld\n",
+           is_hw, frame->buf[0], buf, buf_size, num_extra_outputs,
+           frame->duration);
+
+    buf = frame->buf[0]->data;
+
+    // retrieve side data if available
+    ni_dec_retrieve_aux_data(xfme);
+
+    // update avctx framerate with timing info
+    if (xfme->vui_time_scale && xfme->vui_num_units_in_tick) {
+        av_reduce(&avctx->framerate.den, &avctx->framerate.num,
+                  xfme->vui_num_units_in_tick * (AV_CODEC_ID_H264 == avctx->codec_id ? 2 : 1),
+                  xfme->vui_time_scale, 1 << 30);
+    }
+
+    if (xfme->vui_len > 0) {
+        enum AVColorRange color_range = xfme->video_full_range_flag ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
+        if ((avctx->color_range != color_range) ||
+            (avctx->color_trc != xfme->color_trc) ||
+            (avctx->colorspace != xfme->color_space) ||
+            (avctx->color_primaries != xfme->color_primaries)) {
+            avctx->color_range = frame->color_range = color_range;
+            avctx->color_trc = frame->color_trc = xfme->color_trc;
+            avctx->colorspace = frame->colorspace = xfme->color_space;
+            avctx->color_primaries = frame->color_primaries = xfme->color_primaries;
+        }
+
+        if (avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) {
+            if (frame->format == AV_PIX_FMT_YUVJ420P && color_range == AVCOL_RANGE_MPEG)
+                frame->format = AV_PIX_FMT_YUV420P;
+            else if (frame->format == AV_PIX_FMT_YUV420P && color_range == AVCOL_RANGE_JPEG)
+                frame->format = AV_PIX_FMT_YUVJ420P;
+        }
+    }
+    // User Data Unregistered SEI if available
+    av_log(avctx, AV_LOG_VERBOSE, "#SEI# UDU (offset=%u len=%u)\n",
+           xfme->sei_user_data_unreg_offset, xfme->sei_user_data_unreg_len);
+    if (xfme->sei_user_data_unreg_offset) {
+        if ((aux_data = ni_frame_get_aux_data(xfme, NI_FRAME_AUX_DATA_UDU_SEI))) {
+            av_side_data = av_frame_new_side_data(
+                frame, AV_FRAME_DATA_SEI_UNREGISTERED, aux_data->size);
+            if (!av_side_data) {
+                return AVERROR(ENOMEM);
+            } else {
+                memcpy(av_side_data->data, aux_data->data, aux_data->size);
+            }
+            av_log(avctx, AV_LOG_VERBOSE, "UDU SEI added (len=%d type=5)\n",
+                   xfme->sei_user_data_unreg_len);
+        } else {
+            av_log(avctx, AV_LOG_ERROR, "UDU SEI dropped! (len=%d type=5)\n",
+                   xfme->sei_user_data_unreg_len);
+        }
+    }
+
+    // close caption data if available
+    av_log(avctx, AV_LOG_VERBOSE, "#SEI# CC (offset=%u len=%u)\n",
+           xfme->sei_cc_offset, xfme->sei_cc_len);
+    if ((aux_data = ni_frame_get_aux_data(xfme, NI_FRAME_AUX_DATA_A53_CC))) {
+        av_side_data =
+            av_frame_new_side_data(frame, AV_FRAME_DATA_A53_CC, aux_data->size);
+
+        if (!av_side_data) {
+            return AVERROR(ENOMEM);
+        } else {
+            memcpy(av_side_data->data, aux_data->data, aux_data->size);
+        }
+    }
+
+    // hdr10 sei data if available
+    av_log(avctx, AV_LOG_VERBOSE, "#SEI# MDCV (offset=%u len=%u)\n",
+           xfme->sei_hdr_mastering_display_color_vol_offset,
+           xfme->sei_hdr_mastering_display_color_vol_len);
+    if ((aux_data = ni_frame_get_aux_data(
+             xfme, NI_FRAME_AUX_DATA_MASTERING_DISPLAY_METADATA))) {
+        AVMasteringDisplayMetadata *mdm =
+            av_mastering_display_metadata_create_side_data(frame);
+        if (!mdm) {
+            return AVERROR(ENOMEM);
+        } else {
+            memcpy(mdm, aux_data->data, aux_data->size);
+        }
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "#SEI# CLL (offset=%u len=%u)\n",
+           xfme->sei_hdr_content_light_level_info_offset,
+           xfme->sei_hdr_content_light_level_info_len);
+    if ((aux_data = ni_frame_get_aux_data(
+             xfme, NI_FRAME_AUX_DATA_CONTENT_LIGHT_LEVEL))) {
+        AVContentLightMetadata *clm =
+            av_content_light_metadata_create_side_data(frame);
+        if (!clm) {
+            return AVERROR(ENOMEM);
+        } else {
+            memcpy(clm, aux_data->data, aux_data->size);
+        }
+    }
+
+    // hdr10+ sei data if available
+    av_log(avctx, AV_LOG_VERBOSE, "#SEI# HDR10+ (offset=%u len=%u)\n",
+           xfme->sei_hdr_plus_offset, xfme->sei_hdr_plus_len);
+    if ((aux_data = ni_frame_get_aux_data(xfme, NI_FRAME_AUX_DATA_HDR_PLUS))) {
+        AVDynamicHDRPlus *hdrp = av_dynamic_hdr_plus_create_side_data(frame);
+
+        if (!hdrp) {
+            return AVERROR(ENOMEM);
+        } else {
+            memcpy(hdrp, aux_data->data, aux_data->size);
+        }
+    } // hdr10+ sei
+
+    // remember to clean up auxiliary data of ni_frame after their use
+    ni_frame_wipe_aux_data(xfme);
+
+    // NI decoders in public FFmpeg will not have custom SEI feature
+    // If the feature was not already disabled, free its used memory here
+    if (xfme->p_custom_sei_set) {
+        free(xfme->p_custom_sei_set);
+        xfme->p_custom_sei_set = NULL;
+    }
+
+    frame->pkt_dts = xfme->dts;
+    frame->pts     = xfme->pts;
+    if (xfme->pts != NI_NOPTS_VALUE) {
+        s->current_pts = frame->pts;
+    }
+
+    if (is_hw) {
+        p_data3 = (niFrameSurface1_t*)(xfme->p_buffer + xfme->data_len[0] + xfme->data_len[1] + xfme->data_len[2]);
+        frame->data[3] = xfme->p_buffer + xfme->data_len[0] + xfme->data_len[1] + xfme->data_len[2];
+
+        av_log(avctx, AV_LOG_DEBUG, "retrieve_frame: OUT0 data[3] trace ui16FrameIdx = [%d], device_handle=%d bitdep=%d, WxH %d x %d\n",
+               p_data3->ui16FrameIdx,
+               p_data3->device_handle,
+               p_data3->bit_depth,
+               p_data3->ui16width,
+               p_data3->ui16height);
+
+        if (num_extra_outputs) {
+            p_data3_1 = (niFrameSurface1_t*)buf1;
+            av_log(avctx, AV_LOG_DEBUG, "retrieve_frame: OUT1 data[3] trace ui16FrameIdx = [%d], device_handle=%d bitdep=%d, WxH %d x %d\n",
+                   p_data3_1->ui16FrameIdx,
+                   p_data3_1->device_handle,
+                   p_data3_1->bit_depth,
+                   p_data3_1->ui16width,
+                   p_data3_1->ui16height);
+            if (num_extra_outputs > 1) {
+                p_data3_2 = (niFrameSurface1_t*)buf2;
+                av_log(avctx, AV_LOG_DEBUG, "retrieve_frame: OUT2 data[3] trace ui16FrameIdx = [%d], device_handle=%d bitdep=%d, WxH %d x %d\n",
+                       p_data3_2->ui16FrameIdx,
+                       p_data3_2->device_handle,
+                       p_data3_2->bit_depth,
+                       p_data3_2->ui16width,
+                       p_data3_2->ui16height);
+            }
+        }
+    }
+    av_log(avctx, AV_LOG_VERBOSE, "retrieve_frame: frame->buf[0]=%p, "
+           "frame->data=%p, frame->pts=%" PRId64 ", frame size=%d, "
+           "s->current_pts=%" PRId64 ", frame->pkt_duration=%" PRId64 
+           " sei size %d offset %u\n", frame->buf[0], frame->data, frame->pts,
+           buf_size, s->current_pts, frame->duration, xfme->sei_cc_len,
+           xfme->sei_cc_offset);
+
+    /* av_buffer_ref(avpkt->buf); */
+    if (!frame->buf[0])
+        return AVERROR(ENOMEM);
+
+    if (!is_hw &&
+        ((res = av_image_fill_arrays(
+              frame->data, frame->linesize, buf, avctx->sw_pix_fmt,
+              (int)(s->api_ctx.active_video_width / s->api_ctx.bit_depth_factor),
+              s->api_ctx.active_video_height, 1)) < 0)) {
+        av_buffer_unref(&frame->buf[0]);
+        return res;
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "retrieve_frame: success av_image_fill_arrays "
+           "return %d\n", res);
+
+    if (!is_hw) {
+        frame->linesize[1] = frame->linesize[2] = (((frame->width / ((frame_planar == 0) ? 1 : 2) * s->api_ctx.bit_depth_factor) + 127) / 128) * 128;
+        frame->linesize[2] = (frame_planar == 0) ? 0 : frame->linesize[1];
+        frame->data[2] = (frame_planar == 0) ? 0 : frame->data[1] + (frame->linesize[1] * frame->height / 2);
+    }
+
+    frame->crop_top = xfme->crop_top;
+    frame->crop_bottom = frame->height - xfme->crop_bottom; // ppu auto crop should have cropped out padding, crop_bottom should be 0
+    frame->crop_left = xfme->crop_left;
+    frame->crop_right = frame->width - xfme->crop_right; // ppu auto crop should have cropped out padding, crop_right should be 0
+
+    if (is_hw && frame->hw_frames_ctx && dst_ctx != NULL) {
+        av_log(avctx, AV_LOG_TRACE,
+               "retrieve_frame: hw_frames_ctx av_buffer_get_ref_count=%d\n",
+               av_buffer_get_ref_count(frame->hw_frames_ctx));
+        dst_ctx->split_ctx.enabled = (num_extra_outputs >= 1) ? 1 : 0;
+        dst_ctx->split_ctx.w[0]    = p_data3->ui16width;
+        dst_ctx->split_ctx.h[0]    = p_data3->ui16height;
+        dst_ctx->split_ctx.f[0]    = (int)p_data3->encoding_type;
+        dst_ctx->split_ctx.f8b[0]  = (int)p_data3->bit_depth;
+        dst_ctx->split_ctx.w[1] =
+            (num_extra_outputs >= 1) ? p_data3_1->ui16width : 0;
+        dst_ctx->split_ctx.h[1] =
+            (num_extra_outputs >= 1) ? p_data3_1->ui16height : 0;
+        dst_ctx->split_ctx.f[1] =
+            (num_extra_outputs >= 1) ? p_data3_1->encoding_type : 0;
+        dst_ctx->split_ctx.f8b[1] =
+            (num_extra_outputs >= 1) ? p_data3_1->bit_depth : 0;
+        dst_ctx->split_ctx.w[2] =
+            (num_extra_outputs == 2) ? p_data3_2->ui16width : 0;
+        dst_ctx->split_ctx.h[2] =
+            (num_extra_outputs == 2) ? p_data3_2->ui16height : 0;
+        dst_ctx->split_ctx.f[2] =
+            (num_extra_outputs == 2) ? p_data3_2->encoding_type : 0;
+        dst_ctx->split_ctx.f8b[2] =
+            (num_extra_outputs == 2) ? p_data3_2->bit_depth : 0;
+    }
+
+    /* retrive the opaque pointers saved earlier by matching the pkt_pos between output
+     * frame and input packet, assuming that the pkt_pos of every input packet is unique */
+    if (avctx->flags & AV_CODEC_FLAG_COPY_OPAQUE) {
+        opaque_data = NULL;
+        for (i = 0; i < s->opaque_data_nb; i++) {
+            if (s->opaque_data_array[i].pkt_pos == (int64_t)xfme->pkt_pos) {
+                opaque_data = &s->opaque_data_array[i];
+                break;
+            }
+        }
+        /* copy the pointers over to AVFrame if a matching entry found, otherwise it's unexpected so don't do anything */
+        if (opaque_data) {
+            frame->opaque = opaque_data->opaque;
+            av_buffer_replace(&frame->opaque_ref, opaque_data->opaque_ref);
+            av_buffer_unref(&opaque_data->opaque_ref);
+            opaque_data->pkt_pos = -1;
+        }
+    }
+
+    *got_frame = 1;
+    return buf_size;
+}
+
+int ff_xcoder_dec_receive(AVCodecContext *avctx, XCoderDecContext *s,
+                          AVFrame *frame, bool wait)
+{
+    /* call xcode_dec_receive to get a decoded YUV frame from the decoder
+       instance */
+    int ret = 0;
+    int got_frame = 0;
+    ni_session_data_io_t session_io_data;
+    ni_session_data_io_t * p_session_data = &session_io_data;
+    int alloc_mem, height, actual_width, cropped_width, cropped_height;
+    bool bSequenceChange = 0;
+    int frame_planar;
+
+    if (s->draining && s->eos) {
+        return AVERROR_EOF;
+    }
+
+read_op:
+    memset(p_session_data, 0, sizeof(ni_session_data_io_t));
+
+    if (s->draining) {
+        s->api_ctx.burst_control = 0;
+    } else
+    if (s->api_ctx.frame_num % 2 == 0) {
+        s->api_ctx.burst_control = (s->api_ctx.burst_control == 0 ? 1 : 0); //toggle
+    }
+    if (s->api_ctx.burst_control) {
+        av_log(avctx, AV_LOG_DEBUG, "ff_xcoder_dec_receive burst return%" PRId64 " frame\n", s->api_ctx.frame_num);
+        return AVERROR(EAGAIN);
+    }
+
+    // if active video resolution has been obtained we just use it as it's the
+    // exact size of frame to be returned, otherwise we use what we are told by
+    // upper stream as the initial setting and it will be adjusted.
+    height =
+        (int)(s->api_ctx.active_video_height > 0 ? s->api_ctx.active_video_height
+                                                : avctx->height);
+    actual_width =
+        (int)(s->api_ctx.actual_video_width > 0 ? s->api_ctx.actual_video_width
+                                               : avctx->width);
+
+    // allocate memory only after resolution is known (buffer pool set up)
+    alloc_mem = (s->api_ctx.active_video_width > 0 &&
+                 s->api_ctx.active_video_height > 0 ? 1 : 0);
+    switch (avctx->sw_pix_fmt) {
+    case AV_PIX_FMT_NV12:
+    case AV_PIX_FMT_P010LE:
+        frame_planar = NI_PIXEL_PLANAR_FORMAT_SEMIPLANAR;
+        break;
+    case AV_PIX_FMT_NI_QUAD_8_TILE_4X4:
+    case AV_PIX_FMT_NI_QUAD_10_TILE_4X4:
+        frame_planar = NI_PIXEL_PLANAR_FORMAT_TILED4X4;
+        break;
+    default:
+        frame_planar = NI_PIXEL_PLANAR_FORMAT_PLANAR;
+        break;
+    }
+
+    if (avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) {
+        ret = ni_decoder_frame_buffer_alloc(
+            s->api_ctx.dec_fme_buf_pool, &(p_session_data->data.frame), alloc_mem,
+            actual_width, height, (avctx->codec_id == AV_CODEC_ID_H264),
+            s->api_ctx.bit_depth_factor, frame_planar);
+    } else {
+        ret = ni_frame_buffer_alloc(&(p_session_data->data.frame), actual_width,
+                                   height, (avctx->codec_id == AV_CODEC_ID_H264),
+                                   1, s->api_ctx.bit_depth_factor, 3,
+                                   frame_planar);
+    }
+
+    if (NI_RETCODE_SUCCESS != ret) {
+        return AVERROR_EXTERNAL;
+    }
+
+    if (avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) {
+        ret = ni_device_session_read(&s->api_ctx, p_session_data, NI_DEVICE_TYPE_DECODER);
+    } else {
+        ret = ni_device_session_read_hwdesc(&s->api_ctx, p_session_data, NI_DEVICE_TYPE_DECODER);
+    }
+
+    if (ret == 0) {
+        s->eos = p_session_data->data.frame.end_of_stream;
+        if (avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) {
+            ni_decoder_frame_buffer_free(&(p_session_data->data.frame));
+        } else {
+            ni_frame_buffer_free(&(p_session_data->data.frame));
+        }
+
+        if (s->eos) {
+            return AVERROR_EOF;
+        } else if (s->draining) {
+            av_log(avctx, AV_LOG_ERROR, "ERROR: %s draining ret == 0 but not EOS\n", __func__);
+            return AVERROR_EXTERNAL;
+        }
+        return AVERROR(EAGAIN);
+    } else if (ret > 0) {
+        int dec_ff_pix_fmt;
+
+        if (p_session_data->data.frame.flags & AV_PKT_FLAG_DISCARD) {
+            av_log(avctx, AV_LOG_DEBUG,
+                   "Current frame is dropped when AV_PKT_FLAG_DISCARD is set\n");
+            if (avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) {
+                ni_decoder_frame_buffer_free(&(p_session_data->data.frame));
+            } else {
+                // recycle frame mem bin buffer of all PPU outputs & free p_buffer
+                int num_outputs = (s->api_param.dec_input_params.enable_out1 > 0) +
+                                (s->api_param.dec_input_params.enable_out2 > 0) + 1;
+                ni_frame_free(&num_outputs, p_session_data->data.frame.p_buffer);
+            }
+
+            if (s->draining) {
+                goto read_op;
+            }
+            return AVERROR(EAGAIN);
+        }
+
+        av_log(avctx, AV_LOG_VERBOSE, "Got output buffer pts=%lld "
+                                     "dts=%lld eos=%d sos=%d\n",
+               p_session_data->data.frame.pts, p_session_data->data.frame.dts,
+               p_session_data->data.frame.end_of_stream, p_session_data->data.frame.start_of_stream);
+
+        s->eos = p_session_data->data.frame.end_of_stream;
+
+        // update ctxt resolution if change has been detected
+        frame->width = cropped_width = p_session_data->data.frame.video_width; // ppu auto crop reports wdith as cropped width
+        frame->height = cropped_height = p_session_data->data.frame.video_height; // ppu auto crop reports heigth as cropped height
+
+        if (cropped_width != avctx->width || cropped_height != avctx->height) {
+            av_log(avctx, AV_LOG_WARNING, "ff_xcoder_dec_receive: resolution "
+                   "changed: %dx%d to %dx%d\n", avctx->width, avctx->height,
+                   cropped_width, cropped_height);
+            avctx->width = cropped_width;
+            avctx->height = cropped_height;
+            bSequenceChange = 1;
+        }
+
+        dec_ff_pix_fmt = ni_pix_fmt_2_ff_pix_fmt(s->api_ctx.pixel_format);
+
+        // If the codec is Jpeg or color range detected is a full range,
+        // yuv420p from xxx_ni_quadra_dec means a full range.
+        // Change it to yuvj420p so that FFmpeg can process it as a full range.
+        if ((avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) &&
+            (dec_ff_pix_fmt == AV_PIX_FMT_YUV420P) &&
+            ((avctx->codec_id == AV_CODEC_ID_MJPEG) ||
+             (avctx->color_range == AVCOL_RANGE_JPEG))) {
+            avctx->sw_pix_fmt = avctx->pix_fmt = dec_ff_pix_fmt = AV_PIX_FMT_YUVJ420P;
+            avctx->color_range = AVCOL_RANGE_JPEG;
+        }
+
+        if (avctx->sw_pix_fmt != dec_ff_pix_fmt) {
+            av_log(avctx, AV_LOG_VERBOSE, "update sw_pix_fmt from %d to %d\n",
+                   avctx->sw_pix_fmt, dec_ff_pix_fmt);
+            avctx->sw_pix_fmt = dec_ff_pix_fmt;
+            if (avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) {
+                avctx->pix_fmt = avctx->sw_pix_fmt;
+            }
+            bSequenceChange = 1;
+        }
+
+        frame->format = avctx->pix_fmt;
+
+        av_log(avctx, AV_LOG_VERBOSE, "ff_xcoder_dec_receive: frame->format %d, sw_pix_fmt = %d\n", frame->format, avctx->sw_pix_fmt);
+
+        if (avctx->pix_fmt == AV_PIX_FMT_NI_QUAD) {
+            AVNIFramesContext *ni_hwf_ctx;
+
+            if (bSequenceChange) {
+                AVHWFramesContext *ctx;
+                AVNIFramesContext *dst_ctx;
+
+                av_buffer_unref(&avctx->hw_frames_ctx);
+                avctx->hw_frames_ctx = av_hwframe_ctx_alloc(avctx->hw_device_ctx);
+                if (!avctx->hw_frames_ctx) {
+                    ret = AVERROR(ENOMEM);
+                    return ret;
+                }
+
+                s->frames = (AVHWFramesContext*)avctx->hw_frames_ctx->data;
+                s->frames->format = AV_PIX_FMT_NI_QUAD;
+                s->frames->width = avctx->width;
+                s->frames->height = avctx->height;
+                s->frames->sw_format = avctx->sw_pix_fmt;
+                s->frames->initial_pool_size = -1; //Decoder has its own dedicated pool
+                ret = av_hwframe_ctx_init(avctx->hw_frames_ctx);
+                if (ret < 0) {
+                    return ret;
+                }
+
+                ctx = (AVHWFramesContext*)avctx->hw_frames_ctx->data;
+                dst_ctx = (AVNIFramesContext*) ctx->hwctx;
+                av_log(avctx, AV_LOG_VERBOSE, "ff_xcoder_dec_receive: sequence change, set hw_frame_context to copy decode sessions threads\n");
+                ret = ni_device_session_copy(&s->api_ctx, &dst_ctx->api_ctx);
+                if (NI_RETCODE_SUCCESS != ret) {
+                    return ret;
+                }
+            }
+            frame->hw_frames_ctx = av_buffer_ref(avctx->hw_frames_ctx);
+
+            /* Set the hw_id/card number in AVNIFramesContext */
+            ni_hwf_ctx = (AVNIFramesContext*)((AVHWFramesContext*)frame->hw_frames_ctx->data)->hwctx;
+            ni_hwf_ctx->hw_id = s->dev_dec_idx;
+        }
+        if (s->api_ctx.frame_num == 1) {
+            av_log(avctx, AV_LOG_DEBUG, "NI:%s:out\n",
+                   (frame_planar == 0)   ? "semiplanar"
+                   : (frame_planar == 2) ? "tiled"
+                                        : "planar");
+        }
+        retrieve_frame(avctx, frame, &got_frame, &(p_session_data->data.frame));
+        av_log(avctx, AV_LOG_VERBOSE, "ff_xcoder_dec_receive: got_frame=%d, frame->width=%d, frame->height=%d, crop top %" SIZE_SPECIFIER " bottom %" SIZE_SPECIFIER " left %" SIZE_SPECIFIER " right %" SIZE_SPECIFIER ", frame->format=%d, frame->linesize=%d/%d/%d\n", got_frame, frame->width, frame->height, frame->crop_top, frame->crop_bottom, frame->crop_left, frame->crop_right, frame->format, frame->linesize[0], frame->linesize[1], frame->linesize[2]);
+
+#if FF_API_PKT_PTS
+        FF_DISABLE_DEPRECATION_WARNINGS
+        frame->pkt_pts = frame->pts;
+        FF_ENABLE_DEPRECATION_WARNINGS
+#endif
+        frame->best_effort_timestamp = frame->pts;
+
+        av_log(avctx, AV_LOG_VERBOSE, "ff_xcoder_dec_receive: pkt_timebase= %d/%d, frame_rate=%d/%d, frame->pts=%" PRId64 ", frame->pkt_dts=%" PRId64 "\n", avctx->pkt_timebase.num, avctx->pkt_timebase.den, avctx->framerate.num, avctx->framerate.den, frame->pts, frame->pkt_dts);
+
+        // release buffer ownership and let frame owner return frame buffer to
+        // buffer pool later
+        p_session_data->data.frame.dec_buf = NULL;
+
+        ni_memfree(p_session_data->data.frame.p_custom_sei_set);
+    } else {
+        av_log(avctx, AV_LOG_ERROR, "Failed to get output buffer (status = %d)\n",
+               ret);
+
+        if (NI_RETCODE_ERROR_VPU_RECOVERY == ret) {
+            av_log(avctx, AV_LOG_WARNING, "ff_xcoder_dec_receive VPU recovery, need to reset ..\n");
+            ni_decoder_frame_buffer_free(&(p_session_data->data.frame));
+            return ret;
+        } else if (ret == NI_RETCODE_ERROR_INVALID_SESSION ||
+                  ret == NI_RETCODE_ERROR_NVME_CMD_FAILED) {
+            return AVERROR_EOF;
+        }
+        return AVERROR(EIO);
+    }
+
+    ret = 0;
+
+    return ret;
+}
+
+int ff_xcoder_dec_is_flushing(AVCodecContext *avctx, XCoderDecContext *s)
+{
+    return s->flushing;
+}
+
+int ff_xcoder_dec_flush(AVCodecContext *avctx, XCoderDecContext *s)
+{
+    s->draining = 0;
+    s->flushing = 0;
+    s->eos = 0;
+
+    /* Future: for now, always return 1 to indicate the codec has been flushed
+       and it leaves the flushing state and can process again ! will consider
+       case of user retaining frames in HW "surface" usage */
+    return 1;
+}
diff --git a/libavcodec/nicodec.h b/libavcodec/nicodec.h
new file mode 100644
index 0000000000..7a0e22faa0
--- /dev/null
+++ b/libavcodec/nicodec.h
@@ -0,0 +1,215 @@
+/*
+ * XCoder Codec Lib Wrapper
+ * Copyright (c) 2018 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * XCoder codec lib wrapper header.
+ */
+
+#ifndef AVCODEC_NICODEC_H
+#define AVCODEC_NICODEC_H
+
+#include <stdbool.h>
+#include <time.h>
+#include "avcodec.h"
+#include "startcode.h"
+#include "bsf.h"
+#include "libavutil/fifo.h"
+
+#include <ni_device_api.h>
+#include "libavutil/hwcontext_ni_quad.h"
+#include "libavutil/hwcontext.h"
+
+#define NI_NAL_VPS_BIT (0x01)
+#define NI_NAL_SPS_BIT (0x01 << 1)
+#define NI_NAL_PPS_BIT (0x01 << 2)
+#define NI_GENERATE_ALL_NAL_HEADER_BIT (0x01 << 3)
+
+/* enum for specifying xcoder device/coder index; can be specified in either
+   decoder or encoder options. */
+enum {
+    BEST_DEVICE_INST = -2,
+    BEST_DEVICE_LOAD = -1
+};
+
+enum {
+    HW_FRAMES_OFF = 0,
+    HW_FRAMES_ON = 1
+};
+
+enum {
+    GEN_GLOBAL_HEADERS_AUTO = -1,
+    GEN_GLOBAL_HEADERS_OFF = 0,
+    GEN_GLOBAL_HEADERS_ON = 1
+};
+
+typedef struct OpaqueData {
+    int64_t pkt_pos;
+    void *opaque;
+    AVBufferRef *opaque_ref;
+} OpaqueData;
+
+typedef struct XCoderDecContext {
+    AVClass *avclass;
+
+    /* from the command line, which resource allocation method we use */
+    char *dev_xcoder;
+    char *dev_xcoder_name;          /* dev name of the xcoder card to use */
+    char *blk_xcoder_name;          /* blk name of the xcoder card to use */
+    int dev_dec_idx;                /* user-specified decoder index */
+    char *dev_blk_name;             /* user-specified decoder block device name */
+    int keep_alive_timeout;         /* keep alive timeout setting */
+    ni_device_context_t *rsrc_ctx;  /* resource management context */
+
+    ni_session_context_t api_ctx;
+    ni_xcoder_params_t api_param;
+    ni_session_data_io_t api_pkt;
+
+    AVPacket buffered_pkt;
+    AVPacket lone_sei_pkt;
+
+    // stream header copied/saved from AVCodecContext.extradata
+    int got_first_key_frame;
+    uint8_t *extradata;
+    int extradata_size;
+
+    int64_t current_pts;
+    unsigned long long offset;
+    int svct_skip_next_packet;
+
+    int started;
+    int draining;
+    int flushing;
+    int is_lone_sei_pkt;
+    int eos;
+    AVHWFramesContext    *frames;
+
+    /* for temporarily storing the opaque pointers when AV_CODEC_FLAG_COPY_OPAQUE is set */
+    OpaqueData *opaque_data_array;
+    int opaque_data_nb;
+    int opaque_data_pos;
+
+    /* below are all command line options */
+    char *xcoder_opts;
+    int low_delay;
+    int pkt_nal_bitmap;
+} XCoderDecContext;
+
+typedef struct XCoderEncContext {
+    AVClass *avclass;
+
+    /* from the command line, which resource allocation method we use */
+    char *dev_xcoder;
+    char *dev_xcoder_name;          /* dev name of the xcoder card to use */
+    char *blk_xcoder_name;          /* blk name of the xcoder card to use */
+    int dev_enc_idx;                /* user-specified encoder index */
+    char *dev_blk_name;             /* user-specified encoder block device name */
+    int nvme_io_size;               /* custom nvme io size */
+    int keep_alive_timeout;         /* keep alive timeout setting */
+    ni_device_context_t *rsrc_ctx;  /* resource management context */
+    uint64_t xcode_load_pixel; /* xcode load in pixels by this encode task */
+
+    AVFifo *fme_fifo;
+    int eos_fme_received;
+    AVFrame buffered_fme; // buffered frame for sequence change handling
+
+    ni_session_data_io_t  api_pkt; /* used for receiving bitstream from xcoder */
+    ni_session_data_io_t   api_fme; /* used for sending YUV data to xcoder */
+    ni_session_context_t api_ctx;
+    ni_xcoder_params_t api_param;
+
+    int started;
+    uint8_t *p_spsPpsHdr;
+    int spsPpsHdrLen;
+    int spsPpsArrived;
+    int firstPktArrived;
+    int64_t dtsOffset;
+    int gop_offset_count;/*this is a counter to guess the pts only dtsOffset times*/
+    uint64_t total_frames_received;
+    int64_t first_frame_pts;
+    int64_t latest_dts;
+
+    int encoder_flushing;
+    int encoder_eof;
+
+    // ROI
+    int roi_side_data_size;
+    AVRegionOfInterest *av_rois;  // last passed in AVRegionOfInterest
+    int nb_rois;
+
+    /* backup copy of original values of -enc command line option */
+    int  orig_dev_enc_idx;
+
+    AVFrame *sframe_pool[MAX_NUM_FRAMEPOOL_HWAVFRAME];
+    int aFree_Avframes_list[MAX_NUM_FRAMEPOOL_HWAVFRAME + 1];
+    int freeHead;
+    int freeTail;
+
+    /* below are all command line options */
+    char *xcoder_opts;
+    char *xcoder_gop;
+    int gen_global_headers;
+    int udu_sei;
+
+    int reconfigCount;
+    int seqChangeCount;
+    // actual enc_change_params is in ni_session_context !
+
+} XCoderEncContext;
+
+// copy maximum number of bytes of a string from src to dst, ensuring null byte
+// terminated
+static inline void ff_xcoder_strncpy(char *dst, const char *src, int max) {
+    if (dst && src && max) {
+        *dst = '\0';
+        strncpy(dst, src, max);
+        *(dst + max - 1) = '\0';
+    }
+}
+
+int ff_xcoder_dec_close(AVCodecContext *avctx,
+                        XCoderDecContext *s);
+
+int ff_xcoder_dec_init(AVCodecContext *avctx,
+                       XCoderDecContext *s);
+
+int ff_xcoder_dec_send(AVCodecContext *avctx,
+                       XCoderDecContext *s,
+                       AVPacket *pkt);
+
+int ff_xcoder_dec_receive(AVCodecContext *avctx,
+                          XCoderDecContext *s,
+                          AVFrame *frame,
+                          bool wait);
+
+int ff_xcoder_dec_is_flushing(AVCodecContext *avctx,
+                              XCoderDecContext *s);
+
+int ff_xcoder_dec_flush(AVCodecContext *avctx,
+                        XCoderDecContext *s);
+
+int parse_symbolic_decoder_param(XCoderDecContext *s);
+
+int retrieve_frame(AVCodecContext *avctx, AVFrame *data, int *got_frame,
+                   ni_frame_t *xfme);
+int ff_xcoder_add_headers(AVCodecContext *avctx, AVPacket *pkt,
+                          uint8_t *extradata, int extradata_size);
+#endif /* AVCODEC_NICODEC_H */
diff --git a/libavcodec/nidec.c b/libavcodec/nidec.c
new file mode 100644
index 0000000000..5624cbe670
--- /dev/null
+++ b/libavcodec/nidec.c
@@ -0,0 +1,539 @@
+/*
+ * NetInt XCoder H.264/HEVC Decoder common code
+ * Copyright (c) 2018-2019 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * XCoder decoder.
+ */
+
+#include "nidec.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/hwcontext.h"
+#include "libavutil/hwcontext_ni_quad.h"
+#include "libavutil/mem.h"
+#include "fftools/ffmpeg_sched.h"
+
+#define USER_DATA_UNREGISTERED_SEI_PAYLOAD_TYPE 5
+#define NETINT_SKIP_PROFILE 0
+
+int xcoder_decode_close(AVCodecContext *avctx) {
+    int i;
+    XCoderDecContext *s = avctx->priv_data;
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder decode close\n");
+
+    /* this call shall release resource based on s->api_ctx */
+    ff_xcoder_dec_close(avctx, s);
+
+    av_packet_unref(&s->buffered_pkt);
+    av_packet_unref(&s->lone_sei_pkt);
+
+    av_freep(&s->extradata);
+    s->extradata_size      = 0;
+    s->got_first_key_frame = 0;
+
+    if (s->opaque_data_array) {
+        for (i = 0; i < s->opaque_data_nb; i++)
+            av_buffer_unref(&s->opaque_data_array[i].opaque_ref);
+        av_freep(&s->opaque_data_array);
+    }
+
+    ni_rsrc_free_device_context(s->rsrc_ctx);
+    s->rsrc_ctx = NULL;
+    return 0;
+}
+
+static int xcoder_setup_decoder(AVCodecContext *avctx) {
+    XCoderDecContext *s = avctx->priv_data;
+    ni_xcoder_params_t *p_param =
+        &s->api_param; // dec params in union with enc params struct
+    int min_resolution_width, min_resolution_height;
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder setup device decoder\n");
+
+    if (ni_device_session_context_init(&(s->api_ctx)) < 0) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Error XCoder init decoder context failure\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    min_resolution_width  = NI_MIN_RESOLUTION_WIDTH;
+    min_resolution_height = NI_MIN_RESOLUTION_HEIGHT;
+
+    // Check codec id or format as well as profile idc.
+    switch (avctx->codec_id) {
+        case AV_CODEC_ID_HEVC:
+            s->api_ctx.codec_format = NI_CODEC_FORMAT_H265;
+            switch (avctx->profile) {
+                case AV_PROFILE_HEVC_MAIN:
+                case AV_PROFILE_HEVC_MAIN_10:
+                case AV_PROFILE_HEVC_MAIN_STILL_PICTURE:
+                case AV_PROFILE_UNKNOWN:
+                    break;
+                case NETINT_SKIP_PROFILE:
+                    av_log(avctx, AV_LOG_WARNING, "Warning: HEVC profile %d not supported, skip setting it\n", avctx->profile);
+                    break;
+                default:
+                    av_log(avctx, AV_LOG_ERROR, "Error: profile %d not supported.\n", avctx->profile);
+                    return AVERROR_INVALIDDATA;
+            }
+            break;
+        case AV_CODEC_ID_VP9:
+            s->api_ctx.codec_format = NI_CODEC_FORMAT_VP9;
+            switch (avctx->profile) {
+                case AV_PROFILE_VP9_0:
+                case AV_PROFILE_VP9_2:
+                case AV_PROFILE_UNKNOWN:
+                    break;
+                default:
+                    av_log(avctx, AV_LOG_ERROR, "Error: profile %d not supported.\n", avctx->profile);
+                    return AVERROR_INVALIDDATA;
+            }
+            break;
+        case AV_CODEC_ID_MJPEG:
+            s->api_ctx.codec_format = NI_CODEC_FORMAT_JPEG;
+            min_resolution_width    = NI_MIN_RESOLUTION_WIDTH_JPEG;
+            min_resolution_height   = NI_MIN_RESOLUTION_HEIGHT_JPEG;
+            switch (avctx->profile) {
+                case AV_PROFILE_MJPEG_HUFFMAN_BASELINE_DCT:
+                case AV_PROFILE_UNKNOWN:
+                    break;
+                default:
+                    av_log(avctx, AV_LOG_ERROR, "Error: profile %d not supported.\n", avctx->profile);
+                    return AVERROR_INVALIDDATA;
+            }
+            break;
+        default:
+            s->api_ctx.codec_format = NI_CODEC_FORMAT_H264;
+            switch (avctx->profile) {
+                case AV_PROFILE_H264_BASELINE:
+                case AV_PROFILE_H264_CONSTRAINED_BASELINE:
+                case AV_PROFILE_H264_MAIN:
+                case AV_PROFILE_H264_EXTENDED:
+                case AV_PROFILE_H264_HIGH:
+                case AV_PROFILE_H264_HIGH_10:
+                case AV_PROFILE_UNKNOWN:
+                    break;
+                case NETINT_SKIP_PROFILE:
+                    av_log(avctx, AV_LOG_WARNING, "Warning: H264 profile %d not supported, skip setting it.\n", avctx->profile);
+                    break;
+                default:
+                    av_log(avctx, AV_LOG_ERROR, "Error: profile %d not supported.\n", avctx->profile);
+                    return AVERROR_INVALIDDATA;
+            }
+            break;
+    }
+
+    if (avctx->width > NI_MAX_RESOLUTION_WIDTH ||
+        avctx->height > NI_MAX_RESOLUTION_HEIGHT ||
+        avctx->width * avctx->height > NI_MAX_RESOLUTION_AREA) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Error XCoder resolution %dx%d not supported\n", avctx->width,
+               avctx->height);
+        av_log(avctx, AV_LOG_ERROR, "Max Supported Width: %d Height %d Area %d\n",
+               NI_MAX_RESOLUTION_WIDTH, NI_MAX_RESOLUTION_HEIGHT,
+               NI_MAX_RESOLUTION_AREA);
+        return AVERROR_EXTERNAL;
+    } else if (avctx->width < min_resolution_width ||
+               avctx->height < min_resolution_height) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Error XCoder resolution %dx%d not supported\n", avctx->width,
+               avctx->height);
+        av_log(avctx, AV_LOG_ERROR, "Min Supported Width: %d Height %d\n",
+               min_resolution_width, min_resolution_height);
+        return AVERROR_EXTERNAL;
+    }
+
+    s->offset = 0LL;
+
+    s->draining = 0;
+
+    s->api_ctx.pic_reorder_delay = avctx->has_b_frames;
+    s->api_ctx.bit_depth_factor = 1;
+    if (AV_PIX_FMT_YUV420P10BE == avctx->pix_fmt ||
+        AV_PIX_FMT_YUV420P10LE == avctx->pix_fmt ||
+        AV_PIX_FMT_P010LE == avctx->pix_fmt) {
+        s->api_ctx.bit_depth_factor = 2;
+    }
+    av_log(avctx, AV_LOG_VERBOSE, "xcoder_setup_decoder: pix_fmt %u bit_depth_factor %u\n", avctx->pix_fmt, s->api_ctx.bit_depth_factor);
+
+    //Xcoder User Configuration
+    if (ni_decoder_init_default_params(p_param, avctx->framerate.num, avctx->framerate.den, avctx->bit_rate, avctx->width, avctx->height) < 0) {
+        av_log(avctx, AV_LOG_INFO, "Error setting params\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (s->xcoder_opts) {
+        AVDictionary *dict = NULL;
+        AVDictionaryEntry *en = NULL;
+
+        if (av_dict_parse_string(&dict, s->xcoder_opts, "=", ":", 0)) {
+            av_log(avctx, AV_LOG_ERROR, "Xcoder options provided contain error(s)\n");
+            av_dict_free(&dict);
+            return AVERROR_EXTERNAL;
+        } else {
+            while ((en = av_dict_get(dict, "", en, AV_DICT_IGNORE_SUFFIX))) {
+                int parse_ret = ni_decoder_params_set_value(p_param, en->key, en->value);
+                if (parse_ret != NI_RETCODE_SUCCESS) {
+                    switch (parse_ret) {
+                        case NI_RETCODE_PARAM_INVALID_NAME:
+                            av_log(avctx, AV_LOG_ERROR, "Unknown option: %s.\n", en->key);
+                            av_dict_free(&dict);
+                            return AVERROR_EXTERNAL;
+                        case NI_RETCODE_PARAM_ERROR_TOO_BIG:
+                            av_log(avctx, AV_LOG_ERROR,
+                                   "Invalid %s: too big, max char len = %d\n", en->key,
+                                   NI_MAX_PPU_PARAM_EXPR_CHAR);
+                            av_dict_free(&dict);
+                            return AVERROR_EXTERNAL;
+                        case NI_RETCODE_PARAM_ERROR_TOO_SMALL:
+                            av_log(avctx, AV_LOG_ERROR, "Invalid %s: too small\n", en->key);
+                            av_dict_free(&dict);
+                            return AVERROR_EXTERNAL;
+                        case NI_RETCODE_PARAM_ERROR_OOR:
+                            av_log(avctx, AV_LOG_ERROR, "Invalid %s: out of range\n",
+                                   en->key);
+                            av_dict_free(&dict);
+                            return AVERROR_EXTERNAL;
+                        case NI_RETCODE_PARAM_ERROR_ZERO:
+                            av_log(avctx, AV_LOG_ERROR,
+                                   "Error setting option %s to value 0\n", en->key);
+                            av_dict_free(&dict);
+                            return AVERROR_EXTERNAL;
+                        case NI_RETCODE_PARAM_INVALID_VALUE:
+                            av_log(avctx, AV_LOG_ERROR, "Invalid value for %s: %s.\n",
+                                   en->key, en->value);
+                            av_dict_free(&dict);
+                            return AVERROR_EXTERNAL;
+                        case NI_RETCODE_PARAM_WARNING_DEPRECATED:
+                            av_log(avctx, AV_LOG_WARNING, "Parameter %s is deprecated\n",
+                                   en->key);
+                            break;
+                        default:
+                            av_log(avctx, AV_LOG_ERROR, "Invalid %s: ret %d\n", en->key,
+                                   parse_ret);
+                            av_dict_free(&dict);
+                            return AVERROR_EXTERNAL;
+                    }
+                }
+            }
+            av_dict_free(&dict);
+        }
+
+        for (size_t i = 0; i < NI_MAX_NUM_OF_DECODER_OUTPUTS; i++) {
+            if (p_param->dec_input_params.crop_mode[i] != NI_DEC_CROP_MODE_AUTO) {
+                continue;
+            }
+            for (size_t j = 0; j < 4; j++) {
+                if (strlen(p_param->dec_input_params.cr_expr[i][j])) {
+                    av_log(avctx, AV_LOG_ERROR, "Setting crop parameters without setting crop mode to manual?\n");
+                    return AVERROR_EXTERNAL;
+                }
+            }
+        }
+    }
+    parse_symbolic_decoder_param(s);
+    return 0;
+}
+
+int xcoder_decode_init(AVCodecContext *avctx) {
+    int i;
+    int ret = 0;
+    XCoderDecContext *s = avctx->priv_data;
+    const AVPixFmtDescriptor *desc;
+    ni_xcoder_params_t *p_param = &s->api_param;
+    uint32_t xcoder_timeout;
+
+    ni_log_set_level(ff_to_ni_log_level(av_log_get_level()));
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder decode init\n");
+
+    avctx->sw_pix_fmt = avctx->pix_fmt;
+
+    desc = av_pix_fmt_desc_get(avctx->sw_pix_fmt);
+    av_log(avctx, AV_LOG_VERBOSE, "width: %d height: %d sw_pix_fmt: %s\n",
+           avctx->width, avctx->height, desc ? desc->name : "NONE");
+
+    if (0 == avctx->width || 0 == avctx->height) {
+        av_log(avctx, AV_LOG_ERROR, "Error probing input stream\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    switch (avctx->pix_fmt) {
+        case AV_PIX_FMT_YUV420P:
+        case AV_PIX_FMT_YUV420P10BE:
+        case AV_PIX_FMT_YUV420P10LE:
+        case AV_PIX_FMT_YUVJ420P:
+        case AV_PIX_FMT_GRAY8:
+            break;
+        case AV_PIX_FMT_NONE:
+            av_log(avctx, AV_LOG_WARNING, "Warning: pixel format is not specified\n");
+            break;
+        default:
+            av_log(avctx, AV_LOG_ERROR, "Error: pixel format %s not supported.\n",
+                   desc ? desc->name : "NONE");
+            return AVERROR_INVALIDDATA;
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "(avctx->field_order = %d)\n", avctx->field_order);
+    if (avctx->field_order > AV_FIELD_PROGRESSIVE) { //AVFieldOrder with bottom or top coding order represents interlaced video
+        av_log(avctx, AV_LOG_ERROR, "interlaced video not supported!\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    if ((ret = xcoder_setup_decoder(avctx)) < 0) {
+        return ret;
+    }
+
+    //--------reassign pix format based on user param------------//
+    if (p_param->dec_input_params.semi_planar[0]) {
+        if (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10BE ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10LE ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P) {
+            av_log(avctx, AV_LOG_VERBOSE, "XCoder decode init: YV12 forced to NV12\n");
+            avctx->sw_pix_fmt = (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P) ? AV_PIX_FMT_NV12 : AV_PIX_FMT_P010LE;
+        }
+    }
+    if (p_param->dec_input_params.force_8_bit[0]) {
+        if (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10BE ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10LE ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_P010LE) {
+            av_log(avctx, AV_LOG_VERBOSE, "XCoder decode init: 10Bit input forced to 8bit\n");
+            avctx->sw_pix_fmt = (avctx->sw_pix_fmt == AV_PIX_FMT_P010LE) ? AV_PIX_FMT_NV12 : AV_PIX_FMT_YUV420P;
+            s->api_ctx.bit_depth_factor = 1;
+        }
+    }
+    if (p_param->dec_input_params.hwframes) { //need to set before open decoder
+        s->api_ctx.hw_action = NI_CODEC_HW_ENABLE;
+    } else {
+        s->api_ctx.hw_action = NI_CODEC_HW_NONE;
+    }
+
+    if (p_param->dec_input_params.hwframes && p_param->dec_input_params.max_extra_hwframe_cnt == 255)
+        p_param->dec_input_params.max_extra_hwframe_cnt = 0;
+    if (p_param->dec_input_params.hwframes && (DEFAULT_FRAME_THREAD_QUEUE_SIZE > 1))
+        p_param->dec_input_params.hwframes |= DEFAULT_FRAME_THREAD_QUEUE_SIZE << 4;
+    //------reassign pix format based on user param done--------//
+
+    s->api_ctx.enable_user_data_sei_passthru = 1; // Enable by default
+
+    s->started = 0;
+    memset(&s->api_pkt, 0, sizeof(ni_packet_t));
+    s->pkt_nal_bitmap = 0;
+    s->svct_skip_next_packet = 0;
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder decode init: time_base = %d/%d, frame rate = %d/%d\n", avctx->time_base.num, avctx->time_base.den, avctx->framerate.num, avctx->framerate.den);
+
+    // overwrite keep alive timeout value here with a custom value if it was
+    // provided
+    // if xcoder option is set then overwrite the (legacy) decoder option
+    xcoder_timeout = s->api_param.dec_input_params.keep_alive_timeout;
+    if (xcoder_timeout != NI_DEFAULT_KEEP_ALIVE_TIMEOUT) {
+        s->api_ctx.keep_alive_timeout = xcoder_timeout;
+    } else {
+        s->api_ctx.keep_alive_timeout = s->keep_alive_timeout;
+    }
+    av_log(avctx, AV_LOG_VERBOSE, "Custom NVME Keep Alive Timeout set to %d\n",
+           s->api_ctx.keep_alive_timeout);
+
+    if (s->api_param.dec_input_params.decoder_low_delay != 0) {
+        s->low_delay = s->api_param.dec_input_params.decoder_low_delay;
+    } else {
+        s->api_param.dec_input_params.decoder_low_delay = s->low_delay;
+    }
+    s->api_ctx.enable_low_delay_check = s->api_param.dec_input_params.enable_low_delay_check;
+    if (avctx->has_b_frames && s->api_ctx.enable_low_delay_check) {
+        // If has B frame, must set lowdelay to 0
+        av_log(avctx, AV_LOG_WARNING,"Warning: decoder lowDelay mode "
+               "is cancelled due to has_b_frames with enable_low_delay_check\n");
+        s->low_delay = s->api_param.dec_input_params.decoder_low_delay = 0;
+    }
+    s->api_ctx.decoder_low_delay = s->low_delay;
+
+    s->api_ctx.p_session_config = &s->api_param;
+
+    if ((ret = ff_xcoder_dec_init(avctx, s)) < 0) {
+        goto done;
+    }
+
+    s->current_pts = NI_NOPTS_VALUE;
+
+    /* The size opaque pointers buffer is chosen by max buffered packets in FW (4) +
+     * max output buffer in FW (24) + some extra room to be safe. If the delay of any
+     * frame is larger than this, we assume that the frame is dropped so the buffered
+     * opaque pointer can be overwritten when the opaque_data_array wraps around */
+    s->opaque_data_nb = 30;
+    s->opaque_data_pos = 0;
+    if (!s->opaque_data_array) {
+        s->opaque_data_array = av_calloc(s->opaque_data_nb, sizeof(OpaqueData));
+        if (!s->opaque_data_array) {
+            ret = AVERROR(ENOMEM);
+            goto done;
+        }
+    }
+    for (i = 0; i < s->opaque_data_nb; i++) {
+        s->opaque_data_array[i].pkt_pos = -1;
+    }
+
+done:
+    return ret;
+}
+
+// reset and restart when xcoder decoder resets
+int xcoder_decode_reset(AVCodecContext *avctx) {
+    XCoderDecContext *s = avctx->priv_data;
+    int ret = NI_RETCODE_FAILURE;
+    int64_t bcp_current_pts;
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder decode reset\n");
+
+    ni_device_session_close(&s->api_ctx, s->eos, NI_DEVICE_TYPE_DECODER);
+
+    ni_device_session_context_clear(&s->api_ctx);
+
+#ifdef _WIN32
+    ni_device_close(s->api_ctx.device_handle);
+#elif __linux__
+    ni_device_close(s->api_ctx.device_handle);
+    ni_device_close(s->api_ctx.blk_io_handle);
+#endif
+    s->api_ctx.device_handle = NI_INVALID_DEVICE_HANDLE;
+    s->api_ctx.blk_io_handle = NI_INVALID_DEVICE_HANDLE;
+
+    ni_packet_buffer_free(&(s->api_pkt.data.packet));
+    bcp_current_pts = s->current_pts;
+    ret = xcoder_decode_init(avctx);
+    s->current_pts = bcp_current_pts;
+    s->api_ctx.session_run_state = SESSION_RUN_STATE_RESETTING;
+    return ret;
+}
+
+static int xcoder_send_receive(AVCodecContext *avctx, XCoderDecContext *s,
+                               AVFrame *frame, bool wait) {
+    int ret;
+
+    /* send any pending data from buffered packet */
+    while (s->buffered_pkt.size) {
+        ret = ff_xcoder_dec_send(avctx, s, &s->buffered_pkt);
+        if (ret == AVERROR(EAGAIN))
+            break;
+        else if (ret < 0) {
+            av_packet_unref(&s->buffered_pkt);
+            return ret;
+        }
+        av_packet_unref(&s->buffered_pkt);
+    }
+
+    /* check for new frame */
+    return ff_xcoder_dec_receive(avctx, s, frame, wait);
+}
+
+int xcoder_receive_frame(AVCodecContext *avctx, AVFrame *frame) {
+    XCoderDecContext *s = avctx->priv_data;
+    const AVPixFmtDescriptor *desc;
+    int ret;
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder receive frame\n");
+    /*
+     * After we have buffered an input packet, check if the codec is in the
+     * flushing state. If it is, we need to call ff_xcoder_dec_flush.
+     *
+     * ff_xcoder_dec_flush returns 0 if the flush cannot be performed on
+     * the codec (because the user retains frames). The codec stays in the
+     * flushing state.
+     * For now we don't consider this case of user retaining the frame
+     * (connected decoder-encoder case), so the return can only be 1
+     * (flushed successfully), or < 0 (failure)
+     *
+     * ff_xcoder_dec_flush returns 1 if the flush can actually be
+     * performed on the codec. The codec leaves the flushing state and can
+     * process again packets.
+     *
+     * ff_xcoder_dec_flush returns a negative value if an error has
+     * occurred.
+     */
+    if (ff_xcoder_dec_is_flushing(avctx, s)) {
+        if (!ff_xcoder_dec_flush(avctx, s)) {
+            return AVERROR(EAGAIN);
+        }
+    }
+
+    // give priority to sending data to decoder
+    if (s->buffered_pkt.size == 0) {
+        ret = ff_decode_get_packet(avctx, &s->buffered_pkt);
+        if (ret < 0) {
+            av_log(avctx, AV_LOG_VERBOSE, "ff_decode_get_packet 1 rc: %s\n",
+                   av_err2str(ret));
+        } else {
+            av_log(avctx, AV_LOG_DEBUG, "ff_decode_get_packet 1 rc: Success\n");
+        }
+    }
+
+    /* flush buffered packet and check for new frame */
+    ret = xcoder_send_receive(avctx, s, frame, false);
+    if (NI_RETCODE_ERROR_VPU_RECOVERY == ret) {
+        ret = xcoder_decode_reset(avctx);
+        if (0 == ret) {
+            return AVERROR(EAGAIN);
+        } else {
+            return ret;
+        }
+    } else if (ret != AVERROR(EAGAIN)) {
+        return ret;
+    }
+
+    /* skip fetching new packet if we still have one buffered */
+    if (s->buffered_pkt.size > 0) {
+        return xcoder_send_receive(avctx, s, frame, true);
+    }
+
+    /* fetch new packet or eof */
+    ret = ff_decode_get_packet(avctx, &s->buffered_pkt);
+    if (ret < 0) {
+        av_log(avctx, AV_LOG_VERBOSE, "ff_decode_get_packet 2 rc: %s\n",
+               av_err2str(ret));
+    } else {
+        av_log(avctx, AV_LOG_DEBUG, "ff_decode_get_packet 2 rc: Success\n");
+    }
+
+    if (ret == AVERROR_EOF) {
+        AVPacket null_pkt = {0};
+        ret = ff_xcoder_dec_send(avctx, s, &null_pkt);
+        if (ret < 0) {
+            return ret;
+        }
+    } else if (ret < 0) {
+        return ret;
+    } else {
+        av_log(avctx, AV_LOG_VERBOSE, "width: %d  height: %d\n", avctx->width, avctx->height);
+        desc = av_pix_fmt_desc_get(avctx->pix_fmt);
+        av_log(avctx, AV_LOG_VERBOSE, "pix_fmt: %s\n", desc ? desc->name : "NONE");
+    }
+
+    /* crank decoder with new packet */
+    return xcoder_send_receive(avctx, s, frame, true);
+}
+
+void xcoder_decode_flush(AVCodecContext *avctx) {
+    XCoderDecContext *s = avctx->priv_data;
+    ni_device_dec_session_flush(&s->api_ctx);
+    s->draining = 0;
+    s->flushing = 0;
+    s->eos = 0;
+}
diff --git a/libavcodec/nidec.h b/libavcodec/nidec.h
new file mode 100644
index 0000000000..7575bc83d0
--- /dev/null
+++ b/libavcodec/nidec.h
@@ -0,0 +1,86 @@
+/*
+ * NetInt XCoder H.264/HEVC Decoder common code header
+ * Copyright (c) 2018-2019 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_NIDEC_H
+#define AVCODEC_NIDEC_H
+
+#include <stdbool.h>
+#include <ni_rsrc_api.h>
+#include <ni_device_api.h>
+#include <ni_util.h>
+
+#include "avcodec.h"
+#include "codec_internal.h"
+#include "decode.h"
+#include "internal.h"
+
+#include "libavutil/internal.h"
+#include "libavutil/frame.h"
+#include "libavutil/buffer.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/opt.h"
+
+#include "nicodec.h"
+
+#define OFFSETDEC(x) offsetof(XCoderDecContext, x)
+#define VD           AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM
+
+// Common Netint decoder options
+#define NI_DEC_OPTIONS\
+    { "xcoder", "Select which XCoder card to use.", OFFSETDEC(dev_xcoder), \
+      AV_OPT_TYPE_STRING, {.str = NI_BEST_MODEL_LOAD_STR}, CHAR_MIN, CHAR_MAX, VD, "xcoder"}, \
+    {     "bestmodelload", "Pick the least model load XCoder/decoder available.", 0,\
+          AV_OPT_TYPE_CONST, {.str = NI_BEST_MODEL_LOAD_STR}, 0, 0, VD, "xcoder"},\
+    {     "bestload", "Pick the least real load XCoder/decoder available.", 0,\
+          AV_OPT_TYPE_CONST, {.str = NI_BEST_REAL_LOAD_STR}, 0, 0, VD, "xcoder"},\
+    \
+    { "ni_dec_idx", "Select which decoder to use by index. First is 0, second is 1, and so on.", \
+      OFFSETDEC(dev_dec_idx), AV_OPT_TYPE_INT, {.i64 = BEST_DEVICE_LOAD}, -1, INT_MAX, VD, "ni_dec_idx"}, \
+    \
+    { "ni_dec_name", "Select which decoder to use by NVMe block device name, e.g. /dev/nvme0n1.", \
+      OFFSETDEC(dev_blk_name), AV_OPT_TYPE_STRING, {0}, 0, 0, VD, "ni_dec_name"}, \
+    \
+    { "decname", "Select which decoder to use by NVMe block device name, e.g. /dev/nvme0n1.", \
+      OFFSETDEC(dev_blk_name), AV_OPT_TYPE_STRING, {0}, 0, 0, VD, "decname"}, \
+    \
+    { "xcoder-params", "Set the XCoder configuration using a :-separated list of key=value parameters.", \
+      OFFSETDEC(xcoder_opts), AV_OPT_TYPE_STRING, {0}, 0, 0, VD}, \
+    \
+    { "keep_alive_timeout", "Specify a custom session keep alive timeout in seconds.", \
+      OFFSETDEC(keep_alive_timeout), AV_OPT_TYPE_INT, {.i64 = NI_DEFAULT_KEEP_ALIVE_TIMEOUT}, \
+      NI_MIN_KEEP_ALIVE_TIMEOUT, NI_MAX_KEEP_ALIVE_TIMEOUT, VD, "keep_alive_timeout"}
+
+#define NI_DEC_OPTION_LOW_DELAY\
+    { "low_delay", "Enable low delay decoding mode for 1 in, 1 out decoding sequence. " \
+      "Set 1 to enable low delay mode. Should be used only for streams that are in sequence.", \
+      OFFSETDEC(low_delay), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, VD, "low_delay"}
+
+int xcoder_decode_close(AVCodecContext *avctx);
+
+int xcoder_decode_init(AVCodecContext *avctx);
+
+int xcoder_decode_reset(AVCodecContext *avctx);
+
+int xcoder_receive_frame(AVCodecContext *avctx, AVFrame *frame);
+
+void xcoder_decode_flush(AVCodecContext *avctx);
+
+#endif /* AVCODEC_NIDEC_H */
diff --git a/libavcodec/nidec_h264.c b/libavcodec/nidec_h264.c
new file mode 100644
index 0000000000..f6fa0e149d
--- /dev/null
+++ b/libavcodec/nidec_h264.c
@@ -0,0 +1,73 @@
+/*
+ * XCoder H.264 Decoder
+ * Copyright (c) 2018 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * XCoder decoder.
+ */
+
+#include "nidec.h"
+#include "hwconfig.h"
+static const AVCodecHWConfigInternal *ff_ni_quad_hw_configs[] = {
+    &(const AVCodecHWConfigInternal) {
+        .public = {
+            .pix_fmt = AV_PIX_FMT_NI_QUAD,
+            .methods = AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX |
+                       AV_CODEC_HW_CONFIG_METHOD_AD_HOC |
+                       AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX,
+            .device_type = AV_HWDEVICE_TYPE_NI_QUADRA,
+        },
+        .hwaccel = NULL,
+    },
+    NULL
+};
+
+static const AVOption dec_options[] = {
+    NI_DEC_OPTIONS,
+    NI_DEC_OPTION_LOW_DELAY,
+    {NULL}};
+
+static const AVClass h264_xcoderdec_class = {
+    .class_name = "h264_ni_quadra_dec",
+    .item_name  = av_default_item_name,
+    .option     = dec_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+FFCodec ff_h264_ni_quadra_decoder = {
+    .p.name         = "h264_ni_quadra_dec",
+    CODEC_LONG_NAME("H.264 NETINT Quadra decoder v" NI_XCODER_REVISION),
+    .p.long_name    = NULL_IF_CONFIG_SMALL("H.264 NETINT Quadra decoder v" NI_XCODER_REVISION),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_H264,
+    .p.priv_class   = &h264_xcoderdec_class,
+    .p.capabilities = AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE,
+    .p.pix_fmts     = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12,
+                                                  AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_P010LE,
+                                                  AV_PIX_FMT_NI_QUAD, AV_PIX_FMT_NONE },
+    FF_CODEC_RECEIVE_FRAME_CB(xcoder_receive_frame),
+    .priv_data_size = sizeof(XCoderDecContext),
+    .init           = xcoder_decode_init,
+    .close          = xcoder_decode_close,
+    .hw_configs     = ff_ni_quad_hw_configs,
+    .bsfs           = "h264_mp4toannexb",
+    .flush          = xcoder_decode_flush,
+};
diff --git a/libavcodec/nidec_hevc.c b/libavcodec/nidec_hevc.c
new file mode 100644
index 0000000000..846450f14f
--- /dev/null
+++ b/libavcodec/nidec_hevc.c
@@ -0,0 +1,73 @@
+/*
+ * XCoder HEVC Decoder
+ * Copyright (c) 2018 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * XCoder decoder.
+ */
+
+#include "nidec.h"
+#include "hwconfig.h"
+
+static const AVCodecHWConfigInternal *ff_ni_quad_hw_configs[] = {
+    &(const AVCodecHWConfigInternal) {
+        .public = {
+            .pix_fmt = AV_PIX_FMT_NI_QUAD,
+            .methods = AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX |
+                       AV_CODEC_HW_CONFIG_METHOD_AD_HOC |
+                       AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX,
+            .device_type = AV_HWDEVICE_TYPE_NI_QUADRA,
+        },
+        .hwaccel = NULL,
+    },
+    NULL
+};
+
+static const AVOption dec_options[] = {
+    NI_DEC_OPTIONS,
+    NI_DEC_OPTION_LOW_DELAY,
+    {NULL}};
+
+static const AVClass h265_xcoderdec_class = {
+    .class_name = "h265_ni_quadra_dec",
+    .item_name  = av_default_item_name,
+    .option     = dec_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+FFCodec ff_h265_ni_quadra_decoder = {
+    .p.name         = "h265_ni_quadra_dec",
+    CODEC_LONG_NAME("H.265 NETINT Quadra decoder v" NI_XCODER_REVISION),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_HEVC,
+    .p.priv_class   = &h265_xcoderdec_class,
+    .p.capabilities = AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE,
+    .p.pix_fmts     = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12,
+                                                    AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_P010LE,
+                                                    AV_PIX_FMT_NONE },
+    FF_CODEC_RECEIVE_FRAME_CB(xcoder_receive_frame),
+    .priv_data_size = sizeof(XCoderDecContext),
+    .init           = xcoder_decode_init,
+    .close          = xcoder_decode_close,
+    .hw_configs     = ff_ni_quad_hw_configs,
+    .bsfs           = "hevc_mp4toannexb",
+    .flush          = xcoder_decode_flush,
+};
diff --git a/libavcodec/nidec_jpeg.c b/libavcodec/nidec_jpeg.c
new file mode 100644
index 0000000000..62198bcbaf
--- /dev/null
+++ b/libavcodec/nidec_jpeg.c
@@ -0,0 +1,68 @@
+/*
+ * XCoder JPEG Decoder
+ * Copyright (c) 2021 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "nidec.h"
+#include "hwconfig.h"
+#include "profiles.h"
+static const AVCodecHWConfigInternal *ff_ni_quad_hw_configs[] = {
+    &(const AVCodecHWConfigInternal) {
+        .public = {
+            .pix_fmt = AV_PIX_FMT_NI_QUAD,
+            .methods = AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX |
+                      AV_CODEC_HW_CONFIG_METHOD_AD_HOC |
+                      AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX,
+            .device_type = AV_HWDEVICE_TYPE_NI_QUADRA,
+        },
+        .hwaccel = NULL,
+    },
+    NULL
+};
+
+static const AVOption dec_options[] = {
+    NI_DEC_OPTIONS,
+    {NULL},
+};
+
+#define JPEG_NI_QUADRA_DEC "jpeg_ni_quadra_dec"
+
+static const AVClass jpeg_xcoderdec_class = {
+    .class_name = JPEG_NI_QUADRA_DEC,
+    .item_name  = av_default_item_name,
+    .option     = dec_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+FFCodec ff_jpeg_ni_quadra_decoder = {
+    .p.name         = JPEG_NI_QUADRA_DEC,
+    CODEC_LONG_NAME("JPEG NETINT Quadra decoder v" NI_XCODER_REVISION),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_MJPEG,
+    .p.capabilities = AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE,
+    .p.priv_class   = &jpeg_xcoderdec_class,
+    .p.pix_fmts     = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NI_QUAD,
+                                                    AV_PIX_FMT_NONE },
+    FF_CODEC_RECEIVE_FRAME_CB(xcoder_receive_frame),
+    .hw_configs     = ff_ni_quad_hw_configs,
+    .init           = xcoder_decode_init,
+    .close          = xcoder_decode_close,
+    .priv_data_size = sizeof(XCoderDecContext),
+    .flush          = xcoder_decode_flush,
+};
diff --git a/libavcodec/nidec_vp9.c b/libavcodec/nidec_vp9.c
new file mode 100644
index 0000000000..40d424406b
--- /dev/null
+++ b/libavcodec/nidec_vp9.c
@@ -0,0 +1,72 @@
+/*
+ * XCoder VP9 Decoder
+ * Copyright (c) 2020 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * XCoder decoder.
+ */
+
+#include "nidec.h"
+#include "hwconfig.h"
+#include "profiles.h"
+static const AVCodecHWConfigInternal *ff_ni_quad_hw_configs[] = {
+    &(const AVCodecHWConfigInternal) {
+        .public = {
+            .pix_fmt = AV_PIX_FMT_NI_QUAD,
+            .methods = AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX |
+                      AV_CODEC_HW_CONFIG_METHOD_AD_HOC |
+                      AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX,
+            .device_type = AV_HWDEVICE_TYPE_NI_QUADRA,
+        },
+        .hwaccel = NULL,
+    },
+    NULL
+};
+
+static const AVOption dec_options[] = {
+    NI_DEC_OPTIONS,
+    {NULL}};
+
+static const AVClass vp9_xcoderdec_class = {
+    .class_name = "vp9_ni_quadra_dec",
+    .item_name  = av_default_item_name,
+    .option     = dec_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+FFCodec ff_vp9_ni_quadra_decoder = {
+    .p.name         = "vp9_ni_quadra_dec",
+    CODEC_LONG_NAME("VP9 NETINT Quadra decoder v" NI_XCODER_REVISION),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_VP9,
+    .p.priv_class   = &vp9_xcoderdec_class,
+    .p.capabilities = AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE,
+    .p.pix_fmts     = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12,
+                                                    AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_P010LE,
+                                                    AV_PIX_FMT_NI_QUAD, AV_PIX_FMT_NONE },
+    .p.profiles     = NULL_IF_CONFIG_SMALL(ff_vp9_profiles),
+    FF_CODEC_RECEIVE_FRAME_CB(xcoder_receive_frame),
+    .priv_data_size = sizeof(XCoderDecContext),
+    .init           = xcoder_decode_init,
+    .close          = xcoder_decode_close,
+    .hw_configs     = ff_ni_quad_hw_configs,
+    .flush          = xcoder_decode_flush,
+};
diff --git a/libavcodec/nienc.c b/libavcodec/nienc.c
new file mode 100644
index 0000000000..5843311414
--- /dev/null
+++ b/libavcodec/nienc.c
@@ -0,0 +1,3009 @@
+/*
+ * NetInt XCoder H.264/HEVC Encoder common code
+ * Copyright (c) 2018-2019 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include "nienc.h"
+#include "bytestream.h"
+#include "libavcodec/h264.h"
+#include "libavcodec/h264_sei.h"
+#include "libavcodec/hevc/hevc.h"
+#include "libavcodec/hevc/sei.h"
+#include "libavutil/mem.h"
+
+#include "libavcodec/put_bits.h"
+#include "libavutil/avstring.h"
+#include "libavutil/hdr_dynamic_metadata.h"
+#include "libavutil/hwcontext.h"
+#include "libavutil/hwcontext_ni_quad.h"
+#include "libavutil/mastering_display_metadata.h"
+#include "ni_av_codec.h"
+#include "ni_util.h"
+#include "put_bits.h"
+#include "packet_internal.h"
+
+#include <unistd.h>
+#include "encode.h"
+
+static bool gop_params_check(AVDictionary *dict, AVCodecContext *avctx)
+{
+    XCoderEncContext *s = avctx->priv_data;
+    AVDictionaryEntry *en = NULL;
+    char *key;
+
+    while ((en = av_dict_get(dict, "", en, AV_DICT_IGNORE_SUFFIX))) {
+        key = en->key;
+        ni_gop_params_check_set(&s->api_param, key);
+    }
+    return ni_gop_params_check(&s->api_param);
+}
+
+static int xcoder_encoder_headers(AVCodecContext *avctx)
+{
+    // use a copy of encoder context, take care to restore original config
+    // cropping setting
+    XCoderEncContext *ctx = NULL;
+    ni_xcoder_params_t *p_param = NULL;
+    ni_packet_t *xpkt = NULL;
+    int orig_conf_win_right;
+    int orig_conf_win_bottom;
+    int linesize_aligned, height_aligned;
+    int ret, recv;
+
+    ctx = av_malloc(sizeof(XCoderEncContext));
+    if (!ctx) {
+        return AVERROR(ENOMEM);
+    }
+
+    memcpy(ctx, (XCoderEncContext *)(avctx->priv_data),
+           sizeof(XCoderEncContext));
+
+    p_param = (ni_xcoder_params_t *)(ctx->api_ctx.p_session_config);
+
+    orig_conf_win_right  = p_param->cfg_enc_params.conf_win_right;
+    orig_conf_win_bottom = p_param->cfg_enc_params.conf_win_bottom;
+
+    linesize_aligned = avctx->width;
+    if (linesize_aligned < NI_MIN_WIDTH) {
+        p_param->cfg_enc_params.conf_win_right +=
+            (NI_MIN_WIDTH - avctx->width) / 2 * 2;
+        linesize_aligned = NI_MIN_WIDTH;
+    } else {
+        if (avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_8_TILE_4X4 ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_10_TILE_4X4) {
+            linesize_aligned = FFALIGN(avctx->width, 4);
+            p_param->cfg_enc_params.conf_win_right +=
+                (linesize_aligned - avctx->width) / 2 * 2;
+        } else {
+            linesize_aligned = FFALIGN(avctx->width, 2);
+            p_param->cfg_enc_params.conf_win_right +=
+                (linesize_aligned - avctx->width) / 2 * 2;
+        }
+    }
+    p_param->source_width = linesize_aligned;
+
+    height_aligned = avctx->height;
+    if (height_aligned < NI_MIN_HEIGHT) {
+        p_param->cfg_enc_params.conf_win_bottom +=
+            (NI_MIN_HEIGHT - avctx->height) / 2 * 2;
+        height_aligned = NI_MIN_HEIGHT;
+    } else {
+        if (avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_8_TILE_4X4 ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_10_TILE_4X4) {
+            height_aligned = FFALIGN(avctx->height, 4);
+            p_param->cfg_enc_params.conf_win_bottom +=
+                (height_aligned - avctx->height) / 4 * 4;
+        } else {
+            height_aligned = FFALIGN(avctx->height, 2);
+            p_param->cfg_enc_params.conf_win_bottom +=
+                (height_aligned - avctx->height) / 2 * 2;
+        }
+    }
+    p_param->source_height = height_aligned;
+    p_param->cfg_enc_params.enable_acq_limit = 1;
+
+    ctx->api_ctx.hw_id = ctx->dev_enc_idx;
+    ff_xcoder_strncpy(ctx->api_ctx.blk_dev_name, ctx->dev_blk_name,
+                      NI_MAX_DEVICE_NAME_LEN);
+    ff_xcoder_strncpy(ctx->api_ctx.dev_xcoder_name, ctx->dev_xcoder,
+                      MAX_CHAR_IN_DEVICE_NAME);
+
+    ret = ni_device_session_open(&(ctx->api_ctx), NI_DEVICE_TYPE_ENCODER);
+
+    ctx->dev_xcoder_name = ctx->api_ctx.dev_xcoder_name;
+    ctx->blk_xcoder_name = ctx->api_ctx.blk_xcoder_name;
+    ctx->dev_enc_idx = ctx->api_ctx.hw_id;
+
+    switch (ret) {
+    case NI_RETCODE_SUCCESS:
+        av_log(avctx, AV_LOG_VERBOSE,
+               "XCoder %s.%d (inst: %d) opened successfully\n",
+               ctx->dev_xcoder_name, ctx->dev_enc_idx, ctx->api_ctx.session_id);
+        break;
+    case NI_RETCODE_INVALID_PARAM:
+        av_log(avctx, AV_LOG_ERROR,
+               "Failed to open encoder (status = %d), invalid parameter values "
+               "given: %s\n", ret, ctx->api_ctx.param_err_msg);
+        ret = AVERROR_EXTERNAL;
+        goto end;
+    default:
+        av_log(avctx, AV_LOG_ERROR,
+               "Failed to open encoder (status = %d), resource unavailable\n",
+               ret);
+        ret = AVERROR_EXTERNAL;
+        goto end;
+    }
+
+    xpkt = &(ctx->api_pkt.data.packet);
+    ni_packet_buffer_alloc(xpkt, NI_MAX_TX_SZ);
+
+    while (1) {
+        recv = ni_device_session_read(&(ctx->api_ctx), &(ctx->api_pkt),
+                                    NI_DEVICE_TYPE_ENCODER);
+
+        if (recv > 0) {
+            av_freep(&avctx->extradata);
+            avctx->extradata_size = recv - (int)(ctx->api_ctx.meta_size);
+            avctx->extradata =
+                av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
+            memcpy(avctx->extradata,
+                   (uint8_t *)xpkt->p_data + ctx->api_ctx.meta_size,
+                   avctx->extradata_size);
+            av_log(avctx, AV_LOG_VERBOSE, "Xcoder encoder headers len: %d\n",
+                   avctx->extradata_size);
+            break;
+        }
+    }
+
+end:
+    // close and clean up the temporary session
+    if (ret != 0) {
+        ni_device_session_close(&(ctx->api_ctx), ctx->encoder_eof,
+                               NI_DEVICE_TYPE_ENCODER);
+    } else {
+        ret = ni_device_session_close(&(ctx->api_ctx), ctx->encoder_eof,
+                                     NI_DEVICE_TYPE_ENCODER);
+    }
+#ifdef _WIN32
+    ni_device_close(ctx->api_ctx.device_handle);
+#elif __linux__
+    ni_device_close(ctx->api_ctx.device_handle);
+    ni_device_close(ctx->api_ctx.blk_io_handle);
+#endif
+    ctx->api_ctx.device_handle = NI_INVALID_DEVICE_HANDLE;
+    ctx->api_ctx.blk_io_handle = NI_INVALID_DEVICE_HANDLE;
+
+    ni_packet_buffer_free(&(ctx->api_pkt.data.packet));
+
+    ni_rsrc_free_device_context(ctx->rsrc_ctx);
+    ctx->rsrc_ctx = NULL;
+
+    p_param->cfg_enc_params.conf_win_right  = orig_conf_win_right;
+    p_param->cfg_enc_params.conf_win_bottom = orig_conf_win_bottom;
+
+    av_freep(&ctx);
+
+    return ret;
+}
+
+static int xcoder_encoder_header_check_set(AVCodecContext *avctx)
+{
+    XCoderEncContext *ctx = avctx->priv_data;
+    ni_xcoder_params_t *p_param;
+    // set color metrics
+    enum AVColorPrimaries color_primaries = avctx->color_primaries;
+    enum AVColorTransferCharacteristic color_trc = avctx->color_trc;
+    enum AVColorSpace color_space = avctx->colorspace;
+
+    p_param = (ni_xcoder_params_t *)ctx->api_ctx.p_session_config;
+
+    if (5 == p_param->dolby_vision_profile) {
+        switch (avctx->codec_id) {
+        case AV_CODEC_ID_HEVC:
+            color_primaries = AVCOL_PRI_UNSPECIFIED;
+            color_trc       = AVCOL_TRC_UNSPECIFIED;
+            color_space     = AVCOL_SPC_UNSPECIFIED;
+            p_param->cfg_enc_params.hrdEnable =
+                p_param->cfg_enc_params.EnableAUD        = 1;
+            p_param->cfg_enc_params.forced_header_enable = 1;
+            p_param->cfg_enc_params.videoFullRange       = 1;
+            break;
+        case AV_CODEC_ID_AV1:
+            av_log(avctx, AV_LOG_ERROR,
+                   "dolbyVisionProfile is not supported on av1 encoder.\n");
+            return -1;
+        case AV_CODEC_ID_MJPEG:
+            av_log(avctx, AV_LOG_ERROR,
+                   "dolbyVisionProfile is not supported on jpeg encoder.\n");
+            return -1;
+        case AV_CODEC_ID_H264:
+            av_log(avctx, AV_LOG_ERROR,
+                   "dolbyVisionProfile is not supported on h264 encoder.\n");
+            return -1;
+        default:
+            break;
+        }
+    }
+
+    if (avctx->codec_id != AV_CODEC_ID_MJPEG &&
+        ((5 == p_param->dolby_vision_profile &&
+          AV_CODEC_ID_HEVC == avctx->codec_id) ||
+         color_primaries != AVCOL_PRI_UNSPECIFIED ||
+         color_trc != AVCOL_TRC_UNSPECIFIED ||
+         color_space != AVCOL_SPC_UNSPECIFIED)) {
+        p_param->cfg_enc_params.colorDescPresent = 1;
+        p_param->cfg_enc_params.colorPrimaries   = color_primaries;
+        p_param->cfg_enc_params.colorTrc         = color_trc;
+        p_param->cfg_enc_params.colorSpace       = color_space;
+
+        av_log(avctx, AV_LOG_VERBOSE,
+               "XCoder HDR color info color_primaries: %d "
+               "color_trc: %d  color_space %d\n",
+               color_primaries, color_trc, color_space);
+    }
+    if (avctx->color_range == AVCOL_RANGE_JPEG ||
+        AV_PIX_FMT_YUVJ420P == avctx->pix_fmt ||
+        AV_PIX_FMT_YUVJ420P == avctx->sw_pix_fmt) {
+        p_param->cfg_enc_params.videoFullRange = 1;
+    }
+
+    return 0;
+}
+
+static int xcoder_setup_encoder(AVCodecContext *avctx)
+{
+    XCoderEncContext *s = avctx->priv_data;
+    int i, ret = 0;
+    uint32_t  xcoder_timeout;
+    ni_xcoder_params_t *p_param       = &s->api_param;
+    ni_xcoder_params_t *pparams       = NULL;
+    ni_session_run_state_t prev_state = s->api_ctx.session_run_state;
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder setup device encoder\n");
+
+    if (ni_device_session_context_init(&(s->api_ctx)) < 0) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Error XCoder init encoder context failure\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    switch (avctx->codec_id) {
+    case AV_CODEC_ID_HEVC:
+        s->api_ctx.codec_format = NI_CODEC_FORMAT_H265;
+        break;
+    case AV_CODEC_ID_AV1:
+        s->api_ctx.codec_format = NI_CODEC_FORMAT_AV1;
+        break;
+    case AV_CODEC_ID_MJPEG:
+        s->api_ctx.codec_format = NI_CODEC_FORMAT_JPEG;
+        break;
+    default:
+        s->api_ctx.codec_format = NI_CODEC_FORMAT_H264;
+        break;
+    }
+
+    s->api_ctx.session_run_state = prev_state;
+    s->av_rois = NULL;
+    s->firstPktArrived = 0;
+    s->spsPpsArrived = 0;
+    s->spsPpsHdrLen = 0;
+    s->p_spsPpsHdr = NULL;
+    s->xcode_load_pixel = 0;
+    s->reconfigCount = 0;
+    s->latest_dts = 0;
+    s->first_frame_pts = INT_MIN;
+
+    if (SESSION_RUN_STATE_SEQ_CHANGE_DRAINING != s->api_ctx.session_run_state) {
+        av_log(avctx, AV_LOG_INFO, "Session state: %d allocate frame fifo.\n",
+               s->api_ctx.session_run_state);
+        s->fme_fifo = av_fifo_alloc2((size_t) 1, sizeof(AVFrame), 0);
+    } else {
+        av_log(avctx, AV_LOG_INFO, "Session seq change, fifo size: %lu.\n",
+               av_fifo_can_read(s->fme_fifo));
+    }
+
+    if (!s->fme_fifo) {
+        return AVERROR(ENOMEM);
+    }
+    s->eos_fme_received = 0;
+
+    //Xcoder User Configuration
+    ret = ni_encoder_init_default_params(
+        p_param, avctx->framerate.num,
+        avctx->framerate.den, avctx->bit_rate,
+        avctx->width, avctx->height, s->api_ctx.codec_format);
+    switch (ret) {
+    case NI_RETCODE_PARAM_ERROR_WIDTH_TOO_BIG:
+        if (avctx->codec_id == AV_CODEC_ID_AV1 && avctx->width < NI_PARAM_MAX_WIDTH) {
+            // AV1 resolution will be checked again when encoder session open (ni_validate_custom_template) since crop size may meet AV1 resolution constraint (E.g. AV1 tile encode)
+            av_log(avctx, AV_LOG_ERROR, "AV1 Picture Width exceeds %d - picture needs to be cropped:\n",
+                   NI_PARAM_AV1_MAX_WIDTH);
+            ret = NI_RETCODE_SUCCESS;
+        } else {
+            av_log(avctx, AV_LOG_ERROR, "Invalid Picture Width: too big\n");
+            return AVERROR_EXTERNAL;
+        }
+        break;
+    case NI_RETCODE_PARAM_ERROR_WIDTH_TOO_SMALL:
+        av_log(avctx, AV_LOG_ERROR, "Invalid Picture Width: too small\n");
+        return AVERROR_EXTERNAL;
+    case NI_RETCODE_PARAM_ERROR_HEIGHT_TOO_BIG:
+        if (avctx->codec_id == AV_CODEC_ID_AV1) {
+            // AV1 resolution will be checked again when encoder session open (ni_validate_custom_template) since crop size may meet AV1 resolution constraint (E.g. AV1 tile encode)
+            av_log(avctx, AV_LOG_ERROR, "AV1 Picture Height exceeds %d - picture needs to be cropped:\n",
+                   NI_PARAM_AV1_MAX_HEIGHT);
+            ret = NI_RETCODE_SUCCESS;
+        } else {
+            av_log(avctx, AV_LOG_ERROR, "Invalid Picture Height: too big\n");
+            return AVERROR_EXTERNAL;
+        }
+        break;
+    case NI_RETCODE_PARAM_ERROR_HEIGHT_TOO_SMALL:
+        av_log(avctx, AV_LOG_ERROR, "Invalid Picture Height: too small\n");
+        return AVERROR_EXTERNAL;
+    case NI_RETCODE_PARAM_ERROR_AREA_TOO_BIG:
+        if (avctx->codec_id == AV_CODEC_ID_AV1) {
+            // AV1 resolution will be checked again when encoder session open (ni_validate_custom_template) since crop size may meet AV1 resolution constraint (E.g. AV1 tile encode)
+            av_log(avctx, AV_LOG_ERROR, "AV1 Picture Width x Height exceeds %d - picture needs to be cropped:\n",
+                   NI_PARAM_AV1_MAX_AREA);
+            ret = NI_RETCODE_SUCCESS;
+        } else {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Invalid Picture Width x Height: exceeds %d\n",
+                   NI_MAX_RESOLUTION_AREA);
+            return AVERROR_EXTERNAL;
+        }
+        break;
+    case NI_RETCODE_PARAM_ERROR_PIC_WIDTH:
+        av_log(avctx, AV_LOG_ERROR, "Invalid Picture Width\n");
+        return AVERROR_EXTERNAL;
+    case NI_RETCODE_PARAM_ERROR_PIC_HEIGHT:
+        av_log(avctx, AV_LOG_ERROR, "Invalid Picture Height\n");
+        return AVERROR_EXTERNAL;
+    default:
+        if (ret < 0) {
+            av_log(avctx, AV_LOG_ERROR, "Error setting preset or log.\n");
+            av_log(avctx, AV_LOG_INFO, "Possible presets:");
+            for (i = 0; g_xcoder_preset_names[i]; i++)
+                av_log(avctx, AV_LOG_INFO, " %s", g_xcoder_preset_names[i]);
+            av_log(avctx, AV_LOG_INFO, "\n");
+
+            av_log(avctx, AV_LOG_INFO, "Possible log:");
+            for (i = 0; g_xcoder_log_names[i]; i++)
+                av_log(avctx, AV_LOG_INFO, " %s", g_xcoder_log_names[i]);
+            av_log(avctx, AV_LOG_INFO, "\n");
+
+            return AVERROR(EINVAL);
+        }
+        break;
+    }
+
+    av_log(avctx, AV_LOG_INFO, "pix_fmt is %d, sw_pix_fmt is %d resolution %dx%d\n", avctx->pix_fmt, avctx->sw_pix_fmt, avctx->width, avctx->height);
+    if (avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) {
+        av_log(avctx, AV_LOG_INFO, "sw_pix_fmt assigned to pix_fmt was %d, is now %d\n", avctx->pix_fmt, avctx->sw_pix_fmt);
+        avctx->sw_pix_fmt = avctx->pix_fmt;
+    } else {
+        if ((avctx->height >= NI_MIN_HEIGHT) && (avctx->width >= NI_MIN_WIDTH)) {
+            p_param->hwframes = 1;
+        } else if (avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_8_TILE_4X4 ||
+                   avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_10_TILE_4X4) {
+            av_log(avctx, AV_LOG_ERROR, "Invalid Picture Height or Width: too small\n");
+            return AVERROR_EXTERNAL;
+        }
+
+        if (avctx->codec_id == AV_CODEC_ID_MJPEG) {
+            if (avctx->sw_pix_fmt == AV_PIX_FMT_YUVJ420P) {
+                av_log(avctx, AV_LOG_DEBUG, "Pixfmt %s supported in %s encoder\n",
+                       av_get_pix_fmt_name(avctx->sw_pix_fmt), avctx->codec->name);
+            } else if ((avctx->color_range == AVCOL_RANGE_JPEG || avctx->color_range == AVCOL_RANGE_UNSPECIFIED) &&
+                      (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P || avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10LE ||
+                       avctx->sw_pix_fmt == AV_PIX_FMT_NV12 || avctx->sw_pix_fmt == AV_PIX_FMT_P010LE)) {
+                av_log(avctx, AV_LOG_DEBUG, "Pixfmt %s supported in %s encoder when color_range is AVCOL_RANGE_JPEG\n",
+                       av_get_pix_fmt_name(avctx->sw_pix_fmt), avctx->codec->name);
+            } else {
+                av_log(avctx, AV_LOG_ERROR, "Pixfmt %s not supported in %s encoder when color_range is %d\n",
+                       av_get_pix_fmt_name(avctx->sw_pix_fmt), avctx->codec->name, avctx->color_range);
+                return AVERROR_INVALIDDATA;
+            }
+        }
+    }
+
+    switch (avctx->sw_pix_fmt) {
+    case AV_PIX_FMT_YUV420P:
+    case AV_PIX_FMT_YUVJ420P:
+        s->api_ctx.pixel_format = NI_PIX_FMT_YUV420P;
+        break;
+    case AV_PIX_FMT_YUV420P10LE:
+        s->api_ctx.pixel_format = NI_PIX_FMT_YUV420P10LE;
+        break;
+    case AV_PIX_FMT_NV12:
+        s->api_ctx.pixel_format = NI_PIX_FMT_NV12;
+        break;
+    case AV_PIX_FMT_P010LE:
+        s->api_ctx.pixel_format = NI_PIX_FMT_P010LE;
+        break;
+    case AV_PIX_FMT_NI_QUAD_8_TILE_4X4:
+        s->api_ctx.pixel_format = NI_PIX_FMT_8_TILED4X4;
+        break;
+    case AV_PIX_FMT_NI_QUAD_10_TILE_4X4:
+        s->api_ctx.pixel_format = NI_PIX_FMT_10_TILED4X4;
+        break;
+    case AV_PIX_FMT_ARGB:
+        s->api_ctx.pixel_format = NI_PIX_FMT_ARGB;
+        break;
+    case AV_PIX_FMT_ABGR:
+        s->api_ctx.pixel_format = NI_PIX_FMT_ABGR;
+        break;
+    case AV_PIX_FMT_RGBA:
+        s->api_ctx.pixel_format = NI_PIX_FMT_RGBA;
+        break;
+    case AV_PIX_FMT_BGRA:
+        s->api_ctx.pixel_format = NI_PIX_FMT_BGRA;
+        break;
+    default:
+        av_log(avctx, AV_LOG_ERROR, "Pixfmt %s not supported in Quadra encoder\n",
+               av_get_pix_fmt_name(avctx->sw_pix_fmt));
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (s->xcoder_opts) {
+        AVDictionary *dict = NULL;
+        AVDictionaryEntry *en = NULL;
+
+        if (!av_dict_parse_string(&dict, s->xcoder_opts, "=", ":", 0)) {
+            while ((en = av_dict_get(dict, "", en, AV_DICT_IGNORE_SUFFIX))) {
+                int parse_ret = ni_encoder_params_set_value(p_param, en->key, en->value);
+                if (parse_ret != NI_RETCODE_SUCCESS) {
+                    switch (parse_ret) {
+                    case NI_RETCODE_PARAM_INVALID_NAME:
+                        av_log(avctx, AV_LOG_ERROR, "Unknown option: %s.\n", en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_ERROR_TOO_BIG:
+                        av_log(avctx, AV_LOG_ERROR, "Invalid %s: too big\n", en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_ERROR_TOO_SMALL:
+                        av_log(avctx, AV_LOG_ERROR, "Invalid %s: too small\n", en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_ERROR_OOR:
+                        av_log(avctx, AV_LOG_ERROR, "Invalid %s: out of range\n",
+                               en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_ERROR_ZERO:
+                        av_log(avctx, AV_LOG_ERROR,
+                               "Error setting option %s to value 0\n", en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_INVALID_VALUE:
+                        av_log(avctx, AV_LOG_ERROR, "Invalid value for %s: %s.\n",
+                               en->key, en->value);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_WARNING_DEPRECATED:
+                        av_log(avctx, AV_LOG_WARNING, "Parameter %s is deprecated\n",
+                               en->key);
+                        break;
+                    default:
+                        av_log(avctx, AV_LOG_ERROR, "Invalid %s: ret %d\n", en->key,
+                               parse_ret);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    }
+                }
+            }
+            av_dict_free(&dict);
+        }
+    }
+
+    if (p_param->enable_vfr) {
+        // in the vfr mode, if the initial framerate is out of [5-120]
+        // think the initial framerate is incorrect, set it to default 30 fps
+        if (p_param->cfg_enc_params.frame_rate < 5 ||
+            p_param->cfg_enc_params.frame_rate > 120) {
+            p_param->cfg_enc_params.frame_rate = 30;
+            s->api_ctx.prev_fps                = 30;
+        } else {
+            s->api_ctx.prev_fps = p_param->cfg_enc_params.frame_rate;
+        }
+        s->api_ctx.last_change_framenum    = 0;
+        s->api_ctx.fps_change_detect_count = 0;
+    }
+
+    av_log(avctx, AV_LOG_DEBUG, "p_param->hwframes = %d\n", p_param->hwframes);
+    if (s->xcoder_gop) {
+        AVDictionary *dict = NULL;
+        AVDictionaryEntry *en = NULL;
+
+        if (!av_dict_parse_string(&dict, s->xcoder_gop, "=", ":", 0)) {
+            if (!gop_params_check(dict, avctx)) {
+                av_dict_free(&dict);
+                return AVERROR_EXTERNAL;
+            }
+
+            while ((en = av_dict_get(dict, "", en, AV_DICT_IGNORE_SUFFIX))) {
+                int parse_ret = ni_encoder_gop_params_set_value(p_param, en->key, en->value);
+                if (parse_ret != NI_RETCODE_SUCCESS) {
+                    switch (parse_ret) {
+                    case NI_RETCODE_PARAM_INVALID_NAME:
+                        av_log(avctx, AV_LOG_ERROR, "Unknown option: %s.\n", en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_ERROR_TOO_BIG:
+                        av_log(avctx, AV_LOG_ERROR,
+                               "Invalid custom GOP parameters: %s too big\n", en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_ERROR_TOO_SMALL:
+                        av_log(avctx, AV_LOG_ERROR,
+                               "Invalid custom GOP parameters: %s too small\n",
+                               en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_ERROR_OOR:
+                        av_log(avctx, AV_LOG_ERROR,
+                               "Invalid custom GOP parameters: %s out of range \n",
+                               en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_ERROR_ZERO:
+                        av_log(avctx, AV_LOG_ERROR,
+                               "Invalid custom GOP paramaters: Error setting option %s "
+                               "to value 0 \n",
+                               en->key);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_INVALID_VALUE:
+                        av_log(avctx, AV_LOG_ERROR,
+                               "Invalid value for GOP param %s: %s.\n", en->key,
+                               en->value);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    case NI_RETCODE_PARAM_WARNING_DEPRECATED:
+                        av_log(avctx, AV_LOG_WARNING, "Parameter %s is deprecated\n",
+                               en->key);
+                        break;
+                    default:
+                        av_log(avctx, AV_LOG_ERROR, "Invalid %s: ret %d\n", en->key,
+                               parse_ret);
+                        av_dict_free(&dict);
+                        return AVERROR_EXTERNAL;
+                    }
+                }
+            }
+            av_dict_free(&dict);
+        }
+    }
+    if (s->nvme_io_size > 0 && s->nvme_io_size % 4096 != 0) {
+        av_log(avctx, AV_LOG_ERROR, "Error XCoder iosize is not 4KB aligned!\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    s->api_ctx.p_session_config = &s->api_param;
+    pparams = (ni_xcoder_params_t *)s->api_ctx.p_session_config;
+    switch (pparams->cfg_enc_params.gop_preset_index) {
+    /* dtsOffset is the max number of non-reference frames in a GOP
+     * (derived from x264/5 algo) In case of IBBBP the first dts of the I
+     * frame should be input_pts-(3*ticks_per_frame) In case of IBP the
+     * first dts of the I frame should be input_pts-(1*ticks_per_frame)
+     * thus we ensure pts>dts in all cases */
+    case 1:
+    case 9:
+    case 10:
+        s->dtsOffset = 0;
+        break;
+    /* ts requires dts/pts of I frame not same when there are B frames in
+       streams */
+    case 3:
+    case 4:
+    case 7:
+        s->dtsOffset = 1;
+        break;
+    case 5:
+        s->dtsOffset = 2;
+        break;
+    case -1: // adaptive GOP
+    case 8:
+        s->dtsOffset = 3;
+        break;
+    default:
+        s->dtsOffset = 7;
+        break;
+    }
+
+    if (pparams->cfg_enc_params.custom_gop_params.custom_gop_size) {
+        int dts_offset = 0;
+        s->dtsOffset   = 0;
+        bool has_b_frame = false;
+        for (int idx = 0;
+             idx < pparams->cfg_enc_params.custom_gop_params.custom_gop_size;
+             idx++) {
+            if (pparams->cfg_enc_params.custom_gop_params.pic_param[idx].poc_offset <
+                idx + 1) {
+                dts_offset = (idx + 1) -
+                            pparams->cfg_enc_params.custom_gop_params.pic_param[idx].
+                            poc_offset;
+                if (s->dtsOffset < dts_offset) {
+                    s->dtsOffset = dts_offset;
+                }
+            }
+
+            if (!has_b_frame &&
+                (pparams->cfg_enc_params.custom_gop_params.pic_param[idx].pic_type ==
+                 PIC_TYPE_B)) {
+                has_b_frame = true;
+            }
+        }
+
+        if (has_b_frame && !s->dtsOffset) {
+            s->dtsOffset = 1;
+        }
+    }
+    av_log(avctx, AV_LOG_VERBOSE, "dts offset set to %ld\n", s->dtsOffset);
+
+    s->total_frames_received = 0;
+    s->gop_offset_count = 0;
+    av_log(avctx, AV_LOG_INFO, "dts offset: %ld, gop_offset_count: %d\n",
+           s->dtsOffset, s->gop_offset_count);
+
+    //overwrite the nvme io size here with a custom value if it was provided
+    if (s->nvme_io_size > 0) {
+        s->api_ctx.max_nvme_io_size = s->nvme_io_size;
+        av_log(avctx, AV_LOG_VERBOSE, "Custom NVME IO Size set to = %u\n",
+               s->api_ctx.max_nvme_io_size);
+        av_log(avctx, AV_LOG_INFO, "Encoder user specified NVMe IO Size set to: %u\n",
+               s->api_ctx.max_nvme_io_size);
+    }
+
+    // overwrite keep alive timeout value here with a custom value if it was
+    // provided
+    // if xcoder option is set then overwrite the (legacy) decoder option
+    xcoder_timeout = s->api_param.cfg_enc_params.keep_alive_timeout;
+    if (xcoder_timeout != NI_DEFAULT_KEEP_ALIVE_TIMEOUT) {
+        s->api_ctx.keep_alive_timeout = xcoder_timeout;
+    } else {
+        s->api_ctx.keep_alive_timeout = s->keep_alive_timeout;
+    }
+    av_log(avctx, AV_LOG_VERBOSE, "Custom NVME Keep Alive Timeout set to = %d\n",
+           s->api_ctx.keep_alive_timeout);
+
+    s->encoder_eof = 0;
+    avctx->bit_rate = pparams->bitrate;
+
+    s->api_ctx.src_bit_depth = 8;
+    s->api_ctx.src_endian = NI_FRAME_LITTLE_ENDIAN;
+    s->api_ctx.roi_len = 0;
+    s->api_ctx.roi_avg_qp = 0;
+    s->api_ctx.bit_depth_factor = 1;
+    if (AV_PIX_FMT_YUV420P10BE == avctx->sw_pix_fmt ||
+        AV_PIX_FMT_YUV420P10LE == avctx->sw_pix_fmt ||
+        AV_PIX_FMT_P010LE == avctx->sw_pix_fmt ||
+        AV_PIX_FMT_NI_QUAD_10_TILE_4X4 == avctx->sw_pix_fmt) {
+        s->api_ctx.bit_depth_factor = 2;
+        s->api_ctx.src_bit_depth = 10;
+        if (AV_PIX_FMT_YUV420P10BE == avctx->sw_pix_fmt) {
+            s->api_ctx.src_endian = NI_FRAME_BIG_ENDIAN;
+        }
+    }
+    switch (avctx->sw_pix_fmt) {
+        case AV_PIX_FMT_NV12:
+        case AV_PIX_FMT_P010LE:
+            pparams->cfg_enc_params.planar = NI_PIXEL_PLANAR_FORMAT_SEMIPLANAR;
+            break;
+        case AV_PIX_FMT_NI_QUAD_8_TILE_4X4:
+        case AV_PIX_FMT_NI_QUAD_10_TILE_4X4:
+            pparams->cfg_enc_params.planar = NI_PIXEL_PLANAR_FORMAT_TILED4X4;
+            break;
+        default:
+            pparams->cfg_enc_params.planar = NI_PIXEL_PLANAR_FORMAT_PLANAR;
+            break;
+    }
+
+    if (1) {
+        s->freeHead = 0;
+        s->freeTail = 0;
+        for (i = 0; i < MAX_NUM_FRAMEPOOL_HWAVFRAME; i++) {
+            s->sframe_pool[i] = av_frame_alloc();
+            if (!s->sframe_pool[i]) {
+                return AVERROR(ENOMEM);
+            }
+            s->aFree_Avframes_list[i] = i;
+            s->freeTail++;
+        }
+        s->aFree_Avframes_list[i] = -1;
+    }
+
+    // init HDR SEI stuff
+    s->api_ctx.sei_hdr_content_light_level_info_len =
+        s->api_ctx.light_level_data_len =
+        s->api_ctx.sei_hdr_mastering_display_color_vol_len =
+        s->api_ctx.mdcv_max_min_lum_data_len = 0;
+    s->api_ctx.p_master_display_meta_data = NULL;
+
+    memset( &(s->api_fme), 0, sizeof(ni_session_data_io_t) );
+    memset( &(s->api_pkt), 0, sizeof(ni_session_data_io_t) );
+
+    s->api_pkt.data.packet.av1_buffer_index = 0;
+
+    //validate encoded bitstream headers struct for encoder open
+    if (xcoder_encoder_header_check_set(avctx) < 0) {
+        return AVERROR_EXTERNAL;
+    }
+
+    // aspect ratio
+    // Use the value passed in from FFmpeg if aspect ratio from xcoder-params have default values
+    if ((p_param->cfg_enc_params.aspectRatioWidth == 0) && (p_param->cfg_enc_params.aspectRatioHeight == 1)) {
+        p_param->cfg_enc_params.aspectRatioWidth  = avctx->sample_aspect_ratio.num;
+        p_param->cfg_enc_params.aspectRatioHeight = avctx->sample_aspect_ratio.den;
+    }
+
+    // generate encoded bitstream headers in advance if configured to do so
+    if ((avctx->codec_id != AV_CODEC_ID_MJPEG) &&
+        (s->gen_global_headers == 1 ||
+         ((avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) &&
+          (s->gen_global_headers == GEN_GLOBAL_HEADERS_AUTO)))) {
+        ret = xcoder_encoder_headers(avctx);
+    }
+
+    // original resolution this stream started with, this is used by encoder sequence change
+    s->api_ctx.ori_width = avctx->width;
+    s->api_ctx.ori_height = avctx->height;
+    s->api_ctx.ori_bit_depth_factor = s->api_ctx.bit_depth_factor;
+    s->api_ctx.ori_pix_fmt = s->api_ctx.pixel_format;
+
+    av_log(avctx, AV_LOG_INFO, "xcoder_setup_encoder "
+           "sw_pix_fmt %d ori_pix_fmt %d\n",
+           avctx->sw_pix_fmt, s->api_ctx.ori_pix_fmt);
+
+    s->api_ctx.ori_luma_linesize = 0;
+    s->api_ctx.ori_chroma_linesize = 0;
+
+    return ret;
+}
+
+av_cold int xcoder_encode_init(AVCodecContext *avctx)
+{
+    XCoderEncContext *ctx = avctx->priv_data;
+    AVHWFramesContext *avhwf_ctx;
+    int ret;
+    ni_log_set_level(ff_to_ni_log_level(av_log_get_level()));
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder encode init\n");
+
+    if (ctx->api_ctx.session_run_state == SESSION_RUN_STATE_SEQ_CHANGE_DRAINING) {
+        ctx->dev_enc_idx = ctx->orig_dev_enc_idx;
+    } else {
+        ctx->orig_dev_enc_idx = ctx->dev_enc_idx;
+    }
+
+    if ((ret = xcoder_setup_encoder(avctx)) < 0) {
+        xcoder_encode_close(avctx);
+        return ret;
+    }
+
+    if (!avctx->hw_device_ctx) {
+        if (avctx->hw_frames_ctx) {
+            avhwf_ctx = (AVHWFramesContext *)avctx->hw_frames_ctx->data;
+            avctx->hw_device_ctx = av_buffer_ref(avhwf_ctx->device_ref);
+        }
+    }
+
+    return 0;
+}
+
+int xcoder_encode_close(AVCodecContext *avctx)
+{
+    XCoderEncContext *ctx = avctx->priv_data;
+    ni_retcode_t ret = NI_RETCODE_FAILURE;
+    int i;
+
+    for (i = 0; i < MAX_NUM_FRAMEPOOL_HWAVFRAME; i++) {
+        av_frame_free(&(ctx->sframe_pool[i])); //any remaining stored AVframes that have not been unref will die here
+        ctx->sframe_pool[i] = NULL;
+    }
+
+    ret = ni_device_session_close(&ctx->api_ctx, ctx->encoder_eof,
+                                  NI_DEVICE_TYPE_ENCODER);
+    if (NI_RETCODE_SUCCESS != ret) {
+        av_log(avctx, AV_LOG_ERROR, "Failed to close Encoder Session (status = %d)\n", ret);
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder encode close: session_run_state %d\n", ctx->api_ctx.session_run_state);
+    if (ctx->api_ctx.session_run_state != SESSION_RUN_STATE_SEQ_CHANGE_DRAINING) {
+        av_log(avctx, AV_LOG_VERBOSE, "XCoder encode close: close blk_io_handle %d device_handle %d\n", ctx->api_ctx.blk_io_handle, ctx->api_ctx.device_handle);
+#ifdef _WIN32
+        ni_device_close(ctx->api_ctx.device_handle);
+#elif __linux__
+        ni_device_close(ctx->api_ctx.device_handle);
+        ni_device_close(ctx->api_ctx.blk_io_handle);
+#endif
+        ctx->api_ctx.device_handle = NI_INVALID_DEVICE_HANDLE;
+        ctx->api_ctx.blk_io_handle = NI_INVALID_DEVICE_HANDLE;
+        ctx->api_ctx.auto_dl_handle = NI_INVALID_DEVICE_HANDLE;
+        ctx->api_ctx.sender_handle = NI_INVALID_DEVICE_HANDLE;
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder encode close (status = %d)\n", ret);
+
+    if (ctx->api_fme.data.frame.buffer_size
+        || ctx->api_fme.data.frame.metadata_buffer_size
+        || ctx->api_fme.data.frame.start_buffer_size) {
+        ni_frame_buffer_free(&(ctx->api_fme.data.frame));
+    }
+    ni_packet_buffer_free(&(ctx->api_pkt.data.packet));
+    if (AV_CODEC_ID_AV1 == avctx->codec_id &&
+        ctx->api_pkt.data.packet.av1_buffer_index)
+        ni_packet_buffer_free_av1(&(ctx->api_pkt.data.packet));
+
+    av_log(avctx, AV_LOG_DEBUG, "fifo num frames: %lu\n",
+    av_fifo_can_read(ctx->fme_fifo));
+    if (ctx->api_ctx.session_run_state != SESSION_RUN_STATE_SEQ_CHANGE_DRAINING) {
+        av_fifo_freep2(&ctx->fme_fifo);
+        av_log(avctx, AV_LOG_DEBUG, " , freed.\n");
+    } else {
+        av_log(avctx, AV_LOG_DEBUG, " , kept.\n");
+    }
+
+    ni_device_session_context_clear(&ctx->api_ctx);
+
+    ni_rsrc_free_device_context(ctx->rsrc_ctx);
+    ctx->rsrc_ctx = NULL;
+
+    ni_memfree(ctx->av_rois);
+    av_freep(&ctx->p_spsPpsHdr);
+
+    if (avctx->hw_device_ctx) {
+        av_buffer_unref(&avctx->hw_device_ctx);
+    }
+    ctx->started = 0;
+
+    return 0;
+}
+
+int xcoder_encode_sequence_change(AVCodecContext *avctx, int width, int height, int bit_depth_factor)
+{
+    XCoderEncContext *ctx = avctx->priv_data;
+    ni_retcode_t ret = NI_RETCODE_FAILURE;
+    ni_xcoder_params_t *p_param = &ctx->api_param;
+    ni_xcoder_params_t *pparams = (ni_xcoder_params_t *)ctx->api_ctx.p_session_config;
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder encode sequence change: session_run_state %d\n", ctx->api_ctx.session_run_state);
+
+    ret = ni_device_session_sequence_change(&ctx->api_ctx, width, height, bit_depth_factor, NI_DEVICE_TYPE_ENCODER);
+
+    if (NI_RETCODE_SUCCESS != ret) {
+        av_log(avctx, AV_LOG_ERROR, "Failed to send Sequence Change to Encoder Session (status = %d)\n", ret);
+        return ret;
+    }
+
+    // update AvCodecContext
+    if (avctx->pix_fmt != AV_PIX_FMT_NI_QUAD) {
+        av_log(avctx, AV_LOG_INFO, "sw_pix_fmt assigned to pix_fmt was %d, is now %d\n", avctx->pix_fmt, avctx->sw_pix_fmt);
+        avctx->sw_pix_fmt = avctx->pix_fmt;
+    } else {
+        if ((avctx->height >= NI_MIN_HEIGHT) && (avctx->width >= NI_MIN_WIDTH)) {
+            p_param->hwframes = 1;
+        }
+    }
+
+    switch (avctx->sw_pix_fmt) {
+        case AV_PIX_FMT_YUV420P:
+        case AV_PIX_FMT_YUVJ420P:
+        case AV_PIX_FMT_YUV420P10LE:
+        case AV_PIX_FMT_NV12:
+        case AV_PIX_FMT_P010LE:
+        case AV_PIX_FMT_NI_QUAD_8_TILE_4X4:
+        case AV_PIX_FMT_NI_QUAD_10_TILE_4X4:
+        case AV_PIX_FMT_ARGB:
+        case AV_PIX_FMT_ABGR:
+        case AV_PIX_FMT_RGBA:
+        case AV_PIX_FMT_BGRA:
+            break;
+        case AV_PIX_FMT_YUV420P12:
+        case AV_PIX_FMT_YUV422P:
+        case AV_PIX_FMT_YUV422P10:
+        case AV_PIX_FMT_YUV422P12:
+        case AV_PIX_FMT_GBRP:
+        case AV_PIX_FMT_GBRP10:
+        case AV_PIX_FMT_GBRP12:
+        case AV_PIX_FMT_YUV444P:
+        case AV_PIX_FMT_YUV444P10:
+        case AV_PIX_FMT_YUV444P12:
+        case AV_PIX_FMT_GRAY8:
+        case AV_PIX_FMT_GRAY10:
+        case AV_PIX_FMT_GRAY12:
+        default:
+            return AVERROR_INVALIDDATA;
+            break;
+    }
+
+    // update session context
+    ctx->api_ctx.bit_depth_factor = bit_depth_factor;
+    ctx->api_ctx.src_bit_depth = (bit_depth_factor == 1) ? 8 : 10;
+    ctx->api_ctx.src_endian = (AV_PIX_FMT_YUV420P10BE == avctx->sw_pix_fmt) ? NI_FRAME_BIG_ENDIAN : NI_FRAME_LITTLE_ENDIAN;
+    ctx->api_ctx.ready_to_close = 0;
+    ctx->api_ctx.frame_num = 0; // need to reset frame_num because pkt_num is set to 1 when header received after sequnce change, and low delay mode compares frame_num and pkt_num
+    ctx->api_ctx.pkt_num = 0; // also need to reset pkt_num because before header received, pkt_num > frame_num will also cause low delay mode stuck
+    ctx->api_pkt.data.packet.end_of_stream = 0;
+
+    switch (avctx->sw_pix_fmt) {
+        case AV_PIX_FMT_NV12:
+        case AV_PIX_FMT_P010LE:
+            pparams->cfg_enc_params.planar = NI_PIXEL_PLANAR_FORMAT_SEMIPLANAR;
+            break;
+        case AV_PIX_FMT_NI_QUAD_8_TILE_4X4:
+        case AV_PIX_FMT_NI_QUAD_10_TILE_4X4:
+            pparams->cfg_enc_params.planar = NI_PIXEL_PLANAR_FORMAT_TILED4X4;
+            break;
+        default:
+            pparams->cfg_enc_params.planar = NI_PIXEL_PLANAR_FORMAT_PLANAR;
+            break;
+    }
+    return ret;
+}
+
+static int xcoder_encode_reset(AVCodecContext *avctx)
+{
+    av_log(avctx, AV_LOG_WARNING, "XCoder encode reset\n");
+    xcoder_encode_close(avctx);
+    return xcoder_encode_init(avctx);
+}
+
+// frame fifo operations
+static int is_input_fifo_empty(XCoderEncContext *s)
+{
+    if (!s->fme_fifo) {
+        return 1;
+    }
+    return av_fifo_can_read(s->fme_fifo) ? 0 : 1;
+}
+
+static int enqueue_frame(AVCodecContext *avctx, const AVFrame *inframe)
+{
+    XCoderEncContext *ctx = avctx->priv_data;
+    size_t nb_elems;
+    int ret = 0;
+
+    // expand frame buffer fifo if not enough space
+    if (!av_fifo_can_write(ctx->fme_fifo)) {
+        if (av_fifo_can_read(ctx->fme_fifo) >= NI_MAX_FIFO_CAPACITY) {
+            av_log(avctx, AV_LOG_ERROR, "Encoder frame buffer fifo capacity (%lu) reached maximum (%d)\n",
+                   av_fifo_can_read(ctx->fme_fifo), NI_MAX_FIFO_CAPACITY);
+            return AVERROR_EXTERNAL;
+        }
+
+        ret = av_fifo_grow2(ctx->fme_fifo, (size_t) 1);
+        if (ret < 0) {
+            av_log(avctx, AV_LOG_ERROR, "Cannot grow FIFO: out of memory\n");
+            return ret;
+        }
+
+        nb_elems = av_fifo_can_read(ctx->fme_fifo) + av_fifo_can_write(ctx->fme_fifo);
+        if ((nb_elems % 100) == 0) {
+            av_log(avctx, AV_LOG_INFO, "Enc fifo being extended to: %lu\n", nb_elems);
+        }
+    }
+
+    if (inframe == &ctx->buffered_fme) {
+        av_fifo_write(ctx->fme_fifo, (void *)inframe, (size_t) 1);
+    } else {
+        AVFrame temp_frame;
+        memset(&temp_frame, 0, sizeof(AVFrame));
+        // In case double free for external input frame and our buffered frame.
+        av_frame_ref(&temp_frame, inframe);
+        av_fifo_write(ctx->fme_fifo, &temp_frame, 1);
+    }
+
+    av_log(avctx, AV_LOG_DEBUG, "fme queued, fifo num frames: %lu\n",
+           av_fifo_can_read(ctx->fme_fifo));
+    return ret;
+}
+
+int xcoder_send_frame(AVCodecContext *avctx, const AVFrame *frame)
+{
+    XCoderEncContext *ctx = avctx->priv_data;
+    bool ishwframe;
+    bool isnv12frame;
+    bool alignment_2pass_wa;
+    int format_in_use;
+    int ret = 0;
+    int sent;
+    int i, j;
+    int orig_avctx_width = avctx->width;
+    int orig_avctx_height = avctx->height;
+    ni_xcoder_params_t *p_param;
+    int need_to_copy = 1;
+    AVHWFramesContext *avhwf_ctx;
+    AVNIFramesContext *nif_src_ctx;
+    AVFrameSideData *side_data;
+    const AVFrame *first_frame = NULL;
+    // employ a ni_frame_t as a data holder to convert/prepare for side data
+    // of the passed in frame
+    ni_frame_t dec_frame    = {0};
+    ni_aux_data_t *aux_data = NULL;
+    // data buffer for various SEI: HDR mastering display color volume, HDR
+    // content light level, close caption, User data unregistered, HDR10+ etc.
+    int send_sei_with_idr;
+    uint8_t mdcv_data[NI_MAX_SEI_DATA];
+    uint8_t cll_data[NI_MAX_SEI_DATA];
+    uint8_t cc_data[NI_MAX_SEI_DATA];
+    uint8_t udu_data[NI_MAX_SEI_DATA];
+    uint8_t hdrp_data[NI_MAX_SEI_DATA];
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder send frame\n");
+
+    p_param = (ni_xcoder_params_t *) ctx->api_ctx.p_session_config;
+    alignment_2pass_wa = ((p_param->cfg_enc_params.lookAheadDepth ||
+                           p_param->cfg_enc_params.crf >= 0 ||
+                           p_param->cfg_enc_params.crfFloat >= 0) &&
+                          (avctx->codec_id == AV_CODEC_ID_HEVC ||
+                           avctx->codec_id == AV_CODEC_ID_AV1));
+
+    // leave encoder instance open to when the first frame buffer arrives so that
+    // its stride size is known and handled accordingly.
+    if (ctx->started == 0) {
+        if (!is_input_fifo_empty(ctx)) {
+            av_log(avctx, AV_LOG_VERBOSE, "first frame: use fme from fifo peek\n");
+            av_fifo_peek(ctx->fme_fifo, &ctx->buffered_fme, (size_t) 1, NULL);
+            ctx->buffered_fme.extended_data = ctx->buffered_fme.data;
+            first_frame = &ctx->buffered_fme;
+
+        } else if (frame) {
+            av_log(avctx, AV_LOG_VERBOSE, "first frame: use input frame\n");
+            first_frame = frame;
+        } else {
+            av_log(avctx, AV_LOG_ERROR, "first frame: NULL is unexpected!\n");
+        }
+    } else if (ctx->api_ctx.session_run_state == SESSION_RUN_STATE_SEQ_CHANGE_OPENING) {
+        if (!is_input_fifo_empty(ctx)) {
+            av_log(avctx, AV_LOG_VERBOSE, "first frame: use fme from fifo peek\n");
+            av_fifo_peek(ctx->fme_fifo, &ctx->buffered_fme, (size_t) 1, NULL);
+            ctx->buffered_fme.extended_data = ctx->buffered_fme.data;
+            first_frame = &ctx->buffered_fme;
+        } else {
+            av_log(avctx, AV_LOG_ERROR, "No buffered frame - Sequence Change Fail");
+            ret = AVERROR_EXTERNAL;
+            return ret;
+        }
+    }
+
+    if (first_frame && ctx->started == 0) {
+        // if frame stride size is not as we expect it,
+        // adjust using xcoder-params conf_win_right
+        int linesize_aligned = first_frame->width;
+        int height_aligned = first_frame->height;
+        ishwframe = first_frame->format == AV_PIX_FMT_NI_QUAD;
+
+        if (linesize_aligned < NI_MIN_WIDTH) {
+            p_param->cfg_enc_params.conf_win_right +=
+                (NI_MIN_WIDTH - first_frame->width) / 2 * 2;
+            linesize_aligned = NI_MIN_WIDTH;
+        } else {
+            if (avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_8_TILE_4X4 ||
+                avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_10_TILE_4X4) {
+                linesize_aligned = FFALIGN(first_frame->width, 4);
+                p_param->cfg_enc_params.conf_win_right +=
+                    (linesize_aligned - first_frame->width) / 2 * 2;
+            } else {
+                linesize_aligned = FFALIGN(first_frame->width, 2);
+                p_param->cfg_enc_params.conf_win_right +=
+                    (linesize_aligned - first_frame->width) / 2 * 2;
+            }
+        }
+        p_param->source_width = linesize_aligned;
+
+        if (height_aligned < NI_MIN_HEIGHT) {
+            p_param->cfg_enc_params.conf_win_bottom +=
+                (NI_MIN_HEIGHT - first_frame->height) / 2 * 2;
+            height_aligned = NI_MIN_HEIGHT;
+        } else {
+            if (avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_8_TILE_4X4 ||
+                avctx->sw_pix_fmt == AV_PIX_FMT_NI_QUAD_10_TILE_4X4) {
+                height_aligned = FFALIGN(first_frame->height, 4);
+                p_param->cfg_enc_params.conf_win_bottom +=
+                    (height_aligned - first_frame->height) / 4 * 4;
+            } else {
+                height_aligned = FFALIGN(first_frame->height, 2);
+                p_param->cfg_enc_params.conf_win_bottom +=
+                    (height_aligned - first_frame->height) / 2 * 2;
+            }
+        }
+        p_param->source_height = height_aligned;
+
+        av_log(avctx, AV_LOG_DEBUG,
+               "color primaries (%u %u) colorspace (%u %u) color_range (%u %u)\n",
+               avctx->color_primaries, first_frame->color_primaries,
+               avctx->colorspace, first_frame->colorspace,
+               avctx->color_range, first_frame->color_range);
+
+        if (avctx->color_primaries == AVCOL_PRI_UNSPECIFIED) {
+            avctx->color_primaries = first_frame->color_primaries;
+        }
+        if (avctx->color_trc == AVCOL_TRC_UNSPECIFIED) {
+            avctx->color_trc = first_frame->color_trc;
+        }
+        if (avctx->colorspace == AVCOL_SPC_UNSPECIFIED) {
+            avctx->colorspace = first_frame->colorspace;
+        }
+        avctx->color_range = first_frame->color_range;
+
+        if (xcoder_encoder_header_check_set(avctx) < 0) {
+            return AVERROR_EXTERNAL;
+        }
+
+        av_log(avctx, AV_LOG_VERBOSE,
+               "XCoder frame->linesize: %d/%d/%d frame width/height %dx%d"
+               " conf_win_right %d  conf_win_bottom %d , color primaries %u trc %u "
+               "space %u format %d\n",
+               first_frame->linesize[0], first_frame->linesize[1],
+               first_frame->linesize[2], first_frame->width, first_frame->height,
+               p_param->cfg_enc_params.conf_win_right,
+               p_param->cfg_enc_params.conf_win_bottom,
+               first_frame->color_primaries, first_frame->color_trc,
+               first_frame->colorspace, first_frame->format);
+
+        if (SESSION_RUN_STATE_SEQ_CHANGE_OPENING != ctx->api_ctx.session_run_state) {
+            // sequence change backup / restore encoder device handles, hw_id and
+            // block device name, so no need to overwrite hw_id/blk_dev_name to user
+            // set values
+            ctx->api_ctx.hw_id = ctx->dev_enc_idx;
+            ff_xcoder_strncpy(ctx->api_ctx.dev_xcoder_name, ctx->dev_xcoder,
+                              MAX_CHAR_IN_DEVICE_NAME);
+
+            ff_xcoder_strncpy(ctx->api_ctx.blk_dev_name, ctx->dev_blk_name,
+                              NI_MAX_DEVICE_NAME_LEN);
+        }
+
+        p_param->cfg_enc_params.enable_acq_limit = 1;
+        p_param->rootBufId = (ishwframe) ? ((niFrameSurface1_t*)((uint8_t*)first_frame->data[3]))->ui16FrameIdx : 0;
+        if (ishwframe) {
+            ctx->api_ctx.hw_action = NI_CODEC_HW_ENABLE;
+            ctx->api_ctx.sender_handle = (ni_device_handle_t)(
+                (int64_t)(((niFrameSurface1_t *)((uint8_t *)first_frame->data[3]))
+                            ->device_handle));
+        }
+
+        if (first_frame->hw_frames_ctx && ctx->api_ctx.hw_id == -1 &&
+            0 == strcmp(ctx->api_ctx.blk_dev_name, "")) {
+            ctx->api_ctx.hw_id = ni_get_cardno(first_frame);
+            av_log(avctx, AV_LOG_VERBOSE,
+                   "xcoder_send_frame: hw_id -1, empty blk_dev_name, collocated "
+                   "to %d\n",
+                   ctx->api_ctx.hw_id);
+        }
+
+        // AUD insertion has to be handled differently in the firmware
+        // if it is global header
+        if (p_param->cfg_enc_params.EnableAUD) {
+            if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {
+                p_param->cfg_enc_params.EnableAUD = NI_ENABLE_AUD_FOR_GLOBAL_HEADER;
+            }
+
+            av_log(avctx, AV_LOG_VERBOSE,
+                   "%s: EnableAUD %d global header flag %d\n", __FUNCTION__,
+                   (p_param->cfg_enc_params.EnableAUD),
+                   (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) ? 1 : 0);
+        }
+
+        // config linesize for zero copy (if input resolution is zero copy compatible)
+        ni_encoder_frame_zerocopy_check(&ctx->api_ctx,
+            p_param, first_frame->width, first_frame->height,
+            first_frame->linesize, true);
+
+        ret = ni_device_session_open(&ctx->api_ctx, NI_DEVICE_TYPE_ENCODER);
+
+        // As the file handle may change we need to assign back
+        ctx->dev_xcoder_name = ctx->api_ctx.dev_xcoder_name;
+        ctx->blk_xcoder_name = ctx->api_ctx.blk_xcoder_name;
+        ctx->dev_enc_idx = ctx->api_ctx.hw_id;
+
+        switch (ret) {
+        case NI_RETCODE_SUCCESS:
+            av_log(avctx, AV_LOG_VERBOSE,
+                   "XCoder %s.%d (inst: %d) opened successfully\n",
+                   ctx->dev_xcoder_name, ctx->dev_enc_idx, ctx->api_ctx.session_id);
+            break;
+        case NI_RETCODE_INVALID_PARAM:
+            av_log(avctx, AV_LOG_ERROR,
+                   "Failed to open encoder (status = %d), invalid parameter values "
+                   "given: %s\n",
+                   ret, ctx->api_ctx.param_err_msg);
+            ret = AVERROR_EXTERNAL;
+            return ret;
+        default:
+            av_log(avctx, AV_LOG_ERROR,
+                   "Failed to open encoder (status = %d), "
+                   "resource unavailable\n",
+                   ret);
+            // for FFmpeg >= 6.1 sequence change session open fail: unlike previous
+            // FFmpeg versions which terminate streams and codecs right away
+            // by calling exit_program after submit_encode_frame returns error,
+            // FFmpeg 6.1 calls enc_flush after error, which enters this function again,
+            // but the buffered frame would have been unref'ed by then, and therefore
+            // we must remove buffered frame upon session open fail to prevent
+            // accessing or unref'ing invalid frame
+            if (SESSION_RUN_STATE_SEQ_CHANGE_DRAINING !=
+                ctx->api_ctx.session_run_state) {
+                if (! is_input_fifo_empty(ctx)) {
+                    av_fifo_drain2(ctx->fme_fifo, (size_t) 1);
+                    av_log(avctx, AV_LOG_DEBUG, "fme popped, fifo num frames: %lu\n",
+                           av_fifo_can_read(ctx->fme_fifo));
+                }
+            }
+            ret = AVERROR_EXTERNAL;
+            return ret;
+        }
+
+        // set up ROI map if in ROI demo mode
+        // Note: this is for demo purpose, and its direct access to QP map in
+        //       session context is not the usual way to do ROI; the normal way is
+        //       through side data of AVFrame in libavcodec, or aux data of ni_frame
+        //       in libxcoder
+        if (p_param->cfg_enc_params.roi_enable &&
+            (1 == p_param->roi_demo_mode || 2 == p_param->roi_demo_mode)) {
+            if (ni_set_demo_roi_map(&ctx->api_ctx) < 0) {
+                return AVERROR(ENOMEM);
+            }
+        }
+    } //end if (first_frame && ctx->started == 0)
+
+    if (ctx->encoder_flushing) {
+        if (! frame && is_input_fifo_empty(ctx)) {
+            av_log(avctx, AV_LOG_DEBUG, "XCoder EOF: null frame && fifo empty\n");
+            return AVERROR_EOF;
+        }
+    }
+
+    if (! frame) {
+        if (is_input_fifo_empty(ctx)) {
+            ctx->eos_fme_received = 1;
+            av_log(avctx, AV_LOG_DEBUG, "null frame, eos_fme_received = 1\n");
+        } else {
+            avctx->internal->draining = 0;
+            av_log(avctx, AV_LOG_DEBUG, "null frame, but fifo not empty, clear draining = 0\n");
+        }
+    } else {
+        av_log(avctx, AV_LOG_DEBUG, "XCoder send frame #%"PRIu64"\n",
+               ctx->api_ctx.frame_num);
+
+        // queue up the frame if fifo is NOT empty, or: sequence change ongoing !
+        if (! is_input_fifo_empty(ctx) ||
+            SESSION_RUN_STATE_SEQ_CHANGE_DRAINING == ctx->api_ctx.session_run_state) {
+            ret = enqueue_frame(avctx, frame);
+            if (ret < 0) {
+                return ret;
+            }
+
+            if (SESSION_RUN_STATE_SEQ_CHANGE_DRAINING ==
+                ctx->api_ctx.session_run_state) {
+                av_log(avctx, AV_LOG_TRACE, "XCoder doing sequence change, frame #%"PRIu64" "
+                       "queued and return 0 !\n", ctx->api_ctx.frame_num);
+                return 0;
+            }
+        } else if (frame != &ctx->buffered_fme) {
+            ret = av_frame_ref(&ctx->buffered_fme, frame);
+        }
+    }
+
+resend:
+
+    if (ctx->started == 0) {
+        ctx->api_fme.data.frame.start_of_stream = 1;
+        ctx->started = 1;
+    } else if (ctx->api_ctx.session_run_state == SESSION_RUN_STATE_SEQ_CHANGE_OPENING) {
+        ctx->api_fme.data.frame.start_of_stream = 1;
+    } else {
+        ctx->api_fme.data.frame.start_of_stream = 0;
+    }
+
+    if (is_input_fifo_empty(ctx)) {
+        av_log(avctx, AV_LOG_DEBUG,
+               "no frame in fifo to send, just send/receive ..\n");
+        if (ctx->eos_fme_received) {
+            av_log(avctx, AV_LOG_DEBUG,
+                   "no frame in fifo to send, send eos ..\n");
+        }
+    } else {
+        av_log(avctx, AV_LOG_DEBUG, "fifo peek fme\n");
+        av_fifo_peek(ctx->fme_fifo, &ctx->buffered_fme, (size_t) 1, NULL);
+        ctx->buffered_fme.extended_data = ctx->buffered_fme.data;
+    }
+
+    if (!ctx->eos_fme_received) {
+        int8_t bit_depth = 1;
+        ishwframe        = ctx->buffered_fme.format == AV_PIX_FMT_NI_QUAD;
+        if (ishwframe) {
+            // Superframe early cleanup of unused outputs
+            niFrameSurface1_t *pOutExtra;
+            if (ctx->buffered_fme.buf[1]) {
+                // NOLINTNEXTLINE(clang-diagnostic-incompatible-pointer-types)
+                pOutExtra= (niFrameSurface1_t *)ctx->buffered_fme.buf[1]->data;
+                if (pOutExtra->ui16FrameIdx != 0) {
+                    av_log(avctx, AV_LOG_DEBUG, "Unref unused index %d\n",
+                           pOutExtra->ui16FrameIdx);
+                } else {
+                    av_log(avctx, AV_LOG_ERROR,
+                           "ERROR: Should not be getting superframe with dead "
+                           "outputs\n");
+                }
+                av_buffer_unref(&ctx->buffered_fme.buf[1]);
+                if (ctx->buffered_fme.buf[2]) {
+                    // NOLINTNEXTLINE(clang-diagnostic-incompatible-pointer-types)
+                    pOutExtra = (niFrameSurface1_t *)ctx->buffered_fme.buf[2]->data;
+                    if (pOutExtra->ui16FrameIdx != 0) {
+                        av_log(avctx, AV_LOG_DEBUG, "Unref unused index %d\n",
+                               pOutExtra->ui16FrameIdx);
+                    } else {
+                        av_log(
+                            avctx, AV_LOG_ERROR,
+                            "ERROR: Should not be getting superframe with dead "
+                            "outputs\n");
+                    }
+                    av_buffer_unref(&ctx->buffered_fme.buf[2]);
+                }
+            }
+            pOutExtra = (niFrameSurface1_t *)ctx->buffered_fme.data[3];
+            if (ctx->api_ctx.pixel_format == NI_PIX_FMT_ARGB
+                || ctx->api_ctx.pixel_format == NI_PIX_FMT_ABGR
+                || ctx->api_ctx.pixel_format == NI_PIX_FMT_RGBA
+                || ctx->api_ctx.pixel_format == NI_PIX_FMT_BGRA) {
+                bit_depth = 1;
+            } else {
+                bit_depth = pOutExtra->bit_depth;
+            }
+
+            switch (bit_depth) {
+            case 1:
+            case 2:
+                break;
+            default:
+                av_log(avctx, AV_LOG_ERROR, "ERROR: Unknown bit depth %d!\n", bit_depth);
+                return AVERROR_INVALIDDATA;
+            }
+        } else {
+            if (AV_PIX_FMT_YUV420P10BE == ctx->buffered_fme.format ||
+                AV_PIX_FMT_YUV420P10LE == ctx->buffered_fme.format ||
+                AV_PIX_FMT_P010LE == ctx->buffered_fme.format) {
+                bit_depth = 2;
+            }
+        }
+
+        if ((ctx->buffered_fme.height && ctx->buffered_fme.width &&
+             (ctx->buffered_fme.height != avctx->height ||
+              ctx->buffered_fme.width != avctx->width)) ||
+            bit_depth != ctx->api_ctx.bit_depth_factor) {
+            av_log(avctx, AV_LOG_INFO,
+                   "xcoder_send_frame resolution change %dx%d "
+                   "-> %dx%d or bit depth change %d -> %d\n",
+                   avctx->width, avctx->height, ctx->buffered_fme.width,
+                   ctx->buffered_fme.height, ctx->api_ctx.bit_depth_factor,
+                   bit_depth);
+
+            ctx->api_ctx.session_run_state =
+                SESSION_RUN_STATE_SEQ_CHANGE_DRAINING;
+            ctx->eos_fme_received = 1;
+
+            // have to queue this frame if not done so: an empty queue
+            if (is_input_fifo_empty(ctx)) {
+                av_log(avctx, AV_LOG_TRACE,
+                       "resolution change when fifo empty, frame "
+                       "#%" PRIu64 " being queued ..\n",
+                       ctx->api_ctx.frame_num);
+                // unref buffered frame (this buffered frame is taken from input
+                // AVFrame) because we are going to send EOS (instead of sending
+                // buffered frame)
+                if (frame != &ctx->buffered_fme) {
+                    av_frame_unref(&ctx->buffered_fme);
+                }
+                ret = enqueue_frame(avctx, frame);
+                if (ret < 0) {
+                    return ret;
+                }
+            }
+        }
+    }
+
+    ctx->api_fme.data.frame.preferred_characteristics_data_len = 0;
+    ctx->api_fme.data.frame.end_of_stream                      = 0;
+    ctx->api_fme.data.frame.force_key_frame =
+        ctx->api_fme.data.frame.use_cur_src_as_long_term_pic =
+            ctx->api_fme.data.frame.use_long_term_ref = 0;
+
+    ctx->api_fme.data.frame.sei_total_len =
+        ctx->api_fme.data.frame.sei_cc_offset = ctx->api_fme.data.frame
+                                                    .sei_cc_len =
+            ctx->api_fme.data.frame.sei_hdr_mastering_display_color_vol_offset =
+                ctx->api_fme.data.frame
+                    .sei_hdr_mastering_display_color_vol_len =
+                    ctx->api_fme.data.frame
+                        .sei_hdr_content_light_level_info_offset =
+                        ctx->api_fme.data.frame
+                            .sei_hdr_content_light_level_info_len =
+                            ctx->api_fme.data.frame.sei_hdr_plus_offset =
+                                ctx->api_fme.data.frame.sei_hdr_plus_len = 0;
+
+    ctx->api_fme.data.frame.roi_len      = 0;
+    ctx->api_fme.data.frame.reconf_len   = 0;
+    ctx->api_fme.data.frame.force_pic_qp = 0;
+
+    if (SESSION_RUN_STATE_SEQ_CHANGE_DRAINING ==
+            ctx->api_ctx.session_run_state ||
+        (ctx->eos_fme_received && is_input_fifo_empty(ctx))) {
+        av_log(avctx, AV_LOG_VERBOSE, "XCoder start flushing\n");
+        ctx->api_fme.data.frame.end_of_stream = 1;
+        ctx->encoder_flushing                 = 1;
+    } else {
+        format_in_use = ctx->buffered_fme.format;
+
+        // extra data starts with metadata header, various aux data sizes
+        // have been reset above
+        ctx->api_fme.data.frame.extra_data_len =
+            NI_APP_ENC_FRAME_META_DATA_SIZE;
+
+        ctx->api_fme.data.frame.ni_pict_type    = 0;
+
+        ret = ni_enc_prep_reconf_demo_data(&ctx->api_ctx, &dec_frame);
+        if (ret < 0) {
+            return ret;
+        }
+
+        // support VFR
+        if (ctx->api_param.enable_vfr) {
+            int cur_fps = 0, pre_fps = 0;
+
+            pre_fps = ctx->api_ctx.prev_fps;
+
+            if (ctx->buffered_fme.pts > ctx->api_ctx.prev_pts) {
+                ctx->api_ctx.passed_time_in_timebase_unit += ctx->buffered_fme.pts - ctx->api_ctx.prev_pts;
+                ctx->api_ctx.count_frame_num_in_sec++;
+                //change the FrameRate for VFR
+                //1. Only when the fps change, setting the new bitrate
+                //2. The interval between two framerate chagne settings shall be greater than 1 seconds
+                //   or at the start the transcoding
+                if (ctx->api_ctx.passed_time_in_timebase_unit >= (avctx->time_base.den / avctx->time_base.num)) {
+                    //this is a workaround for small resolution vfr mode
+                    //when detect framerate change, the reconfig framerate will trigger bitrate params to reset
+                    //the cost related to bitrate estimate is all tuned with downsample flow
+                    //but for small resolution, the lookahead won't downsample
+                    int slow_down_vfr = 0;
+                    cur_fps = ctx->api_ctx.count_frame_num_in_sec;
+                    if (ctx->buffered_fme.width < 288 || ctx->buffered_fme.height < 256) {
+                        slow_down_vfr = 1;
+                    }
+                    if ((ctx->api_ctx.frame_num != 0) && (pre_fps != cur_fps) && (slow_down_vfr ? (abs(cur_fps - pre_fps) > 2) : 1) &&
+                        ((ctx->api_ctx.frame_num < ctx->api_param.cfg_enc_params.frame_rate) ||
+                         (ctx->api_ctx.frame_num - ctx->api_ctx.last_change_framenum >= ctx->api_param.cfg_enc_params.frame_rate))) {
+                        aux_data = ni_frame_new_aux_data(&dec_frame, NI_FRAME_AUX_DATA_FRAMERATE, sizeof(ni_framerate_t));
+                        if (aux_data) {
+                            ni_framerate_t *framerate = (ni_framerate_t *)aux_data->data;
+                            framerate->framerate_num = cur_fps;
+                            framerate->framerate_denom = 1;
+                        }
+
+                        ctx->api_ctx.last_change_framenum = ctx->api_ctx.frame_num;
+                        ctx->api_ctx.prev_fps = cur_fps;
+                    }
+                    ctx->api_ctx.count_frame_num_in_sec = 0;
+                    ctx->api_ctx.passed_time_in_timebase_unit = 0;
+                }
+                ctx->api_ctx.prev_pts = ctx->buffered_fme.pts;
+            } else if (ctx->buffered_fme.pts < ctx->api_ctx.prev_pts) {
+                //error handle for the case that pts jump back
+                //this may cause a little error in the bitrate setting, This little error is acceptable.
+                //As long as the subsequent, PTS is normal, it will be repaired quickly.
+                ctx->api_ctx.prev_pts = ctx->buffered_fme.pts;
+            } else {
+                //do nothing, when the pts of two adjacent frames are the same
+                //this may cause a little error in the bitrate setting, This little error is acceptable.
+                //As long as the subsequent, PTS is normal, it will be repaired quickly.
+            }
+        }
+
+        // force pic qp demo mode: initial QP (200 frames) -> QP value specified by
+        // ForcePicQpDemoMode (100 frames) -> initial QP (remaining frames)
+        if (p_param->force_pic_qp_demo_mode) {
+            if (ctx->api_ctx.frame_num >= 300) {
+                ctx->api_fme.data.frame.force_pic_qp =
+                    p_param->cfg_enc_params.rc.intra_qp;
+            } else if (ctx->api_ctx.frame_num >= 200) {
+                ctx->api_fme.data.frame.force_pic_qp = p_param->force_pic_qp_demo_mode;
+            }
+        }
+
+        // supply QP map if ROI enabled and if ROIs passed in
+        // Note: ROI demo mode takes higher priority over side data !
+        side_data = av_frame_get_side_data(&ctx->buffered_fme, AV_FRAME_DATA_REGIONS_OF_INTEREST);
+
+        if (!p_param->roi_demo_mode && p_param->cfg_enc_params.roi_enable &&
+            side_data) {
+            aux_data = ni_frame_new_aux_data(
+                &dec_frame, NI_FRAME_AUX_DATA_REGIONS_OF_INTEREST, side_data->size);
+            if (aux_data) {
+                memcpy(aux_data->data, side_data->data, side_data->size);
+            }
+        }
+
+        // Note: when ROI demo modes enabled, supply ROI map for the specified range
+        //       frames, and 0 map for others
+        if (QUADRA && p_param->roi_demo_mode &&
+            p_param->cfg_enc_params.roi_enable) {
+            if (ctx->api_ctx.frame_num > 90 && ctx->api_ctx.frame_num < 300) {
+                ctx->api_fme.data.frame.roi_len = ctx->api_ctx.roi_len;
+            } else {
+                ctx->api_fme.data.frame.roi_len = 0;
+            }
+            // when ROI enabled, always have a data buffer for ROI
+            // Note: this is handled separately from ROI through side/aux data
+            ctx->api_fme.data.frame.extra_data_len += ctx->api_ctx.roi_len;
+        }
+
+        if (!p_param->cfg_enc_params.enable_all_sei_passthru) {
+            // SEI (HDR)
+            // content light level info
+            if (!(p_param->cfg_enc_params.HDR10CLLEnable)) { // not user set
+                side_data = av_frame_get_side_data(&ctx->buffered_fme, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+
+                if (side_data && side_data->size == sizeof(AVContentLightMetadata)) {
+                    aux_data = ni_frame_new_aux_data(
+                        &dec_frame, NI_FRAME_AUX_DATA_CONTENT_LIGHT_LEVEL,
+                        sizeof(ni_content_light_level_t));
+                    if (aux_data) {
+                        memcpy(aux_data->data, side_data->data, side_data->size);
+                    }
+                }
+            } else if ((AV_CODEC_ID_H264 == avctx->codec_id ||
+                        ctx->api_ctx.bit_depth_factor == 1) &&
+                      ctx->api_ctx.light_level_data_len == 0) {
+                // User input maxCLL so create SEIs for h264 and don't touch for (h265 &&
+                // hdr10) since that is conveyed in config step
+                // Quadra autoset only for hdr10 format with hevc
+                aux_data = ni_frame_new_aux_data(&dec_frame,
+                                                NI_FRAME_AUX_DATA_CONTENT_LIGHT_LEVEL,
+                                                sizeof(ni_content_light_level_t));
+                if (aux_data) {
+                    ni_content_light_level_t *cll =
+                        (ni_content_light_level_t *)(aux_data->data);
+                    cll->max_cll  = p_param->cfg_enc_params.HDR10MaxLight;
+                    cll->max_fall = p_param->cfg_enc_params.HDR10AveLight;
+                }
+            }
+
+            // mastering display color volume
+            if (!(p_param->cfg_enc_params.HDR10Enable)) { // not user set
+                side_data = av_frame_get_side_data(&ctx->buffered_fme, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+                if (side_data && side_data->size == sizeof(AVMasteringDisplayMetadata)) {
+                    aux_data = ni_frame_new_aux_data(
+                        &dec_frame, NI_FRAME_AUX_DATA_MASTERING_DISPLAY_METADATA,
+                        sizeof(ni_mastering_display_metadata_t));
+                    if (aux_data) {
+                        memcpy(aux_data->data, side_data->data, side_data->size);
+                    }
+                }
+            } else if ((AV_CODEC_ID_H264 == avctx->codec_id ||
+                       ctx->api_ctx.bit_depth_factor == 1) &&
+                      ctx->api_ctx.sei_hdr_mastering_display_color_vol_len == 0) {
+                // User input masterDisplay so create SEIs for h264 and don't touch for (h265 &&
+                // hdr10) since that is conveyed in config step
+                // Quadra autoset only for hdr10 format with hevc
+                aux_data = ni_frame_new_aux_data(&dec_frame,
+                    NI_FRAME_AUX_DATA_MASTERING_DISPLAY_METADATA,
+                    sizeof(ni_mastering_display_metadata_t));
+                if (aux_data) {
+                    ni_mastering_display_metadata_t *mst_dsp =
+                        (ni_mastering_display_metadata_t *)(aux_data->data);
+
+                    //X, Y display primaries for RGB channels and white point(WP) in units of 0.00002
+                    //and max, min luminance(L) values in units of 0.0001 nits
+                    //xy are denom = 50000 num = HDR10dx0/y
+                    mst_dsp->display_primaries[0][0].den = MASTERING_DISP_CHROMA_DEN;
+                    mst_dsp->display_primaries[0][1].den = MASTERING_DISP_CHROMA_DEN;
+                    mst_dsp->display_primaries[1][0].den = MASTERING_DISP_CHROMA_DEN;
+                    mst_dsp->display_primaries[1][1].den = MASTERING_DISP_CHROMA_DEN;
+                    mst_dsp->display_primaries[2][0].den = MASTERING_DISP_CHROMA_DEN;
+                    mst_dsp->display_primaries[2][1].den = MASTERING_DISP_CHROMA_DEN;
+                    mst_dsp->white_point[0].den = MASTERING_DISP_CHROMA_DEN;
+                    mst_dsp->white_point[1].den = MASTERING_DISP_CHROMA_DEN;
+                    mst_dsp->min_luminance.den = MASTERING_DISP_LUMA_DEN;
+                    mst_dsp->max_luminance.den = MASTERING_DISP_LUMA_DEN;
+                    // ni_mastering_display_metadata_t has to be filled with R,G,B
+                    // values, in that order, while HDR10d is filled in order of G,B,R,
+                    // so do the conversion here.
+                    mst_dsp->display_primaries[0][0].num = p_param->cfg_enc_params.HDR10dx2;
+                    mst_dsp->display_primaries[0][1].num = p_param->cfg_enc_params.HDR10dy2;
+                    mst_dsp->display_primaries[1][0].num = p_param->cfg_enc_params.HDR10dx0;
+                    mst_dsp->display_primaries[1][1].num = p_param->cfg_enc_params.HDR10dy0;
+                    mst_dsp->display_primaries[2][0].num = p_param->cfg_enc_params.HDR10dx1;
+                    mst_dsp->display_primaries[2][1].num = p_param->cfg_enc_params.HDR10dy1;
+                    mst_dsp->white_point[0].num = p_param->cfg_enc_params.HDR10wx;
+                    mst_dsp->white_point[1].num = p_param->cfg_enc_params.HDR10wy;
+                    mst_dsp->min_luminance.num = p_param->cfg_enc_params.HDR10minluma;
+                    mst_dsp->max_luminance.num = p_param->cfg_enc_params.HDR10maxluma;
+                    mst_dsp->has_primaries = 1;
+                    mst_dsp->has_luminance = 1;
+                }
+            }
+
+            // SEI (HDR10+)
+            side_data = av_frame_get_side_data(&ctx->buffered_fme, AV_FRAME_DATA_DYNAMIC_HDR_PLUS);
+            if (side_data && side_data->size == sizeof(AVDynamicHDRPlus)) {
+                aux_data = ni_frame_new_aux_data(&dec_frame, NI_FRAME_AUX_DATA_HDR_PLUS,
+                                                sizeof(ni_dynamic_hdr_plus_t));
+                if (aux_data) {
+                    memcpy(aux_data->data, side_data->data, side_data->size);
+                }
+            } // hdr10+
+
+            // SEI (close caption)
+            side_data = av_frame_get_side_data(&ctx->buffered_fme, AV_FRAME_DATA_A53_CC);
+
+            if (side_data && side_data->size > 0) {
+                aux_data = ni_frame_new_aux_data(&dec_frame, NI_FRAME_AUX_DATA_A53_CC,
+                                                side_data->size);
+                if (aux_data) {
+                    memcpy(aux_data->data, side_data->data, side_data->size);
+                }
+            }
+
+            // User data unregistered SEI
+            side_data = av_frame_get_side_data(&ctx->buffered_fme, AV_FRAME_DATA_SEI_UNREGISTERED);
+            if (ctx->udu_sei && side_data && side_data->size > 0) {
+                aux_data = ni_frame_new_aux_data(&dec_frame, NI_FRAME_AUX_DATA_UDU_SEI,
+                                                 side_data->size);
+                if (aux_data) {
+                    memcpy(aux_data->data, (uint8_t *)side_data->data, side_data->size);
+                }
+            }
+        }
+        if (ctx->api_ctx.force_frame_type) {
+            switch (ctx->buffered_fme.pict_type) {
+            case AV_PICTURE_TYPE_I:
+                ctx->api_fme.data.frame.ni_pict_type = PIC_TYPE_IDR;
+                break;
+            case AV_PICTURE_TYPE_P:
+                ctx->api_fme.data.frame.ni_pict_type = PIC_TYPE_P;
+                break;
+            default:
+                ;
+            }
+        } else if (ctx->buffered_fme.pict_type == AV_PICTURE_TYPE_I) {
+            ctx->api_fme.data.frame.force_key_frame = 1;
+            ctx->api_fme.data.frame.ni_pict_type = PIC_TYPE_IDR;
+        }
+
+        av_log(avctx, AV_LOG_TRACE,
+               "xcoder_send_frame: #%" PRIu64 " ni_pict_type %d"
+               " forced_header_enable %d intraPeriod %d\n",
+               ctx->api_ctx.frame_num, ctx->api_fme.data.frame.ni_pict_type,
+               p_param->cfg_enc_params.forced_header_enable,
+               p_param->cfg_enc_params.intra_period);
+
+        // whether should send SEI with this frame
+        send_sei_with_idr = ni_should_send_sei_with_frame(
+            &ctx->api_ctx, ctx->api_fme.data.frame.ni_pict_type, p_param);
+
+        // prep for auxiliary data (various SEI, ROI) in encode frame, based on the
+        // data returned in decoded frame
+        ni_enc_prep_aux_data(&ctx->api_ctx, &ctx->api_fme.data.frame, &dec_frame,
+                             ctx->api_ctx.codec_format, send_sei_with_idr,
+                             mdcv_data, cll_data, cc_data, udu_data, hdrp_data);
+
+        if (ctx->api_fme.data.frame.sei_total_len > NI_ENC_MAX_SEI_BUF_SIZE) {
+            av_log(avctx, AV_LOG_ERROR, "xcoder_send_frame: sei total length %u exceeds maximum sei size %u.\n",
+                   ctx->api_fme.data.frame.sei_total_len, NI_ENC_MAX_SEI_BUF_SIZE);
+            ret = AVERROR(EINVAL);
+            return ret;
+        }
+
+        ctx->api_fme.data.frame.extra_data_len += ctx->api_fme.data.frame.sei_total_len;
+
+        // data layout requirement: leave space for reconfig data if at least one of
+        // reconfig, SEI or ROI is present
+        // Note: ROI is present when enabled, so use encode config flag instead of
+        //       frame's roi_len as it can be 0 indicating a 0'd ROI map setting !
+        if (ctx->api_fme.data.frame.reconf_len ||
+            ctx->api_fme.data.frame.sei_total_len ||
+            p_param->cfg_enc_params.roi_enable) {
+            ctx->api_fme.data.frame.extra_data_len +=
+                sizeof(ni_encoder_change_params_t);
+        }
+
+        ctx->api_fme.data.frame.pts = ctx->buffered_fme.pts;
+        ctx->api_fme.data.frame.dts = ctx->buffered_fme.pkt_dts;
+
+        ctx->api_fme.data.frame.video_width = avctx->width;
+        ctx->api_fme.data.frame.video_height = avctx->height;
+
+        ishwframe = ctx->buffered_fme.format == AV_PIX_FMT_NI_QUAD;
+        if (ctx->api_ctx.auto_dl_handle != 0 || (avctx->height < NI_MIN_HEIGHT) ||
+            (avctx->width < NI_MIN_WIDTH)) {
+            format_in_use          = avctx->sw_pix_fmt;
+            ctx->api_ctx.hw_action = 0;
+            ishwframe              = 0;
+        }
+        isnv12frame = (format_in_use == AV_PIX_FMT_NV12 || format_in_use == AV_PIX_FMT_P010LE);
+
+        if (ishwframe) {
+            ret = sizeof(niFrameSurface1_t);
+        } else {
+            ret = av_image_get_buffer_size(format_in_use,
+                                         ctx->buffered_fme.width, ctx->buffered_fme.height, 1);
+        }
+
+    #if FF_API_PKT_PTS
+        // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
+        av_log(avctx, AV_LOG_TRACE, "xcoder_send_frame: pts=%" PRId64 ", pkt_dts=%" PRId64 ", pkt_pts=%" PRId64 "\n", ctx->buffered_fme.pts, ctx->buffered_fme.pkt_dts, ctx->buffered_fme.pkt_pts);
+    #endif
+        av_log(avctx, AV_LOG_TRACE, "xcoder_send_frame: frame->format=%d, frame->width=%d, frame->height=%d, frame->pict_type=%d, size=%d\n", format_in_use, ctx->buffered_fme.width, ctx->buffered_fme.height, ctx->buffered_fme.pict_type, ret);
+        if (ret < 0) {
+            return ret;
+        }
+
+        int dst_stride[NI_MAX_NUM_DATA_POINTERS]     = {0};
+        int height_aligned[NI_MAX_NUM_DATA_POINTERS] = {0};
+        int src_height[NI_MAX_NUM_DATA_POINTERS]     = {0};
+
+        src_height[0] = ctx->buffered_fme.height;
+        src_height[1] = ctx->buffered_fme.height / 2;
+        src_height[2] = (isnv12frame) ? 0 : (ctx->buffered_fme.height / 2);
+        if (avctx->sw_pix_fmt == AV_PIX_FMT_ARGB ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_RGBA ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_ABGR ||
+            avctx->sw_pix_fmt == AV_PIX_FMT_BGRA) {
+            src_height[0] = ctx->buffered_fme.height;
+            src_height[1] = 0;
+            src_height[2] = 0;
+            alignment_2pass_wa = 0;
+        }
+        /* The main reason for the problem is that when using out=sw and noautoscale=0,
+        * the incoming stream initially conforms to zerocopy, so the firmware will allocate memory according to zerocopy.
+        * The resolution after this changes, but the software inserts autoscale,
+        * so the encoder cannot detect the change in resolution and cannot reopen.
+        * However, due to ffmpeg being 64 bit aligned, So the linesize is not consistent with the linesize we initially decoded,
+        * so the encoding will take the path of non-zero copy. At this time, due to lookahead>0,
+        * the software will send the firmware a size larger than the originally requested size,
+        * so it will not be able to send it. Moreover, at this point,
+        * the firmware is unable to perceive changes in linesize and respond accordingly.
+        * For fix this, we can check the linesize and disable 2-pass workaround.
+        */
+        if (ctx->api_param.luma_linesize) {
+            alignment_2pass_wa = false;
+        }
+
+        ni_get_min_frame_dim(ctx->buffered_fme.width,
+                             ctx->buffered_fme.height,
+                             ctx->api_ctx.pixel_format,
+                             dst_stride, height_aligned);
+
+        av_log(avctx, AV_LOG_TRACE,
+               "xcoder_send_frame frame->width %d "
+               "ctx->api_ctx.bit_depth_factor %d dst_stride[0/1/2] %d/%d/%d sw_pix_fmt %d\n",
+               ctx->buffered_fme.width, ctx->api_ctx.bit_depth_factor,
+               dst_stride[0], dst_stride[1], dst_stride[2], avctx->sw_pix_fmt);
+
+        if (alignment_2pass_wa && !ishwframe) {
+            if (isnv12frame) {
+                // for 2-pass encode output mismatch WA, need to extend (and
+                // pad) CbCr plane height, because 1st pass assume input 32
+                // align
+                height_aligned[1] = FFALIGN(height_aligned[0], 32) / 2;
+            } else {
+                // for 2-pass encode output mismatch WA, need to extend (and
+                // pad) Cr plane height, because 1st pass assume input 32 align
+                height_aligned[2] = FFALIGN(height_aligned[0], 32) / 2;
+            }
+        }
+
+        // alignment(16) extra padding for H.264 encoding
+        if (ishwframe) {
+            uint8_t *dsthw;
+            const uint8_t *srchw;
+
+            ni_frame_buffer_alloc_hwenc(
+                &(ctx->api_fme.data.frame), ctx->buffered_fme.width,
+                ctx->buffered_fme.height,
+                (int)ctx->api_fme.data.frame.extra_data_len);
+            if (!ctx->api_fme.data.frame.p_data[3]) {
+                return AVERROR(ENOMEM);
+            }
+            dsthw       = ctx->api_fme.data.frame.p_data[3];
+            srchw = (const uint8_t *)ctx->buffered_fme.data[3];
+            av_log(avctx, AV_LOG_TRACE, "dst=%p src=%p len=%d\n", dsthw, srchw,
+                   ctx->api_fme.data.frame.data_len[3]);
+            memcpy(dsthw, srchw, ctx->api_fme.data.frame.data_len[3]);
+            av_log(avctx, AV_LOG_TRACE,
+                   "ctx->buffered_fme.data[3] %p memcpy to %p\n",
+                   ctx->buffered_fme.data[3], dsthw);
+        } else {  // traditional yuv transfer
+            av_log(avctx, AV_LOG_TRACE, "%s %s %d buffered_fme.data[0] %p data[3] %p wxh %u %u dst_stride[0] %d %d  linesize[0] %d %d data[1] %p %p data[2] %p %p data[3] %p buf[0] %p crop(t:b:l:r) %lu:%lu:%lu:%lu avctx(w:h:cw:ch) %u:%u:%u:%u\n",
+              __FILE__, __FUNCTION__, __LINE__,
+              ctx->buffered_fme.data[0], ctx->buffered_fme.data[3],
+              ctx->buffered_fme.width, ctx->buffered_fme.height,
+              dst_stride[0], dst_stride[1],
+              ctx->buffered_fme.linesize[0], ctx->buffered_fme.linesize[1],
+              ctx->buffered_fme.data[1], ctx->buffered_fme.data[0] + dst_stride[0] * ctx->buffered_fme.height,
+              ctx->buffered_fme.data[2], ctx->buffered_fme.data[1] + dst_stride[1] * ctx->buffered_fme.height / 2,
+              ctx->buffered_fme.data[3],
+              ctx->buffered_fme.buf[0],
+              ctx->buffered_fme.crop_top, ctx->buffered_fme.crop_bottom, ctx->buffered_fme.crop_left, ctx->buffered_fme.crop_right,
+              avctx->width, avctx->height,
+              avctx->coded_width, avctx->coded_height);
+
+            // check input resolution zero copy compatible or not
+            if (ni_encoder_frame_zerocopy_check(&ctx->api_ctx,
+                p_param, ctx->buffered_fme.width, ctx->buffered_fme.height,
+                (const int *)ctx->buffered_fme.linesize, false) == NI_RETCODE_SUCCESS) {
+                need_to_copy = 0;
+                // alloc metadata buffer etc. (if needed)
+                ret = ni_encoder_frame_zerocopy_buffer_alloc(
+                    &(ctx->api_fme.data.frame), ctx->buffered_fme.width,
+                    ctx->buffered_fme.height, (const int *)ctx->buffered_fme.linesize, (const uint8_t **)ctx->buffered_fme.data,
+                    (int)ctx->api_fme.data.frame.extra_data_len);
+                if (ret != NI_RETCODE_SUCCESS)
+                    return AVERROR(ENOMEM);
+            } else {
+                // if linesize changes (while resolution remains the same), copy to previously configured linesizes
+                if (p_param->luma_linesize && p_param->chroma_linesize) {
+                    dst_stride[0] = p_param->luma_linesize;
+                    dst_stride[1] = p_param->chroma_linesize;
+                    dst_stride[2] = isnv12frame ? 0 : p_param->chroma_linesize;
+                }
+                ni_encoder_sw_frame_buffer_alloc(
+                    !isnv12frame, &(ctx->api_fme.data.frame), ctx->buffered_fme.width,
+                    height_aligned[0], dst_stride, (avctx->codec_id == AV_CODEC_ID_H264),
+                    (int)ctx->api_fme.data.frame.extra_data_len, alignment_2pass_wa);
+            }
+            av_log(avctx, AV_LOG_TRACE, "%p need_to_copy %d! pts = %ld\n", ctx->api_fme.data.frame.p_buffer, need_to_copy, ctx->buffered_fme.pts);
+            if (!ctx->api_fme.data.frame.p_data[0]) {
+                return AVERROR(ENOMEM);
+            }
+
+            // if this is indeed sw frame, do the YUV data layout, otherwise may need
+            // to do frame download
+            if (ctx->buffered_fme.format != AV_PIX_FMT_NI_QUAD) {
+                av_log(
+                    avctx, AV_LOG_TRACE,
+                    "xcoder_send_frame: fme.data_len[0]=%d, "
+                    "buf_fme->linesize=%d/%d/%d, dst alloc linesize = %d/%d/%d, "
+                    "src height = %d/%d/%d, dst height aligned = %d/%d/%d, "
+                    "force_key_frame=%d, extra_data_len=%d sei_size=%d "
+                    "(hdr_content_light_level %u hdr_mastering_display_color_vol %u "
+                    "hdr10+ %u cc %u udu %u prefC %u) roi_size=%u reconf_size=%u "
+                    "force_pic_qp=%u "
+                    "use_cur_src_as_long_term_pic %u use_long_term_ref %u\n",
+                    ctx->api_fme.data.frame.data_len[0],
+                    ctx->buffered_fme.linesize[0], ctx->buffered_fme.linesize[1],
+                    ctx->buffered_fme.linesize[2], dst_stride[0], dst_stride[1],
+                    dst_stride[2], src_height[0], src_height[1], src_height[2],
+                    height_aligned[0], height_aligned[1], height_aligned[2],
+                    ctx->api_fme.data.frame.force_key_frame,
+                    ctx->api_fme.data.frame.extra_data_len,
+                    ctx->api_fme.data.frame.sei_total_len,
+                    ctx->api_fme.data.frame.sei_hdr_content_light_level_info_len,
+                    ctx->api_fme.data.frame.sei_hdr_mastering_display_color_vol_len,
+                    ctx->api_fme.data.frame.sei_hdr_plus_len,
+                    ctx->api_fme.data.frame.sei_cc_len,
+                    ctx->api_fme.data.frame.sei_user_data_unreg_len,
+                    ctx->api_fme.data.frame.preferred_characteristics_data_len,
+                    (p_param->cfg_enc_params.roi_enable ? ctx->api_ctx.roi_len : 0),
+                    ctx->api_fme.data.frame.reconf_len,
+                    ctx->api_fme.data.frame.force_pic_qp,
+                    ctx->api_fme.data.frame.use_cur_src_as_long_term_pic,
+                    ctx->api_fme.data.frame.use_long_term_ref);
+
+                // YUV part of the encoder input data layout
+                if (need_to_copy) {
+                    ni_copy_frame_data(
+                        (uint8_t **)(ctx->api_fme.data.frame.p_data),
+                        ctx->buffered_fme.data, ctx->buffered_fme.width,
+                        ctx->buffered_fme.height, ctx->api_ctx.bit_depth_factor,
+                        ctx->api_ctx.pixel_format, p_param->cfg_enc_params.conf_win_right, dst_stride,
+                        height_aligned, ctx->buffered_fme.linesize, src_height);
+                }
+            } else {
+                ni_session_data_io_t *p_session_data;
+                ni_session_data_io_t niframe;
+                niFrameSurface1_t *src_surf;
+
+                av_log(avctx, AV_LOG_DEBUG,
+                       "xcoder_send_frame:Autodownload to be run: hdl: %d w: %d h: %d\n",
+                       ctx->api_ctx.auto_dl_handle, avctx->width, avctx->height);
+                avhwf_ctx =
+                    (AVHWFramesContext *)ctx->buffered_fme.hw_frames_ctx->data;
+                nif_src_ctx = (AVNIFramesContext*) avhwf_ctx->hwctx;
+
+                src_surf = (niFrameSurface1_t *)ctx->buffered_fme.data[3];
+
+                if (avctx->height < NI_MIN_HEIGHT || avctx->width < NI_MIN_WIDTH) {
+                    int bit_depth;
+                    int is_planar;
+
+                    p_session_data = &niframe;
+                    memset(&niframe, 0, sizeof(niframe));
+                    bit_depth = ((avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10LE) ||
+                               (avctx->sw_pix_fmt == AV_PIX_FMT_P010LE))
+                                  ? 2
+                                  : 1;
+                    is_planar = (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P) ||
+                              (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10LE);
+
+                    /* Allocate a minimal frame */
+                    ni_enc_frame_buffer_alloc(&niframe.data.frame, avctx->width,
+                                        avctx->height, 0, /* alignment */
+                                        1,                /* metadata */
+                                        bit_depth, 0,     /* hw_frame_count */
+                                        is_planar, ctx->api_ctx.pixel_format);
+                } else {
+                    p_session_data = &(ctx->api_fme);
+                }
+
+                nif_src_ctx->api_ctx.is_auto_dl = true;
+                ret = ni_device_session_hwdl(&nif_src_ctx->api_ctx, p_session_data,
+                                           src_surf);
+                ishwframe = false;
+                if (ret <= 0) {
+                    av_log(avctx, AV_LOG_ERROR,
+                           "nienc.c:ni_hwdl_frame() failed to retrieve frame\n");
+                    return AVERROR_EXTERNAL;
+                }
+
+                if ((avctx->height < NI_MIN_HEIGHT) ||
+                    (avctx->width < NI_MIN_WIDTH)) {
+                    int nb_planes = av_pix_fmt_count_planes(avctx->sw_pix_fmt);
+                    int ni_fmt = ctx->api_ctx.pixel_format;
+                    ni_expand_frame(&ctx->api_fme.data.frame,
+                                  &p_session_data->data.frame, dst_stride,
+                                  avctx->width, avctx->height, ni_fmt, nb_planes);
+
+                    ni_frame_buffer_free(&niframe.data.frame);
+                }
+            }
+        } // end if hwframe else
+
+        // auxiliary data part of the encoder input data layout
+        ni_enc_copy_aux_data(&ctx->api_ctx, &ctx->api_fme.data.frame, &dec_frame,
+                             ctx->api_ctx.codec_format, mdcv_data, cll_data,
+                             cc_data, udu_data, hdrp_data, ishwframe, isnv12frame);
+
+        ni_frame_buffer_free(&dec_frame);
+    } // end non seq change
+
+    sent = ni_device_session_write(&ctx->api_ctx, &ctx->api_fme, NI_DEVICE_TYPE_ENCODER);
+
+    av_log(avctx, AV_LOG_DEBUG, "xcoder_send_frame: size %d sent to xcoder\n", sent);
+
+    // return EIO at error
+    if (NI_RETCODE_ERROR_VPU_RECOVERY == sent) {
+        sent = xcoder_encode_reset(avctx);
+        if (sent < 0) {
+            av_log(avctx, AV_LOG_ERROR, "xcoder_send_frame(): VPU recovery failed:%d, returning EIO\n", sent);
+            ret = AVERROR(EIO);
+        }
+    } else if (sent < 0) {
+        av_log(avctx, AV_LOG_ERROR, "xcoder_send_frame(): failure sent (%d) , "
+               "returning EIO\n", sent);
+        ret = AVERROR(EIO);
+
+        // if rejected due to sequence change in progress, revert resolution
+        // setting and will do it again next time.
+        if (ctx->api_fme.data.frame.start_of_stream &&
+            (avctx->width != orig_avctx_width ||
+             avctx->height != orig_avctx_height)) {
+            avctx->width = orig_avctx_width;
+            avctx->height = orig_avctx_height;
+        }
+        return ret;
+    } else {
+        av_log(avctx, AV_LOG_DEBUG, "xcoder_send_frame(): sent (%d)\n", sent);
+        if (sent == 0) {
+            // case of sequence change in progress
+            if (ctx->api_fme.data.frame.start_of_stream &&
+                (avctx->width != orig_avctx_width ||
+                 avctx->height != orig_avctx_height)) {
+                avctx->width = orig_avctx_width;
+                avctx->height = orig_avctx_height;
+            }
+
+            // when buffer_full, drop the frame and return EAGAIN if in strict timeout
+            // mode, otherwise buffer the frame and it is to be sent out using encode2
+            // API: queue the frame only if not done so yet, i.e. queue is empty
+            // *and* it's a valid frame.
+            if (ctx->api_ctx.status == NI_RETCODE_NVME_SC_WRITE_BUFFER_FULL) {
+                ishwframe = ctx->buffered_fme.format == AV_PIX_FMT_NI_QUAD;
+                if (ishwframe) {
+                    // Do not queue frames to avoid FFmpeg stuck when multiple HW frames are queued up in nienc, causing decoder unable to acquire buffer, which led to FFmpeg stuck
+                    av_log(avctx, AV_LOG_ERROR, "xcoder_send_frame(): device WRITE_BUFFER_FULL cause HW frame drop! (approx. Frame num #%" PRIu64 "\n", ctx->api_ctx.frame_num);
+                    av_frame_unref(&ctx->buffered_fme);
+                    ret = 1;
+                } else {
+                    av_log(avctx, AV_LOG_DEBUG, "xcoder_send_frame(): Write buffer full, enqueue frame and return 0\n");
+                    ret = 0;
+
+                    if (frame && is_input_fifo_empty(ctx)) {
+                        ret = enqueue_frame(avctx, frame);
+                        if (ret < 0) {
+                            return ret;
+                        }
+                    }
+                }
+            }
+        } else {
+            ishwframe = (ctx->buffered_fme.format == AV_PIX_FMT_NI_QUAD) &&
+                        (ctx->api_ctx.auto_dl_handle == 0) &&
+                        (avctx->height >= NI_MIN_HEIGHT) &&
+                        (avctx->width >= NI_MIN_WIDTH);
+
+            if (!ctx->eos_fme_received && ishwframe) {
+                av_log(avctx, AV_LOG_TRACE, "AVframe_index = %d at head %d\n",
+                       ctx->aFree_Avframes_list[ctx->freeHead], ctx->freeHead);
+                av_frame_ref(
+                    ctx->sframe_pool[ctx->aFree_Avframes_list[ctx->freeHead]],
+                    &ctx->buffered_fme);
+                av_log(avctx, AV_LOG_TRACE,
+                       "AVframe_index = %d popped from free head %d\n",
+                       ctx->aFree_Avframes_list[ctx->freeHead], ctx->freeHead);
+                av_log(avctx, AV_LOG_TRACE,
+                       "ctx->buffered_fme.data[3] %p sframe_pool[%d]->data[3] %p\n",
+                       ctx->buffered_fme.data[3],
+                       ctx->aFree_Avframes_list[ctx->freeHead],
+                       ctx->sframe_pool[ctx->aFree_Avframes_list[ctx->freeHead]]
+                           ->data[3]);
+                if (ctx->sframe_pool[ctx->aFree_Avframes_list[ctx->freeHead]]
+                        ->data[3]) {
+                    av_log(avctx, AV_LOG_DEBUG,
+                           "nienc.c sframe_pool[%d] trace ui16FrameIdx = [%u] sent\n",
+                           ctx->aFree_Avframes_list[ctx->freeHead],
+                           ((niFrameSurface1_t
+                                 *)((uint8_t *)ctx
+                                        ->sframe_pool
+                                            [ctx->aFree_Avframes_list[ctx->freeHead]]
+                                        ->data[3]))
+                               ->ui16FrameIdx);
+                    av_log(
+                        avctx, AV_LOG_TRACE,
+                        "xcoder_send_frame: after ref sframe_pool, hw frame "
+                        "av_buffer_get_ref_count=%d, data[3]=%p\n",
+                        av_buffer_get_ref_count(
+                            ctx->sframe_pool[ctx->aFree_Avframes_list[ctx->freeHead]]
+                                ->buf[0]),
+                        ctx->sframe_pool[ctx->aFree_Avframes_list[ctx->freeHead]]
+                            ->data[3]);
+                }
+                if (deq_free_frames(ctx) != 0) {
+                    ret = AVERROR_EXTERNAL;
+                    return ret;
+                }
+            }
+
+            // only if it's NOT sequence change flushing (in which case only the eos
+            // was sent and not the first sc pkt) AND
+            // only after successful sending will it be removed from fifo
+            if (SESSION_RUN_STATE_SEQ_CHANGE_DRAINING !=
+                ctx->api_ctx.session_run_state) {
+                if (!is_input_fifo_empty(ctx)) {
+                    av_fifo_drain2(ctx->fme_fifo, (size_t) 1);
+                    av_log(avctx, AV_LOG_DEBUG, "fme popped, fifo num frames: %lu\n",
+                           av_fifo_can_read(ctx->fme_fifo));
+                }
+                av_frame_unref(&ctx->buffered_fme);
+                ishwframe = (ctx->buffered_fme.format == AV_PIX_FMT_NI_QUAD) &&
+                            (ctx->api_ctx.auto_dl_handle == 0);
+                if (ishwframe) {
+                    if (ctx->buffered_fme.buf[0])
+                        av_log(avctx, AV_LOG_TRACE, "xcoder_send_frame: after unref buffered_fme, hw frame av_buffer_get_ref_count=%d\n", av_buffer_get_ref_count(ctx->buffered_fme.buf[0]));
+                    else
+                        av_log(avctx, AV_LOG_TRACE, "xcoder_send_frame: after unref buffered_fme, hw frame av_buffer_get_ref_count=0 (buf[0] is NULL)\n");
+                }
+            } else {
+                av_log(avctx, AV_LOG_TRACE, "XCoder frame(eos) sent, sequence changing!"
+                       " NO fifo pop !\n");
+            }
+
+            // pushing input pts in circular FIFO
+            ctx->api_ctx.enc_pts_list[ctx->api_ctx.enc_pts_w_idx % NI_FIFO_SZ] = ctx->api_fme.data.frame.pts;
+            ctx->api_ctx.enc_pts_w_idx++;
+
+            // have another check before return: if no more frames in fifo to send and
+            // we've got eos (NULL) frame from upper stream, flag for flushing
+            if (ctx->eos_fme_received && is_input_fifo_empty(ctx)) {
+                av_log(avctx, AV_LOG_DEBUG, "Upper stream EOS frame received, fifo "
+                       "empty, start flushing ..\n");
+                ctx->encoder_flushing = 1;
+            }
+
+            ret = 0;
+        }
+    }
+
+    // try to flush encoder input fifo if it's not in seqchange draining state.
+    // Sending a frame before seqchange done may lead to stuck because the new frame's
+    // resolution could be different from that of the last sequence. Need to flush the
+    // fifo because its size increases with seqchange.
+    if (ret == 0 && frame && !is_input_fifo_empty(ctx) &&
+        SESSION_RUN_STATE_SEQ_CHANGE_DRAINING != ctx->api_ctx.session_run_state) {
+        av_log(avctx, AV_LOG_DEBUG, "try to flush encoder input fifo. Fifo num frames: %lu\n",
+               av_fifo_can_read(ctx->fme_fifo));
+        goto resend;
+    }
+
+    if (ctx->encoder_flushing) {
+        av_log(avctx, AV_LOG_DEBUG, "xcoder_send_frame flushing ..\n");
+        ret = ni_device_session_flush(&ctx->api_ctx, NI_DEVICE_TYPE_ENCODER);
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder send frame return %d \n", ret);
+    return ret;
+}
+
+static int xcoder_encode_reinit(AVCodecContext *avctx)
+{
+    int ret = 0;
+    XCoderEncContext *ctx = avctx->priv_data;
+    bool ishwframe;
+    ni_device_handle_t device_handle = ctx->api_ctx.device_handle;
+    ni_device_handle_t blk_io_handle = ctx->api_ctx.blk_io_handle;
+    int hw_id = ctx->api_ctx.hw_id;
+    char tmp_blk_dev_name[NI_MAX_DEVICE_NAME_LEN];
+    int bit_depth = 1;
+    int pix_fmt = AV_PIX_FMT_YUV420P;
+    int stride, ori_stride;
+    bool bIsSmallPicture = false;
+    AVFrame temp_frame;
+    ni_xcoder_params_t *p_param = &ctx->api_param;
+
+    ff_xcoder_strncpy(tmp_blk_dev_name, ctx->api_ctx.blk_dev_name,
+                      NI_MAX_DEVICE_NAME_LEN);
+
+    // re-init avctx's resolution to the changed one that is
+    // stored in the first frame of the fifo
+    av_fifo_peek(ctx->fme_fifo, &temp_frame, (size_t) 1, NULL);
+    temp_frame.extended_data = temp_frame.data;
+
+    ishwframe = temp_frame.format == AV_PIX_FMT_NI_QUAD;
+
+    if (ishwframe) {
+        bit_depth = (uint8_t)((niFrameSurface1_t*)((uint8_t*)temp_frame.data[3]))->bit_depth;
+        av_log(avctx, AV_LOG_INFO, "xcoder_receive_packet hw frame bit depth "
+               "changing %d -> %d\n",
+               ctx->api_ctx.bit_depth_factor, bit_depth);
+
+        switch (avctx->sw_pix_fmt) {
+            case AV_PIX_FMT_YUV420P:
+            case AV_PIX_FMT_YUVJ420P:
+                if (bit_depth == 2) {
+                    avctx->sw_pix_fmt = AV_PIX_FMT_YUV420P10LE;
+                    pix_fmt = NI_PIX_FMT_YUV420P10LE;
+                } else {
+                    pix_fmt = NI_PIX_FMT_YUV420P;
+                }
+                break;
+            case AV_PIX_FMT_YUV420P10LE:
+                if (bit_depth == 1) {
+                    avctx->sw_pix_fmt = AV_PIX_FMT_YUV420P;
+                    pix_fmt = NI_PIX_FMT_YUV420P;
+                } else {
+                    pix_fmt = NI_PIX_FMT_YUV420P10LE;
+                }
+                break;
+            case AV_PIX_FMT_NV12:
+                if (bit_depth == 2) {
+                    avctx->sw_pix_fmt = AV_PIX_FMT_P010LE;
+                    pix_fmt = NI_PIX_FMT_P010LE;
+                } else {
+                    pix_fmt = NI_PIX_FMT_NV12;
+                }
+                break;
+            case AV_PIX_FMT_P010LE:
+                if (bit_depth == 1) {
+                    avctx->sw_pix_fmt = AV_PIX_FMT_NV12;
+                    pix_fmt = NI_PIX_FMT_NV12;
+                } else {
+                    pix_fmt = NI_PIX_FMT_P010LE;
+                }
+                break;
+            case AV_PIX_FMT_NI_QUAD_10_TILE_4X4:
+                if (bit_depth == 1) {
+                    avctx->sw_pix_fmt = AV_PIX_FMT_NI_QUAD_8_TILE_4X4;
+                    pix_fmt = NI_PIX_FMT_8_TILED4X4;
+                } else {
+                    pix_fmt = NI_PIX_FMT_10_TILED4X4;
+                }
+                break;
+            case AV_PIX_FMT_NI_QUAD_8_TILE_4X4:
+                if (bit_depth == 2) {
+                    avctx->sw_pix_fmt = AV_PIX_FMT_NI_QUAD_10_TILE_4X4;
+                    pix_fmt = NI_PIX_FMT_10_TILED4X4;
+                } else {
+                    pix_fmt = NI_PIX_FMT_8_TILED4X4;
+                }
+                break;
+            case AV_PIX_FMT_ARGB:
+                pix_fmt = NI_PIX_FMT_ARGB;
+                break;
+            case AV_PIX_FMT_ABGR:
+                pix_fmt = NI_PIX_FMT_ABGR;
+                break;
+            case AV_PIX_FMT_RGBA:
+                pix_fmt = NI_PIX_FMT_RGBA;
+                break;
+            case AV_PIX_FMT_BGRA:
+                pix_fmt = NI_PIX_FMT_BGRA;
+                break;
+            default:
+                pix_fmt = NI_PIX_FMT_NONE;
+                break;
+        }
+    } else {
+        switch (temp_frame.format) {
+            case AV_PIX_FMT_YUV420P:
+            case AV_PIX_FMT_YUVJ420P:
+                pix_fmt = NI_PIX_FMT_YUV420P;
+                bit_depth = 1;
+                break;
+            case AV_PIX_FMT_NV12:
+                pix_fmt = NI_PIX_FMT_NV12;
+                bit_depth = 1;
+                break;
+            case AV_PIX_FMT_YUV420P10LE:
+                pix_fmt = NI_PIX_FMT_YUV420P10LE;
+                bit_depth = 2;
+                break;
+            case AV_PIX_FMT_P010LE:
+                pix_fmt = NI_PIX_FMT_P010LE;
+                bit_depth = 2;
+                break;
+            default:
+                pix_fmt = NI_PIX_FMT_NONE;
+                break;
+        }
+    }
+
+    ctx->eos_fme_received = 0;
+    ctx->encoder_eof = 0;
+    ctx->encoder_flushing = 0;
+    ctx->firstPktArrived = 0;
+    ctx->spsPpsArrived = 0;
+    ctx->spsPpsHdrLen = 0;
+    av_freep(&ctx->p_spsPpsHdr);
+    ctx->seqChangeCount++;
+
+    // check if resolution is zero copy compatible and set linesize according to new resolution
+    if (ni_encoder_frame_zerocopy_check(&ctx->api_ctx,
+        p_param, temp_frame.width, temp_frame.height,
+        (const int *)temp_frame.linesize, true) == NI_RETCODE_SUCCESS) {
+        stride = p_param->luma_linesize; // new sequence is zero copy compatible
+    } else {
+        stride = FFALIGN(temp_frame.width*bit_depth, 128);
+    }
+
+    if (ctx->api_ctx.ori_luma_linesize && ctx->api_ctx.ori_chroma_linesize) {
+        ori_stride = ctx->api_ctx.ori_luma_linesize; // previous sequence was zero copy compatible
+    } else {
+        ori_stride = FFALIGN(ctx->api_ctx.ori_width*bit_depth, 128);
+    }
+
+    if (pix_fmt == NI_PIX_FMT_ARGB
+       || pix_fmt == NI_PIX_FMT_ABGR
+       || pix_fmt == NI_PIX_FMT_RGBA
+       || pix_fmt == NI_PIX_FMT_BGRA) {
+        stride = temp_frame.width;
+        ori_stride = ctx->api_ctx.ori_width;
+    }
+
+    if (ctx->api_param.cfg_enc_params.lookAheadDepth
+        || ctx->api_param.cfg_enc_params.crf >= 0
+        || ctx->api_param.cfg_enc_params.crfFloat >= 0) {
+        av_log(avctx, AV_LOG_DEBUG, "xcoder_encode_reinit 2-pass "
+               "lookaheadDepth  %d and/or CRF %d and/or CRFFloat %f\n",
+               ctx->api_param.cfg_enc_params.lookAheadDepth,
+               ctx->api_param.cfg_enc_params.crf,
+               ctx->api_param.cfg_enc_params.crfFloat);
+        if ((temp_frame.width < NI_2PASS_ENCODE_MIN_WIDTH) ||
+            (temp_frame.height < NI_2PASS_ENCODE_MIN_HEIGHT)) {
+            bIsSmallPicture = true;
+        }
+    } else {
+        if ((temp_frame.width < NI_MIN_WIDTH) ||
+           (temp_frame.height < NI_MIN_HEIGHT)) {
+            bIsSmallPicture = true;
+        }
+    }
+
+    if (ctx->api_param.cfg_enc_params.multicoreJointMode) {
+        av_log(avctx, AV_LOG_DEBUG, "xcoder_encode_reinit multicore "
+               "joint mode\n");
+        if ((temp_frame.width < 256) ||
+           (temp_frame.height < 256)) {
+            bIsSmallPicture = true;
+        }
+    }
+
+    if (ctx->api_param.cfg_enc_params.crop_width || ctx->api_param.cfg_enc_params.crop_height) {
+        av_log(avctx, AV_LOG_DEBUG, "xcoder_encode_reinit needs to close and re-open "
+               " due to crop width x height \n");
+        bIsSmallPicture = true;
+    }
+
+    av_log(avctx, AV_LOG_INFO, "%s resolution "
+           "changing %dx%d -> %dx%d "
+           "format %d -> %d "
+           "original stride %d height %d pix fmt %d "
+           "new stride %d height %d pix fmt %d \n",
+           __func__, avctx->width, avctx->height,
+           temp_frame.width, temp_frame.height,
+           avctx->pix_fmt, temp_frame.format,
+           ori_stride, ctx->api_ctx.ori_height, ctx->api_ctx.ori_pix_fmt,
+           stride, temp_frame.height, pix_fmt);
+
+    avctx->width = temp_frame.width;
+    avctx->height = temp_frame.height;
+    avctx->pix_fmt = temp_frame.format;
+
+    // fast sequence change without close / open only if new resolution < original resolution
+    if ((ori_stride*ctx->api_ctx.ori_height < stride*temp_frame.height) ||
+        (ctx->api_ctx.ori_pix_fmt != pix_fmt) ||
+        bIsSmallPicture ||
+        (avctx->codec_id == AV_CODEC_ID_MJPEG) ||
+        ctx->api_param.cfg_enc_params.disable_adaptive_buffers) {
+        xcoder_encode_close(avctx);
+        ret = xcoder_encode_init(avctx);
+        // clear crop parameters upon sequence change because cropping values may not be compatible to new resolution
+        // (except for Motion Constrained mode 2, for which we crop to 64x64 alignment)
+        if (ctx->api_param.cfg_enc_params.motionConstrainedMode == MOTION_CONSTRAINED_QUALITY_MODE && avctx->codec_id == AV_CODEC_ID_HEVC) {
+            ctx->api_param.cfg_enc_params.crop_width = (temp_frame.width / 64 * 64);
+            ctx->api_param.cfg_enc_params.crop_height = (temp_frame.height / 64 * 64);
+            ctx->api_param.cfg_enc_params.hor_offset = ctx->api_param.cfg_enc_params.ver_offset = 0;
+            av_log(avctx, AV_LOG_DEBUG, "xcoder_encode_reinit sets "
+                   "crop width x height to %d x %d for Motion Constrained mode 2\n",
+                   ctx->api_param.cfg_enc_params.crop_width,
+                   ctx->api_param.cfg_enc_params.crop_height);
+        } else {
+            ctx->api_param.cfg_enc_params.crop_width = ctx->api_param.cfg_enc_params.crop_height = 0;
+            ctx->api_param.cfg_enc_params.hor_offset = ctx->api_param.cfg_enc_params.ver_offset = 0;
+        }
+    } else {
+        if (avctx->codec_id == AV_CODEC_ID_AV1) {
+            // AV1 8x8 alignment HW limitation is now worked around by FW cropping input resolution
+            if (temp_frame.width % NI_PARAM_AV1_ALIGN_WIDTH_HEIGHT)
+                av_log(avctx, AV_LOG_ERROR,
+                       "resolution change: AV1 Picture Width not aligned to %d - picture will be cropped\n",
+                       NI_PARAM_AV1_ALIGN_WIDTH_HEIGHT);
+
+            if (temp_frame.height % NI_PARAM_AV1_ALIGN_WIDTH_HEIGHT)
+                av_log(avctx, AV_LOG_ERROR,
+                       "resolution change: AV1 Picture Height not aligned to %d - picture will be cropped\n",
+                       NI_PARAM_AV1_ALIGN_WIDTH_HEIGHT);
+        }
+        ret = xcoder_encode_sequence_change(avctx, temp_frame.width, temp_frame.height, bit_depth);
+    }
+
+    // keep device handle(s) open during sequence change to fix mem bin buffer not recycled
+    ctx->api_ctx.device_handle  = device_handle;
+    ctx->api_ctx.blk_io_handle = blk_io_handle;
+    ctx->api_ctx.hw_id = hw_id;
+    ff_xcoder_strncpy(ctx->api_ctx.blk_dev_name, tmp_blk_dev_name,
+                      NI_MAX_DEVICE_NAME_LEN);
+    ctx->api_ctx.session_run_state = SESSION_RUN_STATE_SEQ_CHANGE_OPENING; // this state is referenced when sending first frame after sequence change
+
+    return ret;
+}
+
+int xcoder_receive_packet(AVCodecContext *avctx, AVPacket *pkt)
+{
+    XCoderEncContext *ctx = avctx->priv_data;
+    int i, ret = 0;
+    int recv;
+    AVFrame *frame = NULL;
+    ni_packet_t *xpkt = &ctx->api_pkt.data.packet;
+    bool av1_output_frame = 0;
+
+    av_log(avctx, AV_LOG_VERBOSE, "XCoder receive packet\n");
+
+    if (ctx->encoder_eof) {
+        av_log(avctx, AV_LOG_VERBOSE, "xcoder_receive_packet: EOS\n");
+        return AVERROR_EOF;
+    }
+
+    if (ni_packet_buffer_alloc(xpkt, NI_MAX_TX_SZ)) {
+        av_log(avctx, AV_LOG_ERROR,
+               "xcoder_receive_packet: packet buffer size %d allocation failed\n",
+               NI_MAX_TX_SZ);
+        return AVERROR(ENOMEM);
+    }
+
+    if (avctx->codec_id == AV_CODEC_ID_MJPEG && (!ctx->spsPpsArrived)) {
+        ctx->spsPpsArrived = 1;
+        // for Jpeg, start pkt_num counter from 1, because unlike video codecs
+        // (1st packet is header), there is no header for Jpeg
+        ctx->api_ctx.pkt_num = 1;
+    }
+
+    while (1) {
+        xpkt->recycle_index = -1;
+        recv = ni_device_session_read(&ctx->api_ctx, &(ctx->api_pkt), NI_DEVICE_TYPE_ENCODER);
+
+        av_log(avctx, AV_LOG_TRACE,
+               "XCoder receive packet: xpkt.end_of_stream=%d, xpkt.data_len=%d, "
+               "xpkt.frame_type=%d, recv=%d, encoder_flushing=%d, encoder_eof=%d\n",
+               xpkt->end_of_stream, xpkt->data_len, xpkt->frame_type, recv,
+               ctx->encoder_flushing, ctx->encoder_eof);
+
+        if (recv <= 0) {
+            ctx->encoder_eof = xpkt->end_of_stream;
+            if (ctx->encoder_eof || xpkt->end_of_stream) {
+                if (SESSION_RUN_STATE_SEQ_CHANGE_DRAINING ==
+                    ctx->api_ctx.session_run_state) {
+                    // after sequence change completes, reset codec state
+                    av_log(avctx, AV_LOG_INFO, "xcoder_receive_packet 1: sequence "
+                           "change completed, return AVERROR(EAGAIN) and will reopen "
+                           "codec!\n");
+
+                    ret = xcoder_encode_reinit(avctx);
+                    av_log(avctx, AV_LOG_DEBUG, "xcoder_receive_packet: xcoder_encode_reinit ret %d\n", ret);
+                    if (ret >= 0) {
+                        ret = AVERROR(EAGAIN);
+
+                        xcoder_send_frame(avctx, NULL);
+
+                        ctx->api_ctx.session_run_state = SESSION_RUN_STATE_NORMAL;
+                    }
+                    break;
+                }
+
+                ret = AVERROR_EOF;
+                av_log(avctx, AV_LOG_VERBOSE, "xcoder_receive_packet: got encoder_eof, return AVERROR_EOF\n");
+                break;
+            } else {
+                bool bIsReset = false;
+                if (NI_RETCODE_ERROR_VPU_RECOVERY == recv) {
+                    xcoder_encode_reset(avctx);
+                    bIsReset = true;
+                } else if (NI_RETCODE_ERROR_INVALID_SESSION == recv) {
+                    av_log(ctx, AV_LOG_ERROR, "encoder read retval %d\n", recv);
+                    ret = AVERROR(EIO);
+                    break;
+                }
+                ret = AVERROR(EAGAIN);
+                if ((!ctx->encoder_flushing && !ctx->eos_fme_received) || bIsReset) { // if encode session was reset, can't read again with invalid session, must break out first
+                    av_log(avctx, AV_LOG_TRACE, "xcoder_receive_packet: NOT encoder_"
+                           "flushing, NOT eos_fme_received, return AVERROR(EAGAIN)\n");
+                    break;
+                }
+            }
+        } else {
+            /* got encoded data back */
+            uint8_t *p_src, *p_end;
+            int64_t local_pts;
+            ni_custom_sei_set_t *p_custom_sei_set;
+            int meta_size = ctx->api_ctx.meta_size;
+            uint32_t copy_len = 0;
+            uint32_t data_len = 0;
+            int total_custom_sei_size = 0;
+            int custom_sei_count = 0;
+
+            if (avctx->pix_fmt == AV_PIX_FMT_NI_QUAD && xpkt->recycle_index >= 0 &&
+                avctx->height >= NI_MIN_HEIGHT && avctx->width >= NI_MIN_WIDTH &&
+                xpkt->recycle_index < NI_GET_MAX_HWDESC_P2P_BUF_ID(ctx->api_ctx.ddr_config)) {
+                int avframe_index =
+                    recycle_index_2_avframe_index(ctx, xpkt->recycle_index);
+                av_log(avctx, AV_LOG_VERBOSE, "UNREF trace ui16FrameIdx = [%d].\n",
+                       xpkt->recycle_index);
+                if (avframe_index >= 0 && ctx->sframe_pool[avframe_index]) {
+                    av_frame_unref(ctx->sframe_pool[avframe_index]);
+                    av_log(avctx, AV_LOG_DEBUG,
+                           "AVframe_index = %d pushed to free tail %d\n",
+                           avframe_index, ctx->freeTail);
+                    enq_free_frames(ctx, avframe_index);
+                    // enqueue the index back to free
+                    xpkt->recycle_index = -1;
+                } else {
+                    av_log(avctx, AV_LOG_DEBUG,
+                           "can't push to tail - avframe_index %d sframe_pool %p\n",
+                           avframe_index, ctx->sframe_pool[avframe_index]);
+                }
+            }
+
+            if (!ctx->spsPpsArrived) {
+                ret = AVERROR(EAGAIN);
+                ctx->spsPpsArrived = 1;
+                ctx->spsPpsHdrLen = recv - meta_size;
+                ctx->p_spsPpsHdr = av_malloc(ctx->spsPpsHdrLen);
+                if (!ctx->p_spsPpsHdr) {
+                    ret = AVERROR(ENOMEM);
+                    break;
+                }
+
+                memcpy(ctx->p_spsPpsHdr, (uint8_t *)xpkt->p_data + meta_size,
+                       xpkt->data_len - meta_size);
+
+                // start pkt_num counter from 1 to get the real first frame
+                ctx->api_ctx.pkt_num = 1;
+                // for low-latency mode, keep reading until the first frame is back
+                if (ctx->api_param.low_delay_mode) {
+                    av_log(avctx, AV_LOG_TRACE, "XCoder receive packet: low delay mode,"
+                           " keep reading until 1st pkt arrives\n");
+                    continue;
+                }
+                break;
+            }
+
+            // handle pic skip
+            if (xpkt->frame_type == 3) { // 0=I, 1=P, 2=B, 3=not coded / skip
+                ret = AVERROR(EAGAIN);
+                if (ctx->first_frame_pts == INT_MIN)
+                    ctx->first_frame_pts = xpkt->pts;
+                if (AV_CODEC_ID_AV1 == avctx->codec_id) {
+                    ctx->latest_dts = xpkt->pts;
+                } else if (ctx->total_frames_received < ctx->dtsOffset) {
+                    // guess dts
+                    ctx->latest_dts = ctx->first_frame_pts +
+                                      ctx->gop_offset_count - ctx->dtsOffset;
+                    ctx->gop_offset_count++;
+                } else {
+                    // get dts from pts FIFO
+                    ctx->latest_dts =
+                        ctx->api_ctx
+                            .enc_pts_list[ctx->api_ctx.enc_pts_r_idx % NI_FIFO_SZ];
+                    ctx->api_ctx.enc_pts_r_idx++;
+                }
+                if (ctx->latest_dts > xpkt->pts) {
+                    ctx->latest_dts = xpkt->pts;
+                }
+                ctx->total_frames_received++;
+
+                if (!ctx->encoder_flushing && !ctx->eos_fme_received) {
+                    av_log(avctx, AV_LOG_TRACE, "xcoder_receive_packet: skip"
+                           " picture output, return AVERROR(EAGAIN)\n");
+                    break;
+                } else {
+                    continue;
+                }
+            }
+
+            // store av1 packets to be merged & sent along with future packet
+            if (avctx->codec_id == AV_CODEC_ID_AV1) {
+                av_log(
+                    avctx, AV_LOG_TRACE,
+                    "xcoder_receive_packet: AV1 xpkt buf %p size %d show_frame %d\n",
+                    xpkt->p_data, xpkt->data_len, xpkt->av1_show_frame);
+                if (!xpkt->av1_show_frame) {
+                    // store AV1 packets
+                    xpkt->av1_p_buffer[xpkt->av1_buffer_index] = xpkt->p_buffer;
+                    xpkt->av1_p_data[xpkt->av1_buffer_index] = xpkt->p_data;
+                    xpkt->av1_buffer_size[xpkt->av1_buffer_index] = xpkt->buffer_size;
+                    xpkt->av1_data_len[xpkt->av1_buffer_index] = xpkt->data_len;
+                    xpkt->av1_buffer_index++;
+                    xpkt->p_buffer = NULL;
+                    xpkt->p_data = NULL;
+                    xpkt->buffer_size = 0;
+                    xpkt->data_len = 0;
+                    if (xpkt->av1_buffer_index >= MAX_AV1_ENCODER_GOP_NUM) {
+                        av_log(avctx, AV_LOG_ERROR,
+                               "xcoder_receive_packet: recv AV1 not shown frame "
+                               "number %d >= %d, return AVERROR_EXTERNAL\n",
+                               xpkt->av1_buffer_index, MAX_AV1_ENCODER_GOP_NUM);
+                        ret = AVERROR_EXTERNAL;
+                        break;
+                    } else if (!ctx->encoder_flushing && !ctx->eos_fme_received) {
+                        av_log(avctx, AV_LOG_TRACE,
+                               "xcoder_receive_packet: recv AV1 not shown frame, "
+                               "return AVERROR(EAGAIN)\n");
+                        ret = AVERROR(EAGAIN);
+                        break;
+                    } else {
+                        if (ni_packet_buffer_alloc(xpkt, NI_MAX_TX_SZ)) {
+                            av_log(avctx, AV_LOG_ERROR,
+                                   "xcoder_receive_packet: AV1 packet buffer size %d "
+                                   "allocation failed during flush\n",
+                                   NI_MAX_TX_SZ);
+                            ret = AVERROR(ENOMEM);
+                            break;
+                        }
+                        av_log(avctx, AV_LOG_TRACE,
+                               "xcoder_receive_packet: recv AV1 not shown frame "
+                               "during flush, continue..\n");
+                        continue;
+                    }
+                } else {
+                    // calculate length of previously received AV1 packets pending for merge
+                    av1_output_frame = 1;
+                    for (i = 0; i < xpkt->av1_buffer_index; i++) {
+                        data_len += xpkt->av1_data_len[i] - meta_size;
+                    }
+                }
+            }
+
+            p_src = (uint8_t*)xpkt->p_data + meta_size;
+            p_end = p_src + (xpkt->data_len - meta_size);
+            local_pts = xpkt->pts;
+
+            p_custom_sei_set = ctx->api_ctx.pkt_custom_sei_set[local_pts % NI_FIFO_SZ];
+            if (p_custom_sei_set != NULL) {
+                custom_sei_count = p_custom_sei_set->count;
+                for (i = 0; i < p_custom_sei_set->count; i++) {
+                    total_custom_sei_size += p_custom_sei_set->custom_sei[i].size;
+                }
+            }
+
+            if (custom_sei_count) {
+                // if HRD or custom sei enabled, search for pic_timing or custom SEI insertion point by
+                // skipping non-VCL until video data is found.
+                uint32_t nalu_type = 0;
+                const uint8_t *p_start_code = p_src;
+                uint32_t stc = -1;
+                if (AV_CODEC_ID_HEVC == avctx->codec_id) {
+                    do {
+                        stc = -1;
+                        p_start_code = avpriv_find_start_code(p_start_code, p_end, &stc);
+                        nalu_type = (stc >> 1) & 0x3F;
+                    } while (nalu_type > HEVC_NAL_RSV_VCL31);
+
+                    // calc. length to copy
+                    copy_len = p_start_code - 5 - p_src;
+                } else if (AV_CODEC_ID_H264 == avctx->codec_id) {
+                    do {
+                        stc = -1;
+                        p_start_code = avpriv_find_start_code(p_start_code, p_end, &stc);
+                        nalu_type = stc & 0x1F;
+                    } while (nalu_type > H264_NAL_IDR_SLICE);
+
+                    // calc. length to copy
+                    copy_len = p_start_code - 5 - p_src;
+                } else {
+                    av_log(avctx, AV_LOG_ERROR, "xcoder_receive packet: codec %d not "
+                           "supported for SEI !\n", avctx->codec_id);
+                }
+            }
+
+            if (avctx->codec_id == AV_CODEC_ID_MJPEG && !ctx->firstPktArrived) {
+                // there is no header for Jpeg, so skip header copy
+                ctx->firstPktArrived = 1;
+                if (ctx->first_frame_pts == INT_MIN) {
+                    ctx->first_frame_pts = xpkt->pts;
+                }
+            }
+
+            if (!ctx->firstPktArrived) {
+                int sizeof_spspps_attached_to_idr = ctx->spsPpsHdrLen;
+                if ((avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) &&
+                    (avctx->codec_id != AV_CODEC_ID_AV1) &&
+                    (ctx->seqChangeCount == 0)) {
+                    sizeof_spspps_attached_to_idr = 0;
+                }
+                ctx->firstPktArrived = 1;
+                if (ctx->first_frame_pts == INT_MIN) {
+                    ctx->first_frame_pts = xpkt->pts;
+                }
+
+                data_len += xpkt->data_len - meta_size + sizeof_spspps_attached_to_idr + total_custom_sei_size;
+                if (avctx->codec_id == AV_CODEC_ID_AV1)
+                    av_log(avctx, AV_LOG_TRACE, "xcoder_receive_packet: AV1 first output pkt size %d\n", data_len);
+
+                ret = ff_get_encode_buffer(avctx, pkt, data_len, 0);
+
+                if (!ret) {
+                    uint8_t *p_dst, *p_side_data;
+
+                    // fill in AVC/HEVC sidedata
+                    if ((avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) &&
+                        (avctx->extradata_size != ctx->spsPpsHdrLen ||
+                         (memcmp(avctx->extradata, ctx->p_spsPpsHdr, ctx->spsPpsHdrLen) !=
+                          0))) {
+                        avctx->extradata_size = ctx->spsPpsHdrLen;
+                        av_freep(&avctx->extradata);
+                        avctx->extradata = av_mallocz(avctx->extradata_size +
+                                                  AV_INPUT_BUFFER_PADDING_SIZE);
+                        if (!avctx->extradata) {
+                            av_log(avctx, AV_LOG_ERROR,
+                                   "Cannot allocate AVC/HEVC header of size %d.\n",
+                                   avctx->extradata_size);
+                            return AVERROR(ENOMEM);
+                        }
+                        memcpy(avctx->extradata, ctx->p_spsPpsHdr, avctx->extradata_size);
+                    }
+
+                    p_side_data = av_packet_new_side_data(
+                        pkt, AV_PKT_DATA_NEW_EXTRADATA, ctx->spsPpsHdrLen);
+                    if (p_side_data) {
+                        memcpy(p_side_data, ctx->p_spsPpsHdr, ctx->spsPpsHdrLen);
+                    }
+
+                    p_dst = pkt->data;
+                    if (sizeof_spspps_attached_to_idr) {
+                        memcpy(p_dst, ctx->p_spsPpsHdr, ctx->spsPpsHdrLen);
+                        p_dst += ctx->spsPpsHdrLen;
+                    }
+
+                    if (custom_sei_count && avctx->codec_id != AV_CODEC_ID_AV1) {
+                        // copy buf_period
+                        memcpy(p_dst, p_src, copy_len);
+                        p_dst += copy_len;
+
+                        for (i = 0; i < custom_sei_count; i++) {
+                            // copy custom sei
+                            ni_custom_sei_t *p_custom_sei = &p_custom_sei_set->custom_sei[i];
+                            if (p_custom_sei->location == NI_CUSTOM_SEI_LOC_AFTER_VCL) {
+                                break;
+                            }
+                            memcpy(p_dst, &p_custom_sei->data[0], p_custom_sei->size);
+                            p_dst += p_custom_sei->size;
+                        }
+
+                        // copy the IDR data
+                        memcpy(p_dst, p_src + copy_len,
+                               xpkt->data_len - meta_size - copy_len);
+                        p_dst += xpkt->data_len - meta_size - copy_len;
+
+                        // copy custom sei after slice
+                        for (; i < custom_sei_count; i++) {
+                            ni_custom_sei_t *p_custom_sei = &p_custom_sei_set->custom_sei[i];
+                            memcpy(p_dst, &p_custom_sei->data[0], p_custom_sei->size);
+                            p_dst += p_custom_sei->size;
+                        }
+                    } else {
+                        // merge AV1 packets
+                        if (avctx->codec_id == AV_CODEC_ID_AV1) {
+                            for (i = 0; i < xpkt->av1_buffer_index; i++) {
+                                memcpy(p_dst, (uint8_t *)xpkt->av1_p_data[i] + meta_size,
+                                       xpkt->av1_data_len[i] - meta_size);
+                                p_dst += (xpkt->av1_data_len[i] - meta_size);
+                            }
+                        }
+
+                        memcpy(p_dst, (uint8_t*)xpkt->p_data + meta_size,
+                               xpkt->data_len - meta_size);
+                    }
+                }
+            } else {
+                data_len += xpkt->data_len - meta_size + total_custom_sei_size;
+                if (avctx->codec_id == AV_CODEC_ID_AV1)
+                    av_log(avctx, AV_LOG_TRACE, "xcoder_receive_packet: AV1 output pkt size %d\n", data_len);
+
+                ret = ff_get_encode_buffer(avctx, pkt, data_len, 0);
+
+                if (!ret) {
+                    uint8_t *p_dst = pkt->data;
+
+                    if (custom_sei_count && avctx->codec_id != AV_CODEC_ID_AV1) {
+                        // copy buf_period
+                        memcpy(p_dst, p_src, copy_len);
+                        p_dst += copy_len;
+
+                        for (i = 0; i < custom_sei_count; i++) {
+                            // copy custom sei
+                            ni_custom_sei_t *p_custom_sei = &p_custom_sei_set->custom_sei[i];
+                            if (p_custom_sei->location == NI_CUSTOM_SEI_LOC_AFTER_VCL) {
+                                break;
+                            }
+                            memcpy(p_dst, &p_custom_sei->data[0], p_custom_sei->size);
+                            p_dst += p_custom_sei->size;
+                        }
+
+                        // copy the packet data
+                        memcpy(p_dst, p_src + copy_len,
+                               xpkt->data_len - meta_size - copy_len);
+                        p_dst += xpkt->data_len - meta_size - copy_len;
+
+                        // copy custom sei after slice
+                        for (; i < custom_sei_count; i++) {
+                            ni_custom_sei_t *p_custom_sei = &p_custom_sei_set->custom_sei[i];
+                            memcpy(p_dst, &p_custom_sei->data[0], p_custom_sei->size);
+                            p_dst += p_custom_sei->size;
+                        }
+                    } else {
+                        // merge AV1 packets
+                        if (avctx->codec_id == AV_CODEC_ID_AV1) {
+                            for (i = 0; i < xpkt->av1_buffer_index; i++) {
+                                memcpy(p_dst, (uint8_t *)xpkt->av1_p_data[i] + meta_size,
+                                       xpkt->av1_data_len[i] - meta_size);
+                                p_dst += (xpkt->av1_data_len[i] - meta_size);
+                            }
+                        }
+
+                        memcpy(p_dst, (uint8_t *)xpkt->p_data + meta_size,
+                               xpkt->data_len - meta_size);
+                    }
+                }
+            }
+
+            // free buffer
+            if (custom_sei_count) {
+                ni_memfree(p_custom_sei_set);
+                ctx->api_ctx.pkt_custom_sei_set[local_pts % NI_FIFO_SZ] = NULL;
+            }
+
+            if (!ret) {
+                if (xpkt->frame_type == 0) {
+                    pkt->flags |= AV_PKT_FLAG_KEY;
+                }
+
+                pkt->pts = xpkt->pts;
+                /* to ensure pts>dts for all frames, we assign a guess pts for the first 'dtsOffset' frames and then the pts from input stream
+                 * is extracted from input pts FIFO.
+                 * if GOP = IBBBP and PTSs = 0 1 2 3 4 5 .. then out DTSs = -3 -2 -1 0 1 ... and -3 -2 -1 are the guessed values
+                 * if GOP = IBPBP and PTSs = 0 1 2 3 4 5 .. then out DTSs = -1 0 1 2 3 ... and -1 is the guessed value
+                 * the number of guessed values is equal to dtsOffset
+                 */
+                if (AV_CODEC_ID_AV1 == avctx->codec_id) {
+                    pkt->dts = pkt->pts;
+                    av_log(avctx, AV_LOG_TRACE, "Packet dts (av1): %ld\n", pkt->dts);
+                } else if (ctx->total_frames_received < ctx->dtsOffset) {
+                    // guess dts
+                    pkt->dts = ctx->first_frame_pts + ctx->gop_offset_count - ctx->dtsOffset;
+                    ctx->gop_offset_count++;
+                    av_log(avctx, AV_LOG_TRACE, "Packet dts (guessed): %ld\n",
+                           pkt->dts);
+                } else {
+                    // get dts from pts FIFO
+                    pkt->dts =
+                        ctx->api_ctx
+                            .enc_pts_list[ctx->api_ctx.enc_pts_r_idx % NI_FIFO_SZ];
+                    ctx->api_ctx.enc_pts_r_idx++;
+                    av_log(avctx, AV_LOG_TRACE, "Packet dts: %ld\n", pkt->dts);
+                }
+                if (ctx->total_frames_received >= 1) {
+                    if (pkt->dts < ctx->latest_dts) {
+                        av_log(NULL, AV_LOG_WARNING, "dts: %ld < latest_dts: %ld.\n",
+                                pkt->dts, ctx->latest_dts);
+                    }
+                }
+                if (pkt->pts < ctx->first_frame_pts) {
+                    av_log(NULL, AV_LOG_WARNING, "pts %ld less than first frame pts %ld. Force it to first frame pts\n",
+                            pkt->pts, ctx->first_frame_pts);
+                    pkt->pts = ctx->first_frame_pts;
+                }
+                if (pkt->dts > pkt->pts) {
+                    av_log(NULL, AV_LOG_WARNING, "dts: %ld, pts: %ld. Forcing dts = pts \n",
+                            pkt->dts, pkt->pts);
+                    pkt->dts = pkt->pts;
+                    av_log(avctx, AV_LOG_TRACE, "Force dts to: %ld\n", pkt->dts);
+                }
+                ctx->total_frames_received++;
+                ctx->latest_dts = pkt->dts;
+                av_log(avctx, AV_LOG_DEBUG, "XCoder recv pkt #%" PRId64 ""
+                       " pts %" PRId64 "  dts %" PRId64 "  size %d  st_index %d frame_type %u avg qp %u\n",
+                       ctx->api_ctx.pkt_num - 1, pkt->pts, pkt->dts, pkt->size,
+                       pkt->stream_index, xpkt->frame_type, xpkt->avg_frame_qp);
+
+                enum AVPictureType pict_type = AV_PICTURE_TYPE_NONE;
+                switch (xpkt->frame_type) {
+                case 0:
+                  pict_type = AV_PICTURE_TYPE_I;
+                  break;
+                case 1:
+                  pict_type = AV_PICTURE_TYPE_P;
+                  break;
+                case 2:
+                  pict_type = AV_PICTURE_TYPE_B;
+                  break;
+                default:
+                  break;
+                }
+
+                int frame_qp = 0;
+                switch (avctx->codec_id) {
+                case AV_CODEC_ID_H264:
+                case AV_CODEC_ID_HEVC:
+                  frame_qp = xpkt->avg_frame_qp;
+                  break;
+                default:
+                  break;
+                }
+
+                ff_side_data_set_encoder_stats(pkt, frame_qp * FF_QP2LAMBDA, NULL, 0, pict_type);
+            }
+            ctx->encoder_eof = xpkt->end_of_stream;
+            if (ctx->encoder_eof &&
+                SESSION_RUN_STATE_SEQ_CHANGE_DRAINING ==
+                ctx->api_ctx.session_run_state) {
+                // after sequence change completes, reset codec state
+                av_log(avctx, AV_LOG_DEBUG, "xcoder_receive_packet 2: sequence change "
+                    "completed, return 0 and will reopen codec !\n");
+                ret = xcoder_encode_reinit(avctx);
+                av_log(avctx, AV_LOG_DEBUG, "xcoder_receive_packet: xcoder_encode_reinit ret %d\n", ret);
+                if (ret >= 0) {
+                    xcoder_send_frame(avctx, NULL);
+                    ctx->api_ctx.session_run_state = SESSION_RUN_STATE_NORMAL;
+                }
+            }
+            break;
+        }
+    }
+
+    if ((AV_CODEC_ID_AV1 == avctx->codec_id) && xpkt->av1_buffer_index &&
+        av1_output_frame) {
+        av_log(avctx, AV_LOG_TRACE,
+               "xcoder_receive_packet: ni_packet_buffer_free_av1 %d packtes\n",
+               xpkt->av1_buffer_index);
+        ni_packet_buffer_free_av1(xpkt);
+    }
+
+    av_log(avctx, AV_LOG_VERBOSE, "xcoder_receive_packet: return %d\n", ret);
+    return ret;
+}
+
+// for FFmpeg 4.4+
+int ff_xcoder_receive_packet(AVCodecContext *avctx, AVPacket *pkt)
+{
+    XCoderEncContext *ctx = avctx->priv_data;
+    AVFrame *frame = &ctx->buffered_fme;
+    int ret;
+
+    ret = ff_encode_get_frame(avctx, frame);
+    if (!ctx->encoder_flushing && ret >= 0 || ret == AVERROR_EOF) {
+        ret = xcoder_send_frame(avctx, (ret == AVERROR_EOF ? NULL : frame));
+        if (ret < 0 && ret != AVERROR_EOF) {
+            av_frame_unref(frame);
+            return ret;
+        }
+    }
+    // Once send_frame returns EOF go on receiving packets until EOS is met.
+    return xcoder_receive_packet(avctx, pkt);
+}
+
+bool free_frames_isempty(XCoderEncContext *ctx)
+{
+    return (ctx->freeHead == ctx->freeTail);
+}
+
+bool free_frames_isfull(XCoderEncContext *ctx)
+{
+    return (ctx->freeHead == ((ctx->freeTail == MAX_NUM_FRAMEPOOL_HWAVFRAME) ? 0 : ctx->freeTail + 1));
+}
+
+int deq_free_frames(XCoderEncContext *ctx)
+{
+    if (free_frames_isempty(ctx)) {
+        return -1;
+    }
+    ctx->aFree_Avframes_list[ctx->freeHead] = -1;
+    ctx->freeHead = (ctx->freeHead == MAX_NUM_FRAMEPOOL_HWAVFRAME) ? 0 : ctx->freeHead + 1;
+    return 0;
+}
+
+int enq_free_frames(XCoderEncContext *ctx, int idx)
+{
+    if (free_frames_isfull(ctx)) {
+        return -1;
+    }
+    ctx->aFree_Avframes_list[ctx->freeTail] = idx;
+    ctx->freeTail = (ctx->freeTail == MAX_NUM_FRAMEPOOL_HWAVFRAME) ? 0 : ctx->freeTail + 1;
+    return 0;
+}
+
+int recycle_index_2_avframe_index(XCoderEncContext *ctx, uint32_t recycleIndex)
+{
+    int i;
+    for (i = 0; i < MAX_NUM_FRAMEPOOL_HWAVFRAME; i++) {
+        if (ctx->sframe_pool[i]->data[3] &&
+            ((niFrameSurface1_t *)(ctx->sframe_pool[i]->data[3]))->ui16FrameIdx == recycleIndex) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+const AVCodecHWConfigInternal *ff_ni_enc_hw_configs[] = {
+    HW_CONFIG_ENCODER_FRAMES(NI_QUAD,  NI_QUADRA),
+    HW_CONFIG_ENCODER_DEVICE(NV12, NI_QUADRA),
+    HW_CONFIG_ENCODER_DEVICE(P010, NI_QUADRA),
+    HW_CONFIG_ENCODER_DEVICE(YUV420P, NI_QUADRA),
+    HW_CONFIG_ENCODER_DEVICE(YUV420P10, NI_QUADRA),
+    NULL,
+};
diff --git a/libavcodec/nienc.h b/libavcodec/nienc.h
new file mode 100644
index 0000000000..5f43e56995
--- /dev/null
+++ b/libavcodec/nienc.h
@@ -0,0 +1,114 @@
+/*
+ * NetInt XCoder H.264/HEVC Encoder common code header
+ * Copyright (c) 2018-2019 NetInt
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_NIENC_H
+#define AVCODEC_NIENC_H
+
+#include <ni_rsrc_api.h>
+#include <ni_device_api.h>
+#include <ni_util.h>
+
+#include "libavutil/internal.h"
+
+#include "avcodec.h"
+#include "codec_internal.h"
+#include "internal.h"
+#include "libavutil/opt.h"
+#include "libavutil/imgutils.h"
+
+#include "hwconfig.h"
+#include "nicodec.h"
+
+#define OFFSETENC(x) offsetof(XCoderEncContext, x)
+#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
+
+// Common Netint encoder options
+#define NI_ENC_OPTIONS\
+    { "xcoder", "Select which XCoder card to use.", OFFSETENC(dev_xcoder), \
+      AV_OPT_TYPE_STRING, { .str = NI_BEST_MODEL_LOAD_STR }, CHAR_MIN, CHAR_MAX, VE, "xcoder" }, \
+    {     "bestmodelload", "Pick the least model load XCoder/encoder available.", 0, AV_OPT_TYPE_CONST, \
+          { .str = NI_BEST_MODEL_LOAD_STR }, 0, 0, VE, "xcoder" }, \
+    {     "bestload", "Pick the least real load XCoder/encoder available.", 0, AV_OPT_TYPE_CONST, \
+          { .str = NI_BEST_REAL_LOAD_STR }, 0, 0, VE, "xcoder" }, \
+    \
+    { "ni_enc_idx", "Select which encoder to use by index. First is 0, second is 1, and so on.", \
+      OFFSETENC(dev_enc_idx), AV_OPT_TYPE_INT, { .i64 = BEST_DEVICE_LOAD }, -1, INT_MAX, VE }, \
+    \
+    { "ni_enc_name", "Select which encoder to use by NVMe block device name, e.g. /dev/nvme0n1.", \
+      OFFSETENC(dev_blk_name), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE }, \
+    \
+    { "encname", "Select which encoder to use by NVMe block device name, e.g. /dev/nvme0n1.", \
+      OFFSETENC(dev_blk_name), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE }, \
+    \
+    { "iosize", "Specify a custom NVMe IO transfer size (multiples of 4096 only).", \
+      OFFSETENC(nvme_io_size), AV_OPT_TYPE_INT, { .i64 = BEST_DEVICE_LOAD }, -1, INT_MAX, VE }, \
+    \
+    { "xcoder-params", "Set the XCoder configuration using a :-separated list of key=value parameters.", \
+      OFFSETENC(xcoder_opts), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE }, \
+    \
+    { "xcoder-gop", "Set the XCoder custom gop using a :-separated list of key=value parameters.", \
+      OFFSETENC(xcoder_gop), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE }, \
+    \
+    { "keep_alive_timeout", "Specify a custom session keep alive timeout in seconds.", \
+      OFFSETENC(keep_alive_timeout), AV_OPT_TYPE_INT, { .i64 = NI_DEFAULT_KEEP_ALIVE_TIMEOUT }, \
+      NI_MIN_KEEP_ALIVE_TIMEOUT, NI_MAX_KEEP_ALIVE_TIMEOUT, VE }
+
+// "gen_global_headers" encoder options
+#define NI_ENC_OPTION_GEN_GLOBAL_HEADERS\
+    { "gen_global_headers", "Generate SPS and PPS headers during codec initialization.", \
+      OFFSETENC(gen_global_headers), AV_OPT_TYPE_INT, { .i64 = GEN_GLOBAL_HEADERS_AUTO }, \
+      GEN_GLOBAL_HEADERS_AUTO, GEN_GLOBAL_HEADERS_ON, VE, "gen_global_headers" }, \
+    {     "auto", NULL, 0, AV_OPT_TYPE_CONST, \
+          { .i64 = GEN_GLOBAL_HEADERS_AUTO }, 0, 0, VE, "gen_global_headers" }, \
+    {     "off", NULL, 0, AV_OPT_TYPE_CONST, \
+          { .i64 = GEN_GLOBAL_HEADERS_OFF }, 0, 0, VE, "gen_global_headers" }, \
+    {     "on", NULL, 0, AV_OPT_TYPE_CONST, \
+          { .i64 = GEN_GLOBAL_HEADERS_ON }, 0, 0, VE, "gen_global_headers" }
+
+#define NI_ENC_OPTION_UDU_SEI \
+    { "udu_sei", "Pass through user data unregistered SEI if available", OFFSETENC(udu_sei), \
+      AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }
+
+int xcoder_encode_init(AVCodecContext *avctx);
+
+int xcoder_encode_close(AVCodecContext *avctx);
+
+int xcoder_encode_sequence_change(AVCodecContext *avctx, int width, int height, int bit_depth_factor);
+
+int xcoder_send_frame(AVCodecContext *avctx, const AVFrame *frame);
+
+int xcoder_receive_packet(AVCodecContext *avctx, AVPacket *pkt);
+
+int ff_xcoder_receive_packet(AVCodecContext *avctx, AVPacket *pkt);
+
+bool free_frames_isempty(XCoderEncContext *ctx);
+
+bool free_frames_isfull(XCoderEncContext *ctx);
+
+int deq_free_frames(XCoderEncContext *ctx);
+
+int enq_free_frames(XCoderEncContext *ctx, int idx);
+
+int recycle_index_2_avframe_index(XCoderEncContext *ctx, uint32_t recycleIndex);
+
+extern const AVCodecHWConfigInternal *ff_ni_enc_hw_configs[];
+
+#endif /* AVCODEC_NIENC_H */
diff --git a/libavcodec/nienc_av1.c b/libavcodec/nienc_av1.c
new file mode 100644
index 0000000000..f81a5921e7
--- /dev/null
+++ b/libavcodec/nienc_av1.c
@@ -0,0 +1,51 @@
+/*
+ * NetInt XCoder HEVC Encoder
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "nienc.h"
+
+static const AVOption enc_options[] = {
+    NI_ENC_OPTIONS,
+    NI_ENC_OPTION_GEN_GLOBAL_HEADERS,
+    {NULL}
+};
+
+static const AVClass av1_xcoderenc_class = {
+    .class_name = "av1_ni_quadra_enc",
+    .item_name  = av_default_item_name,
+    .option     = enc_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+FFCodec ff_av1_ni_quadra_encoder = {
+    .p.name           = "av1_ni_quadra_enc",
+    CODEC_LONG_NAME("AV1 NETINT Quadra encoder v" NI_XCODER_REVISION),
+    .p.type           = AVMEDIA_TYPE_VIDEO,
+    .p.id             = AV_CODEC_ID_AV1,
+    .p.priv_class     = &av1_xcoderenc_class,
+    .p.capabilities   = AV_CODEC_CAP_DELAY,
+    .p.pix_fmts       = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P,
+                                                      AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_NV12,
+                                                      AV_PIX_FMT_P010LE, AV_PIX_FMT_NI_QUAD,
+                                                      AV_PIX_FMT_NONE },
+    FF_CODEC_RECEIVE_PACKET_CB(ff_xcoder_receive_packet),
+    .init             = xcoder_encode_init,
+    .close            = xcoder_encode_close,
+    .priv_data_size   = sizeof(XCoderEncContext),
+    .hw_configs       = ff_ni_enc_hw_configs,
+};
diff --git a/libavcodec/nienc_h264.c b/libavcodec/nienc_h264.c
new file mode 100644
index 0000000000..ff1ff78e13
--- /dev/null
+++ b/libavcodec/nienc_h264.c
@@ -0,0 +1,52 @@
+/*
+ * NetInt XCoder H.264 Encoder
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "nienc.h"
+
+static const AVOption enc_options[] = {
+    NI_ENC_OPTIONS,
+    NI_ENC_OPTION_GEN_GLOBAL_HEADERS,
+    NI_ENC_OPTION_UDU_SEI,
+    {NULL}
+};
+
+static const AVClass h264_xcoderenc_class = {
+    .class_name = "h264_ni_quadra_enc",
+    .item_name  = av_default_item_name,
+    .option     = enc_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+FFCodec ff_h264_ni_quadra_encoder = {
+    .p.name           = "h264_ni_quadra_enc",
+    CODEC_LONG_NAME("H.264 NETINT Quadra encoder v" NI_XCODER_REVISION),
+    .p.type           = AVMEDIA_TYPE_VIDEO,
+    .p.id             = AV_CODEC_ID_H264,
+    .p.priv_class     = &h264_xcoderenc_class,
+    .p.capabilities   = AV_CODEC_CAP_DELAY,
+    .p.pix_fmts       = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P,
+                                                      AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_NV12,
+                                                      AV_PIX_FMT_P010LE, AV_PIX_FMT_NI_QUAD,
+                                                      AV_PIX_FMT_NONE },
+    FF_CODEC_RECEIVE_PACKET_CB(ff_xcoder_receive_packet),
+    .init             = xcoder_encode_init,
+    .close            = xcoder_encode_close,
+    .priv_data_size   = sizeof(XCoderEncContext),
+    .hw_configs       = ff_ni_enc_hw_configs,
+};
diff --git a/libavcodec/nienc_hevc.c b/libavcodec/nienc_hevc.c
new file mode 100644
index 0000000000..640ccb039b
--- /dev/null
+++ b/libavcodec/nienc_hevc.c
@@ -0,0 +1,52 @@
+/*
+ * NetInt XCoder HEVC Encoder
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "nienc.h"
+
+static const AVOption enc_options[] = {
+    NI_ENC_OPTIONS,
+    NI_ENC_OPTION_GEN_GLOBAL_HEADERS,
+    NI_ENC_OPTION_UDU_SEI,
+    {NULL}
+};
+
+static const AVClass h265_xcoderenc_class = {
+    .class_name = "h265_ni_quadra_enc",
+    .item_name  = av_default_item_name,
+    .option     = enc_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+FFCodec ff_h265_ni_quadra_encoder = {
+    .p.name           = "h265_ni_quadra_enc",
+    CODEC_LONG_NAME("H.265 NETINT Quadra encoder v" NI_XCODER_REVISION),
+    .p.type           = AVMEDIA_TYPE_VIDEO,
+    .p.id             = AV_CODEC_ID_H265,
+    .p.priv_class     = &h265_xcoderenc_class,
+    .p.capabilities   = AV_CODEC_CAP_DELAY,
+    .p.pix_fmts       = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P,
+                                                      AV_PIX_FMT_YUV420P10, AV_PIX_FMT_NV12,
+                                                      AV_PIX_FMT_P010LE, AV_PIX_FMT_NI_QUAD,
+                                                      AV_PIX_FMT_NONE },
+    FF_CODEC_RECEIVE_PACKET_CB(ff_xcoder_receive_packet),
+    .init             = xcoder_encode_init,
+    .close            = xcoder_encode_close,
+    .priv_data_size   = sizeof(XCoderEncContext),
+    .hw_configs       = ff_ni_enc_hw_configs,
+};
diff --git a/libavcodec/nienc_jpeg.c b/libavcodec/nienc_jpeg.c
new file mode 100644
index 0000000000..007b371da4
--- /dev/null
+++ b/libavcodec/nienc_jpeg.c
@@ -0,0 +1,48 @@
+/*
+ * NetInt XCoder H.264 Encoder
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "nienc.h"
+
+static const AVOption enc_options[] = {
+    NI_ENC_OPTIONS,
+    {NULL}
+};
+
+static const AVClass jpeg_xcoderenc_class = {
+    .class_name = "jpeg_ni_quadra_enc",
+    .item_name  = av_default_item_name,
+    .option     = enc_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+FFCodec ff_jpeg_ni_quadra_encoder = {
+    .p.name         = "jpeg_ni_quadra_enc",
+    CODEC_LONG_NAME("JPEG NETINT Quadra encoder v" NI_XCODER_REVISION),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_MJPEG,
+    .p.priv_class   = &jpeg_xcoderenc_class,
+    .p.capabilities = AV_CODEC_CAP_DELAY,
+    .p.pix_fmts = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NI_QUAD,
+                                                AV_PIX_FMT_NONE },
+    FF_CODEC_RECEIVE_PACKET_CB(ff_xcoder_receive_packet),
+    .init           = xcoder_encode_init,
+    .close          = xcoder_encode_close,
+    .priv_data_size = sizeof(XCoderEncContext),
+    .hw_configs     = ff_ni_enc_hw_configs,
+};
-- 
2.25.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

             reply	other threads:[~2025-07-02  8:11 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-07-02  8:11 Steven Zhou [this message]
2025-07-02 14:33 ` Derek Buitenhuis
2025-07-02 16:33   ` Steven Zhou

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=YT2PR01MB4701C5ADD8964E3325DC7E80E340A@YT2PR01MB4701.CANPRD01.PROD.OUTLOOK.COM \
    --to=steven.zhou@netint.ca \
    --cc=ffmpeg-devel@ffmpeg.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git