/*
 * Copyright (c) 2017-2020, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
*/

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

#include "cmdline.h"
#include "log_utils.h"
#include "thread_utils.h"
#include "nvmedia_image.h"
#include "nvmedia_iofst.h"
#include "nvmedia_surface.h"
#include "image_utils.h"
#if !NV_IS_SAFETY
#include "nvmedia_array.h"
#endif
#ifdef NVMEDIA_NVSCI_ENABLE
#include "nvmedia_image_nvscibuf.h"
#include "nvscibuf.h"
#include "nvscierror.h"
#include "image_scibuf_utils.h"
#endif

#define NVM_TEST_RESULT(condition, code, message1, message2)                \
    LOG_MSG("[TestResult] suite=\"iofst_negative\" case=\"");     \
    LOG_MSG(message1);                                                      \
    LOG_MSG("\" specs=\"");                                                 \
    LOG_MSG(message2);                                                      \
    if( !(condition)) {                                                     \
        LOG_MSG("\" result=\"failed\" \n");                                 \
        code ;                                                              \
    }                                                                       \
    else{                                                                   \
        LOG_MSG("\" result=\"passed\" \n");         }

typedef enum {
    YUV_400     = 0,
    YUV_420     = 1,
    YUV_422     = 2,
    YUV_444     = 3,
    NONE_CF     = 5
} ChromaFormat;

typedef enum {
    HP_OF     = 0,
    HP_ST     = 1,
    HQ_OF     = 2,
    HQ_ST     = 3,
    UHP_OF    = 4,
    UHP_ST    = 5,
    NONE_PR   = 6
} Presets;

typedef struct {
    uint8_t encOutThreadExit;
    uint8_t encError;
    int framesNum;
    int status_main;
    uint32_t mv_size;
    unsigned int inWidth;
    unsigned int inHeight;
    uint16_t mbWidth;
    uint16_t mbHeight;
    uint16_t outWidth;
    uint16_t outHeight;
    TestArgs *args;
    FILE *crcFile;
    NvSemaphore *processFrameDoneSema;
    NvSemaphore *encOutThreadExitSema;
    NvSemaphore *outBufAvailableSema;
    NvQueue *outputQueue;
    NvQueue *mvsQueue;
} EncOutThreadArgs;

#define HINTS_HEADER_ELEMS                 64
#define HINTS_SEGMENT_MAP_ELEMS_PER_MB     4
#define ARRAY_NUM_ATTRS (1)

#define IMAGE_BUFFERS_POOL_SIZE            5
#define IMAGE_BUFFERS_POOL_SIZE_PERF_TEST  16   // Range [1-16]

static uint32_t encoderOutputThread(void *threadArgs)
{
    EncOutThreadArgs *pOutputThreadArgs = (EncOutThreadArgs *)threadArgs;
    TestArgs *args = pOutputThreadArgs->args;
    int frameCounter = 1;
    int framesNum = pOutputThreadArgs->framesNum;
    FILE *crcFile = pOutputThreadArgs->crcFile;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    FILE *mvsRef = NULL;
    uint8_t *SrcBuf = NULL;
    uint8_t *DstBuf = NULL;
    uint32_t mv_size = pOutputThreadArgs->mv_size;
    uint16_t mbWidth = pOutputThreadArgs->mbWidth;
    uint16_t mbHeight = pOutputThreadArgs->mbHeight;
    uint16_t outWidth = pOutputThreadArgs->outWidth;
    uint16_t outHeight = pOutputThreadArgs->outHeight;
    NvQueue *outputQueue = pOutputThreadArgs->outputQueue;
    NvQueue *mvsQueue = pOutputThreadArgs->mvsQueue;
    uint32_t stride = 0U;
    uint32_t aligned_height = 0U;
    uint32_t frame_size  = 0U;

    if (args->mvsRefCheck)
    {
        stride = (pOutputThreadArgs->inWidth + 63)/64*64;
        aligned_height = ((mbHeight *4)+ 127)/128*128;
        frame_size  = stride*aligned_height;

        SrcBuf = (uint8_t *)malloc(frame_size);
        if (!SrcBuf)
        {
            LOG_ERR("%s: Malloc failed \n", __func__);
            pOutputThreadArgs->status_main = -1;
            goto output_thread_done;
        }
        DstBuf = (uint8_t *)malloc(frame_size);
        if (!DstBuf)
        {
            LOG_ERR("%s: Malloc failed \n", __func__);
            pOutputThreadArgs->status_main = -1;
            goto output_thread_done;
        }
    }

    while (1)
    {
        NvSemaphoreDecrement(pOutputThreadArgs->processFrameDoneSema, NV_TIMEOUT_INFINITE);
        if (pOutputThreadArgs->encError) {
            goto output_thread_done;
        }
        if ((frameCounter-1) == framesNum || (args->numFrames && ((frameCounter-1) == args->numFrames))) {
            LOG_DBG("%s: No more frames\n", __func__);
            break;
        }
        NvMediaTaskStatus taskStatus;
        NvMediaImage *outputImage = NULL;

        status = NvQueueGet(outputQueue, (void *)&outputImage, NV_TIMEOUT_INFINITE);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueueGet failed\n", __func__);
            goto output_thread_done;
        }

        if (NvMediaImageGetStatus(outputImage, NVMEDIA_IMAGE_TIMEOUT_INFINITE, &(taskStatus)))
        {
            LOG_ERR("%s: NvMediaImageLock failed\n", __func__);
            pOutputThreadArgs->status_main = -1;
            goto output_thread_done;
        }
        if (taskStatus.status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Engine returned error %d.\n", __func__, taskStatus.status);
            pOutputThreadArgs->status_main = -1;
            goto output_thread_done;
        }

        LOG_DBG("%s: IOFST successfully completed\n", __func__);

        if (!args->perfTest)
        {
            if (args->outputFilename) {
                if (strchr(args->outputFilename, '%')) {
                    char buffer[512];
                    sprintf(buffer, args->outputFilename, frameCounter-1);
                    status = WriteImage(buffer,
                                        outputImage,
                                        NVMEDIA_FALSE,
                                        NVMEDIA_FALSE,
                                        0,
                                        NULL);
                }
                else
                {
                    status = WriteImage(args->outputFilename,
                                        outputImage,
                                        NVMEDIA_FALSE,
                                        (frameCounter-1) ? NVMEDIA_TRUE : NVMEDIA_FALSE,
                                        0,
                                        NULL);
                }
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Error occured\n", __func__);
                    pOutputThreadArgs->status_main = -1;
                    goto output_thread_done;
                }
            }

            if (args->mvsRefCheck)
            {
                NvMediaBool isMatching=0;
                uint8_t *tempP=NULL;
                uint32_t readSize = mbWidth * 4 * mv_size;

                mvsRef = fopen(args->mvsFilename, "rb");
                if (!mvsRef)
                {
                    LOG_ERR("%s: Error in opening %s for reading \n", __func__, args->mvsFilename);
                    pOutputThreadArgs->status_main = -1;
                    goto output_thread_done;
                }
                fseek(mvsRef, ((frameCounter - 1)*frame_size), SEEK_SET);
                fread(SrcBuf, frame_size, 1, mvsRef);
                tempP=DstBuf;
                for (uint32_t i=0; i< mbHeight*4 ; i++)
                {
                    memcpy(tempP, SrcBuf+(i*stride), readSize);
                    tempP+= readSize;
                }

                status = CheckImageOutput(outputImage,
                                        outWidth,
                                        outHeight,
                                        DstBuf,
                                        &isMatching,
                                        1); /* Passing 1 as default value in rawBytesPerPixel */
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: CheckImageOutput failed: %x\n", __func__, status);
                    pOutputThreadArgs->status_main = -1;
                    goto output_thread_done;
                }
                if (isMatching != NVMEDIA_TRUE)
                {
                    LOG_ERR("%s: Frame %d mvs does not match with reference mvs\n", __func__, frameCounter);
                    pOutputThreadArgs->status_main = -1;
                    goto output_thread_done;
                }

            }

            if (args->crcoption.crcGenMode){
                uint32_t calcCrc;
                calcCrc = 0;
                status = GetImageCrc(outputImage,
                                    outWidth,
                                    outHeight,
                                    &calcCrc,
                                    1); /* Passing 1 as default value in rawBytesPerPixel */
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: GetImageCrc failed: %x\n", __func__, status);
                    pOutputThreadArgs->status_main = -1;
                    goto output_thread_done;
                }

                if (!fprintf(crcFile, "%08x\n", calcCrc)) {
                    LOG_ERR("%s: Failed writing calculated CRC to file %s\n", __func__, crcFile);
                    pOutputThreadArgs->status_main = -1;
                    goto output_thread_done;
                }
            } else if (args->crcoption.crcCheckMode){
                uint32_t refCrc;
                NvMediaBool isMatching;
                if (fscanf(crcFile, "%8x\n", &refCrc) == 1) {
                    status = CheckImageCrc(outputImage,
                                        outWidth,
                                        outHeight,
                                        refCrc,
                                        &isMatching,
                                        1); /* Passing 1 as default value in rawBytesPerPixel */
                    if (status != NVMEDIA_STATUS_OK) {
                        LOG_ERR("%s: CheckImageCrc failed: %x\n", __func__, status);
                        pOutputThreadArgs->status_main = -1;
                        goto output_thread_done;
                    }

                    if (isMatching != NVMEDIA_TRUE){
                        LOG_ERR("%s: Frame %d crc does not match with ref crc 0x%x\n", __func__, frameCounter, refCrc);
                        pOutputThreadArgs->status_main = -1;
                        goto output_thread_done;
                    }
                } else {
                    LOG_ERR("%s: Failed checking CRC. Failed reading file %s\n", __func__, crcFile);
                    pOutputThreadArgs->status_main = -1;
                    goto output_thread_done;
                }
            }
        }
        outputImage->tag = mvsQueue;
        NvQueuePut((NvQueue *)outputImage->tag, (void *)&outputImage, 100);
        NvSemaphoreIncrement(pOutputThreadArgs->outBufAvailableSema);
        frameCounter++;
    }
