/*
 * Copyright (c) 2016-2020, NVIDIA CORPORATION.  All rights reserved. All
 * information contained herein is proprietary and confidential to NVIDIA
 * Corporation.  Any use, reproduction, or disclosure without the written
 * permission of NVIDIA Corporation is prohibited.
 */

#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include "cmdline.h"
#include "display_utils.h"
#include "log_utils.h"
#include "misc_utils.h"
#include "image_utils.h"
#include "buffer_utils.h"
#include "nvmedia_parser.h"
#include "nvmedia_image.h"
#include "nvmedia_imgdec.h"
#include "nvmedia_surface.h"
#include "nvmedia_idp.h"

#define MAX_DECODER_BUFFERS         17
#define MAX_DISPLAY_BUFFERS         4
#define MAX_FRAMES                  (MAX_DECODER_BUFFERS + MAX_DISPLAY_BUFFERS)
#define READ_SIZE                   (32 * 1024)
#define BUFFER_POOL_TIMEOUT         100

#define MAX_FILE_PATH_LENGTH 256
// For VP8 ivf file parsing
#define IVF_FILE_HDR_SIZE           32
#define IVF_FRAME_HDR_SIZE          12

typedef struct _ImagePlayTestCtx {
    NvMediaParser           *parser;
    NvMediaParserSeqInfo    nvsi;
    NvMediaParserParams     nvdp;
    NvMediaVideoCodec       eCodec;

    //  Stream params
    FILE                    *file;
    char                    *filename;
    char                    *OutputYUVFilename;
    int64_t                 fileSize;
    bool                    bRCVfile;

    // Decoder params
    int                      decodeWidth;
    int                      decodeHeight;
    int                      displayWidth;
    int                      displayHeight;
    NvMediaImageDecoder     *decoder;
    int                      decodeCount;
    bool                     stopDecoding;
    float                    totalDecodeTime;
    bool                     showDecodeTimimg;
    int                      numFramesToDecode;
    int                      loop;

    // Picture buffer params
    int                      nBuffers;
    int                      nPicNum;
    int                      sumCompressedLen;
    BufferPool              *RefImagePool;

    // Display params
    NvMediaDevice           *device;
    NvMediaIDP              *imageDisplay;

    NvMediaVideoOutput      *outputList[3];
    int                      lDispCounter;
    NvMediaTime              baseTime;
    double                   frameTimeUSec;
    bool                     videoFullRangeFlag;
    int                      colorPrimaries;
    bool                     displayEnabled;
    uint32_t                 displayId;
    unsigned int             windowId;
    bool                     positionSpecifiedFlag;
    NvMediaRect              position;
    unsigned int             depth;
    NvMediaDecoderInstanceId instanceId;
    NvMediaBool             bMonochrome;
    int                      FrameCount;

    // Crc params
    NvMediaBool             checkCRC;
    NvMediaBool             generateCRC;
    NvMediaBool             cropCRC;
    FILE                     *fpCrc;
    uint32_t                refCrc;
    char                    crcFilePath[MAX_FILE_PATH_LENGTH];
    NvMediaBool             CRCResult;
    NvMediaBool             YUVSaveComplete;
    NvMediaBool             CRCGenComplete;
    // Decoder profiling
    NvMediaBool             decProfiling;
} ImagePlayTestCtx;

static NvMediaBool signal_stop = 0;
static NvMediaBool display_fail = 0;

// call back functions
int32_t       cbBeginSequence(void *ptr, const NvMediaParserSeqInfo *pnvsi);
NvMediaStatus cbDecodePicture(void *ptr,  NvMediaParserPictureData *pd);
NvMediaStatus cbDisplayPicture(void *ptr, NvMediaRefSurface *p, int64_t llPTS);
void          cbUnhandledNALU(void *ptr, const uint8_t *buf, int32_t size);
NvMediaStatus cbAllocPictureBuffer(void *ptr, NvMediaRefSurface **p);
void          cbRelease(void *ptr, NvMediaRefSurface *p);
void          cbAddRef(void *ptr, NvMediaRefSurface *p);
NvMediaStatus cbGetBackwardUpdates(void *ptr, NvMediaVP9BackwardUpdates *backwardUpdate);

int     Init(ImagePlayTestCtx *ctx, TestArgs *testArgs);
void    Deinit(ImagePlayTestCtx *parser);
int     Decode(ImagePlayTestCtx *parser);

// display functions
int     DisplayInit(ImagePlayTestCtx *parser, int width, int height, int videoWidth, int videoHeight);
void    DisplayDestroy(ImagePlayTestCtx *parser);
void    DisplayFrame(ImagePlayTestCtx *parser, ImageBuffer *frame);
void    DisplayFlush(ImagePlayTestCtx *parser);

static void GetNvMediaPictureInfoH264(NvMediaPictureInfoH264 *pictureInfo)
{
    uint32_t i;

    for (i = 0; i < 16; i++)
    {
        NvMediaReferenceFrameH264* dpb_out = &pictureInfo->referenceFrames[i];
        ImageBuffer* picbuf = (ImageBuffer *)dpb_out->surface;
        dpb_out->surface = picbuf ? picbuf->image : NULL;
    }
}

static void GetNvMediaPictureInfoH265(NvMediaPictureInfoH265 *pictureInfo)
{
    uint32_t i;
    ImageBuffer* picbuf;

    for (i = 0; i < 16; i++)
    {
        picbuf = (ImageBuffer *)pictureInfo->RefPics[i];
        pictureInfo->RefPics[i] = picbuf ? picbuf->image : NULL;
    }
}

