Index: pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 3553)
+++ pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 3664)
@@ -125,4 +125,5 @@
     pjmedia_port	   *ring_port;
 
+    int			    vcapture_dev, vrender_dev;
 } app_config;
 
@@ -137,5 +138,11 @@
 static pj_str_t		uri_arg;
 
-static char some_buf[1024 * 3];
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+#   define SOME_BUF_SIZE	(1024 * 10)
+#else
+#   define SOME_BUF_SIZE	(1024 * 3)
+#endif
+
+static char some_buf[SOME_BUF_SIZE];
 
 #ifdef STEREO_DEMO
@@ -195,4 +202,5 @@
     puts  ("  --no-color          Disable colorful logging");
     puts  ("  --light-bg          Use dark colors for light background (default is dark bg)");
+    puts  ("  --no-stderr         Disable stderr");
 
     puts  ("");
@@ -268,5 +276,5 @@
 
     puts  ("");
-    puts  ("Media Options:");
+    puts  ("Audio Options:");
     puts  ("  --add-codec=name    Manually add codec (default is to enable all)");
     puts  ("  --dis-codec=name    Disable codec (can be specified multiple times)");
@@ -302,4 +310,13 @@
     puts  ("  --no-tones          Disable audible tones");
     puts  ("  --jb-max-size       Specify jitter buffer maximum size, in frames (default=-1)");
+    puts  ("  --extra-audio       Add one more audio stream");
+
+#if PJSUA_HAS_VIDEO
+    puts  ("");
+    puts  ("Video Options:");
+    puts  ("  --video             Enable video");
+    puts  ("  --vcapture-dev=id   Video capture device ID (default=-1)");
+    puts  ("  --vrender-dev=id    Video render device ID (default=-1)");
+#endif
 
     puts  ("");
@@ -377,4 +394,7 @@
     for (i=0; i<PJ_ARRAY_SIZE(cfg->buddy_cfg); ++i)
 	pjsua_buddy_config_default(&cfg->buddy_cfg[i]);
+
+    cfg->vcapture_dev = PJSUA_INVALID_ID;
+    cfg->vrender_dev = PJSUA_INVALID_ID;
 }
 
@@ -510,5 +530,5 @@
     int option_index;
     enum { OPT_CONFIG_FILE=127, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL, 
-	   OPT_LOG_APPEND, OPT_COLOR, OPT_NO_COLOR, OPT_LIGHT_BG,
+	   OPT_LOG_APPEND, OPT_COLOR, OPT_NO_COLOR, OPT_LIGHT_BG, OPT_NO_STDERR,
 	   OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO, OPT_SND_AUTO_CLOSE,
 	   OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY, 
@@ -542,5 +562,7 @@
 	   OPT_AUTO_UPDATE_NAT,OPT_USE_COMPACT_FORM,OPT_DIS_CODEC,
 	   OPT_NO_FORCE_LR,
-	   OPT_TIMER, OPT_TIMER_SE, OPT_TIMER_MIN_SE
+	   OPT_TIMER, OPT_TIMER_SE, OPT_TIMER_MIN_SE,
+	   OPT_VIDEO, OPT_EXTRA_AUDIO,
+	   OPT_VCAPTURE_DEV, OPT_VRENDER_DEV,
     };
     struct pj_getopt_option long_options[] = {
@@ -553,4 +575,5 @@
 	{ "no-color",	0, 0, OPT_NO_COLOR},
 	{ "light-bg",		0, 0, OPT_LIGHT_BG},
+	{ "no-stderr",  0, 0, OPT_NO_STDERR},
 	{ "help",	0, 0, OPT_HELP},
 	{ "version",	0, 0, OPT_VERSION},
@@ -661,4 +684,8 @@
 	{ "timer-min-se", 1, 0, OPT_TIMER_MIN_SE},
 	{ "outb-rid",	1, 0, OPT_OUTB_RID},
+	{ "video",	0, 0, OPT_VIDEO},
+	{ "extra-audio",0, 0, OPT_EXTRA_AUDIO},
+	{ "vcapture-dev", 1, 0, OPT_VCAPTURE_DEV},
+	{ "vrender-dev",  1, 0, OPT_VRENDER_DEV},
 	{ NULL, 0, 0, 0}
     };
@@ -751,4 +778,8 @@
 	    pj_log_set_color(5, 0);
 	    pj_log_set_color(77, 0);
+	    break;
+
+	case OPT_NO_STDERR:
+	    freopen("/dev/null", "w", stderr);
 	    break;
 
@@ -1418,4 +1449,24 @@
 	    cfg->udp_cfg.qos_params.dscp_val = 0x18;
 	    break;