output_thread_done:
    if (SrcBuf){
        free(SrcBuf);
    }

    if (DstBuf){
        free(DstBuf);
    }

    if (mvsRef){
        fclose(mvsRef);
    }

    pOutputThreadArgs->encOutThreadExit = 1;
    NvSemaphoreIncrement(pOutputThreadArgs->encOutThreadExitSema);
    return 0;
}

static NvMediaBool prefetchData (TestArgs *args, NvQueue *inputQueue, EncOutThreadArgs *pOutputThreadArgs)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    for (int i = 0; i < args->imagePoolSize; i++)
    {
        NvMediaImage *imageFrame = NULL;
        status = NvQueueGet(inputQueue, (void *)&imageFrame, 100);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueueGet failed\n", __func__);
            return NVMEDIA_FALSE;
        }
        LOG_DBG("%s: Reading image frame: %d\n", __func__, i+1);
        status = ReadImage(args->inputFilename,
                            i+1,    // frame num
                            args->forward_ref ? pOutputThreadArgs->inWidth : args->width,
                            args->forward_ref ? pOutputThreadArgs->inHeight : args->height,
                            imageFrame,
                            args->inputFormat == 0 ? 1 : 0,  //inputUVOrderFlag
                            1,                              //rawBytesPerPixel
                            LSB_ALIGNED);                   //pixelAlignment
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: ReadImage failed\n", __func__);
            pOutputThreadArgs->status_main = -1;
            NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
            return NVMEDIA_FALSE;
        }
        NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
    }
    return NVMEDIA_TRUE;
}