static void GetNvMediaPictureInfoVP9(NvMediaPictureInfoVP9 *pictureInfo, ImageBuffer *pCurrPic)
{
#if 0
    /// take care of resolution change later
    pCurrPic->width = pictureInfo->width;
    pCurrPic->height = pictureInfo->height;

    // First copy ref width/height parameters of pictureInfo then update pointers.
    pictureInfo->ref0_width = pictureInfo->LastReference ? (uint32_t)((ImageBuffer *)pictureInfo->LastReference)->width : pictureInfo->width;
    pictureInfo->ref0_height = pictureInfo->LastReference ? (uint32_t)((ImageBuffer *)pictureInfo->LastReference)->height : pictureInfo->height;
    pictureInfo->ref1_width = pictureInfo->GoldenReference ? (uint32_t)((ImageBuffer *)pictureInfo->GoldenReference)->width : pictureInfo->width;
    pictureInfo->ref1_height = pictureInfo->GoldenReference ? (uint32_t)((ImageBuffer *)pictureInfo->GoldenReference)->height : pictureInfo->height;
    pictureInfo->ref2_width = pictureInfo->AltReference ? (uint32_t)((ImageBuffer *)pictureInfo->AltReference)->width : pictureInfo->width;
    pictureInfo->ref2_height = pictureInfo->AltReference ? (uint32_t)((ImageBuffer *)pictureInfo->AltReference)->height : pictureInfo->height;
#else
    pictureInfo->ref0_width = pictureInfo->width;
    pictureInfo->ref0_height = pictureInfo->height;
    pictureInfo->ref1_width = pictureInfo->width;
    pictureInfo->ref1_height = pictureInfo->height;
    pictureInfo->ref2_width = pictureInfo->width;
    pictureInfo->ref2_height = pictureInfo->height;
#endif

    if (pictureInfo->LastReference)
        pictureInfo->LastReference = ((ImageBuffer *)pictureInfo->LastReference)->image;
    else
        pictureInfo->LastReference = pCurrPic->image;

    if (pictureInfo->GoldenReference)
        pictureInfo->GoldenReference = ((ImageBuffer *)pictureInfo->GoldenReference)->image;
    else
        pictureInfo->GoldenReference = pCurrPic->image;

    if (pictureInfo->AltReference)
        pictureInfo->AltReference = ((ImageBuffer *)pictureInfo->AltReference)->image;
    else
        pictureInfo->AltReference = pCurrPic->image;
}


