/* 
 * Copyright (C) 2011 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_streamutil_c Samples: Video Streaming
 *
 * This example mainly demonstrates how to stream video to remote
 * peer using RTP.
 *
 * This file is pjsip-apps/src/samples/vid_streamutil.c
 *
 * \includelineno vid_streamutil.c
 */

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


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


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

#include "util.h"


static const char *desc = 
 " vid_streamutil                                                       \n"
 "\n"
 " PURPOSE:                                                             \n"
 "  Demonstrate how to use pjmedia video stream component to            \n"
 "  transmit/receive RTP packets to/from video device/file.             \n"
 "\n"
 "\n"
 " USAGE:                                                               \n"
 "  vid_streamutil [options]                                            \n"
 "\n"
 "\n"
 " Options:                                                             \n"
 "  --codec=CODEC         Set the codec name.                           \n"
 "  --local-port=PORT     Set local RTP port (default=4000)             \n"
 "  --remote=IP:PORT      Set the remote peer. If this option is set,   \n"
 "                        the program will transmit RTP audio to the    \n"
 "                        specified address. (default: recv only)       \n"
 "  --play-file=AVI       Send video from the AVI file instead of from  \n"
 "                        the video device.                             \n"
 "  --send-recv           Set stream direction to bidirectional.        \n"
 "  --send-only           Set stream direction to send only             \n"
 "  --recv-only           Set stream direction to recv only (default)   \n"
 
 "  --send-width          Video width to be sent                        \n"
 "  --send-height         Video height to be sent                       \n"
 "                        --send-width and --send-height not applicable \n"
 "                        for file streaming (see --play-file)          \n"

 "  --send-pt             Payload type for sending                      \n"
 "  --recv-pt             Payload type for receiving                    \n"

#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
 "  --use-srtp[=NAME]     Enable SRTP with crypto suite NAME            \n"
 "                        e.g: AES_CM_128_HMAC_SHA1_80 (default),       \n"
 "                             AES_CM_128_HMAC_SHA1_32                  \n"
 "                        Use this option along with the TX & RX keys,  \n"
 "                        formated of 60 hex digits (e.g: E148DA..)     \n"
 "  --srtp-tx-key         SRTP key for transmiting                      \n"
 "  --srtp-rx-key         SRTP key for receiving                        \n"
#endif

 "\n"
;

#define THIS_FILE       "vid_streamutil.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


/* Hexa string to octet array */
int my_hex_string_to_octet_string(char *raw, char *hex, int len)
{
    int i;
    for (i = 0; i < len; i+=2) {
        int tmp;
        if (i+1 >= len || !pj_isxdigit(hex[i]) || !pj_isxdigit(hex[i+1]))
            return i;
        tmp  = pj_hex_digit_to_val((unsigned char)hex[i]) << 4;
        tmp |= pj_hex_digit_to_val((unsigned char)hex[i+1]);
        raw[i/2] = (char)(tmp & 0xFF);
    }
    return 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 pj_status_t create_file_player( pj_pool_t *pool,
                                       const char *file_name,
                                       pjmedia_port **p_play_port)
{
    pjmedia_avi_streams *avi_streams;
    pjmedia_avi_stream *vid_stream;
    pjmedia_port *play_port;
    pj_status_t status;

    status = pjmedia_avi_player_create_streams(pool, file_name, 0, &avi_streams);
    if (status != PJ_SUCCESS)
        return status;

    vid_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams,
                                                         0,
                                                         PJMEDIA_TYPE_VIDEO);
    if (!vid_stream)
        return PJ_ENOTFOUND;

    play_port = pjmedia_avi_stream_get_port(vid_stream);
    pj_assert(play_port);

    *p_play_port = play_port;

    return PJ_SUCCESS;
}

/* 
 * Create stream based on the codec, dir, remote address, etc. 
 */