int main(int argc, char *argv[])
{
    TestArgs args;
    FILE *inputFile = NULL;
    FILE *crcFile = NULL;
    FILE *extHintsFile = NULL;
    unsigned long fileLength;
    NvMediaDevice *device = NULL;
    NVM_SURF_FMT_DEFINE_ATTR(surfFormatAttrs);
    NvMediaSurfAllocAttr surfAllocAttrs[NVM_SURF_ALLOC_ATTR_MAX];
    NvMediaSurfaceType inputSurfaceType = 0;
    NvMediaSurfaceType outputSurfaceType;
    NvMediaSurfaceType testSurfaceType = 0;
    NvMediaIOFST *testOF = NULL;
    NvMediaImage *testImage = NULL;
    NvMediaIOFSTType estimationType = NONE_PR;
#if !NV_IS_SAFETY
    NvMediaArrayAllocAttr arrayAttr[ARRAY_NUM_ATTRS];
    uint32_t *arrayData;
    NvMediaArray *hintsArray = NULL;
    uint32_t hintsCapacity;
    uint32_t hintsSize = 0;
#endif
    NvMediaOFSTInitializeParams ofInitParams;
    NvMediaOFSTExternalHintParams ExtHintParams;
    uint16_t mbWidth, mbHeight;
    uint16_t outWidth, outHeight;
    int framesNum = 0, frameCounter = 1;
    uint32_t imageSize = 0;
    uint32_t numAttrs = 0;
    NvMediaBool done = NVMEDIA_FALSE;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    int i;
    uint64_t startTime, endTime;
    double elapseTime = 0;
    unsigned int  inWidth, inHeight;
    int           inBit_depth, inChroma_format_idc;
    char          *inLayout, *inMemType;
    uint32_t mv_size = 0;
    NvMediaVersion *version = NULL;
    NvQueue *inputQueue = NULL;
    NvQueue *mvsQueue = NULL;
    NvQueue *outputQueue = NULL;
    NvThread *outputThread = NULL;
    NvSemaphore *processFrameDoneSema = NULL;
    NvSemaphore *encOutThreadExitSema = NULL;
    NvSemaphore *outBufAvailableSema = NULL;
    EncOutThreadArgs encOutThreadArgs = {0};
    memset(&args, 0, sizeof(TestArgs));
    if (ParseArgs(argc, argv, &args)) {
        PrintUsage();
        return -1;
    }

    if (args.version)
    {
        version = (NvMediaVersion *)malloc(sizeof(NvMediaVersion));
        status = NvMediaIOFSTGetVersion(version);
        if (status != NVMEDIA_STATUS_OK)
        {
            LOG_ERR("%s: NvMediaIOFSTGetVersion failed\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
        if ((version->major != NVMEDIA_IOFST_VERSION_MAJOR) || (version->minor != NVMEDIA_IOFST_VERSION_MINOR))
        {
            LOG_ERR("%s: NvMediaIOFSTGetVersion version mismatch\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
        LOG_MSG("\nNvMediaIOFST Major version- %d \n",NVMEDIA_IOFST_VERSION_MAJOR);
        LOG_MSG("NvMediaIOFST Minor version- %d \n",NVMEDIA_IOFST_VERSION_MINOR);
    }

    LOG_DBG("%s: Creating NvMedia device\n", __func__);
    device = NvMediaDeviceCreate();
    if (!device) {
        LOG_ERR("%s: NvMediaDeviceCreate failed\n", __func__);
        encOutThreadArgs.status_main = -1;
        goto fail;
    }
#ifdef NVMEDIA_NVSCI_ENABLE
    status = NvMediaImageNvSciBufInit();
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvMediaImageSciBufInit failed\n", __func__);
        encOutThreadArgs.status_main = -1;
        goto fail;
    }
#endif

    if (args.crcoption.crcGenMode && args.crcoption.crcCheckMode) {
        LOG_ERR("%s: crcGenMode and crcCheckMode cannot be enabled at the same time\n", __func__);
        encOutThreadArgs.status_main = -1;
        goto fail;
    }

    if (args.crcoption.crcGenMode){
        crcFile = fopen(args.crcoption.crcFilename, "wt");
        if (!crcFile){
            LOG_ERR("%s: Cannot open crc gen file for writing\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
    } else if (args.crcoption.crcCheckMode){
        crcFile = fopen(args.crcoption.crcFilename, "rb");
        if (!crcFile){
            LOG_ERR("%s: Cannot open crc gen file for reading\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
    }

    LOG_DBG("%s: Openning %s for input\n", __func__, args.inputFilename);
    inputFile = fopen(args.inputFilename, "rb");
    if (!inputFile) {
        LOG_ERR("%s: Error opening '%s' for reading\n", __func__, args.inputFilename);
        encOutThreadArgs.status_main = -1;
        goto fail;
    }

    fseek(inputFile, 0, SEEK_END);
    fileLength = ftell(inputFile);
    fclose(inputFile);
    if (!fileLength) {
        LOG_ERR("%s: Zero file length for file %s\n", __func__, args.inputFilename);
        encOutThreadArgs.status_main = -1;
        goto fail;
    }
    LOG_DBG("%s: Input file length: %ld\n", __func__, fileLength);

    args.imagePoolSize = (args.perfTest ? IMAGE_BUFFERS_POOL_SIZE_PERF_TEST : IMAGE_BUFFERS_POOL_SIZE);

    status = NvQueueCreate(&inputQueue,
                args.imagePoolSize+1,
                sizeof(NvMediaImage *));
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvQueueCreate failed\n", __func__);
        goto fail;
    }

    status = NvQueueCreate(&mvsQueue,
                args.imagePoolSize,
                sizeof(NvMediaImage *));
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvQueueCreate failed\n", __func__);
        goto fail;
    }

    status = NvQueueCreate(&outputQueue,
                args.imagePoolSize,
                sizeof(NvMediaImage *));
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvQueueCreate failed\n", __func__);
        goto fail;
    }

    status = NvSemaphoreCreate(&processFrameDoneSema, 0, args.imagePoolSize);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvSemaphoreCreate failed\n", __func__);
        goto fail;
    }

    status = NvSemaphoreCreate(&outBufAvailableSema, args.imagePoolSize, args.imagePoolSize);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvSemaphoreCreate failed\n", __func__);
        goto fail;
    }

    status = NvSemaphoreCreate(&encOutThreadExitSema, 0, 1);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvSemaphoreCreate failed\n", __func__);
        goto fail;
    }

    inWidth = args.width;
    inHeight = args.height;
    inBit_depth = args.bit_depth;
    inChroma_format_idc = args.chroma_format_idc;
    inLayout = args.layout;
    inMemType = args.memType;
    // Loop to set up input Image surfaces for Current frame and reference frame
    for (i = 0; i < args.imagePoolSize+1; i++)
    {
        // In case of Negtive testing setup Index 0 input Image surfaces as reference surface for normal case as well as forward reference
        if (i == 0 && args.negTest)
        {
            if (args.widthRef != 0 && args.heightRef != 0)
            {
                inWidth = args.widthRef;
                inHeight = args.heightRef;
            }
            if (args.bit_depthRef != 0)
                inBit_depth = args.bit_depthRef;
            if (args.chroma_format_idcRef != 0)
                inChroma_format_idc = args.chroma_format_idcRef;
            if (args.layoutRef != NULL)
                inLayout = args.layoutRef;
            if (args.memTypeRef != NULL)
                inMemType = args.memTypeRef;
        }
        else
        {
            inWidth = args.width;
            inHeight = args.height;
            inBit_depth = args.bit_depth;
            inChroma_format_idc = args.chroma_format_idc;
            inLayout = args.layout;
            inMemType = args.memType;
        }
        switch(inChroma_format_idc) {
            case YUV_400:
                imageSize = (inWidth * inHeight);
                break;
            case YUV_420:
                imageSize = (inWidth * inHeight * 3 / 2);
                break;
            case YUV_422:
                imageSize = (inWidth * inHeight * 2);
                break;
            case YUV_444:
                imageSize = (inWidth * inHeight * 3);
                break;
            default:
                imageSize = (inWidth * inHeight);
                break;
        }
        if (inBit_depth > 8)
        {
            imageSize *= 2;
        }
        framesNum = fileLength / imageSize;
        if (!args.negTest)
        {
            if (framesNum < 2) {
                LOG_ERR("%s: At least 2 frames are needed for IOFST (file %s)\n", __func__, args.inputFilename);
                encOutThreadArgs.status_main = -1;
                goto fail;
            }
        }
        framesNum -= 1;
        estimationType = NVMEDIA_IMAGE_OPTICALFLOW_ESTIMATION_HP_MODE;
        switch(args.estimationType) {
        case HP_OF:
            mv_size = 4;
            estimationType = NVMEDIA_IMAGE_OPTICALFLOW_ESTIMATION_HP_MODE;
            break;
        case HP_ST:
            mv_size = 2;
            estimationType = NVMEDIA_IMAGE_STEREODISPARITY_ESTIMATION_HP_MODE;
            break;
        case HQ_OF:
            mv_size = 4;
            estimationType = NVMEDIA_IMAGE_OPTICALFLOW_ESTIMATION_HQ_MODE;
            break;
        case HQ_ST:
            mv_size = 2;
            estimationType = NVMEDIA_IMAGE_STEREODISPARITY_ESTIMATION_HQ_MODE;
            break;
        case UHP_OF:
            mv_size = 4;
            estimationType = NVMEDIA_IMAGE_OPTICALFLOW_ESTIMATION_UHP_MODE;
            break;
        case UHP_ST:
            mv_size = 2;
            estimationType = NVMEDIA_IMAGE_STEREODISPARITY_ESTIMATION_UHP_MODE;
            break;
        default:
            estimationType = NONE_PR;
        }

        LOG_DBG("%s: Creating image surfaces\n", __func__);
        LOG_DBG("%s: Creating bit_depth = %d \n",__func__, inBit_depth);
        switch(inBit_depth) {
            case 8:
                NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs, YUV, 420, SEMI_PLANAR, UINT, 8, BL);
                break;
            case 10:
                NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs, YUV, 420, SEMI_PLANAR, UINT, 10, BL);
                break;
            case 12:
                NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs, YUV, 420, SEMI_PLANAR, UINT, 12, BL);
                break;
            case 16:
                NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs, YUV, 420, SEMI_PLANAR, UINT, 16, BL);
                break;
            default:
                NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs, YUV, 420, SEMI_PLANAR, UINT, 32, BL);
                break;
        }
        switch(inChroma_format_idc) {
            case YUV_400:
                surfFormatAttrs[NVM_SURF_ATTR_COMPONENT_ORDER].value = NVM_SURF_ATTR_COMPONENT_ORDER_LUMA;
                surfFormatAttrs[NVM_SURF_ATTR_SUB_SAMPLING_TYPE].value = NVM_SURF_ATTR_SUB_SAMPLING_TYPE_NONE;
                surfFormatAttrs[NVM_SURF_ATTR_MEMORY].value = NVM_SURF_ATTR_MEMORY_PACKED;
                break;
            case YUV_420:
                surfFormatAttrs[NVM_SURF_ATTR_SUB_SAMPLING_TYPE].value = NVM_SURF_ATTR_SUB_SAMPLING_TYPE_420;
                break;
            case YUV_422:
                surfFormatAttrs[NVM_SURF_ATTR_SUB_SAMPLING_TYPE].value = NVM_SURF_ATTR_SUB_SAMPLING_TYPE_422;
                break;
            case YUV_444:
                surfFormatAttrs[NVM_SURF_ATTR_SUB_SAMPLING_TYPE].value = NVM_SURF_ATTR_SUB_SAMPLING_TYPE_444;
                break;
            default:
                surfFormatAttrs[NVM_SURF_ATTR_SUB_SAMPLING_TYPE].value = NONE_CF ;
                break;
        }

        if (inLayout != NULL)
        {
            if (strcasecmp(inLayout , "PL") == 0)
                surfFormatAttrs[NVM_SURF_ATTR_LAYOUT].value = NVM_SURF_ATTR_LAYOUT_PL;
        }
        if (inMemType != NULL)
        {
            if (strcasecmp(inMemType , "PL") == 0)
                surfFormatAttrs[NVM_SURF_ATTR_MEMORY].value = NVM_SURF_ATTR_MEMORY_PLANAR;
        }
        surfAllocAttrs[0].type = NVM_SURF_ATTR_WIDTH;
        surfAllocAttrs[0].value = inWidth;
        surfAllocAttrs[1].type = NVM_SURF_ATTR_HEIGHT;
        surfAllocAttrs[1].value = inHeight;
        surfAllocAttrs[2].type = NVM_SURF_ATTR_CPU_ACCESS;