+	case OPT_VIDEO:
+	    ++cur_acc->max_video_cnt;
+	    cur_acc->vid_in_auto_show = PJ_TRUE;
+	    cur_acc->vid_out_auto_transmit = PJ_TRUE;
+	    PJ_TODO(implement_pjsua_option_for_vid_auto_show_and_transmit);
+	    break;
+	case OPT_EXTRA_AUDIO:
+	    ++cur_acc->max_audio_cnt;
+	    break;
+
+	case OPT_VCAPTURE_DEV:
+	    cfg->vcapture_dev = atoi(pj_optarg);
+	    cur_acc->vid_cap_dev = cfg->vcapture_dev;
+	    break;
+
+	case OPT_VRENDER_DEV:
+	    cfg->vrender_dev = atoi(pj_optarg);
+	    cur_acc->vid_rend_dev = cfg->vrender_dev;
+	    break;
+
 	default:
 	    PJ_LOG(1,(THIS_FILE, 
@@ -1662,4 +1713,12 @@
     if (acc_cfg->mwi_enabled)
 	pj_strcat2(result, "--mwi\n");
+
+    /* Video & extra audio */
+    for (i=0; i<acc_cfg->max_video_cnt; ++i) {
+	pj_strcat2(result, "--video\n");
+    }
+    for (i=1; i<acc_cfg->max_audio_cnt; ++i) {
+	pj_strcat2(result, "--extra-audio\n");
+    }
 }
 
@@ -1986,4 +2045,12 @@
     }
 
+    if (config->vcapture_dev != PJSUA_INVALID_ID) {
+	pj_ansi_sprintf(line, "--vcapture-dev %d\n", config->vcapture_dev);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->vrender_dev != PJSUA_INVALID_ID) {
+	pj_ansi_sprintf(line, "--vrender-dev %d\n", config->vrender_dev);
+	pj_strcat2(&cfg, line);
+    }
 
     /* ptime */
@@ -2585,4 +2652,115 @@
 }
 