static pj_status_t create_stream( pj_pool_t *pool,
                                  pjmedia_endpt *med_endpt,
                                  const pjmedia_vid_codec_info *codec_info,
                                  pjmedia_vid_codec_param *codec_param,
                                  pjmedia_dir dir,
                                  pj_int8_t rx_pt,
                                  pj_int8_t tx_pt,
                                  pj_uint16_t local_port,
                                  const pj_sockaddr_in *rem_addr,
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
                                  pj_bool_t use_srtp,
                                  const pj_str_t *crypto_suite,
                                  const pj_str_t *srtp_tx_key,
                                  const pj_str_t *srtp_rx_key,
#endif
                                  pjmedia_vid_stream **p_stream )
{
    pjmedia_vid_stream_info info;
    pjmedia_transport *transport = NULL;
    pj_status_t status;
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
    pjmedia_transport *srtp_tp = NULL;
#endif

    /* Reset stream info. */
    pj_bzero(&info, sizeof(info));

    /* Initialize stream info formats */
    info.type = PJMEDIA_TYPE_VIDEO;
    info.dir = dir;
    info.codec_info = *codec_info;
    info.tx_pt = (tx_pt == -1)? (pj_uint8_t)codec_info->pt : tx_pt;
    info.rx_pt = (rx_pt == -1)? (pj_uint8_t)codec_info->pt : rx_pt;
    info.ssrc = pj_rand();
    if (codec_param)
        info.codec_param = codec_param;
    
    /* Copy remote address */
    pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));

    /* If remote address is not set, set to an arbitrary address
     * (otherwise stream will assert).
     */
    if (info.rem_addr.addr.sa_family == 0) {
        const pj_str_t addr = pj_str("127.0.0.1");
        pj_sockaddr_in_init(&info.rem_addr.ipv4, &addr, 0);
    }

    /* Create media transport */
    status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,
                                          0, &transport);
    if (status != PJ_SUCCESS)
        return status;

#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
    /* Check if SRTP enabled */
    if (use_srtp) {
        pjmedia_srtp_crypto tx_plc, rx_plc;

        status = pjmedia_transport_srtp_create(med_endpt, transport, 
                                               NULL, &srtp_tp);
        if (status != PJ_SUCCESS)
            return status;

        pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
        pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));

        tx_plc.key = *srtp_tx_key;
        tx_plc.name = *crypto_suite;
        rx_plc.key = *srtp_rx_key;
        rx_plc.name = *crypto_suite;
        
        status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
        if (status != PJ_SUCCESS)
            return status;

        transport = srtp_tp;
    }
#endif

    /* Now that the stream info is initialized, we can create the 
     * stream.
     */

    status = pjmedia_vid_stream_create( med_endpt, pool, &info, 
                                        transport, 
                                        NULL, p_stream);

    if (status != PJ_SUCCESS) {
        app_perror(THIS_FILE, "Error creating stream", status);
        pjmedia_transport_close(transport);
        return status;
    }

    /* Start media transport */
    pjmedia_transport_media_start(transport, 0, 0, 0, 0);

    return PJ_SUCCESS;
}


typedef struct play_file_data
{
    const char *file_name;
    pjmedia_port *play_port;
    pjmedia_port *stream_port;
    pjmedia_vid_codec *decoder;
    pjmedia_port *renderer;
    void *read_buf;
    pj_size_t read_buf_size;
    void *dec_buf;
    pj_size_t dec_buf_size;
} play_file_data;


static void clock_cb(const pj_timestamp *ts, void *user_data)
{
    play_file_data *play_file = (play_file_data*)user_data;
    pjmedia_frame read_frame, write_frame;
    pj_status_t status;

    PJ_UNUSED_ARG(ts);

    /* Read frame from file */
    read_frame.buf = play_file->read_buf;
    read_frame.size = play_file->read_buf_size;
    pjmedia_port_get_frame(play_file->play_port, &read_frame);

    /* Decode frame, if needed */
    if (play_file->decoder) {
        pjmedia_vid_codec *decoder = play_file->decoder;

        write_frame.buf = play_file->dec_buf;
        write_frame.size = play_file->dec_buf_size;
        status = pjmedia_vid_codec_decode(decoder, 1, &read_frame,
                                          (unsigned)write_frame.size, 
                                          &write_frame);
        if (status != PJ_SUCCESS || write_frame.size == 0)
            return;
    } else {
        write_frame = read_frame;
    }

    /* Display frame locally */
    if (play_file->renderer)
        pjmedia_port_put_frame(play_file->renderer, &write_frame);

    /* Send frame */
    pjmedia_port_put_frame(play_file->stream_port, &write_frame);
}