#ifdef NVRM_SAFE_API
        surfAllocAttrs[2].value = NVM_SURF_ATTR_CPU_ACCESS_CACHED;
#else
        surfAllocAttrs[2].value = NVM_SURF_ATTR_CPU_ACCESS_UNMAPPED;
#endif
        numAttrs = 3;

        /*NvMedia GetType of the surface*/
        inputSurfaceType = NvMediaSurfaceFormatGetType(surfFormatAttrs, 7);
        if (!args.negTest)
        {
            if (inputSurfaceType == NvMediaSurfaceType_Unsupported) {
                LOG_ERR("Surface attributes specified not supported\n");
                encOutThreadArgs.status_main = -1;
                goto fail;
            }
        }

        NvMediaImage *imageSurface = NULL;
#ifndef NVMEDIA_NVSCI_ENABLE
        imageSurface = NvMediaImageCreateNew(device,
                                                inputSurfaceType,
                                                surfAllocAttrs,
                                                numAttrs,
                                                0);
        if (!imageSurface) {
            LOG_ERR("%s: NvMediaImageCreateNew failed\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
#else
        imageSurface = NvMediaImageCreateUsingNvScibuf(device, inputSurfaceType, surfAllocAttrs, numAttrs, 0);
        if (!args.negTest && !imageSurface) {   // not exiting for negative test here to improve integration code coverage
            LOG_ERR("%s: NvMediaImageCreateUsingNvScibuf failed\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
#endif
        if (imageSurface)   // this check is needed as negTest skips the goto_fail of imageSurface check.
            imageSurface->tag = inputQueue;
        status = NvQueuePut(inputQueue, (void *)&imageSurface, NV_TIMEOUT_INFINITE);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueuePut failed\n",__func__);
            goto fail;
        }
    }

    mbWidth = (args.width + 15) / 16;
    mbHeight = (args.height + 15) / 16;
    outWidth = ((args.width + 3U)/4U);
    outHeight = ((args.height + 3U)/4U);
    LOG_DBG("%s: Source File Resolution: %ux%u (Default size: %ux%u "
            "macroblocks: %ux%u outRes:%ux%u)\n", __func__,
            args.width, inHeight, mbWidth * 16, mbHeight * 16,
            mbWidth, mbHeight, outWidth, outHeight);

    LOG_DBG("%s: Creating mv image surface\n", __func__);
    if ((estimationType == NVMEDIA_IMAGE_OPTICALFLOW_ESTIMATION_HP_MODE) ||
        (estimationType == NVMEDIA_IMAGE_OPTICALFLOW_ESTIMATION_HQ_MODE) ||
        (estimationType == NVMEDIA_IMAGE_OPTICALFLOW_ESTIMATION_UHP_MODE)) {
        NVM_SURF_FMT_SET_ATTR_RGBA(surfFormatAttrs, RG, INT, 16, BL);
    }
    else {
        NVM_SURF_FMT_SET_ATTR_RGBA(surfFormatAttrs, ALPHA, INT, 16, BL);
    }
    if (args.opLayout != NULL)
    {
        if (strcasecmp(args.opLayout , "PL") == 0)
            surfFormatAttrs[NVM_SURF_ATTR_LAYOUT].value = NVM_SURF_ATTR_LAYOUT_PL;
    }
    if (args.opCmpOrder != NULL)
    {
        if (strcasecmp(args.opCmpOrder , "RG") == 0)
            surfFormatAttrs[NVM_SURF_ATTR_COMPONENT_ORDER].value = NVM_SURF_ATTR_COMPONENT_ORDER_RG;
        if (strcasecmp(args.opCmpOrder , "ALPHA") == 0)
            surfFormatAttrs[NVM_SURF_ATTR_COMPONENT_ORDER].value = NVM_SURF_ATTR_COMPONENT_ORDER_ALPHA;
    }
    if (args.negOutSurfType == NVMEDIA_TRUE)
    {
        // set unsupported surface type for output
        NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs, YUV, 420, SEMI_PLANAR, UINT, 16, BL);
    }
    if (args.negOutSurfDataType == NVMEDIA_TRUE)
    {
        // set unsupported surface data type for output
        surfFormatAttrs[NVM_SURF_ATTR_DATA_TYPE].value = NVM_SURF_ATTR_DATA_TYPE_UINT;
    }
    if (args.negOutSurfBitDepth == NVMEDIA_TRUE)
    {
        // set unsupported bit-depth for output surface
        surfFormatAttrs[NVM_SURF_ATTR_BITS_PER_COMPONENT].value = NVM_SURF_ATTR_BITS_PER_COMPONENT_32;
    }

    surfAllocAttrs[0].type = NVM_SURF_ATTR_WIDTH;
    surfAllocAttrs[0].value = outWidth;
    surfAllocAttrs[1].type = NVM_SURF_ATTR_HEIGHT;
    surfAllocAttrs[1].value = outHeight;
    surfAllocAttrs[2].type = NVM_SURF_ATTR_CPU_ACCESS;
#ifdef NVRM_SAFE_API
    surfAllocAttrs[2].value = NVM_SURF_ATTR_CPU_ACCESS_CACHED;
#else
    surfAllocAttrs[2].value = NVM_SURF_ATTR_CPU_ACCESS_UNMAPPED;
#endif
    numAttrs = 3;

    /*NvMedia GetType of the surface*/
    outputSurfaceType = NvMediaSurfaceFormatGetType(surfFormatAttrs, 7);
    if (!args.negTest)
    {
        if (outputSurfaceType == NvMediaSurfaceType_Unsupported) {
            LOG_ERR("Surface attributes specified not supported\n");
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
    }

    if(args.negProcessOSType)
    {
        // set unsupported surface type for output
        NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs, YUV, 420, SEMI_PLANAR, UINT, 16, BL);
        testSurfaceType = NvMediaSurfaceFormatGetType(surfFormatAttrs, 7);
    }
    for (i=0; i<args.imagePoolSize; i++)
    {
        NvMediaImage *mvsImage = NULL;
#ifndef NVMEDIA_NVSCI_ENABLE
        mvsImage = NvMediaImageCreateNew(device,
                                        outputSurfaceType,
                                        surfAllocAttrs,
                                        numAttrs,
                                        0);
        if (!mvsImage) {
            LOG_ERR("%s: NvMediaImageCreateNew mvsImage failed\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
        if(args.negProcessOSType)
        {
            testImage = NvMediaImageCreateNew(device,
                                        testSurfaceType,
                                        surfAllocAttrs,
                                        numAttrs,
                                        0);
            if (!testImage) {
                LOG_ERR("%s: NvMediaImageCreateNew testImage failed\n", __func__);
                encOutThreadArgs.status_main = -1;
                goto fail;
            }
        }
#else
        mvsImage = NvMediaImageCreateUsingNvScibuf(device, outputSurfaceType, surfAllocAttrs, numAttrs, 0);
        if (!args.negTest && !mvsImage) {   // not exiting for negative test here to improve integration code coverage
            LOG_ERR("%s: NvMediaImageCreateUsingNvScibuf failed\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
        if(args.negProcessOSType)
        {
          testImage = NvMediaImageCreateUsingNvScibuf(device, testSurfaceType, surfAllocAttrs, numAttrs, 0);
            if (!args.negTest && !testImage) {   // not exiting for negative test here to improve integration code coverage
                LOG_ERR("%s: NvMediaImageCreateUsingNvScibuf failed\n", __func__);
                encOutThreadArgs.status_main = -1;
                goto fail;
            }
        }
#endif
        if (mvsImage)   // this check is needed as negTest skips the goto_fail of mvsImage check.
            mvsImage->tag = mvsQueue;
        status = NvQueuePut(mvsQueue, (void *)&mvsImage, NV_TIMEOUT_INFINITE);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueuePut failed\n", __func__);
            goto fail;
        }
    }

    // Create NvMedia IOFST device
    LOG_DBG("%s: Creating IOFST device\n", __func__);
    LOG_DBG("%s: Setting IOFST initialization params\n", __func__);
    memset(&ofInitParams, 0, sizeof(NvMediaOFSTInitializeParams));
    ofInitParams.width = args.width;
    ofInitParams.height = args.height;
#if !NV_IS_SAFETY
    if (args.enableExtHints)
    {
        arrayAttr[0].type = NVM_ARRAY_ATTR_CPU_ACCESS;
        arrayAttr[0].value = NVM_ARRAY_ATTR_CPU_ACCESS_UNCACHED;
        if (args.enableSegmentMap)
        {
            hintsCapacity =  HINTS_HEADER_ELEMS + (mbWidth * mbHeight * (args.hintsPerMB + HINTS_SEGMENT_MAP_ELEMS_PER_MB)) + 64 /* Extra buffer */;
            hintsSize = HINTS_HEADER_ELEMS + (mbWidth * mbHeight * (args.hintsPerMB + HINTS_SEGMENT_MAP_ELEMS_PER_MB));
        }
        else
        {
            hintsCapacity =  HINTS_HEADER_ELEMS + (mbWidth * mbHeight * (args.hintsPerMB)) + 64 /* Extra buffer */;
            hintsSize = HINTS_HEADER_ELEMS + (mbWidth * mbHeight * (args.hintsPerMB));
        }
        hintsArray = NvMediaArrayCreate(device,
                                        NVMEDIA_ARRAY_TYPE_UINT32,
                                        sizeof(uint32_t),
                                        hintsCapacity,
                                        arrayAttr,
                                        ARRAY_NUM_ATTRS);
        if (!hintsArray) {
            LOG_ERR("%s: unable to create hints array.\n", __func__);
            encOutThreadArgs.status_main = -1;
            goto fail;
        }
    }
#endif

    if (args.enable_profiling)
    {
        ofInitParams.features |= NVMEDIA_OFST_CONFIG_ENABLE_PROFILING;
    }
    // In case of negetive testing this API will use actual input parameters not Reference frame params
    // In Normal flow there are no Reference frame params so this API will always use actual Input frame params
    testOF = NvMediaIOFSTCreate(args.negNVMEDIADevice ? NULL : device,
                                estimationType,             // motion estimation type
                                inputSurfaceType,           // input surface type
                                outputSurfaceType,          // output surface type
                                args.negInitParam? NULL: &ofInitParams,              // init params
                                args.negMaxBuffering ? 17 : args.imagePoolSize,         // maxInputBuffering
                                args.negCreateHWID ? 0 : (args.instanceId));
    if (!testOF) {
        LOG_ERR("%s: NvMediaIOFSTCreate failed\n", __func__);
        encOutThreadArgs.status_main = -1;
        goto fail;
    }

#if NVRM_SAFE_API
    // Pinning the surfaces with NvMediaIOFSTImageRegister API
    // input queue
    for (i=0; i < args.imagePoolSize+1; i++)
    {
        NvMediaImage *inputImage = NULL;

        status = NvQueueGet(inputQueue, (void *)&inputImage, 100);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueueGet failed\n", __func__);
            goto fail;
        }
        LOG_DBG("%s: Register image \n", __func__);
        if (args.negRegisterCheck)
        {
            status = NvMediaIOFSTImageRegister(NULL, inputImage, NVMEDIA_ACCESS_MODE_READ);
            NVM_TEST_RESULT( status != NVMEDIA_STATUS_OK, goto fail, "invalid_iofst_handle_register","15696600");

            status = NvMediaIOFSTImageRegister((const NvMediaIOFST *)testOF, NULL, NVMEDIA_ACCESS_MODE_READ);
            NVM_TEST_RESULT( status != NVMEDIA_STATUS_OK, goto fail, "invalid_image_handle_register","15696604");

            status = NvMediaIOFSTImageRegister((const NvMediaIOFST *)testOF, inputImage, 4);
            NVM_TEST_RESULT( status != NVMEDIA_STATUS_OK, goto fail, "invalid_access_mode_register","15696622");
        }
        if (NvMediaIOFSTImageRegister((const NvMediaIOFST *)testOF,
                                    inputImage, NVMEDIA_ACCESS_MODE_READ) != NVMEDIA_STATUS_OK)
        {
            if (!args.negIgnoreRegFailure) {
                LOG_ERR("%s: Register input image failed\n", __func__);
                /* Add image back to the queue so that it can be freed */
                if (inputImage != NULL)
                    NvQueuePut((NvQueue *)inputImage->tag, (void *)&inputImage, 100);

                encOutThreadArgs.status_main = -1;
                goto fail;
            } else {
                LOG_DBG("%s: Ignoring failure in NvMediaIOFSTImageRegister\n", __func__);
            }
        }
        NvQueuePut((NvQueue *)inputImage->tag, (void *)&inputImage, 100);
    }
    // mvs queue
    for (i=0; i < args.imagePoolSize; i++)
    {
        NvMediaImage *mvsImage = NULL;

        status = NvQueueGet(mvsQueue, (void *)&mvsImage, 100);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueueGet failed\n", __func__);
            goto fail;
        }
        LOG_DBG("%s: Register mvs image \n", __func__);
        if (NvMediaIOFSTImageRegister((const NvMediaIOFST *)testOF,
                                    mvsImage, NVMEDIA_ACCESS_MODE_READ_WRITE) != NVMEDIA_STATUS_OK)
        {
            if (!args.negIgnoreRegFailure) {
                LOG_ERR("%s: Register mvs image failed\n", __func__);
                /* Add image back to the queue so that it can be freed */
                if (mvsImage != NULL)
                    NvQueuePut((NvQueue *)mvsImage->tag, (void *)&mvsImage, 100);

                encOutThreadArgs.status_main = -1;
                goto fail;
            }
            else {
                LOG_DBG("%s: Ignoring failure in NvMediaIOFSTImageRegister\n", __func__);
            }
        }
        NvQueuePut((NvQueue *)mvsImage->tag, (void *)&mvsImage, 100);
    }
#endif

    encOutThreadArgs.encOutThreadExit = 0;
    encOutThreadArgs.encError = 0;
    encOutThreadArgs.framesNum = framesNum;
    encOutThreadArgs.mv_size = mv_size;
    encOutThreadArgs.inWidth = inWidth;
    encOutThreadArgs.inHeight = inHeight;
    encOutThreadArgs.mbWidth = mbWidth;
    encOutThreadArgs.mbHeight = mbHeight;
    encOutThreadArgs.outWidth = outWidth;
    encOutThreadArgs.outHeight = outHeight;
    encOutThreadArgs.args = &args;
    encOutThreadArgs.crcFile = crcFile;
    encOutThreadArgs.processFrameDoneSema = processFrameDoneSema;
    encOutThreadArgs.encOutThreadExitSema = encOutThreadExitSema;
    encOutThreadArgs.outBufAvailableSema = outBufAvailableSema;
    encOutThreadArgs.outputQueue = outputQueue;
    encOutThreadArgs.mvsQueue = mvsQueue;

    status = NvThreadCreate(&outputThread, encoderOutputThread,
                (void *)&encOutThreadArgs, NV_THREAD_PRIORITY_NORMAL);
    if (status != NVMEDIA_STATUS_OK) {
       LOG_ERR("%s: NvThreadCreate failed\n", __func__);
       goto fail;
    }

    // Read first frame
    if (args.widthRef == 0)
    {
        encOutThreadArgs.inWidth = inWidth = args.width;
        encOutThreadArgs.inHeight = inHeight = args.height;
    }
    else
    {
        encOutThreadArgs.inWidth = inWidth = args.widthRef;
        encOutThreadArgs.inHeight = inHeight = args.heightRef;
    }

    NvMediaImage *imageFrame = NULL;
    NvMediaImage *imageReference = NULL;

    status = NvQueueGet(inputQueue, (args.forward_ref ? (void *)&imageFrame : (void *)&imageReference), 100);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: NvQueueGet failed\n", __func__);
        goto fail;
    }
    LOG_DBG("%s: Reading image frame\n", __func__);
    status = ReadImage(args.inputFilename,
                       0,
                       args.forward_ref ? args.width : inWidth,
                       args.forward_ref ? args.height : inHeight,
                       args.forward_ref ? imageFrame : imageReference,
                       args.inputFormat == 0 ? 1 : 0,  //inputUVOrderFlag
                       1,                              //rawBytesPerPixel
                       LSB_ALIGNED);                   //pixelAlignment
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: ReadYUVFrame failed\n", __func__);
        encOutThreadArgs.status_main = -1;
        NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
        goto fail;
    }

    if (args.perfTest)
    {
        if (!prefetchData(&args, inputQueue, &encOutThreadArgs))
        {
            LOG_ERR("%s: Prefetching data for perf test failed\n", __func__);
            NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
            goto fail;
        }
    }

    // clock begins
    GetTimeMicroSec(&startTime);

    while (!done) {
        NvSemaphoreDecrement(outBufAvailableSema, NV_TIMEOUT_INFINITE);
        if (encOutThreadArgs.encOutThreadExit) {
            NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
            goto fail;
        }
        status = NvQueueGet(inputQueue, (args.forward_ref ? (void *)&imageReference : (void *)&imageFrame), 100);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueueGet failed\n", __func__);
            NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
            goto fail;
        }
        if (!args.perfTest)
        {
            LOG_DBG("%s: Reading image frame: %d\n", __func__, frameCounter);
            status = ReadImage(args.inputFilename,
                            frameCounter,
                            args.forward_ref ? inWidth : args.width,
                            args.forward_ref ? inHeight : args.height,
                            args.forward_ref ? imageReference : imageFrame,
                            args.inputFormat == 0 ? 1 : 0,  //inputUVOrderFlag
                            1,                              //rawBytesPerPixel
                            LSB_ALIGNED);                   //pixelAlignment
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: ReadImage failed\n", __func__);
                encOutThreadArgs.status_main = -1;
                NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
                NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
                goto fail;
            }
        }