int32_t cbBeginSequence(void *ptr, const NvMediaParserSeqInfo *pnvsi)
{
    ImagePlayTestCtx *ctx = (ImagePlayTestCtx*)ptr;
    bool surface12bit = false;
    unsigned int flags = 0;
    ImageBufferPoolConfigNew poolConfig;
    NvMediaStatus status;
    bool bDisplaySupported = false;

    char *chroma[] = {
        "Monochrome",
        "4:2:0",
        "4:2:2",
        "4:4:4"
    };
    uint32_t decodeBuffers = pnvsi->uDecodeBuffers;

    if (pnvsi->eCodec < 0) {
        LOG_ERR("BeginSequence: Invalid codec type: %d\n", pnvsi->eCodec);
        return 0;
    }

    if ((pnvsi->eCodec == NVMEDIA_VIDEO_CODEC_HEVC) ||
        (pnvsi->eCodec == NVMEDIA_VIDEO_CODEC_VP9)) {
        if ((pnvsi->uCodedWidth < 180) || (pnvsi->uCodedHeight < 180)) {
            LOG_ERR("BeginSequence: (Width=%d, Height=%d) < (180, 180) NOT SUPPORTED for %s\n",
                    pnvsi->uCodedWidth, pnvsi->uCodedHeight,
                    "H265");
                return -1;
            }
    }
    LOG_MSG("BeginSequence: %dx%d (disp: %dx%d) codec: %u decode buffers: %d aspect: %d:%d fps: %f chroma: %s\n",
        pnvsi->uCodedWidth, pnvsi->uCodedHeight, pnvsi->uDisplayWidth, pnvsi->uDisplayHeight,
        pnvsi->eCodec, pnvsi->uDecodeBuffers, pnvsi->uDARWidth, pnvsi->uDARHeight,
        pnvsi->fFrameRate, pnvsi->eChromaFormat > 3 ? "Invalid" : chroma[pnvsi->eChromaFormat]);

    if (!ctx->frameTimeUSec && pnvsi->fFrameRate >= 5.0 && pnvsi->fFrameRate <= 120.0) {
        ctx->frameTimeUSec = 1000000.0 / pnvsi->fFrameRate;
    }

    // Check resolution change
    if (pnvsi->uCodedWidth != ctx->decodeWidth || pnvsi->uCodedHeight != ctx->decodeHeight) {
        NvMediaImageCodec codec;
        NvMediaSurfaceType surfType;
        uint32_t maxReferences;
        char *env;

        LOG_INFO("BeginSequence: Resolution changed: Old:%dx%d New:%dx%d\n",
            ctx->decodeWidth, ctx->decodeHeight, pnvsi->uCodedWidth, pnvsi->uCodedHeight);

        ctx->decodeWidth = pnvsi->uCodedWidth;
        ctx->decodeHeight = pnvsi->uCodedHeight;

        ctx->displayWidth = pnvsi->uDisplayWidth;
        ctx->displayHeight = pnvsi->uDisplayHeight;

        ctx->videoFullRangeFlag = pnvsi->eVideoFullRangeFlag;
        ctx->colorPrimaries = pnvsi->eColorPrimaries;

        if (ctx->decoder) {
            // UnregisterPinnedSurfaces before Calling Destroy
            NvMediaImageDecoderUnRegisterPinnedSurfaces(ctx->decoder);
            NvMediaImageDecoderDestroy(ctx->decoder);
        }

        LOG_INFO("Create decoder: ");
        switch (pnvsi->eCodec) {
            case NVMEDIA_VIDEO_CODEC_H264:
                codec = NVMEDIA_IMAGE_CODEC_H264;
                LOG_INFO("NVMEDIA_VIDEO_CODEC_H264");
                if (pnvsi->uBitDepthLumaMinus8 || pnvsi->uBitDepthChromaMinus8) {
                    flags |= NVMEDIA_IMAGE_DECODER_10BIT_DECODE;
                    if (pnvsi->uBitDepthLumaMinus8 > 2 || pnvsi->uBitDepthChromaMinus8 > 2) {
                        surface12bit = true;
                    }
                }
                break;
            case NVMEDIA_VIDEO_CODEC_HEVC:
                codec = NVMEDIA_IMAGE_CODEC_HEVC;
                LOG_INFO("NVMEDIA_VIDEO_CODEC_HEVC");
                if (pnvsi->uBitDepthLumaMinus8 || pnvsi->uBitDepthChromaMinus8) {
                    flags |= NVMEDIA_IMAGE_DECODER_10BIT_DECODE;
                    if (pnvsi->uBitDepthLumaMinus8 > 2 || pnvsi->uBitDepthChromaMinus8 > 2) {
                        surface12bit = true;
                    }
                }

                if (pnvsi->eColorPrimaries == NvMColorPrimaries_BT2020)
                    flags |= NVMEDIA_IMAGE_DECODER_PIXEL_REC_2020;
                break;
            case NVMEDIA_VIDEO_CODEC_VP9:
                codec = NVMEDIA_IMAGE_CODEC_VP9;
                LOG_INFO("NVMEDIA_VIDEO_CODEC_VP9");
                if (pnvsi->uBitDepthLumaMinus8 || pnvsi->uBitDepthChromaMinus8) {
                    flags |= NVMEDIA_IMAGE_DECODER_10BIT_DECODE;
                    if (pnvsi->uBitDepthLumaMinus8 > 2 || pnvsi->uBitDepthChromaMinus8 > 2) {
                        surface12bit = true;
                    }
                }
                break;
            default:
                LOG_ERR("Invalid decoder type\n");
                return 0;
        }

        maxReferences = (decodeBuffers > 0) ? decodeBuffers - 1 : 0;
        maxReferences = (maxReferences > 16) ? 16 : maxReferences;

        if (pnvsi->bProgSeq) {
            flags |= NVMEDIA_IMAGE_DECODER_ATTRIBUTE_PROGRESSIVE_SEQUENCE;
        } else {
            LOG_ERR("Image Decoder cannot decode interlace bitstream\n");
            return 0;
        }

        LOG_DBG(" Size: %dx%d maxReferences: %d\n", ctx->decodeWidth, ctx->decodeHeight,
            maxReferences);
        if (ctx->decProfiling == NVMEDIA_TRUE) {
            flags |= NVMEDIA_IMAGE_DECODER_PROFILING;
        }
        ctx->decoder = NvMediaImageDecoderCreate(ctx->device, //device
                                                   codec, // codec
                                                   ctx->decodeWidth, // width
                                                   ctx->decodeHeight, // height
                                                   maxReferences, // maxReferences
                                                   pnvsi->uMaxBitstreamSize, //maxBitstreamSize
                                                   5, // inputBuffering
                                                   flags, // decoder flags
                                                   ctx->instanceId); // instance ID
        if (!ctx->decoder) {
            LOG_ERR("Unable to create decoder\n");
            return 0;
        }

        if (ctx->RefImagePool)
            BufferPool_Destroy(ctx->RefImagePool);

        ctx->bMonochrome = NVMEDIA_FALSE;

        NVM_SURF_FMT_DEFINE_ATTR(surfFormatAttrs);
        switch (pnvsi->eChromaFormat) {
            case 0: // Monochrome
                ctx->bMonochrome = NVMEDIA_TRUE;
            case 1: // 4:2:0
                if(flags & NVMEDIA_IMAGE_DECODER_10BIT_DECODE) {
                    if(surface12bit) {
                        LOG_INFO("BeginSequence: Chroma format: NvMediaSurfaceType YUV 420 12bit\n");
                        NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,420,SEMI_PLANAR,UINT,12,BL);
                    } else {
                        LOG_INFO("BeginSequence: Chroma format: NvMediaSurfaceType YUV 420 10bit\n");
                        NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,420,SEMI_PLANAR,UINT,10,BL);
                    }
                } else {
                    LOG_INFO("Chroma format: NvMediaSurfaceType YUV 420 8bit\n");
                    NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,420,SEMI_PLANAR,UINT,8,BL);
                    bDisplaySupported = true;
                }

                surfType = NvMediaSurfaceFormatGetType(surfFormatAttrs, NVM_SURF_FMT_ATTR_MAX);
                break;
            case 2: // 4:2:2
                if(flags & NVMEDIA_IMAGE_DECODER_10BIT_DECODE) {
                    if(surface12bit) {
                        LOG_INFO("BeginSequence: Chroma format: NvMediaSurfaceType YUV 422 12bit\n");
                        NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,422,SEMI_PLANAR,UINT,12,BL);
                    } else {
                        LOG_INFO("BeginSequence: Chroma format: NvMediaSurfaceType YUV 422 10bit\n");
                        NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,420,SEMI_PLANAR,UINT,10,BL);
                    }
                } else {
                    LOG_INFO("Chroma format: NvMediaSurfaceType YUV 422 8bit\n");
                    NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,422,SEMI_PLANAR,UINT,8,BL);
                    bDisplaySupported = true;
                }

                surfType = NvMediaSurfaceFormatGetType(surfFormatAttrs, NVM_SURF_FMT_ATTR_MAX);
                break;
            case 3: // 4:4:4
                if(flags & NVMEDIA_IMAGE_DECODER_10BIT_DECODE) {
                    if(surface12bit) {
                        LOG_INFO("BeginSequence: Chroma format: NvMediaSurfaceType YUV 444 12bit\n");
                        NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,444,SEMI_PLANAR,UINT,12,BL);
                    } else {
                        LOG_INFO("BeginSequence: Chroma format: NvMediaSurfaceType YUV 444 10bit\n");
                        NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,444,SEMI_PLANAR,UINT,10,BL);
                    }
                } else {
                    LOG_INFO("Chroma format: NvMediaSurfaceType YUV 444 8bit\n");
                    NVM_SURF_FMT_SET_ATTR_YUV(surfFormatAttrs,YUV,444,SEMI_PLANAR,UINT,8,BL);
                }

                surfType = NvMediaSurfaceFormatGetType(surfFormatAttrs, NVM_SURF_FMT_ATTR_MAX);
                break;
            default:
                LOG_INFO("Invalid chroma format: %d\n", pnvsi->eChromaFormat);
                return 0;
        }

        surfType = NvMediaSurfaceFormatGetType(surfFormatAttrs, NVM_SURF_FMT_ATTR_MAX);
        ctx->nBuffers = decodeBuffers + MAX_DISPLAY_BUFFERS;

        // Create Ref Image bufferpool
        memset(&poolConfig, 0, sizeof(ImageBufferPoolConfigNew));
        poolConfig.surfType = surfType;
        /* Image decoder only supported UNMAPPED and SEMI Planar image surface */
        poolConfig.device = ctx->device;
        poolConfig.surfAllocAttrs[0].type = NVM_SURF_ATTR_WIDTH;
        poolConfig.surfAllocAttrs[0].value = (pnvsi->uCodedWidth + 15) & ~15;
        poolConfig.surfAllocAttrs[1].type = NVM_SURF_ATTR_HEIGHT;
        poolConfig.surfAllocAttrs[1].value = (pnvsi->uCodedHeight + 15) & ~15;
        poolConfig.surfAllocAttrs[2].type = NVM_SURF_ATTR_CPU_ACCESS;
        poolConfig.surfAllocAttrs[2].value = NVM_SURF_ATTR_CPU_ACCESS_UNMAPPED;
        poolConfig.surfAllocAttrs[3].type = NVM_SURF_ATTR_ALLOC_TYPE;
        poolConfig.surfAllocAttrs[3].value = NVM_SURF_ATTR_ALLOC_ISOCHRONOUS;
        poolConfig.numSurfAllocAttrs = 4;
        if ((env = getenv("DISPLAY_VM"))) {
            poolConfig.surfAllocAttrs[4].type = NVM_SURF_ATTR_PEER_VM_ID;
            poolConfig.surfAllocAttrs[4].value = atoi(env);
            poolConfig.numSurfAllocAttrs += 1;
        }
        status = BufferPool_Create_New(&ctx->RefImagePool,        // Buffer pool
                                   ctx->nBuffers,             // Capacity
                                   BUFFER_POOL_TIMEOUT,       // Timeout
                                   IMAGE_BUFFER_POOL,         // Pool type
                                   &poolConfig);               // Config
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: BufferPool_Create_New failed\n", __func__);
            return 0;
        }

        if (ctx->displayEnabled) {
            DisplayDestroy(ctx);
            if(bDisplaySupported == false) {
                LOG_ERR("%s: UnSupported Surface Type to display:%d, "\
                             "Remove -d option\n", __func__, surfType);
                return 0;
            }
            if (false == DisplayInit(ctx, ctx->displayWidth, ctx->displayHeight,
                                        pnvsi->uCodedWidth, pnvsi->uCodedHeight)) {
                LOG_ERR("cbBeginSequence : Unable to create DisplayInit\n");
                return 0;
            }
            LOG_DBG("cbBeginSequence: DisplayInit done: ImageDisplay:%p\n", ctx->imageDisplay);
        }
    } else {
        LOG_INFO("cbBeginSequence: No resolution change\n");
    }

    return decodeBuffers;
}