/*
 * usage()
 */
static void usage()
{
    puts(desc);
}

/*
 * main()
 */
static int main_func(int argc, char *argv[])
{
    pj_caching_pool cp;
    pjmedia_endpt *med_endpt;
    pj_pool_t *pool;
    pjmedia_vid_stream *stream = NULL;
    pjmedia_port *enc_port, *dec_port;
    char addr[PJ_INET_ADDRSTRLEN];
    pj_status_t status; 

    pjmedia_vid_port *capture=NULL, *renderer=NULL;
    pjmedia_vid_port_param vpp;

#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
    /* SRTP variables */
    pj_bool_t use_srtp = PJ_FALSE;
    char tmp_tx_key[64];
    char tmp_rx_key[64];
    pj_str_t  srtp_tx_key = {NULL, 0};
    pj_str_t  srtp_rx_key = {NULL, 0};
    pj_str_t  srtp_crypto_suite = {NULL, 0};
    int tmp_key_len;
#endif

    /* Default values */
    const pjmedia_vid_codec_info *codec_info;
    pjmedia_vid_codec_param codec_param;
    pjmedia_dir dir = PJMEDIA_DIR_DECODING;
    pj_sockaddr_in remote_addr;
    pj_uint16_t local_port = 4000;
    char *codec_id = NULL;
    pjmedia_rect_size tx_size = {0};
    pj_int8_t rx_pt = -1, tx_pt = -1;

    play_file_data play_file = { NULL };
    pjmedia_port *play_port = NULL;
    pjmedia_vid_codec *play_decoder = NULL;
    pjmedia_clock *play_clock = NULL;

    enum {
        OPT_CODEC       = 'c',
        OPT_LOCAL_PORT  = 'p',
        OPT_REMOTE      = 'r',
        OPT_PLAY_FILE   = 'f',
        OPT_SEND_RECV   = 'b',
        OPT_SEND_ONLY   = 's',
        OPT_RECV_ONLY   = 'i',
        OPT_SEND_WIDTH  = 'W',
        OPT_SEND_HEIGHT = 'H',
        OPT_RECV_PT     = 't',
        OPT_SEND_PT     = 'T',
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
        OPT_USE_SRTP    = 'S',
#endif
        OPT_SRTP_TX_KEY = 'x',
        OPT_SRTP_RX_KEY = 'y',
        OPT_HELP        = 'h',
    };

    struct pj_getopt_option long_options[] = {
        { "codec",          1, 0, OPT_CODEC },
        { "local-port",     1, 0, OPT_LOCAL_PORT },
        { "remote",         1, 0, OPT_REMOTE },
        { "play-file",      1, 0, OPT_PLAY_FILE },
        { "send-recv",      0, 0, OPT_SEND_RECV },
        { "send-only",      0, 0, OPT_SEND_ONLY },
        { "recv-only",      0, 0, OPT_RECV_ONLY },
        { "send-width",     1, 0, OPT_SEND_WIDTH },
        { "send-height",    1, 0, OPT_SEND_HEIGHT },
        { "recv-pt",        1, 0, OPT_RECV_PT },
        { "send-pt",        1, 0, OPT_SEND_PT },
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
        { "use-srtp",       2, 0, OPT_USE_SRTP },
        { "srtp-tx-key",    1, 0, OPT_SRTP_TX_KEY },
        { "srtp-rx-key",    1, 0, OPT_SRTP_RX_KEY },
#endif
        { "help",           0, 0, OPT_HELP },
        { NULL, 0, 0, 0 },
    };

    int c;
    int option_index;


    pj_bzero(&remote_addr, sizeof(remote_addr));


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


    /* Parse arguments */
    pj_optind = 0;
    while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1)
    {
        switch (c) {
        case OPT_CODEC:
            codec_id = pj_optarg;
            break;

        case OPT_LOCAL_PORT:
            local_port = (pj_uint16_t) atoi(pj_optarg);
            if (local_port < 1) {
                printf("Error: invalid local port %s\n", pj_optarg);
                return 1;
            }
            break;

        case OPT_REMOTE:
            {
                pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
                pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));

                status = pj_sockaddr_in_init(&remote_addr, &ip, port);
                if (status != PJ_SUCCESS) {
                    app_perror(THIS_FILE, "Invalid remote address", status);
                    return 1;
                }
            }
            break;

        case OPT_PLAY_FILE:
            play_file.file_name = pj_optarg;
            break;

        case OPT_SEND_RECV:
            dir = PJMEDIA_DIR_ENCODING_DECODING;
            break;

        case OPT_SEND_ONLY:
            dir = PJMEDIA_DIR_ENCODING;
            break;

        case OPT_RECV_ONLY:
            dir = PJMEDIA_DIR_DECODING;
            break;

        case OPT_SEND_WIDTH:
            tx_size.w = (unsigned)atoi(pj_optarg);
            break;

        case OPT_SEND_HEIGHT:
            tx_size.h = (unsigned)atoi(pj_optarg);
            break;

        case OPT_RECV_PT:
            rx_pt = (pj_int8_t)atoi(pj_optarg);
            break;

        case OPT_SEND_PT:
            tx_pt = (pj_int8_t)atoi(pj_optarg);
            break;