#if !NV_IS_SAFETY
        if (args.enableExtHints)
        {
            char filename[128];
            // Setup ExtHint paraters
            memset(&ExtHintParams, 0, sizeof(NvMediaOFSTExternalHintParams));

            sprintf(filename, "%s_%05u.bin", args.extHintsFileBaseName, (frameCounter - 1));
            LOG_DBG("%s: Openning %s for hints\n", __func__, filename);
            extHintsFile = fopen(filename, "rb");
            if (!extHintsFile) {
                LOG_ERR("%s: Error opening '%s' for reading\n", __func__, filename);
                encOutThreadArgs.status_main = -1;
                NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
                NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
                goto fail;
            }

            fseek(extHintsFile, 0, SEEK_END);
            fileLength = ftell(extHintsFile);
            fseek(extHintsFile, SEEK_SET, 0);

            if (fileLength < (hintsSize * sizeof(uint32_t)))
            {
                LOG_ERR("%s: Insufficient hints fileLength = %d , required = %d\n", __func__, fileLength, (hintsSize * sizeof(uint32_t)));
                encOutThreadArgs.status_main = -1;
                NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
                NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
                goto fail;
            }
            else
            {
                /* write input data for hints. */
                status = NvMediaArrayLock(hintsArray, NVMEDIA_ARRAY_ACCESS_READ_WRITE, (void **)&arrayData);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("NvMediaArrayLock Failed\n");
                    encOutThreadArgs.status_main = -1;
                    NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
                    NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
                    goto fail;
                }
                status = NvMediaArraySetSize(hintsArray, hintsSize);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("NvMediaArraySetSize Failed\n");
                    encOutThreadArgs.status_main = -1;
                    NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
                    NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
                    goto fail;
                }
                fread(arrayData, (hintsSize * sizeof(uint32_t)), 1, extHintsFile);
                NvMediaArrayUnlock(hintsArray);

                if (args.enableSegmentMap)
                    ExtHintParams.enableSegmentMap = NVMEDIA_TRUE;
                ExtHintParams.meExternalHints = hintsArray;
            }
        }