+/* General processing for media state. "mi" is the media index */
+static void on_call_generic_media_state(pjsua_call_info *ci, unsigned mi,
+                                         pj_bool_t *has_error)
+{
+    const char *status_name[] = {
+        "None",
+        "Active",
+        "Local hold",
+        "Remote hold",
+        "Error"
+    };
+
+    pj_assert(ci->media[mi].status <= PJ_ARRAY_SIZE(status_name));
+    pj_assert(PJSUA_CALL_MEDIA_ERROR == 4);
+
+    PJ_LOG(4,(THIS_FILE, "Call %d media %d [type=%s], status is %s",
+	      ci->id, mi, pjmedia_type_name(ci->media[mi].type),
+	      status_name[ci->media[mi].status]));
+}
+
+/* Process audio media state. "mi" is the media index. */
+static void on_call_audio_state(pjsua_call_info *ci, unsigned mi,
+                                pj_bool_t *has_error)
+{
+    /* Stop ringback */
+    ring_stop(ci->id);
+
+    /* Connect ports appropriately when media status is ACTIVE or REMOTE HOLD,
+     * otherwise we should NOT connect the ports.
+     */
+    if (ci->media[mi].status == PJSUA_CALL_MEDIA_ACTIVE ||
+	ci->media[mi].status == PJSUA_CALL_MEDIA_REMOTE_HOLD)
+    {
+	pj_bool_t connect_sound = PJ_TRUE;
+	pjsua_conf_port_id call_conf_slot;
+
+	call_conf_slot = ci->media[mi].stream.aud.conf_slot;
+
+	/* Loopback sound, if desired */
+	if (app_config.auto_loop) {
+	    pjsua_conf_connect(call_conf_slot, call_conf_slot);
+	    connect_sound = PJ_FALSE;
+	}
+
+	/* Automatically record conversation, if desired */
+	if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
+	    pjsua_conf_connect(call_conf_slot, app_config.rec_port);
+	}
+
+	/* Stream a file, if desired */
+	if ((app_config.auto_play || app_config.auto_play_hangup) && 
+	    app_config.wav_port != PJSUA_INVALID_ID)
+	{
+	    pjsua_conf_connect(app_config.wav_port, call_conf_slot);
+	    connect_sound = PJ_FALSE;
+	}
+
+	/* Put call in conference with other calls, if desired */
+	if (app_config.auto_conf) {
+	    pjsua_call_id call_ids[PJSUA_MAX_CALLS];
+	    unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
+	    unsigned i;
+
+	    /* Get all calls, and establish media connection between
+	     * this call and other calls.
+	     */
+	    pjsua_enum_calls(call_ids, &call_cnt);
+
+	    for (i=0; i<call_cnt; ++i) {
+		if (call_ids[i] == ci->id)
+		    continue;
+		
+		if (!pjsua_call_has_media(call_ids[i]))
+		    continue;
+
+		pjsua_conf_connect(call_conf_slot,
+				   pjsua_call_get_conf_port(call_ids[i]));
+		pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
+		                   call_conf_slot);
+
+		/* Automatically record conversation, if desired */
+		if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
+		    pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]), 
+				       app_config.rec_port);
+		}
+
+	    }
+
+	    /* Also connect call to local sound device */
+	    connect_sound = PJ_TRUE;
+	}
+
+	/* Otherwise connect to sound device */
+	if (connect_sound) {
+	    pjsua_conf_connect(call_conf_slot, 0);
+	    pjsua_conf_connect(0, call_conf_slot);
+
+	    /* Automatically record conversation, if desired */
+	    if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
+		pjsua_conf_connect(call_conf_slot, app_config.rec_port);
+		pjsua_conf_connect(0, app_config.rec_port);
+	    }
+	}
+    }
+}
+
+/* Process video media state. "mi" is the media index. */
+static void on_call_video_state(pjsua_call_info *ci, unsigned mi,
+                                pj_bool_t *has_error)
+{
+}
 
 /*
@@ -2594,120 +2772,25 @@
 {
     pjsua_call_info call_info;
+    unsigned mi;
+    pj_bool_t has_error = PJ_FALSE;
 
     pjsua_call_get_info(call_id, &call_info);
 
-    /* Stop ringback */
-    ring_stop(call_id);
-
-    /* Connect ports appropriately when media status is ACTIVE or REMOTE HOLD,
-     * otherwise we should NOT connect the ports.
-     */
-    if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE ||
-	call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD)
-    {
-	pj_bool_t connect_sound = PJ_TRUE;
-
-	/* Loopback sound, if desired */
-	if (app_config.auto_loop) {
-	    pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
-	    connect_sound = PJ_FALSE;
+    for (mi=0; mi<call_info.media_cnt; ++mi) {
+	on_call_generic_media_state(&call_info, mi, &has_error);
+
+	switch (call_info.media[mi].type) {
+	case PJMEDIA_TYPE_AUDIO:
+	    on_call_audio_state(&call_info, mi, &has_error);
+	    break;
+	case PJMEDIA_TYPE_VIDEO:
+	    on_call_video_state(&call_info, mi, &has_error);
+	    break;
 	}
-
-	/* Automatically record conversation, if desired */
-	if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
-	    pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
-	}
-
-	/* Stream a file, if desired */
-	if ((app_config.auto_play || app_config.auto_play_hangup) && 
-	    app_config.wav_port != PJSUA_INVALID_ID)
-	{
-	    pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
-	    connect_sound = PJ_FALSE;
-	}
-
-	/* Put call in conference with other calls, if desired */
-	if (app_config.auto_conf) {
-	    pjsua_call_id call_ids[PJSUA_MAX_CALLS];
-	    unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
-	    unsigned i;
-
-	    /* Get all calls, and establish media connection between
-	     * this call and other calls.
-	     */
-	    pjsua_enum_calls(call_ids, &call_cnt);
-
-	    for (i=0; i<call_cnt; ++i) {
-		if (call_ids[i] == call_id)
-		    continue;
-		
-		if (!pjsua_call_has_media(call_ids[i]))
-		    continue;
-
-		pjsua_conf_connect(call_info.conf_slot,
-				   pjsua_call_get_conf_port(call_ids[i]));
-		pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
-				   call_info.conf_slot);
-
-		/* Automatically record conversation, if desired */
-		if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
-		    pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]), 
-				       app_config.rec_port);
-		}
-
-	    }
-
-	    /* Also connect call to local sound device */
-	    connect_sound = PJ_TRUE;
-	}
-
-	/* Otherwise connect to sound device */
-	if (connect_sound) {
-	    pjsua_conf_connect(call_info.conf_slot, 0);
-	    pjsua_conf_connect(0, call_info.conf_slot);
-
-	    /* Automatically record conversation, if desired */
-	    if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
-		pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
-		pjsua_conf_connect(0, app_config.rec_port);
-	    }
-	}
-    }
-
-    /* Handle media status */
-    switch (call_info.media_status) {
-    case PJSUA_CALL_MEDIA_ACTIVE:
-	PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
-	break;
-
-    case PJSUA_CALL_MEDIA_LOCAL_HOLD:
-	PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
-		  call_id));
-	break;
-
-    case PJSUA_CALL_MEDIA_REMOTE_HOLD:
-	PJ_LOG(3,(THIS_FILE, 
-		  "Media for call %d is suspended (hold) by remote",
-		  call_id));
-	break;
-
-    case PJSUA_CALL_MEDIA_ERROR:
-	PJ_LOG(3,(THIS_FILE,
-		  "Media has reported error, disconnecting call"));
-	{
-	    pj_str_t reason = pj_str("ICE negotiation failed");
-	    pjsua_call_hangup(call_id, 500, &reason, NULL);
-	}
-	break;
-
-    case PJSUA_CALL_MEDIA_NONE:
-	PJ_LOG(3,(THIS_FILE, 
-		  "Media for call %d is inactive",
-		  call_id));
-	break;
-
-    default:
-	pj_assert(!"Unhandled media status");
-	break;
+    }
+
+    if (has_error) {
+	pj_str_t reason = pj_str("Media failed");
+	pjsua_call_hangup(call_id, 500, &reason, NULL);
     }
 }