#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
        case OPT_USE_SRTP:
            use_srtp = PJ_TRUE;
            if (pj_optarg) {
                pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg));
            } else {
                srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
            }
            break;

        case OPT_SRTP_TX_KEY:
            tmp_key_len = my_hex_string_to_octet_string(tmp_tx_key, pj_optarg, 
                                                        (int)strlen(pj_optarg));
            pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);
            break;

        case OPT_SRTP_RX_KEY:
            tmp_key_len = my_hex_string_to_octet_string(tmp_rx_key, pj_optarg,
                                                        (int)strlen(pj_optarg));
            pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);
            break;
#endif

        case OPT_HELP:
            usage();
            return 1;

        default:
            printf("Invalid options %s\n", argv[pj_optind]);
            return 1;
        }

    }


    /* Verify arguments. */
    if (dir & PJMEDIA_DIR_ENCODING) {
        if (remote_addr.sin_addr.s_addr == 0) {
            printf("Error: remote address must be set\n");
            return 1;
        }
    }

    if (play_file.file_name != NULL && dir != PJMEDIA_DIR_ENCODING) {
        printf("Direction is set to --send-only because of --play-file\n");
        dir = PJMEDIA_DIR_ENCODING;
    }

#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
    /* SRTP validation */
    if (use_srtp) {
        if (!srtp_tx_key.slen || !srtp_rx_key.slen)
        {
            printf("Error: Key for each SRTP stream direction must be set\n");
            return 1;
        }
    }
#endif

    /* 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.
     * This will implicitly initialize PJMEDIA too.
     */
    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);

    /* Init video subsystem */
    pjmedia_vid_dev_subsys_init(&cp.factory);

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


    /* 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);
            return 1;
        }
    } 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);
    
    /* Set outgoing video size */
    if (tx_size.w && tx_size.h)
        codec_param.enc_fmt.det.vid.size = tx_size;

#if DEF_RENDERER_WIDTH && DEF_RENDERER_HEIGHT
    /* Set incoming video size */
    if (DEF_RENDERER_WIDTH > codec_param.dec_fmt.det.vid.size.w)
        codec_param.dec_fmt.det.vid.size.w = DEF_RENDERER_WIDTH;
    if (DEF_RENDERER_HEIGHT > codec_param.dec_fmt.det.vid.size.h)
        codec_param.dec_fmt.det.vid.size.h = DEF_RENDERER_HEIGHT;