#endif

        NvMediaImage *mvsImage = NULL;
        status = NvQueueGet(mvsQueue, (void *)&mvsImage, 100);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueueGet failed\n", __func__);
            NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
            NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
            goto fail;
        }

        LOG_DBG("%s: Start IOFST process frame #%d\n", __func__, frameCounter);

        if (args.negTest) {
            if (args.refIsNull) {
                NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
                imageReference = NULL;
            }
            if (args.ipIsNull) {
                NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
                imageFrame = NULL;
            }
            if (args.opIsNull) {
                NvQueuePut((NvQueue *)mvsImage->tag, (void *)&mvsImage, 100);
                mvsImage = NULL;
            }
        }
        if(args.negProcessHandle)
        {
            testOF=NULL;
        }
#if !NV_IS_SAFETY
        status = NvMediaIOFSTProcessFrame(testOF,
                                       imageFrame,      //current frame
                                       imageReference,  //reference frame
                                       args.negProcessOSType? testImage: mvsImage,
                                       args.enableExtHints ? &ExtHintParams : NULL,
                                       (args.instanceId));
#else
        status = NvMediaIOFSTProcessFrame(testOF,
                                       imageFrame,      //current frame
                                       imageReference,  //reference frame
                                       args.negProcessOSType? testImage: mvsImage,
                                       args.negExtHint ? &ExtHintParams : NULL,
                                       args.negProcessHWID ? 0 : (args.instanceId));