@@ -3050,4 +3133,23 @@
     PJ_PERROR(1,(THIS_FILE, status,
 	         "ICE keep alive failure for transport %d", index));
+}
+
+/*
+ * Notification on sound device operation.
+ */
+static pj_status_t on_snd_dev_operation(int operation)
+{
+    PJ_LOG(3,(THIS_FILE, "Turning sound device %s", (operation? "ON":"OFF")));
+    return PJ_SUCCESS;
+}
+
+/* Callback on media events */
+static void on_call_media_event(pjsua_call_id call_id,
+                                unsigned med_idx,
+                                pjmedia_event *event)
+{
+    char event_name[5];
+    PJ_LOG(4,(THIS_FILE, "Event %s",
+	      pjmedia_fourcc_name(event->type, event_name)));
 }
 
@@ -3194,6 +3296,10 @@
     puts("| dq  Dump curr. call quality  | cd  Disconnect port      | dc  Dump config   |");
     puts("|                              |  V  Adjust audio Volume  |  f  Save config   |");
-    puts("|  S  Send arbitrary REQUEST   | Cp  Codec priorities     |  f  Save config   |");
-    puts("+------------------------------+--------------------------+-------------------+");
+    puts("|  S  Send arbitrary REQUEST   | Cp  Codec priorities     |                   |");
+    puts("+-----------------------------------------------------------------------------+");
+#if PJSUA_HAS_VIDEO
+    puts("| Video: \"vid help\" for more info                                             |");
+    puts("+-----------------------------------------------------------------------------+");
+#endif
     puts("|  q  QUIT   L  ReLoad   sleep MS   echo [0|1|txt]     n: detect NAT type     |");
     puts("+=============================================================================+");
@@ -3211,4 +3317,29 @@
 }
 
