/* 
 * Copyright (C) 2014 Teluu Inc. (http://www.teluu.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */


/**
 * \page page_pjmedia_samples_vid_codec_test_c Samples: Video Codec Test
 *
 * Video codec encode and decode test.
 *
 * This file is pjsip-apps/src/samples/vid_codec_test.c
 *
 * \includelineno vid_codec_test.c
 */

#include <pjlib.h>
#include <pjlib-util.h>
#include <pjmedia.h>
#include <pjmedia-codec.h>


#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)


#include <stdlib.h>     /* atoi() */
#include <stdio.h>

#include "util.h"


#define THIS_FILE       "vid_vodec_test.c"


/* If set, local renderer will be created to play original file */
#define HAS_LOCAL_RENDERER_FOR_PLAY_FILE    1


/* Default width and height for the renderer, better be set to maximum
 * acceptable size.
 */
#define DEF_RENDERER_WIDTH                  640
#define DEF_RENDERER_HEIGHT                 480


/* Prototype for LIBSRTP utility in file datatypes.c */
int hex_string_to_octet_string(char *raw, char *hex, int len);

/* 
 * Register all codecs. 
 */
static pj_status_t init_codecs(pj_pool_factory *pf)
{
    pj_status_t status;

    /* To suppress warning about unused var when all codecs are disabled */
    PJ_UNUSED_ARG(status);
    PJ_UNUSED_ARG(pf);

#if defined(PJMEDIA_HAS_OPENH264_CODEC) && PJMEDIA_HAS_OPENH264_CODEC != 0
    status = pjmedia_codec_openh264_vid_init(NULL, pf);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
#endif

#if defined(PJMEDIA_HAS_VID_TOOLBOX_CODEC) && \
    PJMEDIA_HAS_VID_TOOLBOX_CODEC != 0
    status = pjmedia_codec_vid_toolbox_init(NULL, pf);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
#endif

#if defined(PJMEDIA_HAS_VPX_CODEC) && PJMEDIA_HAS_VPX_CODEC != 0
    status = pjmedia_codec_vpx_vid_init(NULL, pf);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
#endif

#if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && PJMEDIA_HAS_FFMPEG_VID_CODEC != 0
    status = pjmedia_codec_ffmpeg_vid_init(NULL, pf);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
#endif

    return PJ_SUCCESS;
}

/* 
 * Register all codecs. 
 */
static void deinit_codecs()
{
#if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && PJMEDIA_HAS_FFMPEG_VID_CODEC != 0
    pjmedia_codec_ffmpeg_vid_deinit();
#endif

#if defined(PJMEDIA_HAS_OPENH264_CODEC) && PJMEDIA_HAS_OPENH264_CODEC != 0
    pjmedia_codec_openh264_vid_deinit();
#endif

#if defined(PJMEDIA_HAS_VID_TOOLBOX_CODEC) && \
    PJMEDIA_HAS_VID_TOOLBOX_CODEC != 0
    pjmedia_codec_vid_toolbox_deinit();
#endif

#if defined(PJMEDIA_HAS_VPX_CODEC) && PJMEDIA_HAS_VPX_CODEC != 0
    pjmedia_codec_vpx_vid_deinit();
#endif

}


static void show_diff(const pj_uint8_t *buf1, const pj_uint8_t *buf2,
                      unsigned size)
{
    enum {
        STEP = 50
    };
    unsigned i=0;

    for (; i<size; ) {
        const pj_uint8_t *p1 = buf1 + i, *p2 = buf2 + i;
        unsigned j;

        printf("%8d ", i);
        for (j=0; j<STEP && i+j<size; ++j) {
            printf(" %02x", *(p1+j));
        }
        printf("\n");
        printf("         ");
        for (j=0; j<STEP && i+j<size; ++j) {
            if (*(p1+j) == *(p2+j)) {
                printf(" %02x", *(p2+j));
            } else {
                printf(" %02x", *(p2+j));
            }
        }
        printf("\n");

        i += j;
    }
}