NvMediaStatus cbDecodePicture(void *ptr, NvMediaParserPictureData *pd)
{
    ImagePlayTestCtx *ctx = (ImagePlayTestCtx*)ptr;
    NvMediaStatus status;
    ImageBuffer *targetBuffer = NULL;
    uint64_t timeEnd, timeStart = 0;
    NvMediaBitstreamBuffer bitStreamBuffer[1];

    if (pd->pCurrPic) {
        GetTimeMicroSec(&timeStart);
        targetBuffer = (ImageBuffer *)pd->pCurrPic;
        targetBuffer->imageID = ctx->nPicNum;
        switch (ctx->eCodec) {
            case NVMEDIA_VIDEO_CODEC_H264:
                GetNvMediaPictureInfoH264((NvMediaPictureInfoH264 *)&pd->CodecSpecificInfo.h264);
                break;
            case NVMEDIA_VIDEO_CODEC_HEVC:
                GetNvMediaPictureInfoH265((NvMediaPictureInfoH265 *)&pd->CodecSpecificInfo.hevc);
                break;
            case NVMEDIA_VIDEO_CODEC_VP9:
                GetNvMediaPictureInfoVP9((NvMediaPictureInfoVP9 *)&pd->CodecSpecificInfo.vp9, targetBuffer);
                break;
            default:
                LOG_ERR("cbDecodePicture: Invalid decoder type\n");
                return NVMEDIA_STATUS_ERROR;
        }


        bitStreamBuffer[0].bitstream = (uint8_t *)pd->pBitstreamData;
        bitStreamBuffer[0].bitstreamBytes = pd->uBitstreamDataLen;

        LOG_DBG("cbDecodePicture: %d Ptr: %p Surface: %p (stream ptr: %p size: %d)\n",
            ctx->nPicNum, targetBuffer, targetBuffer->image, pd->pBitstreamData, pd->uBitstreamDataLen);
        ctx->nPicNum++;

        if (targetBuffer->image) {
            status = NvMediaImageDecoderRender(ctx->decoder,                                 // decoder
                                               targetBuffer->image,                          // target
                                               (NvMediaPictureInfo *)&pd->CodecSpecificInfo, // pictureInfo
                                               1,                                            // numBitstreamBuffers
                                               &bitStreamBuffer[0],
                                               ctx->instanceId);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("cbDecodePicture: Decode failed: %d\n", status);
                return NVMEDIA_STATUS_ERROR;
            }
            LOG_DBG("cbDecodePicture: Frame decode done\n");

            if (ctx->showDecodeTimimg) {
                NvMediaImageSurfaceMap surfaceMap;
                // Wait for decode completion
                status = NvMediaImageLock(targetBuffer->image, NVMEDIA_IMAGE_ACCESS_WRITE, &surfaceMap);
                if(status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: NvMediaImageLock() failed\n", __func__);
                    return status;
                }
                NvMediaImageUnlock(targetBuffer->image);
            }
        } else {
            LOG_ERR("cbDecodePicture: Invalid target surface\n");
        }

        GetTimeMicroSec(&timeEnd);
        ctx->totalDecodeTime += (timeEnd - timeStart) / 1000.0;
        if (ctx->showDecodeTimimg) {
            LOG_DBG("cbDecodePicture: %03d %lld us\n", ctx->decodeCount, timeEnd - timeStart);
        }

        ctx->decodeCount++;
        if (ctx->numFramesToDecode && ctx->numFramesToDecode == ctx->decodeCount) {
            LOG_DBG("cbDecodePicture: Requested number of frames read (%d). Setting stop decoding flag to TRUE\n", ctx->numFramesToDecode);
            ctx->stopDecoding = true;
        }

        ctx->sumCompressedLen += pd->uBitstreamDataLen;
    } else {
        LOG_ERR("cbDecodePicture: No valid frame\n");
        return NVMEDIA_STATUS_ERROR;
    }

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus cbDisplayPicture(void *ptr, NvMediaRefSurface *p, int64_t llPTS)
{
    ImagePlayTestCtx *ctx = (ImagePlayTestCtx*)ptr;
    ImageBuffer* buffer = (ImageBuffer*)p;
    NvMediaStatus status;
    NvMediaBool CRCCheck = NVMEDIA_FALSE;
    uint32_t calculatedCrc = 0;
    uint32_t refCrc = 0;
    NvMediaRect srcRect;

    if (buffer) {
        LOG_DBG("Display: %d ctx:%p frame_buffer:%p Surface:%p\n", buffer->imageID, ctx, buffer, buffer->image);
            /* Display using overlay */
            if (ctx->displayEnabled) {
                DisplayFrame(ctx, buffer);
            }
            if (ctx->OutputYUVFilename) {
                srcRect.x0 = 0;
                srcRect.y0 = 0;
                srcRect.x1 = ctx->displayWidth;
                srcRect.y1 = ctx->displayHeight;

                {
                    status = WriteImage(ctx->OutputYUVFilename,
                                        buffer->image,
                                        NVMEDIA_TRUE,
                                        NVMEDIA_TRUE,
                                        1,
                                        &srcRect);
                    if (status != NVMEDIA_STATUS_OK) {
                        LOG_ERR("cbDecodePicture: Write image to file failed: %d\n", status);
                        ctx->YUVSaveComplete = NVMEDIA_FALSE;
                    }
                    LOG_DBG("cbDecodePicture: Saving YUV file %d ...\n", ctx->FrameCount);
                    ctx->YUVSaveComplete = NVMEDIA_TRUE;
                }
            }
            {
            if(ctx->generateCRC) {
                if(ctx->fpCrc) {
                    calculatedCrc = 0;
                    if(ctx->cropCRC) {
                        status = GetImageCrc(buffer->image,
                                            ctx->displayWidth,
                                            ctx->displayHeight,
                                            &calculatedCrc, 1);
                    } else {
                        status = GetImageCrc(buffer->image,
                                            ctx->decodeWidth,
                                            ctx->decodeHeight,
                                            &calculatedCrc, 1);
                        }

                        if(status != NVMEDIA_STATUS_OK) {
                            LOG_ERR("cbDisplayPicture: GetSurfaceCrc failed for frame %d\n",
                                     ctx->FrameCount);
                            ctx->CRCGenComplete = NVMEDIA_FALSE;
                        }
                        LOG_DBG("cbDisplayPicture: Saving CRC %8x for frame %d to file\n", calculatedCrc, ctx->FrameCount);
                        ctx->CRCGenComplete = NVMEDIA_TRUE;

                        if(!fprintf(ctx->fpCrc, "%08x\n",calculatedCrc)) {
                            LOG_ERR("cbDisplayPicture: Failed writing calculated CRC"
                                    " for frame %d to file\n", ctx->FrameCount);
                            ctx->CRCGenComplete = NVMEDIA_FALSE;
                        }
                    }

                }
                else if(ctx->checkCRC) {
                    if(ctx->fpCrc) {
                        if (fscanf(ctx->fpCrc, "%8x\n", &refCrc) == 1) {
                            LOG_DBG("cbDisplayPicture: Checking CRC.. Expected: %8x\n", refCrc);
                            if(ctx->cropCRC) {
                            status = CheckImageCrc( buffer->image,
                                                    ctx->displayWidth,
                                                    ctx->displayHeight,
                                                    refCrc,
                                                    &CRCCheck, 1);
                            } else {
                            status = CheckImageCrc( buffer->image,
                                                    ctx->decodeWidth,
                                                    ctx->decodeHeight,
                                                    refCrc,
                                                    &CRCCheck, 1);
                            }

                            if(status != NVMEDIA_STATUS_OK) {
                                LOG_ERR("cbDisplayPicture: CheckSurfaceCrc failed\n");
                            }

                            if(!CRCCheck) {
                                LOG_ERR("cbDisplayPicture: CRC check for frame %d : Fail\n",
                                            ctx->FrameCount);
                                ctx->CRCResult = NVMEDIA_FALSE;
                                } else {
                                LOG_INFO("cbDisplayPicture: CRC check for frame %d : Pass\n",
                                            ctx->FrameCount);
                                ctx->CRCResult = NVMEDIA_TRUE;
                            }
                        }
                    }
                else LOG_ERR("cbDisplayPicture: Invalid crc file\n");
                }
            }
            ctx->FrameCount++;
    } else {
        LOG_ERR("Display: Invalid buffer\n");
        return NVMEDIA_STATUS_ERROR;
    }

    return NVMEDIA_STATUS_OK;
}