+/* Help screen for video */
+static void vid_show_help(void)
+{
+#if PJSUA_HAS_VIDEO
+    puts("+=============================================================================+");
+    puts("|                            Video commands:                                  |");
+    puts("|                                                                             |");
+    puts("| vid help                  Show this help screen                             |");
+    puts("| vid call rx on|off N      Enable/disable video rx for stream N in curr call |");
+    puts("| vid call tx on|off N      Enable/disable video tx for stream N in curr call |");
+    puts("| vid call add              Add video stream for current call                 |");
+    puts("| vid call enable/disable N Enable/disable stream #N in current call          |");
+    puts("| vid call set-cap N ID     Set capture dev ID for stream #N in current call  |");
+    puts("| vid dev list              List all video devices                            |");
+    puts("| vid dev refresh           Refresh video device list                         |");
+    puts("| vid dev prev on|off ID    Enable/disable preview for specified device ID    |");
+    puts("| vid codec list            List video codecs                                 |");
+    puts("| vid codec prio PT PRIO    Set codec with pt PT priority to PRIO             |");
+    puts("| vid win list              List all active video windows                     |");
+    puts("| vid win show|hide ID      Show/hide the specified video window ID           |");
+    puts("| vid win move ID X Y       Move window ID to position X,Y                    |");
+    puts("| vid win resize ID w h     Resize window ID to the specified width, height   |");
+    puts("+=============================================================================+");
+#endif
+}
 
 /*
@@ -3482,6 +3613,5 @@
     pj_status_t status;
 
-    printf("List of codecs:\n");
-
+    printf("List of audio codecs:\n");
     pjsua_enum_codecs(c, &count);
     for (i=0; i<count; ++i) {
@@ -3490,7 +3620,21 @@
     }
 
+#if PJSUA_HAS_VIDEO
     puts("");
-    puts("Enter codec id and its new priority "
-	 "(e.g. \"speex/16000 200\"), empty to cancel:");
+    printf("List of video codecs:\n");
+    pjsua_vid_enum_codecs(c, &count);
+    for (i=0; i<count; ++i) {
+	printf("  %d\t%.*s%s%.*s\n", c[i].priority,
+				     (int)c[i].codec_id.slen,
+				     c[i].codec_id.ptr,
+				     c[i].desc.slen? " - ":"",
+				     (int)c[i].desc.slen,
+				     c[i].desc.ptr);
+    }
+#endif
+
+    puts("");
+    puts("Enter codec id and its new priority (e.g. \"speex/16000 200\", ""\"H263 200\"),");
+    puts("or empty to cancel.");
 
     printf("Codec name (\"*\" for all) and priority: ");
@@ -3518,7 +3662,270 @@
     status = pjsua_codec_set_priority(pj_cstr(&id, codec), 
 				      (pj_uint8_t)new_prio);
+#if PJSUA_HAS_VIDEO
+    if (status != PJ_SUCCESS) {
+	status = pjsua_vid_codec_set_priority(pj_cstr(&id, codec), 
+					      (pj_uint8_t)new_prio);
+    }
+#endif
     if (status != PJ_SUCCESS)
 	pjsua_perror(THIS_FILE, "Error setting codec priority", status);
 }
+
+
+#if PJSUA_HAS_VIDEO
+static void vid_print_dev(int id, const pjmedia_vid_dev_info *vdi,
+                          const char *title)
+{
+    char capnames[120];
+    char formats[120];
+    const char *dirname;
+    unsigned i;
+
+    if (vdi->dir == PJMEDIA_DIR_CAPTURE_RENDER) {
+	dirname = "capture, render";
+    } else if (vdi->dir == PJMEDIA_DIR_CAPTURE) {
+	dirname = "capture";
+    } else {
+	dirname = "render";
+    }
+
+
+    capnames[0] = '\0';
+    for (i=0; i<sizeof(int)*8 && (1 << i) < PJMEDIA_VID_DEV_CAP_MAX; ++i) {
+	if (vdi->caps & (1 << i)) {
+	    const char *capname = pjmedia_vid_dev_cap_name(1 << i, NULL);
+	    if (capname) {
+		if (*capnames)
+		    strcat(capnames, ", ");
+		strncat(capnames, capname,
+		        sizeof(capnames)-strlen(capnames)-1);
+	    }
+	}
+    }
+
+    formats[0] = '\0';
+    for (i=0; i<vdi->fmt_cnt; ++i) {
+	const pjmedia_video_format_info *vfi =
+		pjmedia_get_video_format_info(NULL, vdi->fmt[i].id);
+	if (vfi) {
+	    if (*formats)
+		strcat(formats, ", ");
+	    strncat(formats, vfi->name, sizeof(formats)-strlen(formats)-1);
+	}
+    }
+
+    PJ_LOG(3,(THIS_FILE, "%3d %s [%s][%s] %s", id, vdi->name, vdi->driver,
+	      dirname, title));
+    PJ_LOG(3,(THIS_FILE, "    Supported capabilities: %s", capnames));
+    PJ_LOG(3,(THIS_FILE, "    Supported formats: %s", formats));
+}
+
+static void vid_list_devs(void)
+{
+    unsigned i, count;
+    pjmedia_vid_dev_info vdi;
+    pj_status_t status;
+
+    PJ_LOG(3,(THIS_FILE, "Video device list:"));
+    count = pjsua_vid_dev_count();
+    if (count == 0) {
+	PJ_LOG(3,(THIS_FILE, " - no device detected -"));
+	return;
+    } else {
+	PJ_LOG(3,(THIS_FILE, "%d device(s) detected:", count));
+    }
+
+    status = pjsua_vid_dev_get_info(PJMEDIA_VID_DEFAULT_RENDER_DEV, &vdi);
+    if (status == PJ_SUCCESS)
+	vid_print_dev(PJMEDIA_VID_DEFAULT_RENDER_DEV, &vdi,
+	              "(default renderer device)");
+
+    status = pjsua_vid_dev_get_info(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vdi);
+    if (status == PJ_SUCCESS)
+	vid_print_dev(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vdi,
+	              "(default capture device)");
+
+    for (i=0; i<count; ++i) {
+	status = pjsua_vid_dev_get_info(i, &vdi);
+	if (status == PJ_SUCCESS)
+	    vid_print_dev(i, &vdi, "");
+    }
+}
+
+static void vid_handle_menu(char *menuin)
+{
+    char *argv[8];
+    int argc = 0;
+
+    /* Tokenize */
+    argv[argc] = strtok(menuin, " \t\r\n");
+    while (argv[argc] && *argv[argc]) {
+	argc++;
+	argv[argc] = strtok(NULL, " \t\r\n");
+    }
+
+    if (strcmp(argv[1], "help")==0 || argc == 1) {
+	vid_show_help();
+    } else if (strcmp(argv[1], "call")==0) {
+	pjsua_call_vid_strm_op_param param;
+
+	if (argc == 5 && strcmp(argv[2], "rx")==0) {
+	    pjsua_stream_info si;
+	    pj_bool_t on = (strcmp(argv[3], "on") == 0);
+
+	    param.med_idx = atoi(argv[4]);
+	    if (pjsua_call_get_stream_info(current_call, param.med_idx, &si) ||
+		si.type != PJMEDIA_TYPE_VIDEO)
+	    {
+		PJ_PERROR(1,(THIS_FILE, PJ_EINVAL, "Invalid stream"));
+		return;
+	    }
+
+	    if (on) param.dir = (si.info.vid.dir | PJMEDIA_DIR_DECODING);
+	    else param.dir = (si.info.vid.dir & PJMEDIA_DIR_ENCODING);
+
+	    pjsua_call_set_vid_strm(current_call, PJSUA_CALL_VID_STRM_CHANGE_DIR, &param);
+	}
+	else if (argc == 5 && strcmp(argv[2], "tx")==0) {
+	    pj_bool_t on = (strcmp(argv[3], "on") == 0);
+	    pjsua_call_vid_strm_op op = on? PJSUA_CALL_VID_STRM_START_TRANSMIT :
+					    PJSUA_CALL_VID_STRM_STOP_TRANSMIT;
+
+	    param.med_idx = atoi(argv[4]);
+
+	    pjsua_call_set_vid_strm(current_call, op, &param);
+	}
+	else if (argc == 3 && strcmp(argv[2], "add")==0) {
+	    pjsua_call_set_vid_strm(current_call, PJSUA_CALL_VID_STRM_ADD, NULL);
+	}
+	else if (argc >= 3 && 
+		 (strcmp(argv[2], "disable")==0 || strcmp(argv[2], "enable")==0))
+	{
+	    pj_bool_t enable = (strcmp(argv[2], "enable") == 0);
+	    pjsua_call_vid_strm_op op = enable? PJSUA_CALL_VID_STRM_CHANGE_DIR :
+						PJSUA_CALL_VID_STRM_REMOVE;
+
+	    param.med_idx = argc >= 4? atoi(argv[3]) : -1;
+	    param.dir = PJMEDIA_DIR_ENCODING_DECODING;
+	    pjsua_call_set_vid_strm(current_call, op, &param);
+	}
+	else if (argc >= 3 && strcmp(argv[2], "set-cap")==0) {
+	    param.med_idx = argc >= 4? atoi(argv[3]) : -1;
+	    param.cap_dev = argc >= 5? atoi(argv[4]) : PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+	    pjsua_call_set_vid_strm(current_call, PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV, &param);
+	} else
+	    goto on_error;
+    } else if (argc >= 3 && strcmp(argv[1], "dev")==0) {
+	if (strcmp(argv[2], "list")==0) {
+	    vid_list_devs();
+	} else if (strcmp(argv[2], "refresh")==0) {
+	    pjmedia_vid_dev_refresh();
+	} else if (strcmp(argv[2], "prev")==0) {
+	    if (argc != 5) {
+		goto on_error;
+	    } else {
+		pj_bool_t on = (strcmp(argv[3], "on") == 0);
+		int dev_id = atoi(argv[4]);
+		if (on) {
+		    pjsua_vid_preview_start(dev_id, NULL);
+		} else {
+		    pjsua_vid_preview_stop(dev_id);
+		}
+	    }
+	} else
+	    goto on_error;
+    } else if (strcmp(argv[1], "win")==0) {
+	if (argc==3 && strcmp(argv[2], "list")==0) {
+	    pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
+	    unsigned i, cnt = PJ_ARRAY_SIZE(wids);
+
+	    pjsua_vid_enum_wins(wids, &cnt);
+
+	    PJ_LOG(3,(THIS_FILE, "Found %d video windows:", cnt));
+	    PJ_LOG(3,(THIS_FILE, "WID show    pos       size"));
+	    PJ_LOG(3,(THIS_FILE, "------------------------------"));
+	    for (i = 0; i < cnt; ++i) {
+		pjsua_vid_win_info wi;
+		pjsua_vid_win_get_info(wids[i], &wi);
+		PJ_LOG(3,(THIS_FILE, "%3d   %c  (%d,%d)  %dx%d",
+			  wids[i], (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
+			  wi.size.w, wi.size.h));
+	    }
+	} else if (argc==4 && (strcmp(argv[2], "show")==0 ||
+			       strcmp(argv[2], "hide")==0))
+	{
+	    pj_bool_t show = (strcmp(argv[2], "show")==0);
+	    pjsua_vid_win_id wid = atoi(argv[3]);
+	    pjsua_vid_win_set_show(wid, show);
+	} else if (argc==6 && strcmp(argv[2], "move")==0) {
+	    pjsua_vid_win_id wid = atoi(argv[3]);
+	    pjmedia_coord pos;
+
+	    pos.x = atoi(argv[4]);
+	    pos.y = atoi(argv[5]);
+	    pjsua_vid_win_set_pos(wid, &pos);
+	} else if (argc==6 && strcmp(argv[2], "resize")==0) {
+	    pjsua_vid_win_id wid = atoi(argv[3]);
+	    pjmedia_rect_size size;
+
+	    size.w = atoi(argv[4]);
+	    size.h = atoi(argv[5]);
+	    pjsua_vid_win_set_size(wid, &size);
+	} else
+	    goto on_error;
+    } else if (strcmp(argv[1], "codec")==0) {
+	pjmedia_vid_codec_info ci[PJMEDIA_CODEC_MGR_MAX_CODECS];
+	unsigned prio[PJMEDIA_CODEC_MGR_MAX_CODECS];
+	unsigned count = PJMEDIA_CODEC_MGR_MAX_CODECS;
+	pj_status_t status;
+
+	if (argc==3 && strcmp(argv[2], "list")==0) {
+	    status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, ci, prio);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(1,(THIS_FILE, status, "Error enumerating codecs"));
+	    } else {
+		unsigned i;
+		PJ_LOG(3,(THIS_FILE, "Found %d video codecs:", count));
+		PJ_LOG(3,(THIS_FILE, " PT  Prio  Name"));
+		PJ_LOG(3,(THIS_FILE, "-------------------------"));
+		for (i=0; i<count; ++i) {
+		    PJ_LOG(3,(THIS_FILE, "% 3d % 3d  %.*s", ci[i].pt, prio[i],
+			      (int)ci[i].encoding_name.slen,
+			      ci[i].encoding_name.ptr));
+		}
+	    }
+	} else if (argc==5 && strcmp(argv[2], "prio")==0) {
+	    int pt = atoi(argv[3]);
+	    int prio = atoi(argv[4]);
+	    const pjmedia_vid_codec_info *pci;
+
+	    status = pjmedia_vid_codec_mgr_get_codec_info(NULL, pt, &pci);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(1,(THIS_FILE, status, "Unable to find codec"));
+	    } else {
+		char codec_id[40];
+		if (pjmedia_vid_codec_info_to_id(pci, codec_id,
+		                                 sizeof(codec_id)) == NULL)
+		{
+		    PJ_PERROR(1,(THIS_FILE, status, "Unable to get codec id"));
+		} else {
+		    pj_str_t cid = pj_str(codec_id);
+		    status = pjsua_vid_codec_set_priority(&cid,
+		                                          (pj_uint8_t)prio);
+		}
+	    }
+
+	} else
+	    goto on_error;
+    } else
+	goto on_error;
+
+    return;
+
+on_error:
+    PJ_LOG(1,(THIS_FILE, "Invalid command, use 'vid help'"));
+}
+
+#endif /* PJSUA_HAS_VIDEO */
 
 
@@ -3893,4 +4300,6 @@
 		acc_cfg.cred_info[0].data = pj_str(passwd);
 