static void diff_file()
{
    const char *filename[2] = {
        "/home/bennylp/Desktop/opt/src/openh264-svn/testbin/test.264",
        "/home/bennylp/Desktop/opt/src/openh264-svn/testbin/test2.264"
    };
    unsigned size[2];
    pj_uint8_t *buf[2], start_nal[3] = {0, 0, 1};
    unsigned i, pos[2], frame_cnt, mismatch_cnt=0;

    for (i=0; i<2; ++i) {
        FILE *fhnd;
        const pj_uint8_t start_nal[] = { 0, 0, 1};

        fhnd = fopen(filename[i], "rb");
        if (!fhnd) {
            printf("Error opening %s\n", filename[i]);
            return;
        }

        fseek(fhnd, 0, SEEK_END);
        size[i] = ftell(fhnd);
        fseek(fhnd, 0, SEEK_SET);

        buf[i] = (pj_uint8_t*)malloc(size[i] + 4);
        if (!buf[i]){
            fclose(fhnd);
            return;
        }

        if (fread (buf[i], 1, size[i], fhnd) != (unsigned)size[i]) {
            fprintf (stderr, "Unable to read whole file\n");
            fclose(fhnd);
            return;
        }

        memcpy (buf[i] + size[i], start_nal, sizeof(start_nal));

        fclose(fhnd);
    }

    if (size[0] != size[1]) {
        printf("File size mismatch\n");
        return;
    }

    pos[0] = pos[1] = 0;
    for ( frame_cnt=0; ; ++frame_cnt) {
        unsigned nal_len[2];
        for (i = 0; i < size[0]; i++) {
            if (memcmp(buf[0] + pos[0] + i, start_nal,
                       sizeof(start_nal)) == 0 && i > 0)
            {
                break;
            }
        }
        nal_len[0] = i;
        for (i = 0; i < size[1]; i++) {
            if (memcmp(buf[1] + pos[1] + i, start_nal,
                       sizeof(start_nal)) == 0 && i > 0)
            {
                break;
            }
        }
        nal_len[1] = i;

        if (nal_len[0] != nal_len[1]) {
            printf("Different size in frame %d (%d vs %d)\n",
                   frame_cnt, nal_len[0], nal_len[1]);
        }

        if (memcmp(buf[0]+pos[0], buf[1]+pos[1], nal_len[0]) != 0) {
            printf("Mismatch in frame %d\n", frame_cnt);
            show_diff(buf[0]+pos[0], buf[1]+pos[1], nal_len[0]);
            puts("");
            ++mismatch_cnt;
        }

        pos[0] += nal_len[0];
        pos[1] += nal_len[1];

        if (pos[0] >= size[0])
            break;
    }

    free(buf[0]);
    free(buf[1]);

    if (!mismatch_cnt)
        puts("Files the same!");
    else
        printf("%d mismatches\n", mismatch_cnt);
}

/*
 * main()
 */