void cbUnhandledNALU(void *ptr, const uint8_t *buf, int32_t size)
{
    // Empty
}

NvMediaStatus cbAllocPictureBuffer(void *ptr, NvMediaRefSurface **p)
{
    ImagePlayTestCtx *ctx = (ImagePlayTestCtx*)ptr;

    *p = (NvMediaRefSurface *) NULL;

    if (IsFailed(BufferPool_AcquireBuffer(ctx->RefImagePool,
                                     (void *)p))) {
        LOG_ERR("Alloc picture failed\n");
        return NVMEDIA_STATUS_ERROR;
    }

    LOG_DBG("Allocated buffer for picture Ptr:%p Surface:%p\n", *p, ((ImageBuffer *)(*p))->image);
    return NVMEDIA_STATUS_OK;
}

void cbRelease(void *ptr, NvMediaRefSurface *p)
{
    ImageBuffer* buffer = (ImageBuffer*)p;
    BufferPool_ReleaseBuffer(buffer->bufferPool, buffer);

    LOG_DBG("Releasing picture: %d \n", buffer->imageID);
}

void cbAddRef(void *ptr, NvMediaRefSurface *p)
{
    ImageBuffer* buffer = (ImageBuffer*)p;

    LOG_DBG("Adding reference to picture: %d\n", buffer->imageID);

    BufferPool_AddRefToBuffer(buffer);
}

NvMediaStatus cbGetBackwardUpdates(void *ptr, NvMediaVP9BackwardUpdates *backwardUpdate)
{
    NvMediaStatus status;
    ImagePlayTestCtx *ctx = (ImagePlayTestCtx*)ptr;

    if(!ctx) {
        LOG_ERR("cbGetBackwardUpdates: Invalid ImagePlayTestCtx\n");
        return NVMEDIA_STATUS_ERROR;
    }

    status = NvMediaImageDecoderGetBackwardUpdates(ctx->decoder, backwardUpdate);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("cbGetBackwardUpdates : Failed to get Video backward updates : %d\n", status);
    }

    return status;
}


NvMediaParserClientCb TestClientCb =
{
    &cbBeginSequence,
    &cbDecodePicture,
    &cbDisplayPicture,
    &cbUnhandledNALU,
    &cbAllocPictureBuffer,
    &cbRelease,
    &cbAddRef,
    NULL,
    NULL,
    NULL,
    NULL,
    &cbGetBackwardUpdates,
    NULL
};