#endif

    if (play_file.file_name) {
        pjmedia_video_format_detail *file_vfd;
        pjmedia_clock_param clock_param;
        char fmt_name[5];

        /* Create file player */
        status = create_file_player(pool, play_file.file_name, &play_port);
        if (status != PJ_SUCCESS) {
            app_perror(THIS_FILE, "Error opening file", status);
            goto on_exit;
        }

        /* Collect format info */
        file_vfd = pjmedia_format_get_video_format_detail(&play_port->info.fmt,
                                                          PJ_TRUE);
        PJ_LOG(2, (THIS_FILE, "Reading video stream %dx%d %s @%.2ffps",
                   file_vfd->size.w, file_vfd->size.h,
                   pjmedia_fourcc_name(play_port->info.fmt.id, fmt_name),
                   (1.0*file_vfd->fps.num/file_vfd->fps.denum)));

        /* Create decoder, if the file and the stream uses different codec */
        if (codec_info->dec_fmt_id[0] != 
            (pjmedia_format_id)play_port->info.fmt.id)
        {
            const pjmedia_video_format_info *dec_vfi;
            pjmedia_video_apply_fmt_param dec_vafp = {0};
            const pjmedia_vid_codec_info *codec_info2;
            pjmedia_vid_codec_param codec_param2;

            /* Find decoder */
            status = pjmedia_vid_codec_mgr_get_codec_info2(NULL,
                                                           (pjmedia_format_id)
                                                           play_port->info.fmt.id,
                                                           &codec_info2);
            if (status != PJ_SUCCESS)
                goto on_exit;

            /* Init decoder */
            status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info2,
                                                       &play_decoder);
            if (status != PJ_SUCCESS)
                goto on_exit;

            status = play_decoder->op->init(play_decoder, pool);
            if (status != PJ_SUCCESS)
                goto on_exit;

            /* Open decoder */
            status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info2,
                                                             &codec_param2);
            if (status != PJ_SUCCESS)
                goto on_exit;

            codec_param2.dir = PJMEDIA_DIR_DECODING;
            codec_param2.packing = PJMEDIA_VID_PACKING_WHOLE;
            status = play_decoder->op->open(play_decoder, &codec_param2);
            if (status != PJ_SUCCESS)
                goto on_exit;

            /* Get decoder format info and apply param */
            dec_vfi = pjmedia_get_video_format_info(NULL,
                                                    codec_info2->dec_fmt_id[0]);
            if (!dec_vfi || !dec_vfi->apply_fmt) {
                status = PJ_ENOTSUP;
                goto on_exit;
            }
            dec_vafp.size = file_vfd->size;
            (*dec_vfi->apply_fmt)(dec_vfi, &dec_vafp);

            /* Allocate file read buffer */
            play_file.read_buf_size = PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE;
            play_file.read_buf = pj_pool_zalloc(pool, play_file.read_buf_size);

            /* Allocate buffer to receive decoder output */
            play_file.dec_buf_size = dec_vafp.framebytes;
            play_file.dec_buf = pj_pool_zalloc(pool, play_file.dec_buf_size);
        } else {
            const pjmedia_video_format_info *dec_vfi;
            pjmedia_video_apply_fmt_param dec_vafp = {0};

            /* Get decoder format info and apply param */
            dec_vfi = pjmedia_get_video_format_info(NULL,
                                                    play_port->info.fmt.id);
            if (!dec_vfi || !dec_vfi->apply_fmt) {
                status = PJ_ENOTSUP;
                goto on_exit;
            }
            dec_vafp.size = file_vfd->size;
            (*dec_vfi->apply_fmt)(dec_vfi, &dec_vafp);

            /* Allocate file read buffer */
            play_file.read_buf_size = dec_vafp.framebytes;
            play_file.read_buf = pj_pool_zalloc(pool, play_file.read_buf_size);
        }

        /* Create player clock */
        clock_param.usec_interval = PJMEDIA_PTIME(&file_vfd->fps);
        clock_param.clock_rate = codec_info->clock_rate;
        status = pjmedia_clock_create2(pool, &clock_param,
                                       PJMEDIA_CLOCK_NO_HIGHEST_PRIO,
                                       &clock_cb, &play_file, &play_clock);
        if (status != PJ_SUCCESS)
            goto on_exit;

        /* Override stream codec param for encoding direction */
        codec_param.enc_fmt.det.vid.size = file_vfd->size;
        codec_param.enc_fmt.det.vid.fps  = file_vfd->fps;

    } else {
        pjmedia_vid_port_param_default(&vpp);

        /* Set as active for all video devices */
        vpp.active = PJ_TRUE;

        /* Create video device port. */
        if (dir & PJMEDIA_DIR_ENCODING) {
            /* Create capture */
            status = pjmedia_vid_dev_default_param(
                                        pool,
                                        PJMEDIA_VID_DEFAULT_CAPTURE_DEV,
                                        &vpp.vidparam);
            if (status != PJ_SUCCESS)
                goto on_exit;

            pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.enc_fmt);
            vpp.vidparam.fmt.id = codec_param.dec_fmt.id;
            vpp.vidparam.dir = PJMEDIA_DIR_CAPTURE;
            
            status = pjmedia_vid_port_create(pool, &vpp, &capture);
            if (status != PJ_SUCCESS)
                goto on_exit;
        }
        
        if (dir & PJMEDIA_DIR_DECODING) {
            /* Create renderer */
            status = pjmedia_vid_dev_default_param(
                                        pool,
                                        PJMEDIA_VID_DEFAULT_RENDER_DEV,
                                        &vpp.vidparam);
            if (status != PJ_SUCCESS)
                goto on_exit;

            pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.dec_fmt);
            vpp.vidparam.dir = PJMEDIA_DIR_RENDER;
            vpp.vidparam.disp_size = vpp.vidparam.fmt.det.vid.size;
            vpp.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
            vpp.vidparam.window_flags = PJMEDIA_VID_DEV_WND_BORDER |
                                        PJMEDIA_VID_DEV_WND_RESIZABLE;

            status = pjmedia_vid_port_create(pool, &vpp, &renderer);
            if (status != PJ_SUCCESS)
                goto on_exit;
        }
    }

    /* Set to ignore fmtp */
    codec_param.ignore_fmtp = PJ_TRUE;

    /* Create stream based on program arguments */
    status = create_stream(pool, med_endpt, codec_info, &codec_param,
                           dir, rx_pt, tx_pt, local_port, &remote_addr, 
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
                           use_srtp, &srtp_crypto_suite, 
                           &srtp_tx_key, &srtp_rx_key,
#endif
                           &stream);
    if (status != PJ_SUCCESS)
        goto on_exit;

    /* Get the port interface of the stream */
    status = pjmedia_vid_stream_get_port(stream, PJMEDIA_DIR_ENCODING,
                                         &enc_port);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    status = pjmedia_vid_stream_get_port(stream, PJMEDIA_DIR_DECODING,
                                         &dec_port);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    /* Start streaming */
    status = pjmedia_vid_stream_start(stream);
    if (status != PJ_SUCCESS)
        goto on_exit;

    /* Start renderer */
    if (renderer) {
        status = pjmedia_vid_port_connect(renderer, dec_port, PJ_FALSE);
        if (status != PJ_SUCCESS)
            goto on_exit;
        status = pjmedia_vid_port_start(renderer);
        if (status != PJ_SUCCESS)
            goto on_exit;
    }

    /* Start capture */
    if (capture) {
        status = pjmedia_vid_port_connect(capture, enc_port, PJ_FALSE);
        if (status != PJ_SUCCESS)
            goto on_exit;
        status = pjmedia_vid_port_start(capture);
        if (status != PJ_SUCCESS)
            goto on_exit;
    }

    /* Start playing file */
    if (play_file.file_name) {

#if HAS_LOCAL_RENDERER_FOR_PLAY_FILE
        /* Create local renderer */
        pjmedia_vid_port_param_default(&vpp);
        vpp.active = PJ_FALSE;
        status = pjmedia_vid_dev_default_param(
                                pool,
                                PJMEDIA_VID_DEFAULT_RENDER_DEV,
                                &vpp.vidparam);
        if (status != PJ_SUCCESS)
            goto on_exit;

        vpp.vidparam.dir = PJMEDIA_DIR_RENDER;
        pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.dec_fmt);
        vpp.vidparam.fmt.det.vid.size = play_port->info.fmt.det.vid.size;
        vpp.vidparam.fmt.det.vid.fps = play_port->info.fmt.det.vid.fps;
        vpp.vidparam.disp_size = vpp.vidparam.fmt.det.vid.size;
        vpp.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
        vpp.vidparam.window_flags = PJMEDIA_VID_DEV_WND_BORDER |
                                    PJMEDIA_VID_DEV_WND_RESIZABLE;

        status = pjmedia_vid_port_create(pool, &vpp, &renderer);
        if (status != PJ_SUCCESS)
            goto on_exit;
        status = pjmedia_vid_port_start(renderer);
        if (status != PJ_SUCCESS)
            goto on_exit;