+		acc_cfg.rtp_cfg = app_config.rtp_cfg;
+
 		status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
 		if (status != PJ_SUCCESS) {
@@ -3950,9 +4359,15 @@
 
 	case 'v':
-	    /*
-	     * Send re-INVITE (to release hold, etc).
-	     */
+#if PJSUA_HAS_VIDEO
+	    if (menuin[1]=='i' && menuin[2]=='d' && menuin[3]==' ') {
+
+		vid_handle_menu(menuin);
+
+	    } else
+#endif
 	    if (current_call != -1) {
-		
+		/*
+		 * re-INVITE
+		 */
 		pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
 
@@ -4512,4 +4927,57 @@
 }
 
+/*
+ * A simple registrar, invoked by default_mod_on_rx_request()
+ */
+static void simple_registrar(pjsip_rx_data *rdata)
+{
+    pjsip_tx_data *tdata;
+    const pjsip_expires_hdr *exp;
+    const pjsip_hdr *h;
+    unsigned cnt = 0;
+    pjsip_generic_string_hdr *srv;
+    pj_status_t status;
+
+    status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(),
+				 rdata, 200, NULL, &tdata);
+    if (status != PJ_SUCCESS)
+    return;
+
+    exp = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
+
+    h = rdata->msg_info.msg->hdr.next;
+    while (h != &rdata->msg_info.msg->hdr) {
+    if (h->type == PJSIP_H_CONTACT) {
+    const pjsip_contact_hdr *c = (const pjsip_contact_hdr*)h;
+    int e = c->expires;
+
+    if (e < 0) {
+	if (exp)
+	    e = exp->ivalue;
+	else
+	    e = 3600;
+    }
+
+    if (e > 0) {
+	pjsip_contact_hdr *nc = pjsip_hdr_clone(tdata->pool, h);
+	nc->expires = e;
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)nc);
+	++cnt;
+    }
+    }
+    h = h->next;
+    }
+
+    srv = pjsip_generic_string_hdr_create(tdata->pool, NULL, NULL);
+    srv->name = pj_str("Server");
+    srv->hvalue = pj_str("pjsua simple registrar");
+    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)srv);
+
+    pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(),
+		       rdata, tdata, NULL, NULL);
+}
+
+
+
 /*****************************************************************************
  * A simple module to handle otherwise unhandled request. We will register
@@ -4525,7 +4993,15 @@
 
     /* Don't respond to ACK! */