int Init(ImagePlayTestCtx *ctx, TestArgs *testArgs)
{
    struct stat st;
    float defaultFrameRate = 30.0;

    ctx->frameTimeUSec          = testArgs->frameTimeUSec;
    ctx->loop                   = testArgs->loop;
    ctx->numFramesToDecode      = testArgs->numFramesToDecode;
    ctx->eCodec                 = testArgs->eCodec;
    ctx->OutputYUVFilename      = testArgs->OutputYUVFilename;
    ctx->filename               = testArgs->filename;
    ctx->windowId               = testArgs->windowId;
    ctx->displayEnabled         = testArgs->displayEnabled;
    ctx->displayId              = testArgs->displayId;
    ctx->depth                  = testArgs->depth;
    ctx->position               = testArgs->position;
    ctx->positionSpecifiedFlag  = testArgs->positionSpecifiedFlag;
    ctx->instanceId             = testArgs->instanceId;
    ctx->showDecodeTimimg       = testArgs->showDecodeTimimg;
    ctx->decProfiling           = testArgs->decProfiling;
    ctx->generateCRC            = testArgs->generateCRC;
    ctx->checkCRC               = testArgs->checkCRC;
    ctx->cropCRC                = testArgs->cropCRC;
    ctx->CRCResult              = NVMEDIA_FALSE;
    ctx->CRCGenComplete         = NVMEDIA_FALSE;
    ctx->YUVSaveComplete        = NVMEDIA_FALSE;

    LOG_MSG("Init: Opening file %s\n", testArgs->filename);
    ctx->file = fopen(ctx->filename, "rb");
    if (!ctx->file) {
        LOG_ERR("Init: Failed to open stream %s\n", testArgs->filename);
        return -1;
    }

    if((ctx->generateCRC) || (ctx->checkCRC))
    {
        ctx->fpCrc = fopen(testArgs->crcFilePath, ctx->generateCRC ? "wt" : "rb");
        if (ctx->fpCrc == NULL) {
            LOG_ERR("Init: Invalid CRC file %s specified!\n", testArgs->crcFilePath);
            return -1;
        }
        else
            if (ctx->checkCRC)
                LOG_INFO("Init: Opening CRC file %s for crc check\n", testArgs->crcFilePath);
            else
                LOG_INFO("Init: Opening CRC file %s for crc gen\n", testArgs->crcFilePath);
    }

    memset(&ctx->nvsi, 0, sizeof(ctx->nvsi));
    ctx->lDispCounter = 0;

    if (stat(ctx->filename, &st) == -1) {
        fclose(ctx->file);
        LOG_ERR("Init: cannot determine size of stream %s\n", ctx->filename);
        return -1;
    }
    ctx->fileSize = st.st_size;

    // create video parser
    memset(&ctx->nvdp, 0, sizeof(NvMediaParserParams));
    ctx->nvdp.pClient = &TestClientCb;
    ctx->nvdp.pClientCtx = ctx;
    ctx->nvdp.uErrorThreshold = 50;
    ctx->nvdp.uReferenceClockRate = 0;
    ctx->nvdp.eCodec = ctx->eCodec;

    LOG_DBG("Init: Creating parser\n");
    ctx->parser = NvMediaParserCreate(&ctx->nvdp);
    if (!ctx->parser) {
        LOG_ERR("Init: NvMediaParserCreate failed\n");
        return -1;
    }

    NvMediaParserSetAttribute(ctx->parser, NvMParseAttr_SetDefaultFramerate, sizeof(float), &defaultFrameRate);

    LOG_DBG("Init: Creating NvMediaDevice\n");
    ctx->device = NvMediaDeviceCreate();
    if (!ctx->device) {
        LOG_ERR("Init: NvMediaDeviceCreate Failed\n");
        return -1;
    }

    if (ctx->OutputYUVFilename) {
        LOG_MSG("Init: Opening Output YUV file %s\n", ctx->OutputYUVFilename);
        FILE *file = fopen(ctx->OutputYUVFilename, "w");
        if (!file) {
            LOG_ERR("Init: unable to open output file %s\n", ctx->OutputYUVFilename);
            return -1;
        }
        fclose(file);
    }

    return 0;
}

void Deinit(ImagePlayTestCtx *ctx)
{
    NvMediaParserDestroy(ctx->parser);
    DisplayFlush(ctx);

    if (ctx->file)
        fclose(ctx->file);

    if (ctx->decoder)
    {
        // UnregisterPinnedSurfaces before Calling Destroy
        NvMediaImageDecoderUnRegisterPinnedSurfaces(ctx->decoder);
        NvMediaImageDecoderDestroy(ctx->decoder);
    }

    DisplayDestroy(ctx);

    if (ctx->RefImagePool)
        BufferPool_Destroy(ctx->RefImagePool);

    if (ctx->device) {
        NvMediaDeviceDestroy(ctx->device);
        ctx->device = NULL;
    }
    if(ctx->fpCrc)
        fclose(ctx->fpCrc);
}

static int StreamVP9(ImagePlayTestCtx *ctx)
{
    int i;
    uint8_t *bits;
    uint32_t FrameSize;
    uint32_t numFrames;
    uint32_t frameRateNum;
    uint32_t frameRateDen;
    float frameRate;
    uint32_t frameCount;
    bool Vp9IvfFileHdrRead;
    uint32_t readSize = READ_SIZE;

    bits = malloc(readSize);
    if (!bits) {
        LOG_ERR("StreamVP9: Failed allocating memory for file buffer\n");
        return -1;
    }

    for(i = 0; (i < ctx->loop) || (ctx->loop == -1); i++) {
        Vp9IvfFileHdrRead = false;
        frameCount = 0;
        numFrames = 0;
        ctx->lDispCounter = 0;
        while(!feof(ctx->file) && !ctx->stopDecoding && !signal_stop) {
            size_t len;
            NvMediaBitStreamPkt packet;
            memset(&packet, 0, sizeof(NvMediaBitStreamPkt));

            if(Vp9IvfFileHdrRead == false) {
                if(fread(bits, 1, IVF_FILE_HDR_SIZE, ctx-> file) != IVF_FILE_HDR_SIZE) {
                    LOG_ERR("StreamVP9: Failed to read IVF file header\n");
                    free(bits);
                    return -1;
                }
                if(!((bits[0] == 'D') && (bits[1] == 'K') && (bits[2] == 'I') && (bits[3] == 'F'))) {
                    LOG_ERR("StreamVP9: It is not a valid IVF file \n");
                    free(bits);
                    return -1;
                }
                Vp9IvfFileHdrRead = true;
                LOG_DBG("StreamVP9: It is a valid IVF file \n");

                frameRateNum = u32(bits + 16);
                frameRateDen = u32(bits + 20);
                if(frameRateDen)
                    frameRate = (frameRateNum * 1.0)/ frameRateDen;
                else {
                    LOG_INFO("StreamVP9: Value of time scale in IVF heder is zero. Using default frame rate\n");
                    frameRate = 0;
                }
                if(frameRate)
                    NvMediaParserSetAttribute(ctx->parser, NvMParseAttr_SetFramerate, sizeof(float), &frameRate);

                numFrames = u32(bits + 24);
                if(!numFrames) {
                    LOG_INFO("StreamVP9: IVF file number of frames not specified \n");
                }

                LOG_DBG("StreamVP9:Frame Rate: %f \t Frame Count: %d \n",frameRate,numFrames);
            }

            if(fread(bits, 1, IVF_FRAME_HDR_SIZE, ctx->file) == IVF_FRAME_HDR_SIZE) {
                FrameSize = (bits[3]<<24) + (bits[2]<<16) + (bits[1]<<8) + bits[0];
                if(FrameSize > readSize) {
                    bits = realloc(bits, FrameSize);
                    readSize = FrameSize;
                }
                len = fread(bits, 1, FrameSize, ctx->file);
                packet.uDataLength = (uint32_t) len;
                packet.pByteStream = bits;
                frameCount++;
            } else {
                FrameSize = 0;
                packet.uDataLength = 0;
                packet.pByteStream = NULL;
            }

            packet.bEOS = feof(ctx->file) ? NVMEDIA_TRUE : NVMEDIA_FALSE;
            packet.bPTSValid = 0; // (pts != (uint32_t)-1);
            packet.llPts = 0; // packet.bPTSValid ? (1000 * pts / 9)  : 0;    // 100 ns scale
            if (NvMediaParserParse(ctx->parser, &packet) != NVMEDIA_STATUS_OK)
                return -1;
        }

        if(feof(ctx->file)) {
            if (numFrames && (frameCount != numFrames)) {
                LOG_ERR("StreamVP9: Actual (%d) and IVF header (%d) frame count does not match\n",
                    frameCount, numFrames);
                free(bits);
                return -1;
            }
        }
        if(ctx->stopDecoding) {
            if((ctx->loop == 1) && ((int)frameCount != ctx->numFramesToDecode)) {
                LOG_ERR("StreamVP9: Actual frame count (%d) and frames to be decoded count(%d) does not match\n",
                    frameCount, ctx->numFramesToDecode);
                free(bits);
                return -1;
            }
        }
        NvMediaParserFlush(ctx->parser);
        DisplayFlush(ctx);
        rewind(ctx->file);

        if(ctx->loop != 1 && !signal_stop) {
            if(ctx->stopDecoding) {
                ctx->stopDecoding = false;
                ctx->decodeCount = 0;
                ctx->totalDecodeTime = 0;
            }
            LOG_MSG("loop count: %d/%d \n", i+1, ctx->loop);
        } else
            break;
    }
    free(bits);
    if(display_fail)
        return 1;
    return 0;
}