#endif

        /* Init play file data */
        play_file.play_port = play_port;
        play_file.stream_port = enc_port;
        play_file.decoder = play_decoder;
        if (renderer) {
            play_file.renderer = pjmedia_vid_port_get_passive_port(renderer);
        }

        status = pjmedia_clock_start(play_clock);
        if (status != PJ_SUCCESS)
            goto on_exit;
    }

    /* Done */

    if (dir == PJMEDIA_DIR_DECODING)
        printf("Stream is active, dir is recv-only, local port is %d\n",
               local_port);
    else if (dir == PJMEDIA_DIR_ENCODING)
        printf("Stream is active, dir is send-only, sending to %s:%d\n",
               pj_inet_ntop2(pj_AF_INET(), &remote_addr.sin_addr, addr,
                             sizeof(addr)),
               pj_ntohs(remote_addr.sin_port));
    else
        printf("Stream is active, send/recv, local port is %d, "
               "sending to %s:%d\n",
               local_port,
               pj_inet_ntop2(pj_AF_INET(), &remote_addr.sin_addr, addr,
                             sizeof(addr)),
               pj_ntohs(remote_addr.sin_port));

    if (dir & PJMEDIA_DIR_ENCODING)
        PJ_LOG(2, (THIS_FILE, "Sending %dx%d %.*s @%.2ffps",
                   codec_param.enc_fmt.det.vid.size.w,
                   codec_param.enc_fmt.det.vid.size.h,
                   (int)codec_info->encoding_name.slen,
                   codec_info->encoding_name.ptr,
                   (1.0*codec_param.enc_fmt.det.vid.fps.num/
                    codec_param.enc_fmt.det.vid.fps.denum)));

    for (;;) {
        char tmp[10];

        puts("");
        puts("Commands:");
        puts("  q     Quit");
        puts("");

        printf("Command: "); fflush(stdout);

        if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
            puts("EOF while reading stdin, will quit now..");
            break;
        }

        if (tmp[0] == 'q')
            break;

    }



    /* Start deinitialization: */