-    if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, 
+    if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
 			 &pjsip_ack_method) == 0)
 	return PJ_TRUE;
+
+    /* Simple registrar */
+    if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+                         &pjsip_register_method) == 0)
+    {
+	simple_registrar(rdata);
+	return PJ_TRUE;
+    }
 
     /* Create basic response. */
@@ -4652,4 +5128,6 @@
     app_config.cfg.cb.on_transport_state = &on_transport_state;
     app_config.cfg.cb.on_ice_transport_error = &on_ice_transport_error;
+    app_config.cfg.cb.on_snd_dev_operation = &on_snd_dev_operation;
+    app_config.cfg.cb.on_call_media_event = &on_call_media_event;
     app_config.log_cfg.cb = log_cb;
 
@@ -4930,4 +5408,5 @@
     /* Add accounts */
     for (i=0; i<app_config.acc_cnt; ++i) {
+	app_config.acc_cfg[i].rtp_cfg = app_config.rtp_cfg;
 	status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
 	if (status != PJ_SUCCESS)
@@ -4946,4 +5425,7 @@
     for (i=0; i<app_config.codec_dis_cnt; ++i) {
 	pjsua_codec_set_priority(&app_config.codec_dis[i],PJMEDIA_CODEC_PRIO_DISABLED);
+#if PJSUA_HAS_VIDEO
+	pjsua_vid_codec_set_priority(&app_config.codec_dis[i],PJMEDIA_CODEC_PRIO_DISABLED);
+#endif
     }
 
@@ -4952,4 +5434,8 @@
 	pjsua_codec_set_priority(&app_config.codec_arg[i],
 				 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
+#if PJSUA_HAS_VIDEO
+	pjsua_vid_codec_set_priority(&app_config.codec_arg[i],
+				     (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
+#endif
     }
 
@@ -4961,6 +5447,8 @@
     if (app_config.ipv6)
 	status = create_ipv6_media_transports();
+  #if DISABLED_FOR_TICKET_1185
     else
 	status = pjsua_media_transports_create(&app_config.rtp_cfg);
+  #endif
 #endif
     if (status != PJ_SUCCESS)
@@ -4984,4 +5472,16 @@
 	    goto on_error;
     }
+
+#if PJSUA_HAS_VIDEO
+    if (app_config.vcapture_dev != PJSUA_INVALID_ID ||
+        app_config.vrender_dev  != PJSUA_INVALID_ID) 
+    {
+	//status = pjsua_vid_set_dev(app_config.vcapture_dev,
+	//			   app_config.vrender_dev);
+	PJ_TODO(vid_implement_pjsua_vid_set_dev);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+#endif
 
     return PJ_SUCCESS;
@@ -5297,5 +5797,9 @@
     }
 
+#if DISABLED_FOR_TICKET_1185
     return pjsua_media_transports_attach(tp, i, PJ_TRUE);
-}
-
+#else
+    return PJ_ENOTSUP;
+#endif
+}
+