static int Decode_orig (ImagePlayTestCtx *ctx)
{
    uint8_t *bits;
    int i;
    uint32_t readSize = READ_SIZE;

    bits = malloc(readSize);
    if (!bits) {
        LOG_ERR("Decode: Failed allocating memory for file buffer\n");
        return -1;
    }

    LOG_DBG("Decode: Starting %d loops of decode\n", ctx->loop);

    for (i = 0; (i < ctx->loop) || (ctx->loop == -1); i++) {
        LOG_DBG("Decode: loop %d out of %d\n", i, ctx->loop);
        {
            while (!feof(ctx->file) && !ctx->stopDecoding && (signal_stop == 0u)) {
                size_t len;
                NvMediaBitStreamPkt packet;
                memset(&packet, 0, sizeof(NvMediaBitStreamPkt));
                len = fread(bits, 1, readSize, ctx->file);
                packet.uDataLength = (uint32_t) len;
                packet.pByteStream = (const uint8_t *) bits;
                packet.bEOS = feof(ctx->file) ? NVMEDIA_TRUE : NVMEDIA_FALSE;
                packet.bPTSValid = 0;
                packet.llPts = 0;
                if (NvMediaParserParse(ctx->parser, &packet) != NVMEDIA_STATUS_OK) {
                    LOG_ERR("Decode: video_parser_parse returned with failure\n");
                    return -1;
                }
            }
            NvMediaParserFlush(ctx->parser);
            DisplayFlush(ctx);
        }

        LOG_DBG("Decode: Finished decoding. Flushing parser and display\n");
        rewind(ctx->file);

        if (ctx->loop != 1 && !signal_stop) {
            if (ctx->stopDecoding) {
                ctx->stopDecoding = false;
                ctx->decodeCount = 0;
                ctx->totalDecodeTime = 0;
            }
            LOG_MSG("loop count: %d/%d \n", i+1, ctx->loop);
        } else
            break;

    }

    free(bits);
    if(display_fail)
        return 1;
    return 0;
}

int Decode(ImagePlayTestCtx *ctx)
{

    if(ctx->eCodec == NVMEDIA_VIDEO_CODEC_VP9)
        return StreamVP9(ctx);
    else
        return Decode_orig(ctx);
}


int DisplayInit(ImagePlayTestCtx *ctx, int width, int height, int videoWidth, int videoHeight)
{
    NvMediaIDPDeviceParams outputs[MAX_OUTPUT_DEVICES];
    NvMediaDispAttributes attributes;
    int outputDevicesNum = 0;
    int i;
    NvMediaStatus status;

    ctx->FrameCount = 0;

    if (IsFailed(NvMediaIDPQuery(&outputDevicesNum, outputs))) {
       LOG_DBG("%s: Failed querying for available displays\n", __func__);
       return false;
    }

    for (i = 0; i < outputDevicesNum; i++) {
       if (outputs[i].displayId == ctx->displayId) {
          LOG_DBG("image_display_init: IDP create 0\n");
          ctx->imageDisplay = NvMediaIDPCreate(ctx->displayId,      // display ID
                                               ctx->windowId,       // window ID
                                               NULL,                 // display preferences
                                               outputs[i].enabled);  // already created flag
          break;
       }
    }

    if (!ctx->imageDisplay) {
        LOG_DBG("%s: Failed to create image display\n", __func__);
        return false;;
    }

    memset(&attributes, 0, sizeof(NvMediaDispAttributes));
    attributes.depth = ctx->depth;
    status = NvMediaIDPSetAttributes(ctx->imageDisplay,
                                     NVMEDIA_DISP_ATTR_SET_DEPTH,
                                     &attributes);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to set attribute for image display\n", __func__);
        return false;
    }

    LOG_DBG("DisplayInit: Created image display: %p\n", ctx->imageDisplay);
    return true;
}

void DisplayDestroy(ImagePlayTestCtx *ctx)
{
    if (ctx->imageDisplay) {
        NvMediaIDPDestroy(ctx->imageDisplay);
    }
}

static void ReleaseFrame(ImagePlayTestCtx *ctx, NvMediaImage *imageSurface)
{
    ImageBuffer *p = (ImageBuffer *)imageSurface->tag;;

    BufferPool_ReleaseBuffer(p->bufferPool, p);
}

void DisplayFlush(ImagePlayTestCtx *ctx)
{
    NvMediaImage *releaseFrames[9] = {0};
    NvMediaImage **releaseList = &releaseFrames[0];

    if (ctx->imageDisplay) {
        releaseList = &releaseFrames[0];
        NvMediaIDPFlip(ctx->imageDisplay,   // display
                       NULL,                    // image
                       NULL,                    // source rectangle
                       NULL,                    // destination rectangle,
                       releaseList,             // release list
                       NULL);                   // time stamp
        while (*releaseList) {
            ReleaseFrame(ctx, *releaseList++);
        }
    }
}