#endif
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvMediaIOFSTProcessFrame failed: %x\n", __func__, status);
            encOutThreadArgs.status_main = -1;
            if (imageFrame != NULL)
                NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
            if (imageReference != NULL)
                NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
            if (mvsImage != NULL)
                NvQueuePut((NvQueue *)mvsImage->tag, (void *)&mvsImage, 100);
            goto fail;
        }

        LOG_DBG("IOFST successfully submitted\n");

        if (frameCounter == framesNum || (args.numFrames && (frameCounter == args.numFrames))) {
            LOG_DBG("%s: No more frames\n", __func__);
            done = NVMEDIA_TRUE;
        }
        // put the images back in the queue
        if (args.forward_ref) {
            NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
            imageFrame = imageReference;
        }
        else {
            NvQueuePut((NvQueue *)imageReference->tag, (void *)&imageReference, 100);
            imageReference = imageFrame;
        }
        mvsImage->tag = outputQueue;
        status = NvQueuePut((NvQueue *)mvsImage->tag, (void *)&mvsImage, NV_TIMEOUT_INFINITE);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvQueuePut failed\n", __func__);
            goto fail;
        }
        //Signal the encoder output thread
        NvSemaphoreIncrement(processFrameDoneSema);

        // Next frame
        frameCounter++;
    }
    // at this point imageFrame and imageRefernce both are pointing to same image.
    // so not checking (forward_ref ? imageReference : imageFrame)
    NvQueuePut((NvQueue *)imageFrame->tag, (void *)&imageFrame, 100);
    NvSemaphoreIncrement(processFrameDoneSema);
    NvSemaphoreDecrement(encOutThreadExitSema, NV_TIMEOUT_INFINITE);

    GetTimeMicroSec(&endTime);
    elapseTime = (double)(endTime - startTime) / 1000.0;

    if (args.perfTest)
    {
        LOG_MSG("\nTotal Encoding time for %d frames: %.3f ms\n", frameCounter, elapseTime);
        LOG_MSG("Encoding time per frame: %.4f ms \n", elapseTime / frameCounter);
        LOG_MSG("Encode fps achieved: %.4f fps \n", 1000.0 * frameCounter / elapseTime);
        if (args.avgTimePerFrame < (elapseTime / frameCounter)) {
            LOG_ERR("%s: perf test failure\n", __func__);
            goto fail;
        }
    }
    else
    {
        if (args.crcoption.crcGenMode){
            LOG_MSG("\n***crc gold file %s has been generated***\n", args.crcoption.crcFilename);
        } else if (args.crcoption.crcCheckMode){
            LOG_MSG("\n***crc checking with file %s is successful\n", args.crcoption.crcFilename);
        }
    }

