/* $Id$ */
/* 
 * 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)? codec_info->pt : tx_pt;
    info.rx_pt = (rx_pt == -1)? 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)
	    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)
	    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)));

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

	/* Create decoder, if the file and the stream uses different codec */
	if (codec_info->fmt_id != (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,
							   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;
	    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 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);
	}

	/* 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,
		   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:

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