void DisplayFrame(ImagePlayTestCtx *ctx, ImageBuffer *frame)
{
    NvMediaStatus status;
    NvMediaTime timeStamp;
    NvMediaImage *releaseFrames[9] = {0};
    NvMediaImage **releaseList = &releaseFrames[0];
    NvMediaRect *dstRect = ctx->positionSpecifiedFlag ? &(ctx->position) : NULL;
    NvMediaRect primarySourceRect = { 0, 0, ctx->displayWidth, ctx->displayHeight };
    if (!frame || !frame->image) {
        LOG_ERR("DisplayFrame: Invalid surface\n");
        return;
    }

    // Start timing at the first frame
    if (!ctx->lDispCounter)
        GetTimeUtil(&ctx->baseTime);

    NvAddTime(&ctx->baseTime, (uint64_t)((double)(ctx->lDispCounter + 1) * ctx->frameTimeUSec), &timeStamp);

    cbAddRef((void *)ctx, (NvMediaRefSurface *)frame);

    status = NvMediaIDPFlip(ctx->imageDisplay,      // display
                            frame->image,    // image
                            &primarySourceRect,      // source rectangle
                            dstRect,                // destination rectangle,
                            releaseList,            // release list
                            &timeStamp);             // time stamp

    while (*releaseList) {
        ReleaseFrame(ctx, *releaseList++);
    }

    if (status != NVMEDIA_STATUS_OK) {
        ctx->stopDecoding = 1;
        display_fail = 1;
        goto failed;
    }

    ctx->lDispCounter++;

    return;

failed:
    LOG_ERR("%s: Failed to display image\n", __func__);
    return;
}

static NvMediaStatus
CheckVersion(void)
{
    NvMediaVersion version;
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    memset(&version, 0, sizeof(NvMediaVersion));
    status = NvMediaCoreGetVersion(&version);
    if (status != NVMEDIA_STATUS_OK)
        return status;

    if((version.major != NVMEDIA_CORE_VERSION_MAJOR) ||
       (version.minor != NVMEDIA_CORE_VERSION_MINOR)) {
        LOG_ERR("%s: Incompatible core version found \n", __func__);
        LOG_ERR("%s: Client version: %d.%d\n", __func__,
            NVMEDIA_CORE_VERSION_MAJOR, NVMEDIA_CORE_VERSION_MINOR);
        LOG_ERR("%s: Core version: %d.%d\n", __func__,
            version.major, version.minor);
        return NVMEDIA_STATUS_INCOMPATIBLE_VERSION;
    }

    memset(&version, 0, sizeof(NvMediaVersion));
    status = NvMediaImageGetVersion(&version);
    if (status != NVMEDIA_STATUS_OK)
        return status;

    if((version.major != NVMEDIA_IMAGE_VERSION_MAJOR) ||
       (version.minor != NVMEDIA_IMAGE_VERSION_MINOR)) {
        LOG_ERR("%s: Incompatible image version found \n", __func__);
        LOG_ERR("%s: Client version: %d.%d\n", __func__,
            NVMEDIA_IMAGE_VERSION_MAJOR, NVMEDIA_IMAGE_VERSION_MINOR);
        LOG_ERR("%s: Core version: %d.%d\n", __func__,
            version.major, version.minor);
        return NVMEDIA_STATUS_INCOMPATIBLE_VERSION;
    }

    memset(&version, 0, sizeof(NvMediaVersion));
    status = NvMediaImageDecoderGetVersion(&version);
    if (status != NVMEDIA_STATUS_OK)
        return status;

    if((version.major != NVMEDIA_IMAGEDEC_VERSION_MAJOR) ||
       (version.minor != NVMEDIA_IMAGEDEC_VERSION_MINOR)) {
        LOG_ERR("%s: Incompatible ImgDec version found \n", __func__);
        LOG_ERR("%s: Client version: %d.%d\n", __func__,
            NVMEDIA_IMAGEDEC_VERSION_MAJOR, NVMEDIA_IMAGEDEC_VERSION_MINOR);
        LOG_ERR("%s: Core version: %d.%d\n", __func__,
            version.major, version.minor);
        return NVMEDIA_STATUS_INCOMPATIBLE_VERSION;
    }

    return status;
}

static void sig_handler(int sig)
{
    LOG_INFO("sig_handler: Received Signal: %d\n", sig);
    signal_stop = 1;
}

int main(int argc, char *argv[])
{
    ImagePlayTestCtx ctx;
    TestArgs testArgs;
    int status;

    memset(&ctx, 0, sizeof(ctx));
    memset(&testArgs, 0, sizeof(testArgs));

    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);

    if (CheckVersion() != NVMEDIA_STATUS_OK) {
        return -1;
    }

    LOG_DBG("main: Parsing command line arguments\n");
    status = ParseArgs(argc, argv, &testArgs);
    if (status) {
        if (status != 1)
            PrintUsage();
        return -1;
    }

    LOG_DBG("main: Initializing test context\n");
    if (Init(&ctx, &testArgs)) {
        LOG_ERR("Init failed\n");
        return -1;
    }

    LOG_DBG("main: Starting decode process\n");
    if (Decode(&ctx)) {
        LOG_ERR("Decode failed\n");
        Deinit(&ctx);
        LOG_MSG("\ntotal failures: 1\n");
        return -1;
    }

    LOG_DBG("main: Deinitializing\n");
    Deinit(&ctx);

    if(ctx.showDecodeTimimg) {
        //get decoding time info
        LOG_MSG("\nTotal Decoding time for %d frames: %.3f ms\n", ctx.decodeCount, ctx.totalDecodeTime);
        LOG_MSG("Decoding time per frame %.4f ms \n", ctx.totalDecodeTime / ctx.decodeCount);
    }

    LOG_MSG("Total %d frames decoded\n", ctx.FrameCount);

    if ((ctx.generateCRC == NVMEDIA_TRUE) && (ctx.CRCGenComplete == NVMEDIA_TRUE))
    {
        LOG_MSG("CRC file is generated at %s\n", testArgs.crcFilePath);
    }

    if ((ctx.YUVSaveComplete == NVMEDIA_TRUE) && (ctx.OutputYUVFilename))
    {
        LOG_MSG("YUV file is saved at %s\n", ctx.OutputYUVFilename);
    }

    if ((ctx.checkCRC == NVMEDIA_TRUE))
    {
        if (ctx.CRCResult == NVMEDIA_TRUE)
        {
            LOG_MSG("\n*** Test Passed: DECODING PROCESS ENDED SUCCESSFULY***\n");
            LOG_MSG(" total failures: 0\n");
        }
        else
        {
            LOG_MSG("***** Test: Failed ***** \n");
            LOG_MSG(" total failures: 1\n");
        }
    }
    else
    {
        LOG_MSG("\ntotal failures: 0\n");
    }

    return 0;
}