int main(int argc, char *argv[])
{
    pj_caching_pool cp;
    pjmedia_endpt *med_endpt;
    pj_pool_t *pool;
    pj_status_t status;
    PJ_UNUSED_ARG(argc);
    PJ_UNUSED_ARG(argv);

    /* Codec */
    char *codec_id = (char*)"H264";
    const pjmedia_vid_codec_info *codec_info;
    pjmedia_vid_codec_param codec_param;
    pjmedia_vid_codec *codec = NULL;

    //const char *save_filename =
    //  "/home/bennylp/Desktop/opt/src/openh264-svn/testbin/test.264";
    const char *save_filename = NULL;

    /* File */
    enum
    {
        WIDTH = 320,
        HEIGHT = 192,
        FPS = 12,
        YUV_SIZE = WIDTH * HEIGHT * 3 >> 1,
        YUV_BUF_SIZE = YUV_SIZE + WIDTH,
        MAX_FRAMES = 32,
        MTU = 1500
    };
    FILE *fyuv = NULL;
    FILE *f264 = NULL;
    typedef pj_uint8_t enc_buf_type[MTU];
    pj_uint8_t yuv_frame[YUV_BUF_SIZE];
    enc_buf_type enc_buf[MAX_FRAMES];
    unsigned read_cnt = 0,
             pkt_cnt = 0,
             dec_cnt = 0,
             enc_cnt;

    if (0) {
        diff_file();
        return 1;
    }

    /* init PJLIB : */
    status = pj_init();
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    /* Must create a pool factory before we can allocate any memory. */
    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);

    /* Initialize media endpoint. */
    status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    /* Create memory pool for application purpose */
    pool = pj_pool_create( &cp.factory,     /* pool factory         */
                           "app",           /* pool name.           */
                           4000,            /* init size            */
                           4000,            /* increment size       */
                           NULL             /* callback on error    */
                           );

    /* Init video format manager */
    pjmedia_video_format_mgr_create(pool, 64, 0, NULL);

    /* Init video converter manager */
    pjmedia_converter_mgr_create(pool, NULL);

    /* Init event manager */
    pjmedia_event_mgr_create(pool, 0, NULL);

    /* Init video codec manager */
    pjmedia_vid_codec_mgr_create(pool, NULL);

    /* Register all supported codecs */
    status = init_codecs(&cp.factory);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    /* Open YUV file */
    fyuv = fopen("pjsip-apps/bin/CiscoVT2people_320x192_12fps.yuv", "rb");
    if (!fyuv) {
        puts("Unable to open ../CiscoVT2people_320x192_12fps.yuv");
        status = -1;
        goto on_exit;
    }

    /* Write 264 file if wanted */
    if (save_filename) {
        f264 = fopen(save_filename, "wb");
    }

    /* Find which codec to use. */
    if (codec_id) {
        unsigned count = 1;
        pj_str_t str_codec_id = pj_str(codec_id);

        status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL,
                                                         &str_codec_id, &count,
                                                         &codec_info, NULL);
        if (status != PJ_SUCCESS) {
            printf("Error: unable to find codec %s\n", codec_id);
            goto on_exit;
        }
    } else {
        static pjmedia_vid_codec_info info[1];
        unsigned count = PJ_ARRAY_SIZE(info);

        /* Default to first codec */
        pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, NULL);
        codec_info = &info[0];
    }

    /* Get codec default param for info */
    status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info, 
                                                     &codec_param);
    pj_assert(status == PJ_SUCCESS);
    
    /* Alloc encoder */
    status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info, &codec);
    if (status != PJ_SUCCESS) {
        PJ_PERROR(3,(THIS_FILE, status, "Error allocating codec"));
        goto on_exit;
    }

    codec_param.dir = PJMEDIA_DIR_ENCODING_DECODING;
    codec_param.packing = PJMEDIA_VID_PACKING_PACKETS;
    codec_param.enc_mtu = MTU;
    codec_param.enc_fmt.det.vid.size.w = WIDTH;
    codec_param.enc_fmt.det.vid.size.h = HEIGHT;
    codec_param.enc_fmt.det.vid.fps.num = FPS;
    codec_param.enc_fmt.det.vid.avg_bps = WIDTH * HEIGHT * FPS;

    status = pjmedia_vid_codec_init(codec, pool);
    if (status != PJ_SUCCESS) {
        PJ_PERROR(3,(THIS_FILE, status, "Error initializing codec"));
        goto on_exit;
    }

    status = pjmedia_vid_codec_open(codec, &codec_param);
    if (status != PJ_SUCCESS) {
        PJ_PERROR(3,(THIS_FILE, status, "Error opening codec"));
        goto on_exit;
    }

    while (fread(yuv_frame, 1, YUV_SIZE, fyuv) == YUV_SIZE) {
        pjmedia_frame frm_yuv, frm_enc[MAX_FRAMES];
        pj_bool_t has_more = PJ_FALSE;
        const pj_uint8_t start_nal[] = { 0, 0, 1 };

        ++ read_cnt;

        pj_bzero(&frm_enc, sizeof(frm_enc));
        pj_bzero(&frm_yuv, sizeof(frm_yuv));

        frm_yuv.buf = yuv_frame;
        frm_yuv.size = YUV_SIZE;

        enc_cnt = 0;
        frm_enc[enc_cnt].buf = enc_buf[enc_cnt];
        frm_enc[enc_cnt].size = MTU;

        status = pjmedia_vid_codec_encode_begin(codec, NULL, &frm_yuv,
                                                MTU, &frm_enc[enc_cnt],
                                                &has_more);
        if (status != PJ_SUCCESS) {
            PJ_PERROR(3,(THIS_FILE, status, "Codec encode error"));
            goto on_exit;
        }
        if (frm_enc[enc_cnt].size) {
            if (f264) {
                fwrite(start_nal, 1, sizeof(start_nal), f264);
                fwrite(frm_enc[enc_cnt].buf, 1, frm_enc[enc_cnt].size, f264);
            }
            ++pkt_cnt;
            ++enc_cnt;
        }

        while (has_more) {

            if (enc_cnt >= MAX_FRAMES) {
                status = -1;
                puts("Error: too many encoded frames");
                goto on_exit;
            }

            has_more = PJ_FALSE;
            frm_enc[enc_cnt].buf = enc_buf[enc_cnt];
            frm_enc[enc_cnt].size = MTU;

            status = pjmedia_vid_codec_encode_more(codec, MTU,
                                                   &frm_enc[enc_cnt],
                                                   &has_more);
            if (status != PJ_SUCCESS) {
                PJ_PERROR(3,(THIS_FILE, status, "Codec encode error"));
                goto on_exit;
            }

            if (frm_enc[enc_cnt].size) {
                if (f264) {
                    fwrite(start_nal, 1, sizeof(start_nal), f264);
                    fwrite(frm_enc[enc_cnt].buf, 1, frm_enc[enc_cnt].size,
                           f264);
                }
                ++pkt_cnt;
                ++enc_cnt;
            }
        }

        if (enc_cnt) {
            frm_yuv.buf = yuv_frame;
            frm_yuv.size = YUV_BUF_SIZE;
            status = pjmedia_vid_codec_decode(codec, enc_cnt,
                                              frm_enc,
                                              YUV_BUF_SIZE,
                                              &frm_yuv);
            if (status != PJ_SUCCESS) {
                PJ_PERROR(3,(THIS_FILE, status, "Codec decode error"));
                goto on_exit;
            }

            if (frm_yuv.size != 0) {
                ++dec_cnt;
            }
        }
    }

    printf("Done.\n"
           " Read YUV frames:    %d\n"
           " Encoded packets:    %d\n"
           " Decoded YUV frames: %d\n",
           read_cnt, pkt_cnt, dec_cnt);

    /* Start deinitialization: */
on_exit:
    if (codec) {
        pjmedia_vid_codec_close(codec);
        pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec);
    }

    if (f264)
        fclose(f264);

    if (fyuv)
        fclose(fyuv);

    /* Deinit codecs */
    deinit_codecs();

    /* Destroy event manager */
    pjmedia_event_mgr_destroy(NULL);

    /* Release application pool */
    pj_pool_release( pool );

    /* Destroy media endpoint. */
    pjmedia_endpt_destroy( med_endpt );

    /* Destroy pool factory */
    pj_caching_pool_destroy( &cp );

    /* Shutdown PJLIB */
    pj_shutdown();

    /* Avoid compile warning */
    PJ_UNUSED_ARG(app_perror);

    return (status == PJ_SUCCESS) ? 0 : 1;
}


#else

int main(int argc, char *argv[])
{
    PJ_UNUSED_ARG(argc);
    PJ_UNUSED_ARG(argv);
    puts("Error: this sample requires video capability "
            "(PJMEDIA_HAS_VIDEO == 1)");
    return -1;
}

#endif /* PJMEDIA_HAS_VIDEO */