on_exit:

    if (status != PJ_SUCCESS) {
        app_perror(THIS_FILE, "Something wrong", status);
    }

    /* Stop video devices */
    if (capture)
        pjmedia_vid_port_stop(capture);
    if (renderer)
        pjmedia_vid_port_stop(renderer);

    /* Stop and destroy file clock */
    if (play_clock) {
        pjmedia_clock_stop(play_clock);
        pjmedia_clock_destroy(play_clock);
    }

    /* Destroy file reader/player */
    if (play_port)
        pjmedia_port_destroy(play_port);

    /* Destroy file decoder */
    if (play_decoder) {
        play_decoder->op->close(play_decoder);
        pjmedia_vid_codec_mgr_dealloc_codec(NULL, play_decoder);
    }

    /* Destroy video devices */
    if (capture)
        pjmedia_vid_port_destroy(capture);
    if (renderer)
        pjmedia_vid_port_destroy(renderer);

    /* Destroy stream */
    if (stream) {
        pjmedia_transport *tp;

        tp = pjmedia_vid_stream_get_transport(stream);
        pjmedia_vid_stream_destroy(stream);
        
        pjmedia_transport_media_stop(tp);
        pjmedia_transport_close(tp);
    }

    /* Deinit codecs */
    deinit_codecs();

    /* Shutdown video subsystem */
    pjmedia_vid_dev_subsys_shutdown();

    /* 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();

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


int main(int argc, char *argv[])
{
    return pj_run_app(&main_func, argc, argv, 0);
}

#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 */