fail:
    if (encOutThreadArgs.status_main == -1) {
        encOutThreadArgs.encError = 1;
        NvSemaphoreIncrement(processFrameDoneSema);
    }

    if (inputQueue != NULL) {
        NvMediaImage *inputImage = NULL;
        while (NvQueueGet(inputQueue, &inputImage, 0) == NVMEDIA_STATUS_OK) {
#if NVRM_SAFE_API
            if ((NULL != testOF) && (args.negRegisterCheck))
            {
                status = NvMediaIOFSTImageUnRegister(NULL, inputImage);
                NVM_TEST_RESULT( status != NVMEDIA_STATUS_OK, goto fail2, "invalid_iofst_handle_unregister","15696628");

                status = NvMediaIOFSTImageUnRegister((const NvMediaIOFST *)testOF, NULL);
                NVM_TEST_RESULT( status != NVMEDIA_STATUS_OK, goto fail2, "invalid_image_handle_unregister","15696634");

                /* This is the last point in the register-unregister negative
                   test sequence - so, adding it here such that we see a failure
                   if there is some deviation in the flow before this point.
                   The following also ensures that the test passes in the case
                   of negative test sceario. */
                encOutThreadArgs.status_main = -1;

            }
fail2:
            if (NULL != testOF) {
                if (NvMediaIOFSTImageUnRegister((const NvMediaIOFST *)testOF, inputImage) != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: NvMediaIOFSTImageUnRegister image failed\n", __func__);
                }
            }
#endif
            if (inputImage != NULL) {
                NvMediaImageDestroy(inputImage);
            }
        }
        NvQueueDestroy(inputQueue);
    }
    if (mvsQueue != NULL) {
        NvMediaImage *mvsImage = NULL;
        while (NvQueueGet(mvsQueue, &mvsImage, 0) == NVMEDIA_STATUS_OK) {
#if NVRM_SAFE_API
            if (NULL != testOF) {
                if (NvMediaIOFSTImageUnRegister((const NvMediaIOFST *)testOF, mvsImage) != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: NvMediaIOFSTImageUnRegister mvs surface failed\n", __func__);
                }
            }
#endif
            if (mvsImage != NULL) {
                NvMediaImageDestroy(mvsImage);
            }
        }
        NvQueueDestroy(mvsQueue);
    }
    if (outputQueue != NULL) {
        NvMediaImage *outputImage = NULL;
        while (NvQueueGet(outputQueue, &outputImage, 0) == NVMEDIA_STATUS_OK) {
#if NVRM_SAFE_API
            if (NULL != testOF) {
                if (NvMediaIOFSTImageUnRegister((const NvMediaIOFST *)testOF, outputImage) != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: NvMediaIOFSTImageUnRegister output surface failed\n", __func__);
                }
            }
#endif
            if (outputImage != NULL) {
                NvMediaImageDestroy(outputImage);
            }
        }
        NvQueueDestroy(outputQueue);
    }

    if(args.negDestroy)
    {
        void *tempOfstPriv = testOF->ofstPriv;
        testOF->ofstPriv = NULL;
        NvMediaIOFSTDestroy(testOF);
        testOF->ofstPriv = tempOfstPriv;
        encOutThreadArgs.status_main = -1;
    }

    if (NULL != testOF) {
        LOG_DBG("%s: Destroying IOFST Device\n", __func__);
        NvMediaIOFSTDestroy(testOF);
    }

    if (processFrameDoneSema != NULL) {
        NvSemaphoreDestroy(processFrameDoneSema);
    }

    if (encOutThreadExitSema != NULL) {
        NvSemaphoreDestroy(encOutThreadExitSema);
    }

    if (outBufAvailableSema != NULL) {
        NvSemaphoreDestroy(outBufAvailableSema);
    }

    if (outputThread != NULL) {
        status = NvThreadDestroy(outputThread);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to destroy encoder output thread\n", __func__);
        }
    }

#if !NV_IS_SAFETY
    if (hintsArray) {
         LOG_DBG("%s: Destroying hints array\n", __func__);
         NvMediaArrayDestroy(hintsArray);
    }
#endif
#ifdef NVMEDIA_NVSCI_ENABLE
    NvMediaImageNvSciBufDeinit();
#endif
    if (device) {
         LOG_DBG("%s: Destroying device\n", __func__);
         NvMediaDeviceDestroy(device);
    }

    if (crcFile) {
        fclose(crcFile);
    }

    if (extHintsFile) {
        fclose(extHintsFile);
    }

    if (version)
    {
        free(version);
    }

    printf("total processed frames: %d \n", frameCounter);
    if (!args.negTest)
    {
        printf("total failures: %d \n", (encOutThreadArgs.status_main == -1) ? 1 : 0);
    }
    else
    {
        if (encOutThreadArgs.status_main == -1)  // Negetive tests are supposed to fail, So returning pass for same
        {
            printf("total failures: %d \n", 0);
            return 0;
        }
        else
        {
            printf("total failures: %d \n", 1);
            return encOutThreadArgs.status_main;
        }
    }
    return encOutThreadArgs.status_main;
}
