Index: /pjproject/branches/projects/2.0-dev/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip-apps/src/pjsua/pjsua_app.c	(revision 3608)
+++ /pjproject/branches/projects/2.0-dev/pjsip-apps/src/pjsua/pjsua_app.c	(revision 3609)
@@ -312,5 +312,5 @@
     puts  ("  --extra-audio       Add one more audio stream");
 
-#if PJMEDIA_HAS_VIDEO
+#if PJSUA_HAS_VIDEO
     puts  ("");
     puts  ("Video Options:");
@@ -2646,4 +2646,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)
+{
+}
 
 /*
@@ -2655,120 +2766,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);
     }
 }
@@ -3256,5 +3272,9 @@
     puts("|                              |  V  Adjust audio Volume  |  f  Save config   |");
     puts("|  S  Send arbitrary REQUEST   | Cp  Codec priorities     |                   |");
-    puts("+------------------------------+--------------------------+-------------------+");
+    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("+=============================================================================+");
@@ -3272,4 +3292,26 @@
 }
 
+/* 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        Enable/disable incoming video for current call    |");
+    puts("| vid call tx on|off        Enable/disable video tx for 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
+}
 
 /*
@@ -3550,5 +3592,5 @@
     }
 
-#if PJMEDIA_HAS_VIDEO
+#if PJSUA_HAS_VIDEO
     puts("");
     printf("List of video codecs:\n");
@@ -3592,5 +3634,5 @@
     status = pjsua_codec_set_priority(pj_cstr(&id, codec), 
 				      (pj_uint8_t)new_prio);
-#if PJMEDIA_HAS_VIDEO
+#if PJSUA_HAS_VIDEO
     if (status != PJ_SUCCESS) {
 	status = pjsua_vid_codec_set_priority(pj_cstr(&id, codec), 
@@ -3601,4 +3643,208 @@
 	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) {
+	pj_bool_t tx = (strcmp(argv[2], "tx") == 0);
+	pj_bool_t on = (strcmp(argv[3], "on") == 0);
+
+	PJ_TODO(vid_enable_disable_video_on_call);
+	PJ_LOG(1,(THIS_FILE, "Not implemented"));
+    } else if (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) {
+	    PJ_LOG(1,(THIS_FILE, "Not implemented"));
+	    PJ_TODO(vid_list_window);
+	} 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[4]);
+	    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[4]);
+	    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 */
 
 
@@ -4032,9 +4278,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);
 
@@ -5090,5 +5342,5 @@
     for (i=0; i<app_config.codec_dis_cnt; ++i) {
 	pjsua_codec_set_priority(&app_config.codec_dis[i],PJMEDIA_CODEC_PRIO_DISABLED);
-#if PJMEDIA_HAS_VIDEO
+#if PJSUA_HAS_VIDEO
 	pjsua_vid_codec_set_priority(&app_config.codec_dis[i],PJMEDIA_CODEC_PRIO_DISABLED);
 #endif
@@ -5099,5 +5351,5 @@
 	pjsua_codec_set_priority(&app_config.codec_arg[i],
 				 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
-#if PJMEDIA_HAS_VIDEO
+#if PJSUA_HAS_VIDEO
 	pjsua_vid_codec_set_priority(&app_config.codec_arg[i],
 				     (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
@@ -5138,10 +5390,11 @@
     }
 
-#if PJMEDIA_HAS_VIDEO
+#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);
+	//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;
Index: /pjproject/branches/projects/2.0-dev/pjsip/build/Makefile
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/build/Makefile	(revision 3608)
+++ /pjproject/branches/projects/2.0-dev/pjsip/build/Makefile	(revision 3609)
@@ -76,5 +76,6 @@
 export PJSUA_LIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
 			pjsua_acc.o pjsua_call.o pjsua_core.o \
-			pjsua_im.o pjsua_media.o pjsua_pres.o
+			pjsua_im.o pjsua_media.o pjsua_pres.o \
+			pjsua_dump.o pjsua_vid.o
 export PJSUA_LIB_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS)
 
Index: /pjproject/branches/projects/2.0-dev/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/include/pjsua-lib/pjsua.h	(revision 3608)
+++ /pjproject/branches/projects/2.0-dev/pjsip/include/pjsua-lib/pjsua.h	(revision 3609)
@@ -325,4 +325,10 @@
 #endif
 
+/**
+ * Is video enabled.
+ */
+#ifndef PJSUA_HAS_VIDEO
+#   define PJSUA_HAS_VIDEO		PJMEDIA_HAS_VIDEO
+#endif
 
 /**
@@ -2439,9 +2445,56 @@
      * Maximum number of simultaneous active video streams to be allowed
      * for calls on this account. Setting this to zero will disable video
-     * in calls on this account.
-     *
-     * Default: 0
+     * in calls on this account, regardless of other video settings.
+     *
+     * Default: 1
      */
     unsigned         max_video_cnt;
+
+    /**
+     * Specify whether incoming video should be shown to screen by default.
+     * This applies to incoming call (INVITE), incoming re-INVITE, and
+     * incoming UPDATE requests.
+     *
+     * Regardless of this setting, application can detect incoming video
+     * by implementing \a on_call_media_state() callback and enumerating
+     * the media stream(s) with #pjsua_call_get_info(). Once incoming
+     * video is recognised, application may retrieve the window associated
+     * with the incoming video and show or hide it with
+     * #pjsua_vid_win_set_show().
+     *
+     * Default: PJ_FALSE
+     */
+    pj_bool_t        vid_in_auto_show;
+
+    /**
+     * Specify whether outgoing video should be activated by default when
+     * making outgoing calls and/or when incoming video is detected. This
+     * applies to incoming and outgoing calls, incoming re-INVITE, and
+     * incoming UPDATE. If the setting is non-zero, outgoing video
+     * transmission will be started as soon as response to these requests
+     * is sent (or received).
+     *
+     * Regardless of the value of this setting, application can start and
+     * stop outgoing video transmission with #pjsua_call_set_vid_out().
+     *
+     * Default: PJ_FALSE
+     */
+    pj_bool_t        vid_out_auto_transmit;
+
+    /**
+     * Specify the default capture device to be used by this account. If
+     * \a vid_out_auto_transmit is enabled, this device will be used for
+     * capturing video.
+     *
+     * Default: PJMEDIA_VID_DEFAULT_CAPTURE_DEV
+     */
+    pjmedia_vid_dev_index vid_cap_dev;
+
+    /**
+     * Specify the default rendering device to be used by this account.
+     *
+     * Default: PJMEDIA_VID_DEFAULT_RENDER_DEV
+     */
+    pjmedia_vid_dev_index vid_rend_dev;
 
     /**
@@ -2976,4 +3029,15 @@
 #endif
 
+/**
+ * Maximum active video windows
+ */
+#ifndef PJSUA_MAX_VID_WINS
+#   define PJSUA_MAX_VID_WINS	    16
+#endif
+
+/**
+ * Video window ID.
+ */
+typedef int pjsua_vid_win_id;
 
 
@@ -3084,13 +3148,22 @@
 	    /** Audio stream */
 	    struct {
-		pjsua_conf_port_id   conf_slot; /**< The conference port
-						     number for the call.   */
-	    } audio;
+		/** The conference port number for the call.  */
+		pjsua_conf_port_id   conf_slot;
+	    } aud;
 
 	    /** Video stream */
 	    struct {
-		pjmedia_vid_port    *capturer;  /**< Video capturer.	    */
-		pjmedia_vid_port    *renderer;  /**< Video renderer.	    */
-	    } video;
+		/**
+		 * The window id for incoming video, if any, or
+		 * PJSUA_INVALID_ID.
+		 */
+		pjsua_vid_win_id     win_in;
+
+		/** The video capture device for outgoing transmission,
+		 *  if any, or PJMEDIA_VID_INVALID_DEV
+		 */
+		pjmedia_vid_dev_index	cap_dev;
+
+	    } vid;
 	} stream;
 
@@ -3118,4 +3191,37 @@
 
 
+/**
+ * Media stream info.
+ */
+typedef struct pjsua_stream_info
+{
+    /** Media type of this stream. */
+    pjmedia_type type;
+
+    /** Stream info (union). */
+    union {
+	/** Audio stream info */
+	pjmedia_stream_info	aud;
+
+	/** Video stream info */
+	pjmedia_vid_stream_info	vid;
+    } info;
+
+} pjsua_stream_info;
+
+
+/**
+ * Media stream statistic.
+ */
+typedef struct pjsua_stream_stat
+{
+    /** RTCP statistic. */
+    pjmedia_rtcp_stat	rtcp;
+
+    /** Jitter buffer statistic. */
+    pjmedia_jb_state	jbuf;
+
+} pjsua_stream_stat;
+
 
 /**
@@ -3191,36 +3297,10 @@
 
 
-#if DISABLED_FOR_TICKET_1185
-/**
- * Retrieve the media session associated with this call. Note that the media
- * session may not be available depending on the current call's media status
- * (the pjsua_call_media_status information in pjsua_call_info). Application
- * may use the media session to retrieve more detailed information about the
- * call's media.
+/**
+ * Get the conference port identification associated with the call.
  *
  * @param call_id	Call identification.
  *
- * @return		Call media session.
- */
-PJ_DECL(pjmedia_session*) pjsua_call_get_media_session(pjsua_call_id call_id);
-
-/**
- * Retrieve the media transport instance that is used for this call. 
- * Application may use the media transport to query more detailed information
- * about the media transport.
- *
- * @param cid		Call identification (the call_id).
- *
- * @return		Call media transport.
- */
-PJ_DECL(pjmedia_transport*) pjsua_call_get_media_transport(pjsua_call_id cid);
-#endif /* DISABLED_FOR_TICKET_1185 */
-
-/**
- * Get the conference port identification associated with the call.
- *
- * @param call_id	Call identification.
- *
- * @return		Conference port ID, or PJSUA_INVALID_ID when the 
+ * @return		Conference port ID, or PJSUA_INVALID_ID when the
  *			media has not been established or is not active.
  */
@@ -3571,4 +3651,95 @@
 				     unsigned maxlen,
 				     const char *indent);
+
+/**
+ * Get the media stream index of the default video stream in the call.
+ * Typically this will just retrieve the stream index of the first
+ * activated video stream in the call.
+ *
+ * @param call_id	Call identification.
+ *
+ * @return		The media stream index or -1 if no video stream
+ * 			is present in the call.
+ */
+PJ_DECL(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id);
+
+/**
+ * Start, stop, and/or manipulate video transmission for the specified
+ * call. This would trigger a re-INVITE or UPDATE to be sent for the
+ * call. This function may add, remove, or modify existing video media
+ * stream, depending on the media index specified (the \a med_idx argument).
+ *
+ * To add a new or edit existing video stream (for transmission), specify
+ * a valid video capture device ID or PJMEDIA_VID_DEFAULT_CAPTURE_DEV in
+ * the \a cap_dev argument. If \a med_idx is set to default stream (-1),
+ * then the function will modify existing video stream if one exists, or
+ * add a new one if it doesn't. If \a med_idx is set to a specific stream
+ * index, the function will modify that video stream. Otherwise if \a med_idx
+ * is set to value larger than the current media count, a new video stream
+ * will be added to the call.
+ *
+ * To remove an existing video stream, specify PJMEDIA_VID_INVALID_DEV in
+ * \a cap_dev argument. If \a med_idx is set to default stream (-1), this
+ * will remove the default/first video stream in the call, otherwise
+ * application can put a specific value to request removal of that particular
+ * video stream.
+ *
+ * @param call_id	Call identification.
+ * @param med_idx	The media stream index. Currently the value MUST
+ * 			be -1 to denote the default video stream in the
+ * 			call.
+ * @param cap_dev	To add or modify existing video media stream,
+ * 			specify PJMEDIA_VID_DEFAULT_CAPTURE_DEV to use
+ * 			the default capture device as configured in the
+ * 			account, or specify a specific capture device ID.
+ * 			To disable an existing video stream, specify
+ * 			PJMEDIA_VID_INVALID_DEV for this parameter.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pjsua_call_set_vid_out(pjsua_call_id call_id,
+                                            int med_idx,
+                                            pjmedia_vid_dev_index cap_dev);
+
+/**
+ * Get media stream info for the specified media index.
+ *
+ * @param call_id	The call identification.
+ * @param med_idx	Media stream index.
+ * @param psi		To be filled with the stream info.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pjsua_call_get_stream_info(pjsua_call_id call_id,
+                                                unsigned med_idx,
+                                                pjsua_stream_info *psi);
+
+/**
+ *  Get media stream statistic for the specified media index.
+ *
+ * @param call_id	The call identification.
+ * @param med_idx	Media stream index.
+ * @param psi		To be filled with the stream statistic.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pjsua_call_get_stream_stat(pjsua_call_id call_id,
+                                                unsigned med_idx,
+                                                pjsua_stream_stat *stat);
+
+/**
+ * Get media transport info for the specified media index.
+ *
+ * @param call_id	The call identification.
+ * @param med_idx	Media stream index.
+ * @param t		To be filled with the transport info.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pjsua_call_get_transport_info(pjsua_call_id call_id,
+                                                   unsigned med_idx,
+                                                   pjmedia_transport_info *t);
+
+
 
 /**
@@ -4448,6 +4619,4 @@
 
 
-
-
 /**
  * Get maxinum number of conference ports.
@@ -4940,96 +5109,4 @@
 
 /*****************************************************************************
- * Video devices.
- */
-
-/**
- * Enum all video devices installed in the system.
- *
- * @param info		Array of info to be initialized.
- * @param count		On input, specifies max elements in the array.
- *			On return, it contains actual number of elements
- *			that have been initialized.
- *
- * @return		PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
-					 unsigned *count);
-
-
-/**
- * Get currently active video devices. If video devices has not been created
- * (for example when pjsua_start() is not called), it is possible that
- * the function returns PJ_SUCCESS with -1 as device IDs.
- *
- * @param capture_dev   On return it will be filled with device ID of the 
- *			capture device.
- * @param render_dev	On return it will be filled with device ID of the 
- *			device ID of the render device.
- *
- * @return		PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_get_dev(int *capture_dev, int *render_dev);
-
-
-/**
- * Select video device for the next video sessions.
- *
- * @param capture_dev   Device ID of the capture device.
- * @param render_dev	Device ID of the render device.
- *
- * @return		PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_set_dev(int capture_dev, int render_dev);
-
-
-/**
- * Configure video device setting to the video device being used. If video 
- * device is currently active, the function will forward the setting to the
- * video device instance to be applied immediately, if it supports it. 
- *
- * The setting will be saved for future opening of the video device, if the 
- * "keep" argument is set to non-zero. If the video device is currently
- * inactive, and the "keep" argument is false, this function will return
- * error.
- * 
- * Note that in case the setting is kept for future use, it will be applied
- * to any devices, even when application has changed the video device to be
- * used.
- *
- * See also #pjmedia_vid_dev_stream_set_cap() for more information about
- * setting an video device capability.
- *
- * @param cap		The video device setting to change.
- * @param pval		Pointer to value. Please see #pjmedia_vid_dev_cap
- *			documentation about the type of value to be 
- *			supplied for each setting.
- * @param keep		Specify whether the setting is to be kept for future
- *			use.
- *
- * @return		PJ_SUCCESS on success or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_set_setting(pjmedia_vid_dev_cap cap,
-					   const void *pval,
-					   pj_bool_t keep);
-
-/**
- * Retrieve a video device setting. If video device is currently active,
- * the function will forward the request to the video device. If video device
- * is currently inactive, and if application had previously set the setting
- * and mark the setting as kept, then that setting will be returned.
- * Otherwise, this function will return error.
- *
- * @param cap		The video device setting to retrieve.
- * @param pval		Pointer to receive the value. 
- *			Please see #pjmedia_vid_dev_cap documentation about
- *			the type of value to be supplied for each setting.
- *
- * @return		PJ_SUCCESS on success or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_get_setting(pjmedia_vid_dev_cap cap,
-					   void *pval);
-
-
-/*****************************************************************************
  * Codecs.
  */
@@ -5088,63 +5165,4 @@
 					    const pjmedia_codec_param *param);
 
-/*****************************************************************************
- * Video codecs.
- */
-
-/**
- * Enum all supported video codecs in the system.
- *
- * @param id		Array of ID to be initialized.
- * @param count		On input, specifies max elements in the array.
- *			On return, it contains actual number of elements
- *			that have been initialized.
- *
- * @return		PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
-					    unsigned *count );
-
-
-/**
- * Change video codec priority.
- *
- * @param codec_id	Codec ID, which is a string that uniquely identify
- *			the codec (such as "H263/90000"). Please see pjsua
- *			manual or pjmedia codec reference for details.
- * @param priority	Codec priority, 0-255, where zero means to disable
- *			the codec.
- *
- * @return		PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
-						   pj_uint8_t priority );
-
-
-/**
- * Get video codec parameters.
- *
- * @param codec_id	Codec ID.
- * @param param		Structure to receive video codec parameters.
- *
- * @return		PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_codec_get_param(
-					const pj_str_t *codec_id,
-					pjmedia_vid_codec_param *param);
-
-
-/**
- * Set video codec parameters.
- *
- * @param codec_id	Codec ID.
- * @param param		Codec parameter to set. Set to NULL to reset
- *			codec parameter to library default settings.
- *
- * @return		PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_codec_set_param( 
-					const pj_str_t *codec_id,
-					const pjmedia_vid_codec_param *param);
-
 
 #if DISABLED_FOR_TICKET_1185
@@ -5159,5 +5177,5 @@
  * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
-PJ_DECL(pj_status_t) 
+PJ_DECL(pj_status_t)
 pjsua_media_transports_create(const pjsua_transport_config *cfg);
 
@@ -5176,5 +5194,5 @@
  * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
-PJ_DECL(pj_status_t) 
+PJ_DECL(pj_status_t)
 pjsua_media_transports_attach( pjsua_media_transport tp[],
 			       unsigned count,
@@ -5183,4 +5201,5 @@
 
 
+/* end of MEDIA API */
 /**
  * @}
@@ -5188,9 +5207,250 @@
 
 
-
+/*****************************************************************************
+ * VIDEO API
+ */
+
+
+/**
+ * @defgroup PJSUA_LIB_VIDEO PJSUA-API Video
+ * @ingroup PJSUA_LIB
+ * @brief Video support
+ * @{
+ */
+
+/*
+ * Video devices API
+ */
+
+/**
+ * Get the number of video devices installed in the system.
+ *
+ * @return		The number of devices.
+ */
+PJ_DECL(unsigned) pjsua_vid_dev_count(void);
+
+/**
+ * Retrieve the video device info for the specified device index.
+ *
+ * @param id		The device index.
+ * @param vdi		Device info to be initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,
+                                            pjmedia_vid_dev_info *vdi);
+
+/**
+ * Enum all video devices installed in the system.
+ *
+ * @param info		Array of info to be initialized.
+ * @param count		On input, specifies max elements in the array.
+ *			On return, it contains actual number of elements
+ *			that have been initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
+					 unsigned *count);
+
+
+/*
+ * Video preview API
+ */
+
+/**
+ * Parameters for starting video preview with pjsua_vid_preview_start().
+ * Application should initialize this structure with
+ * pjsua_vid_preview_param_default().
+ */
+typedef struct pjsua_vid_preview_param
+{
+    /**
+     * Device ID for the video renderer to be used for rendering the
+     * capture stream for preview.
+     */
+    pjmedia_vid_dev_index	rend_id;
+} pjsua_vid_preview_param;
+
+
+/**
+ * Start video preview window for the specified capture device.
+ *
+ * @param id		The capture device ID where its preview will be
+ * 			started.
+ * @param prm		Optional video preview parameters. Specify NULL
+ * 			to use default values.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id,
+                                             pjsua_vid_preview_param *prm);
+
+/**
+ * Get the preview window handle associated with the capture device, if any.
+ *
+ * @param id		The capture device ID.
+ *
+ * @return		The window ID of the preview window for the
+ * 			specified capture device ID, or NULL if preview
+ * 			does not exist.
+ */
+PJ_DECL(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id);
+
+/**
+ * Stop video preview.
+ *
+ * @param id		The capture device ID.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id);
+
+
+/*
+ * Video window manipulation API.
+ */
+
+/**
+ * This structure describes video window info.
+ */
+typedef struct pjsua_vid_win_info
+{
+    /**
+     * Window show status. The window is hidden if false.
+     */
+    pj_bool_t	show;
+
+    /**
+     * Window position.
+     */
+    pjmedia_coord pos;
+
+    /**
+     * Window size.
+     */
+    pjmedia_rect_size size;
+
+} pjsua_vid_win_info;
+
+
+/**
+ * Get window info.
+ *
+ * @param wid		The video window ID.
+ * @param wi		The video window info to be initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_win_get_info(pjsua_vid_win_id wid,
+                                            pjsua_vid_win_info *wi);
+
+/**
+ * Show or hide window.
+ *
+ * @param wid		The video window ID.
+ * @param show		Set to PJ_TRUE to show the window, PJ_FALSE to
+ * 			hide the window.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_win_set_show(pjsua_vid_win_id wid,
+                                            pj_bool_t show);
+
+/**
+ * Set video window position.
+ *
+ * @param wid		The video window ID.
+ * @param pos		The window position.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_win_set_pos(pjsua_vid_win_id wid,
+                                           const pjmedia_coord *pos);
+
+/**
+ * Resize window.
+ *
+ * @param wid		The video window ID.
+ * @param size		The new window size.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_win_set_size(pjsua_vid_win_id wid,
+                                            const pjmedia_rect_size *size);
+
+
+
+/*
+ * Video codecs API
+ */
+
+/**
+ * Enum all supported video codecs in the system.
+ *
+ * @param id		Array of ID to be initialized.
+ * @param count		On input, specifies max elements in the array.
+ *			On return, it contains actual number of elements
+ *			that have been initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
+					    unsigned *count );
+
+
+/**
+ * Change video codec priority.
+ *
+ * @param codec_id	Codec ID, which is a string that uniquely identify
+ *			the codec (such as "H263/90000"). Please see pjsua
+ *			manual or pjmedia codec reference for details.
+ * @param priority	Codec priority, 0-255, where zero means to disable
+ *			the codec.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
+						   pj_uint8_t priority );
+
+
+/**
+ * Get video codec parameters.
+ *
+ * @param codec_id	Codec ID.
+ * @param param		Structure to receive video codec parameters.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_codec_get_param(
+					const pj_str_t *codec_id,
+					pjmedia_vid_codec_param *param);
+
+
+/**
+ * Set video codec parameters.
+ *
+ * @param codec_id	Codec ID.
+ * @param param		Codec parameter to set. Set to NULL to reset
+ *			codec parameter to library default settings.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_codec_set_param( 
+					const pj_str_t *codec_id,
+					const pjmedia_vid_codec_param *param);
+
+
+
+/* end of VIDEO API */
 /**
  * @}
  */
 
+
+/**
+ * @}
+ */
+
 PJ_END_DECL
 
Index: /pjproject/branches/projects/2.0-dev/pjsip/include/pjsua-lib/pjsua_internal.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 3608)
+++ /pjproject/branches/projects/2.0-dev/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 3609)
@@ -304,4 +304,20 @@
 } pjsua_stun_resolve;
 
+typedef enum pjsua_vid_win_type
+{
+    PJSUA_WND_TYPE_NONE,
+    PJSUA_WND_TYPE_PREVIEW,
+    PJSUA_WND_TYPE_STREAM
+} pjsua_vid_win_type;
+
+typedef struct pjsua_vid_win
+{
+    pjsua_vid_win_type		 type;		/**< Type.		*/
+    pj_pool_t			*pool;		/**< Own pool.		*/
+    pjsua_call_id	 	 call_id;	/**< Owner call or -1	*/
+    pjmedia_vid_port		*vp_cap;	/**< Capture vidport.	*/
+    pjmedia_vid_port		*vp_rend;	/**< Renderer vidport	*/
+    pjmedia_vid_dev_index	 preview_cap_id;/* Capture dev id	*/
+} pjsua_vid_win;
 
 /**
@@ -398,4 +414,9 @@
     unsigned		 rec_cnt;   /**< Number of file recorders.	*/
     pjsua_file_data	 recorder[PJSUA_MAX_RECORDERS];/**< Array of recs.*/
+
+    /* Video windows */
+#if PJSUA_HAS_VIDEO
+    pjsua_vid_win	 win[PJSUA_MAX_VID_WINS]; /**< Array of windows	*/
+#endif
 };
 
@@ -632,4 +653,25 @@
                 char *buf, pj_size_t size);
 
+/*
+ * Video
+ */
+pj_status_t pjsua_vid_subsys_init(void);
+pj_status_t pjsua_vid_subsys_start(void);
+pj_status_t pjsua_vid_subsys_destroy(void);
+
+PJ_INLINE(void) pjsua_vid_win_reset(pjsua_vid_win_id wid)
+{
+#if PJSUA_HAS_VIDEO
+    pjsua_vid_win *w = &pjsua_var.win[wid];
+    pj_pool_t *pool = w->pool;
+
+    pj_bzero(w, sizeof(*w));
+    if (pool) pj_pool_reset(pool);
+    w->call_id = PJSUA_INVALID_ID;
+    w->pool = pool;
+    w->preview_cap_id = PJMEDIA_VID_INVALID_DEV;
+#endif
+}
+
 
 PJ_END_DECL
Index: /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_call.c	(revision 3608)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_call.c	(revision 3609)
@@ -1117,27 +1117,4 @@
 
 
-#if DISABLED_FOR_TICKET_1185
-/*
- * Retrieve the media session associated with this call.
- */
-PJ_DEF(pjmedia_session*) pjsua_call_get_media_session(pjsua_call_id call_id)
-{
-    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, 
-		     NULL);
-    return pjsua_var.calls[call_id].session;
-}
-
-
-/*
- * Retrieve the media transport instance that is used for this call.
- */
-PJ_DEF(pjmedia_transport*) pjsua_call_get_media_transport(pjsua_call_id cid)
-{
-    PJ_ASSERT_RETURN(cid>=0 && cid<(int)pjsua_var.ua_cfg.max_calls, 
-		     NULL);
-    return pjsua_var.calls[cid].tp;
-}
-#endif /* Removed in 2.0 */
-
 /* Acquire lock to the specified call_id */
 pj_status_t acquire_call(const char *title,
@@ -1337,11 +1314,11 @@
 
 	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
-	    info->media[info->media_cnt].stream.audio.conf_slot = 
+	    info->media[info->media_cnt].stream.aud.conf_slot =
 						call_med->strm.a.conf_slot;
 	} else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
-	    info->media[info->media_cnt].stream.video.capturer =
-						call_med->strm.v.capturer;
-	    info->media[info->media_cnt].stream.video.renderer =
-						call_med->strm.v.renderer;
+	    PJ_TODO(vid_fill_in_call_info);
+	    info->media[info->media_cnt].stream.vid.win_in = PJSUA_INVALID_ID;
+	    info->media[info->media_cnt].stream.vid.cap_dev =
+		    PJMEDIA_VID_INVALID_DEV;
 	} else {
 	    continue;
@@ -2183,927 +2160,4 @@
 }
 
-
-const char *good_number(char *buf, pj_int32_t val)
-{
-    if (val < 1000) {
-	pj_ansi_sprintf(buf, "%d", val);
-    } else if (val < 1000000) {
-	pj_ansi_sprintf(buf, "%d.%dK", 
-			val / 1000,
-			(val % 1000) / 100);
-    } else {
-	pj_ansi_sprintf(buf, "%d.%02dM", 
-			val / 1000000,
-			(val % 1000000) / 10000);
-    }
-
-    return buf;
-}
-
-static unsigned dump_media_stat(const char *indent, 
-				char *buf, unsigned maxlen,
-				const pjmedia_rtcp_stat *stat,
-				const char *rx_info, const char *tx_info)
-{
-    char last_update[64];
-    char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32];
-    pj_time_val media_duration, now;
-    char *p = buf, *end = buf+maxlen;
-    int len;
-
-    if (stat->rx.update_cnt == 0)
-	strcpy(last_update, "never");
-    else {
-	pj_gettimeofday(&now);
-	PJ_TIME_VAL_SUB(now, stat->rx.update);
-	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
-		now.sec / 3600,
-		(now.sec % 3600) / 60,
-		now.sec % 60,
-		now.msec);
-    }
-
-    pj_gettimeofday(&media_duration);
-    PJ_TIME_VAL_SUB(media_duration, stat->start);
-    if (PJ_TIME_VAL_MSEC(media_duration) == 0)
-	media_duration.msec = 1;
-
-    len = pj_ansi_snprintf(p, end-p,
-	   "%s     RX %s last update:%s\n"
-	   "%s        total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
-	   "%s        pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n"
-	   "%s              (msec)    min     avg     max     last    dev\n"
-	   "%s        loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
-	   "%s        jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
-#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
-	   "%s        raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
-#endif
-#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
-	   "%s        IPDV       : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
-#endif
-	   "%s",
-	   indent,
-	   rx_info? rx_info : "",
-	   last_update,
-
-	   indent,
-	   good_number(packets, stat->rx.pkt),
-	   good_number(bytes, stat->rx.bytes),
-	   good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40),
-	   good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
-	   good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->rx.bytes + stat->rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
-	   indent,
-	   stat->rx.loss,
-	   (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
-	   stat->rx.discard, 
-	   (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
-	   stat->rx.dup, 
-	   (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
-	   stat->rx.reorder, 
-	   (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
-	   indent, indent,
-	   stat->rx.loss_period.min / 1000.0, 
-	   stat->rx.loss_period.mean / 1000.0, 
-	   stat->rx.loss_period.max / 1000.0,
-	   stat->rx.loss_period.last / 1000.0,
-	   pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0,
-	   indent,
-	   stat->rx.jitter.min / 1000.0,
-	   stat->rx.jitter.mean / 1000.0,
-	   stat->rx.jitter.max / 1000.0,
-	   stat->rx.jitter.last / 1000.0,
-	   pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0,
-#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
-	   indent,
-	   stat->rx_raw_jitter.min / 1000.0,
-	   stat->rx_raw_jitter.mean / 1000.0,
-	   stat->rx_raw_jitter.max / 1000.0,
-	   stat->rx_raw_jitter.last / 1000.0,
-	   pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0,
-#endif
-#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
-	   indent,
-	   stat->rx_ipdv.min / 1000.0,
-	   stat->rx_ipdv.mean / 1000.0,
-	   stat->rx_ipdv.max / 1000.0,
-	   stat->rx_ipdv.last / 1000.0,
-	   pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0,
-#endif
-	   ""
-	   );
-
-    if (len < 1 || len > end-p) {
-	*p = '\0';
-	return (p-buf);
-    }
-    p += len;
-
-    if (stat->tx.update_cnt == 0)
-	strcpy(last_update, "never");
-    else {
-	pj_gettimeofday(&now);
-	PJ_TIME_VAL_SUB(now, stat->tx.update);
-	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
-		now.sec / 3600,
-		(now.sec % 3600) / 60,
-		now.sec % 60,
-		now.msec);
-    }
-
-    len = pj_ansi_snprintf(p, end-p,
-	   "%s     TX %s last update:%s\n"
-	   "%s        total %spkt %sB (%sB +IP hdr) @avg %sbps/%sbps\n"
-	   "%s        pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
-	   "%s              (msec)    min     avg     max     last    dev \n"
-	   "%s        loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
-	   "%s        jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
-	   indent,
-	   tx_info,
-	   last_update,
-
-	   indent,
-	   good_number(packets, stat->tx.pkt),
-	   good_number(bytes, stat->tx.bytes),
-	   good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40),
-	   good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
-	   good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->tx.bytes + stat->tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
-
-	   indent,
-	   stat->tx.loss,
-	   (stat->tx.loss? stat->tx.loss * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
-	   stat->tx.dup, 
-	   (stat->tx.dup? stat->tx.dup * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
-	   stat->tx.reorder, 
-	   (stat->tx.reorder? stat->tx.reorder * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
-
-	   indent, indent,
-	   stat->tx.loss_period.min / 1000.0, 
-	   stat->tx.loss_period.mean / 1000.0, 
-	   stat->tx.loss_period.max / 1000.0,
-	   stat->tx.loss_period.last / 1000.0,
-	   pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0,
-	   indent,
-	   stat->tx.jitter.min / 1000.0,
-	   stat->tx.jitter.mean / 1000.0,
-	   stat->tx.jitter.max / 1000.0,
-	   stat->tx.jitter.last / 1000.0,
-	   pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0
-	   );
-
-    if (len < 1 || len > end-p) {
-	*p = '\0';
-	return (p-buf);
-    }
-    p += len;
-
-    len = pj_ansi_snprintf(p, end-p,
-	   "%s     RTT msec      : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
-	   indent,
-	   stat->rtt.min / 1000.0,
-	   stat->rtt.mean / 1000.0,
-	   stat->rtt.max / 1000.0,
-	   stat->rtt.last / 1000.0,
-	   pj_math_stat_get_stddev(&stat->rtt) / 1000.0
-	   );
-    if (len < 1 || len > end-p) {
-	*p = '\0';
-	return (p-buf);
-    }
-    p += len;
-
-    return (p-buf);
-}
-
-
-/* Dump media session */
-static void dump_media_session(const char *indent, 
-			       char *buf, unsigned maxlen,
-			       pjsua_call *call)
-{
-    unsigned i;
-    char *p = buf, *end = buf+maxlen;
-    int len;
-
-    for (i=0; i<call->med_cnt; ++i) {
-	pjsua_call_media *call_med = &call->media[i];
-	pjmedia_rtcp_stat stat;
-	pj_bool_t has_stat;
-	pjmedia_transport_info tp_info;
-	char rem_addr_buf[80];
-	char codec_info[32] = {'0'};
-	char rx_info[80] = {'\0'};
-	char tx_info[80] = {'\0'};
-	const char *rem_addr;
-	const char *dir_str;
-	const char *media_type_str;
-
-	switch (call_med->type) {
-	case PJMEDIA_TYPE_AUDIO:
-	    media_type_str = "audio";
-	    break;
-	case PJMEDIA_TYPE_VIDEO:
-	    media_type_str = "video";
-	    break;
-	case PJMEDIA_TYPE_APPLICATION:
-	    media_type_str = "application";
-	    break;
-	default:
-	    media_type_str = "unknown";
-	    break;
-	}
-
-	/* Check if the stream is deactivated */
-	if (call_med->tp == NULL ||
-	    (!call_med->strm.a.stream && !call_med->strm.v.stream))
-	{
-	    len = pj_ansi_snprintf(p, end-p,
-		      "%s #%d %s deactivated\n",
-		      indent, i, media_type_str);
-	    if (len < 1 || len > end-p) {
-		*p = '\0';
-		return;
-	    }
-
-	    p += len;
-	    continue;
-	}
-
-	pjmedia_transport_info_init(&tp_info);
-	pjmedia_transport_get_info(call_med->tp, &tp_info);
-
-	// rem_addr will contain actual address of RTP originator, instead of
-	// remote RTP address specified by stream which is fetched from the SDP.
-	// Please note that we are assuming only one stream per call.
-	//rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr,
-	//			     rem_addr_buf, sizeof(rem_addr_buf), 3);
-	if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) {
-	    rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf, 
-					 sizeof(rem_addr_buf), 3);
-	} else {
-	    pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-");
-	    rem_addr = rem_addr_buf;
-	}
-
-	if (call_med->dir == PJMEDIA_DIR_NONE) {
-	    /* To handle when the stream that is currently being paused
-	     * (http://trac.pjsip.org/repos/ticket/1079)
-	     */
-	    dir_str = "inactive";
-	} else if (call_med->dir == PJMEDIA_DIR_ENCODING)
-	    dir_str = "sendonly";
-	else if (call_med->dir == PJMEDIA_DIR_DECODING)
-	    dir_str = "recvonly";
-	else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING)
-	    dir_str = "sendrecv";
-	else
-	    dir_str = "inactive";
-
-	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
-	    pjmedia_stream *stream = call_med->strm.a.stream;
-	    pjmedia_stream_info info;
-
-	    pjmedia_stream_get_stat(stream, &stat);
-	    has_stat = PJ_TRUE;
-
-	    pjmedia_stream_get_info(stream, &info);
-	    pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz",
-			     (int)info.fmt.encoding_name.slen,
-			     info.fmt.encoding_name.ptr,
-			     info.fmt.clock_rate / 1000);
-	    pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,",
-			     info.fmt.pt);
-	    pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,",
-			     info.tx_pt,
-			     info.param->setting.frm_per_pkt*
-			     info.param->info.frm_ptime);
-	} else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
-	    pjmedia_vid_stream *stream = call_med->strm.v.stream;
-	    pjmedia_vid_stream_info info;
-
-	    pjmedia_vid_stream_get_stat(stream, &stat);
-	    has_stat = PJ_TRUE;
-
-	    pjmedia_vid_stream_get_info(stream, &info);
-	    pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s",
-	                     (int)info.codec_info.encoding_name.slen,
-			     info.codec_info.encoding_name.ptr);
-	    if (call_med->dir & PJMEDIA_DIR_DECODING) {
-		pjmedia_video_format_detail *vfd;
-		vfd = pjmedia_format_get_video_format_detail(
-					&info.codec_param->dec_fmt, PJ_TRUE);
-		pj_ansi_snprintf(rx_info, sizeof(rx_info),
-				 "pt=%d, size=%dx%d, fps=%.2f,",
-				 info.rx_pt,
-				 vfd->size.w, vfd->size.h,
-				 vfd->fps.num*1.0/vfd->fps.denum);
-	    }
-	    if (call_med->dir & PJMEDIA_DIR_ENCODING) {
-		pjmedia_video_format_detail *vfd;
-		vfd = pjmedia_format_get_video_format_detail(
-					&info.codec_param->enc_fmt, PJ_TRUE);
-		pj_ansi_snprintf(tx_info, sizeof(tx_info),
-				 "pt=%d, size=%dx%d, fps=%.2f,",
-				 info.tx_pt,
-				 vfd->size.w, vfd->size.h,
-				 vfd->fps.num*1.0/vfd->fps.denum);
-	    }
-	} else {
-	    has_stat = PJ_FALSE;
-	}
-
-	len = pj_ansi_snprintf(p, end-p,
-		  "%s  #%d %s%s, %s, peer=%s\n",
-		  indent,
-		  call_med->idx,
-		  media_type_str,
-		  codec_info,
-		  dir_str,
-		  rem_addr);
-	if (len < 1 || len > end-p) {
-	    *p = '\0';
-	    return;
-	}
-	p += len;
-
-	/* Get and ICE SRTP status */
-	if (call_med->tp) {
-	    pjmedia_transport_info tp_info;
-
-	    pjmedia_transport_info_init(&tp_info);
-	    pjmedia_transport_get_info(call_med->tp, &tp_info);
-	    if (tp_info.specific_info_cnt > 0) {
-		unsigned j;
-		for (j = 0; j < tp_info.specific_info_cnt; ++j) {
-		    if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
-		    {
-			pjmedia_srtp_info *srtp_info =
-				    (pjmedia_srtp_info*) tp_info.spc_info[j].buffer;
-
-			len = pj_ansi_snprintf(p, end-p,
-					       "   %s  SRTP status: %s Crypto-suite: %s",
-					       indent,
-					       (srtp_info->active?"Active":"Not active"),
-					       srtp_info->tx_policy.name.ptr);
-			if (len > 0 && len < end-p) {
-			    p += len;
-			    *p++ = '\n';
-			    *p = '\0';
-			}
-		    } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
-			const pjmedia_ice_transport_info *ii;
-
-			ii = (const pjmedia_ice_transport_info*)
-			     tp_info.spc_info[j].buffer;
-
-			len = pj_ansi_snprintf(p, end-p,
-					       "   %s  ICE role: %s, state: %s, comp_cnt: %u",
-					       indent,
-					       pj_ice_sess_role_name(ii->role),
-					       pj_ice_strans_state_name(ii->sess_state),
-					       ii->comp_cnt);
-			if (len > 0 && len < end-p) {
-			    p += len;
-			    *p++ = '\n';
-			    *p = '\0';
-			}
-		    }
-		}
-	    }
-	}
-
-
-	if (has_stat) {
-	    len = dump_media_stat(indent, p, end-p, &stat,
-				  rx_info, tx_info);
-	    p += len;
-	}
-
-#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
-#   define SAMPLES_TO_USEC(usec, samples, clock_rate) \
-	do { \
-	    if (samples <= 4294) \
-		usec = samples * 1000000 / clock_rate; \
-	    else { \
-		usec = samples * 1000 / clock_rate; \
-		usec *= 1000; \
-	    } \
-	} while(0)
-
-#   define PRINT_VOIP_MTC_VAL(s, v) \
-	if (v == 127) \
-	    sprintf(s, "(na)"); \
-	else \
-	    sprintf(s, "%d", v)
-
-#   define VALIDATE_PRINT_BUF() \
-	if (len < 1 || len > end-p) { *p = '\0'; return; } \
-	p += len; *p++ = '\n'; *p = '\0'
-
-
-	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
-	    pjmedia_stream_info info;
-	    char last_update[64];
-	    char loss[16], dup[16];
-	    char jitter[80];
-	    char toh[80];
-	    char plc[16], jba[16], jbr[16];
-	    char signal_lvl[16], noise_lvl[16], rerl[16];
-	    char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
-	    pjmedia_rtcp_xr_stat xr_stat;
-	    unsigned clock_rate;
-	    pj_time_val now;
-
-	    if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
-	                                   &xr_stat) != PJ_SUCCESS)
-	    {
-		continue;
-	    }
-
-	    if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
-		    != PJ_SUCCESS)
-	    {
-		continue;
-	    }
-
-	    clock_rate = info.fmt.clock_rate;
-	    pj_gettimeofday(&now);
-
-	    len = pj_ansi_snprintf(p, end-p, "\n%s  Extended reports:", indent);
-	    VALIDATE_PRINT_BUF();
-
-	    /* Statistics Summary */
-	    len = pj_ansi_snprintf(p, end-p, "%s   Statistics Summary", indent);
-	    VALIDATE_PRINT_BUF();
-
-	    if (xr_stat.rx.stat_sum.l)
-		sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
-	    else
-		sprintf(loss, "(na)");
-
-	    if (xr_stat.rx.stat_sum.d)
-		sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
-	    else
-		sprintf(dup, "(na)");
-
-	    if (xr_stat.rx.stat_sum.j) {
-		unsigned jmin, jmax, jmean, jdev;
-
-		SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min, 
-				clock_rate);
-		SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max, 
-				clock_rate);
-		SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean, 
-				clock_rate);
-		SAMPLES_TO_USEC(jdev, 
-			       pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
-			       clock_rate);
-		sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", 
-			jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
-	    } else
-		sprintf(jitter, "(report not available)");
-
-	    if (xr_stat.rx.stat_sum.t) {
-		sprintf(toh, "%11d %11d %11d %11d", 
-			xr_stat.rx.stat_sum.toh.min,
-			xr_stat.rx.stat_sum.toh.mean,
-			xr_stat.rx.stat_sum.toh.max,
-			pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
-	    } else
-		sprintf(toh, "(report not available)");
-
-	    if (xr_stat.rx.stat_sum.update.sec == 0)
-		strcpy(last_update, "never");
-	    else {
-		pj_gettimeofday(&now);
-		PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
-		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
-			now.sec / 3600,
-			(now.sec % 3600) / 60,
-			now.sec % 60,
-			now.msec);
-	    }
-
-	    len = pj_ansi_snprintf(p, end-p, 
-		    "%s     RX last update: %s\n"
-		    "%s        begin seq=%d, end seq=%d\n"
-		    "%s        pkt loss=%s, dup=%s\n"
-		    "%s              (msec)    min     avg     max     dev\n"
-		    "%s        jitter     : %s\n"
-		    "%s        toh        : %s",
-		    indent, last_update,
-		    indent,
-		    xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
-		    indent, loss, dup,
-		    indent, 
-		    indent, jitter,
-		    indent, toh
-		    );
-	    VALIDATE_PRINT_BUF();
-
-	    if (xr_stat.tx.stat_sum.l)
-		sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
-	    else
-		sprintf(loss, "(na)");
-
-	    if (xr_stat.tx.stat_sum.d)
-		sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
-	    else
-		sprintf(dup, "(na)");
-
-	    if (xr_stat.tx.stat_sum.j) {
-		unsigned jmin, jmax, jmean, jdev;
-
-		SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min, 
-				clock_rate);
-		SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max, 
-				clock_rate);
-		SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean, 
-				clock_rate);
-		SAMPLES_TO_USEC(jdev, 
-			       pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
-			       clock_rate);
-		sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", 
-			jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
-	    } else
-		sprintf(jitter, "(report not available)");
-
-	    if (xr_stat.tx.stat_sum.t) {
-		sprintf(toh, "%11d %11d %11d %11d", 
-			xr_stat.tx.stat_sum.toh.min,
-			xr_stat.tx.stat_sum.toh.mean,
-			xr_stat.tx.stat_sum.toh.max,
-			pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
-	    } else
-		sprintf(toh,    "(report not available)");
-
-	    if (xr_stat.tx.stat_sum.update.sec == 0)
-		strcpy(last_update, "never");
-	    else {
-		pj_gettimeofday(&now);
-		PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
-		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
-			now.sec / 3600,
-			(now.sec % 3600) / 60,
-			now.sec % 60,
-			now.msec);
-	    }
-
-	    len = pj_ansi_snprintf(p, end-p, 
-		    "%s     TX last update: %s\n"
-		    "%s        begin seq=%d, end seq=%d\n"
-		    "%s        pkt loss=%s, dup=%s\n"
-		    "%s              (msec)    min     avg     max     dev\n"
-		    "%s        jitter     : %s\n"
-		    "%s        toh        : %s",
-		    indent, last_update,
-		    indent,
-		    xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
-		    indent, loss, dup,
-		    indent,
-		    indent, jitter,
-		    indent, toh
-		    );
-	    VALIDATE_PRINT_BUF();
-
-
-	    /* VoIP Metrics */
-	    len = pj_ansi_snprintf(p, end-p, "%s   VoIP Metrics", indent);
-	    VALIDATE_PRINT_BUF();
-
-	    PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
-	    PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
-	    PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
-	    PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
-	    PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
-	    PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
-	    PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
-
-	    switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
-		case PJMEDIA_RTCP_XR_PLC_DIS:
-		    sprintf(plc, "DISABLED");
-		    break;
-		case PJMEDIA_RTCP_XR_PLC_ENH:
-		    sprintf(plc, "ENHANCED");
-		    break;
-		case PJMEDIA_RTCP_XR_PLC_STD:
-		    sprintf(plc, "STANDARD");
-		    break;
-		case PJMEDIA_RTCP_XR_PLC_UNK:
-		default:
-		    sprintf(plc, "UNKNOWN");
-		    break;
-	    }
-
-	    switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
-		case PJMEDIA_RTCP_XR_JB_FIXED:
-		    sprintf(jba, "FIXED");
-		    break;
-		case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
-		    sprintf(jba, "ADAPTIVE");
-		    break;
-		default:
-		    sprintf(jba, "UNKNOWN");
-		    break;
-	    }
-
-	    sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
-
-	    if (xr_stat.rx.voip_mtc.update.sec == 0)
-		strcpy(last_update, "never");
-	    else {
-		pj_gettimeofday(&now);
-		PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
-		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
-			now.sec / 3600,
-			(now.sec % 3600) / 60,
-			now.sec % 60,
-			now.msec);
-	    }
-
-	    len = pj_ansi_snprintf(p, end-p, 
-		    "%s     RX last update: %s\n"
-		    "%s        packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
-		    "%s        burst      : density=%d (%.2f%%), duration=%d%s\n"
-		    "%s        gap        : density=%d (%.2f%%), duration=%d%s\n"
-		    "%s        delay      : round trip=%d%s, end system=%d%s\n"
-		    "%s        level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
-		    "%s        quality    : R factor=%s, ext R factor=%s\n"
-		    "%s                     MOS LQ=%s, MOS CQ=%s\n"
-		    "%s        config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
-		    "%s        JB delay   : cur=%d%s, max=%d%s, abs max=%d%s",
-		    indent,
-		    last_update,
-		    /* packets */
-		    indent,
-		    xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
-		    xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
-		    /* burst */
-		    indent,
-		    xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
-		    xr_stat.rx.voip_mtc.burst_dur, "ms",
-		    /* gap */
-		    indent,
-		    xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
-		    xr_stat.rx.voip_mtc.gap_dur, "ms",
-		    /* delay */
-		    indent,
-		    xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
-		    xr_stat.rx.voip_mtc.end_sys_delay, "ms",
-		    /* level */
-		    indent,
-		    signal_lvl, "dB",
-		    noise_lvl, "dB",
-		    rerl, "",
-		    /* quality */
-		    indent,
-		    r_factor, ext_r_factor, 
-		    indent,
-		    mos_lq, mos_cq,
-		    /* config */
-		    indent,
-		    plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
-		    /* JB delay */
-		    indent,
-		    xr_stat.rx.voip_mtc.jb_nom, "ms",
-		    xr_stat.rx.voip_mtc.jb_max, "ms",
-		    xr_stat.rx.voip_mtc.jb_abs_max, "ms"
-		    );
-	    VALIDATE_PRINT_BUF();
-
-	    PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
-	    PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
-	    PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
-	    PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
-	    PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
-	    PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
-	    PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
-
-	    switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
-		case PJMEDIA_RTCP_XR_PLC_DIS:
-		    sprintf(plc, "DISABLED");
-		    break;
-		case PJMEDIA_RTCP_XR_PLC_ENH:
-		    sprintf(plc, "ENHANCED");
-		    break;
-		case PJMEDIA_RTCP_XR_PLC_STD:
-		    sprintf(plc, "STANDARD");
-		    break;
-		case PJMEDIA_RTCP_XR_PLC_UNK:
-		default:
-		    sprintf(plc, "unknown");
-		    break;
-	    }
-
-	    switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
-		case PJMEDIA_RTCP_XR_JB_FIXED:
-		    sprintf(jba, "FIXED");
-		    break;
-		case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
-		    sprintf(jba, "ADAPTIVE");
-		    break;
-		default:
-		    sprintf(jba, "unknown");
-		    break;
-	    }
-
-	    sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
-
-	    if (xr_stat.tx.voip_mtc.update.sec == 0)
-		strcpy(last_update, "never");
-	    else {
-		pj_gettimeofday(&now);
-		PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
-		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
-			now.sec / 3600,
-			(now.sec % 3600) / 60,
-			now.sec % 60,
-			now.msec);
-	    }
-
-	    len = pj_ansi_snprintf(p, end-p, 
-		    "%s     TX last update: %s\n"
-		    "%s        packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
-		    "%s        burst      : density=%d (%.2f%%), duration=%d%s\n"
-		    "%s        gap        : density=%d (%.2f%%), duration=%d%s\n"
-		    "%s        delay      : round trip=%d%s, end system=%d%s\n"
-		    "%s        level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
-		    "%s        quality    : R factor=%s, ext R factor=%s\n"
-		    "%s                     MOS LQ=%s, MOS CQ=%s\n"
-		    "%s        config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
-		    "%s        JB delay   : cur=%d%s, max=%d%s, abs max=%d%s",
-		    indent,
-		    last_update,
-		    /* pakcets */
-		    indent,
-		    xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
-		    xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
-		    /* burst */
-		    indent,
-		    xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
-		    xr_stat.tx.voip_mtc.burst_dur, "ms",
-		    /* gap */
-		    indent,
-		    xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
-		    xr_stat.tx.voip_mtc.gap_dur, "ms",
-		    /* delay */
-		    indent,
-		    xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
-		    xr_stat.tx.voip_mtc.end_sys_delay, "ms",
-		    /* level */
-		    indent,
-		    signal_lvl, "dB",
-		    noise_lvl, "dB",
-		    rerl, "",
-		    /* quality */
-		    indent,
-		    r_factor, ext_r_factor, 
-		    indent,
-		    mos_lq, mos_cq,
-		    /* config */
-		    indent,
-		    plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
-		    /* JB delay */
-		    indent,
-		    xr_stat.tx.voip_mtc.jb_nom, "ms",
-		    xr_stat.tx.voip_mtc.jb_max, "ms",
-		    xr_stat.tx.voip_mtc.jb_abs_max, "ms"
-		    );
-	    VALIDATE_PRINT_BUF();
-
-
-	    /* RTT delay (by receiver side) */
-	    len = pj_ansi_snprintf(p, end-p, 
-		    "%s   RTT (from recv)      min     avg     max     last    dev",
-		    indent);
-	    VALIDATE_PRINT_BUF();
-	    len = pj_ansi_snprintf(p, end-p, 
-		    "%s     RTT msec      : %7.3f %7.3f %7.3f %7.3f %7.3f", 
-		    indent,
-		    xr_stat.rtt.min / 1000.0,
-		    xr_stat.rtt.mean / 1000.0,
-		    xr_stat.rtt.max / 1000.0,
-		    xr_stat.rtt.last / 1000.0,
-		    pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
-		   );
-	    VALIDATE_PRINT_BUF();
-	} /* if audio */;
-#endif
-
-    }
-}
-
-
-/* Print call info */
-void print_call(const char *title,
-	        int call_id, 
-	        char *buf, pj_size_t size)
-{
-    int len;
-    pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
-    pjsip_dialog *dlg = inv->dlg;
-    char userinfo[128];
-
-    /* Dump invite sesion info. */
-
-    len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
-    if (len < 0)
-	pj_ansi_strcpy(userinfo, "<--uri too long-->");
-    else
-	userinfo[len] = '\0';
-    
-    len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
-			   title,
-			   pjsip_inv_state_name(inv->state),
-			   userinfo);
-    if (len < 1 || len >= (int)size) {
-	pj_ansi_strcpy(buf, "<--uri too long-->");
-	len = 18;
-    } else
-	buf[len] = '\0';
-}
-
-
-/*
- * Dump call and media statistics to string.
- */
-PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id, 
-				     pj_bool_t with_media, 
-				     char *buffer, 
-				     unsigned maxlen,
-				     const char *indent)
-{
-    pjsua_call *call;
-    pjsip_dialog *dlg;
-    pj_time_val duration, res_delay, con_delay;
-    char tmp[128];
-    char *p, *end;
-    pj_status_t status;
-    int len;
-
-    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
-		     PJ_EINVAL);
-
-    status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    *buffer = '\0';
-    p = buffer;
-    end = buffer + maxlen;
-    len = 0;
-
-    print_call(indent, call_id, tmp, sizeof(tmp));
-    
-    len = pj_ansi_strlen(tmp);
-    pj_ansi_strcpy(buffer, tmp);
-
-    p += len;
-    *p++ = '\r';
-    *p++ = '\n';
-
-    /* Calculate call duration */
-    if (call->conn_time.sec != 0) {
-	pj_gettimeofday(&duration);
-	PJ_TIME_VAL_SUB(duration, call->conn_time);
-	con_delay = call->conn_time;
-	PJ_TIME_VAL_SUB(con_delay, call->start_time);
-    } else {
-	duration.sec = duration.msec = 0;
-	con_delay.sec = con_delay.msec = 0;
-    }
-
-    /* Calculate first response delay */
-    if (call->res_time.sec != 0) {
-	res_delay = call->res_time;
-	PJ_TIME_VAL_SUB(res_delay, call->start_time);
-    } else {
-	res_delay.sec = res_delay.msec = 0;
-    }
-
-    /* Print duration */
-    len = pj_ansi_snprintf(p, end-p, 
-		           "%s  Call time: %02dh:%02dm:%02ds, "
-		           "1st res in %d ms, conn in %dms",
-			   indent,
-		           (int)(duration.sec / 3600),
-		           (int)((duration.sec % 3600)/60),
-		           (int)(duration.sec % 60),
-		           (int)PJ_TIME_VAL_MSEC(res_delay), 
-		           (int)PJ_TIME_VAL_MSEC(con_delay));
-    
-    if (len > 0 && len < end-p) {
-	p += len;
-	*p++ = '\n';
-	*p = '\0';
-    }
-
-    /* Dump session statistics */
-    if (with_media && pjsua_call_has_media(call_id))
-	dump_media_session(indent, p, end-p, call);
-
-    pjsip_dlg_dec_lock(dlg);
-
-    return PJ_SUCCESS;
-}
 
 /* Proto */
Index: /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_core.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_core.c	(revision 3608)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_core.c	(revision 3609)
@@ -68,4 +68,8 @@
 
     pjsua_config_default(&pjsua_var.ua_cfg);
+
+    for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+	pjsua_vid_win_reset(i);
+    }
 }
 
@@ -176,4 +180,6 @@
     cfg->ka_data = pj_str("\r\n");
     cfg->max_audio_cnt = 1;
+    cfg->vid_cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+    cfg->vid_rend_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
     pjsua_transport_config_default(&cfg->rtp_cfg);
     cfg->use_srtp = pjsua_var.ua_cfg.use_srtp;
Index: /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_dump.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_dump.c	(revision 3609)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_dump.c	(revision 3609)
@@ -0,0 +1,944 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2011-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 
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+const char *good_number(char *buf, pj_int32_t val)
+{
+    if (val < 1000) {
+	pj_ansi_sprintf(buf, "%d", val);
+    } else if (val < 1000000) {
+	pj_ansi_sprintf(buf, "%d.%dK",
+			val / 1000,
+			(val % 1000) / 100);
+    } else {
+	pj_ansi_sprintf(buf, "%d.%02dM",
+			val / 1000000,
+			(val % 1000000) / 10000);
+    }
+
+    return buf;
+}
+
+static unsigned dump_media_stat(const char *indent,
+				char *buf, unsigned maxlen,
+				const pjmedia_rtcp_stat *stat,
+				const char *rx_info, const char *tx_info)
+{
+    char last_update[64];
+    char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32];
+    pj_time_val media_duration, now;
+    char *p = buf, *end = buf+maxlen;
+    int len;
+
+    if (stat->rx.update_cnt == 0)
+	strcpy(last_update, "never");
+    else {
+	pj_gettimeofday(&now);
+	PJ_TIME_VAL_SUB(now, stat->rx.update);
+	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+		now.sec / 3600,
+		(now.sec % 3600) / 60,
+		now.sec % 60,
+		now.msec);
+    }
+
+    pj_gettimeofday(&media_duration);
+    PJ_TIME_VAL_SUB(media_duration, stat->start);
+    if (PJ_TIME_VAL_MSEC(media_duration) == 0)
+	media_duration.msec = 1;
+
+    len = pj_ansi_snprintf(p, end-p,
+	   "%s     RX %s last update:%s\n"
+	   "%s        total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
+	   "%s        pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n"
+	   "%s              (msec)    min     avg     max     last    dev\n"
+	   "%s        loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+	   "%s        jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+	   "%s        raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#endif
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+	   "%s        IPDV       : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#endif
+	   "%s",
+	   indent,
+	   rx_info? rx_info : "",
+	   last_update,
+
+	   indent,
+	   good_number(packets, stat->rx.pkt),
+	   good_number(bytes, stat->rx.bytes),
+	   good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40),
+	   good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+	   good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->rx.bytes + stat->rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+	   indent,
+	   stat->rx.loss,
+	   (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+	   stat->rx.discard,
+	   (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+	   stat->rx.dup,
+	   (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+	   stat->rx.reorder,
+	   (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+	   indent, indent,
+	   stat->rx.loss_period.min / 1000.0,
+	   stat->rx.loss_period.mean / 1000.0,
+	   stat->rx.loss_period.max / 1000.0,
+	   stat->rx.loss_period.last / 1000.0,
+	   pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0,
+	   indent,
+	   stat->rx.jitter.min / 1000.0,
+	   stat->rx.jitter.mean / 1000.0,
+	   stat->rx.jitter.max / 1000.0,
+	   stat->rx.jitter.last / 1000.0,
+	   pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0,
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+	   indent,
+	   stat->rx_raw_jitter.min / 1000.0,
+	   stat->rx_raw_jitter.mean / 1000.0,
+	   stat->rx_raw_jitter.max / 1000.0,
+	   stat->rx_raw_jitter.last / 1000.0,
+	   pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0,
+#endif
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+	   indent,
+	   stat->rx_ipdv.min / 1000.0,
+	   stat->rx_ipdv.mean / 1000.0,
+	   stat->rx_ipdv.max / 1000.0,
+	   stat->rx_ipdv.last / 1000.0,
+	   pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0,
+#endif
+	   ""
+	   );
+
+    if (len < 1 || len > end-p) {
+	*p = '\0';
+	return (p-buf);
+    }
+    p += len;
+
+    if (stat->tx.update_cnt == 0)
+	strcpy(last_update, "never");
+    else {
+	pj_gettimeofday(&now);
+	PJ_TIME_VAL_SUB(now, stat->tx.update);
+	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+		now.sec / 3600,
+		(now.sec % 3600) / 60,
+		now.sec % 60,
+		now.msec);
+    }
+
+    len = pj_ansi_snprintf(p, end-p,
+	   "%s     TX %s last update:%s\n"
+	   "%s        total %spkt %sB (%sB +IP hdr) @avg %sbps/%sbps\n"
+	   "%s        pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
+	   "%s              (msec)    min     avg     max     last    dev \n"
+	   "%s        loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+	   "%s        jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
+	   indent,
+	   tx_info,
+	   last_update,
+
+	   indent,
+	   good_number(packets, stat->tx.pkt),
+	   good_number(bytes, stat->tx.bytes),
+	   good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40),
+	   good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+	   good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->tx.bytes + stat->tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+
+	   indent,
+	   stat->tx.loss,
+	   (stat->tx.loss? stat->tx.loss * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+	   stat->tx.dup,
+	   (stat->tx.dup? stat->tx.dup * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+	   stat->tx.reorder,
+	   (stat->tx.reorder? stat->tx.reorder * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+
+	   indent, indent,
+	   stat->tx.loss_period.min / 1000.0,
+	   stat->tx.loss_period.mean / 1000.0,
+	   stat->tx.loss_period.max / 1000.0,
+	   stat->tx.loss_period.last / 1000.0,
+	   pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0,
+	   indent,
+	   stat->tx.jitter.min / 1000.0,
+	   stat->tx.jitter.mean / 1000.0,
+	   stat->tx.jitter.max / 1000.0,
+	   stat->tx.jitter.last / 1000.0,
+	   pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0
+	   );
+
+    if (len < 1 || len > end-p) {
+	*p = '\0';
+	return (p-buf);
+    }
+    p += len;
+
+    len = pj_ansi_snprintf(p, end-p,
+	   "%s     RTT msec      : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
+	   indent,
+	   stat->rtt.min / 1000.0,
+	   stat->rtt.mean / 1000.0,
+	   stat->rtt.max / 1000.0,
+	   stat->rtt.last / 1000.0,
+	   pj_math_stat_get_stddev(&stat->rtt) / 1000.0
+	   );
+    if (len < 1 || len > end-p) {
+	*p = '\0';
+	return (p-buf);
+    }
+    p += len;
+
+    return (p-buf);
+}
+
+
+/* Dump media session */
+static void dump_media_session(const char *indent,
+			       char *buf, unsigned maxlen,
+			       pjsua_call *call)
+{
+    unsigned i;
+    char *p = buf, *end = buf+maxlen;
+    int len;
+
+    for (i=0; i<call->med_cnt; ++i) {
+	pjsua_call_media *call_med = &call->media[i];
+	pjmedia_rtcp_stat stat;
+	pj_bool_t has_stat;
+	pjmedia_transport_info tp_info;
+	char rem_addr_buf[80];
+	char codec_info[32] = {'0'};
+	char rx_info[80] = {'\0'};
+	char tx_info[80] = {'\0'};
+	const char *rem_addr;
+	const char *dir_str;
+	const char *media_type_str;
+
+	switch (call_med->type) {
+	case PJMEDIA_TYPE_AUDIO:
+	    media_type_str = "audio";
+	    break;
+	case PJMEDIA_TYPE_VIDEO:
+	    media_type_str = "video";
+	    break;
+	case PJMEDIA_TYPE_APPLICATION:
+	    media_type_str = "application";
+	    break;
+	default:
+	    media_type_str = "unknown";
+	    break;
+	}
+
+	/* Check if the stream is deactivated */
+	if (call_med->tp == NULL ||
+	    (!call_med->strm.a.stream && !call_med->strm.v.stream))
+	{
+	    len = pj_ansi_snprintf(p, end-p,
+		      "%s #%d %s deactivated\n",
+		      indent, i, media_type_str);
+	    if (len < 1 || len > end-p) {
+		*p = '\0';
+		return;
+	    }
+
+	    p += len;
+	    continue;
+	}
+
+	pjmedia_transport_info_init(&tp_info);
+	pjmedia_transport_get_info(call_med->tp, &tp_info);
+
+	// rem_addr will contain actual address of RTP originator, instead of
+	// remote RTP address specified by stream which is fetched from the SDP.
+	// Please note that we are assuming only one stream per call.
+	//rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr,
+	//			     rem_addr_buf, sizeof(rem_addr_buf), 3);
+	if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) {
+	    rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf,
+					 sizeof(rem_addr_buf), 3);
+	} else {
+	    pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-");
+	    rem_addr = rem_addr_buf;
+	}
+
+	if (call_med->dir == PJMEDIA_DIR_NONE) {
+	    /* To handle when the stream that is currently being paused
+	     * (http://trac.pjsip.org/repos/ticket/1079)
+	     */
+	    dir_str = "inactive";
+	} else if (call_med->dir == PJMEDIA_DIR_ENCODING)
+	    dir_str = "sendonly";
+	else if (call_med->dir == PJMEDIA_DIR_DECODING)
+	    dir_str = "recvonly";
+	else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING)
+	    dir_str = "sendrecv";
+	else
+	    dir_str = "inactive";
+
+	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+	    pjmedia_stream *stream = call_med->strm.a.stream;
+	    pjmedia_stream_info info;
+
+	    pjmedia_stream_get_stat(stream, &stat);
+	    has_stat = PJ_TRUE;
+
+	    pjmedia_stream_get_info(stream, &info);
+	    pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz",
+			     (int)info.fmt.encoding_name.slen,
+			     info.fmt.encoding_name.ptr,
+			     info.fmt.clock_rate / 1000);
+	    pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,",
+			     info.fmt.pt);
+	    pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,",
+			     info.tx_pt,
+			     info.param->setting.frm_per_pkt*
+			     info.param->info.frm_ptime);
+	} else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
+	    pjmedia_vid_stream *stream = call_med->strm.v.stream;
+	    pjmedia_vid_stream_info info;
+
+	    pjmedia_vid_stream_get_stat(stream, &stat);
+	    has_stat = PJ_TRUE;
+
+	    pjmedia_vid_stream_get_info(stream, &info);
+	    pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s",
+	                     (int)info.codec_info.encoding_name.slen,
+			     info.codec_info.encoding_name.ptr);
+	    if (call_med->dir & PJMEDIA_DIR_DECODING) {
+		pjmedia_video_format_detail *vfd;
+		vfd = pjmedia_format_get_video_format_detail(
+					&info.codec_param->dec_fmt, PJ_TRUE);
+		pj_ansi_snprintf(rx_info, sizeof(rx_info),
+				 "pt=%d, size=%dx%d, fps=%.2f,",
+				 info.rx_pt,
+				 vfd->size.w, vfd->size.h,
+				 vfd->fps.num*1.0/vfd->fps.denum);
+	    }
+	    if (call_med->dir & PJMEDIA_DIR_ENCODING) {
+		pjmedia_video_format_detail *vfd;
+		vfd = pjmedia_format_get_video_format_detail(
+					&info.codec_param->enc_fmt, PJ_TRUE);
+		pj_ansi_snprintf(tx_info, sizeof(tx_info),
+				 "pt=%d, size=%dx%d, fps=%.2f,",
+				 info.tx_pt,
+				 vfd->size.w, vfd->size.h,
+				 vfd->fps.num*1.0/vfd->fps.denum);
+	    }
+	} else {
+	    has_stat = PJ_FALSE;
+	}
+
+	len = pj_ansi_snprintf(p, end-p,
+		  "%s  #%d %s%s, %s, peer=%s\n",
+		  indent,
+		  call_med->idx,
+		  media_type_str,
+		  codec_info,
+		  dir_str,
+		  rem_addr);
+	if (len < 1 || len > end-p) {
+	    *p = '\0';
+	    return;
+	}
+	p += len;
+
+	/* Get and ICE SRTP status */
+	if (call_med->tp) {
+	    pjmedia_transport_info tp_info;
+
+	    pjmedia_transport_info_init(&tp_info);
+	    pjmedia_transport_get_info(call_med->tp, &tp_info);
+	    if (tp_info.specific_info_cnt > 0) {
+		unsigned j;
+		for (j = 0; j < tp_info.specific_info_cnt; ++j) {
+		    if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
+		    {
+			pjmedia_srtp_info *srtp_info =
+				    (pjmedia_srtp_info*) tp_info.spc_info[j].buffer;
+
+			len = pj_ansi_snprintf(p, end-p,
+					       "   %s  SRTP status: %s Crypto-suite: %s",
+					       indent,
+					       (srtp_info->active?"Active":"Not active"),
+					       srtp_info->tx_policy.name.ptr);
+			if (len > 0 && len < end-p) {
+			    p += len;
+			    *p++ = '\n';
+			    *p = '\0';
+			}
+		    } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
+			const pjmedia_ice_transport_info *ii;
+
+			ii = (const pjmedia_ice_transport_info*)
+			     tp_info.spc_info[j].buffer;
+
+			len = pj_ansi_snprintf(p, end-p,
+					       "   %s  ICE role: %s, state: %s, comp_cnt: %u",
+					       indent,
+					       pj_ice_sess_role_name(ii->role),
+					       pj_ice_strans_state_name(ii->sess_state),
+					       ii->comp_cnt);
+			if (len > 0 && len < end-p) {
+			    p += len;
+			    *p++ = '\n';
+			    *p = '\0';
+			}
+		    }
+		}
+	    }
+	}
+
+
+	if (has_stat) {
+	    len = dump_media_stat(indent, p, end-p, &stat,
+				  rx_info, tx_info);
+	    p += len;
+	}
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+#   define SAMPLES_TO_USEC(usec, samples, clock_rate) \
+	do { \
+	    if (samples <= 4294) \
+		usec = samples * 1000000 / clock_rate; \
+	    else { \
+		usec = samples * 1000 / clock_rate; \
+		usec *= 1000; \
+	    } \
+	} while(0)
+
+#   define PRINT_VOIP_MTC_VAL(s, v) \
+	if (v == 127) \
+	    sprintf(s, "(na)"); \
+	else \
+	    sprintf(s, "%d", v)
+
+#   define VALIDATE_PRINT_BUF() \
+	if (len < 1 || len > end-p) { *p = '\0'; return; } \
+	p += len; *p++ = '\n'; *p = '\0'
+
+
+	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+	    pjmedia_stream_info info;
+	    char last_update[64];
+	    char loss[16], dup[16];
+	    char jitter[80];
+	    char toh[80];
+	    char plc[16], jba[16], jbr[16];
+	    char signal_lvl[16], noise_lvl[16], rerl[16];
+	    char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
+	    pjmedia_rtcp_xr_stat xr_stat;
+	    unsigned clock_rate;
+	    pj_time_val now;
+
+	    if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
+	                                   &xr_stat) != PJ_SUCCESS)
+	    {
+		continue;
+	    }
+
+	    if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
+		    != PJ_SUCCESS)
+	    {
+		continue;
+	    }
+
+	    clock_rate = info.fmt.clock_rate;
+	    pj_gettimeofday(&now);
+
+	    len = pj_ansi_snprintf(p, end-p, "\n%s  Extended reports:", indent);
+	    VALIDATE_PRINT_BUF();
+
+	    /* Statistics Summary */
+	    len = pj_ansi_snprintf(p, end-p, "%s   Statistics Summary", indent);
+	    VALIDATE_PRINT_BUF();
+
+	    if (xr_stat.rx.stat_sum.l)
+		sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
+	    else
+		sprintf(loss, "(na)");
+
+	    if (xr_stat.rx.stat_sum.d)
+		sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
+	    else
+		sprintf(dup, "(na)");
+
+	    if (xr_stat.rx.stat_sum.j) {
+		unsigned jmin, jmax, jmean, jdev;
+
+		SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
+				clock_rate);
+		SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
+				clock_rate);
+		SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
+				clock_rate);
+		SAMPLES_TO_USEC(jdev,
+			       pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
+			       clock_rate);
+		sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
+			jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
+	    } else
+		sprintf(jitter, "(report not available)");
+
+	    if (xr_stat.rx.stat_sum.t) {
+		sprintf(toh, "%11d %11d %11d %11d",
+			xr_stat.rx.stat_sum.toh.min,
+			xr_stat.rx.stat_sum.toh.mean,
+			xr_stat.rx.stat_sum.toh.max,
+			pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
+	    } else
+		sprintf(toh, "(report not available)");
+
+	    if (xr_stat.rx.stat_sum.update.sec == 0)
+		strcpy(last_update, "never");
+	    else {
+		pj_gettimeofday(&now);
+		PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
+		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+			now.sec / 3600,
+			(now.sec % 3600) / 60,
+			now.sec % 60,
+			now.msec);
+	    }
+
+	    len = pj_ansi_snprintf(p, end-p,
+		    "%s     RX last update: %s\n"
+		    "%s        begin seq=%d, end seq=%d\n"
+		    "%s        pkt loss=%s, dup=%s\n"
+		    "%s              (msec)    min     avg     max     dev\n"
+		    "%s        jitter     : %s\n"
+		    "%s        toh        : %s",
+		    indent, last_update,
+		    indent,
+		    xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
+		    indent, loss, dup,
+		    indent,
+		    indent, jitter,
+		    indent, toh
+		    );
+	    VALIDATE_PRINT_BUF();
+
+	    if (xr_stat.tx.stat_sum.l)
+		sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
+	    else
+		sprintf(loss, "(na)");
+
+	    if (xr_stat.tx.stat_sum.d)
+		sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
+	    else
+		sprintf(dup, "(na)");
+
+	    if (xr_stat.tx.stat_sum.j) {
+		unsigned jmin, jmax, jmean, jdev;
+
+		SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
+				clock_rate);
+		SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
+				clock_rate);
+		SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
+				clock_rate);
+		SAMPLES_TO_USEC(jdev,
+			       pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
+			       clock_rate);
+		sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
+			jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
+	    } else
+		sprintf(jitter, "(report not available)");
+
+	    if (xr_stat.tx.stat_sum.t) {
+		sprintf(toh, "%11d %11d %11d %11d",
+			xr_stat.tx.stat_sum.toh.min,
+			xr_stat.tx.stat_sum.toh.mean,
+			xr_stat.tx.stat_sum.toh.max,
+			pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
+	    } else
+		sprintf(toh,    "(report not available)");
+
+	    if (xr_stat.tx.stat_sum.update.sec == 0)
+		strcpy(last_update, "never");
+	    else {
+		pj_gettimeofday(&now);
+		PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
+		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+			now.sec / 3600,
+			(now.sec % 3600) / 60,
+			now.sec % 60,
+			now.msec);
+	    }
+
+	    len = pj_ansi_snprintf(p, end-p,
+		    "%s     TX last update: %s\n"
+		    "%s        begin seq=%d, end seq=%d\n"
+		    "%s        pkt loss=%s, dup=%s\n"
+		    "%s              (msec)    min     avg     max     dev\n"
+		    "%s        jitter     : %s\n"
+		    "%s        toh        : %s",
+		    indent, last_update,
+		    indent,
+		    xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
+		    indent, loss, dup,
+		    indent,
+		    indent, jitter,
+		    indent, toh
+		    );
+	    VALIDATE_PRINT_BUF();
+
+
+	    /* VoIP Metrics */
+	    len = pj_ansi_snprintf(p, end-p, "%s   VoIP Metrics", indent);
+	    VALIDATE_PRINT_BUF();
+
+	    PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
+	    PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
+	    PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
+	    PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
+	    PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
+	    PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
+	    PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
+
+	    switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
+		case PJMEDIA_RTCP_XR_PLC_DIS:
+		    sprintf(plc, "DISABLED");
+		    break;
+		case PJMEDIA_RTCP_XR_PLC_ENH:
+		    sprintf(plc, "ENHANCED");
+		    break;
+		case PJMEDIA_RTCP_XR_PLC_STD:
+		    sprintf(plc, "STANDARD");
+		    break;
+		case PJMEDIA_RTCP_XR_PLC_UNK:
+		default:
+		    sprintf(plc, "UNKNOWN");
+		    break;
+	    }
+
+	    switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
+		case PJMEDIA_RTCP_XR_JB_FIXED:
+		    sprintf(jba, "FIXED");
+		    break;
+		case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
+		    sprintf(jba, "ADAPTIVE");
+		    break;
+		default:
+		    sprintf(jba, "UNKNOWN");
+		    break;
+	    }
+
+	    sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
+
+	    if (xr_stat.rx.voip_mtc.update.sec == 0)
+		strcpy(last_update, "never");
+	    else {
+		pj_gettimeofday(&now);
+		PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
+		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+			now.sec / 3600,
+			(now.sec % 3600) / 60,
+			now.sec % 60,
+			now.msec);
+	    }
+
+	    len = pj_ansi_snprintf(p, end-p,
+		    "%s     RX last update: %s\n"
+		    "%s        packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
+		    "%s        burst      : density=%d (%.2f%%), duration=%d%s\n"
+		    "%s        gap        : density=%d (%.2f%%), duration=%d%s\n"
+		    "%s        delay      : round trip=%d%s, end system=%d%s\n"
+		    "%s        level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
+		    "%s        quality    : R factor=%s, ext R factor=%s\n"
+		    "%s                     MOS LQ=%s, MOS CQ=%s\n"
+		    "%s        config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
+		    "%s        JB delay   : cur=%d%s, max=%d%s, abs max=%d%s",
+		    indent,
+		    last_update,
+		    /* packets */
+		    indent,
+		    xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
+		    xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
+		    /* burst */
+		    indent,
+		    xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
+		    xr_stat.rx.voip_mtc.burst_dur, "ms",
+		    /* gap */
+		    indent,
+		    xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
+		    xr_stat.rx.voip_mtc.gap_dur, "ms",
+		    /* delay */
+		    indent,
+		    xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
+		    xr_stat.rx.voip_mtc.end_sys_delay, "ms",
+		    /* level */
+		    indent,
+		    signal_lvl, "dB",
+		    noise_lvl, "dB",
+		    rerl, "",
+		    /* quality */
+		    indent,
+		    r_factor, ext_r_factor,
+		    indent,
+		    mos_lq, mos_cq,
+		    /* config */
+		    indent,
+		    plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
+		    /* JB delay */
+		    indent,
+		    xr_stat.rx.voip_mtc.jb_nom, "ms",
+		    xr_stat.rx.voip_mtc.jb_max, "ms",
+		    xr_stat.rx.voip_mtc.jb_abs_max, "ms"
+		    );
+	    VALIDATE_PRINT_BUF();
+
+	    PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
+	    PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
+	    PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
+	    PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
+	    PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
+	    PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
+	    PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
+
+	    switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
+		case PJMEDIA_RTCP_XR_PLC_DIS:
+		    sprintf(plc, "DISABLED");
+		    break;
+		case PJMEDIA_RTCP_XR_PLC_ENH:
+		    sprintf(plc, "ENHANCED");
+		    break;
+		case PJMEDIA_RTCP_XR_PLC_STD:
+		    sprintf(plc, "STANDARD");
+		    break;
+		case PJMEDIA_RTCP_XR_PLC_UNK:
+		default:
+		    sprintf(plc, "unknown");
+		    break;
+	    }
+
+	    switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
+		case PJMEDIA_RTCP_XR_JB_FIXED:
+		    sprintf(jba, "FIXED");
+		    break;
+		case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
+		    sprintf(jba, "ADAPTIVE");
+		    break;
+		default:
+		    sprintf(jba, "unknown");
+		    break;
+	    }
+
+	    sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
+
+	    if (xr_stat.tx.voip_mtc.update.sec == 0)
+		strcpy(last_update, "never");
+	    else {
+		pj_gettimeofday(&now);
+		PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
+		sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+			now.sec / 3600,
+			(now.sec % 3600) / 60,
+			now.sec % 60,
+			now.msec);
+	    }
+
+	    len = pj_ansi_snprintf(p, end-p,
+		    "%s     TX last update: %s\n"
+		    "%s        packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
+		    "%s        burst      : density=%d (%.2f%%), duration=%d%s\n"
+		    "%s        gap        : density=%d (%.2f%%), duration=%d%s\n"
+		    "%s        delay      : round trip=%d%s, end system=%d%s\n"
+		    "%s        level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
+		    "%s        quality    : R factor=%s, ext R factor=%s\n"
+		    "%s                     MOS LQ=%s, MOS CQ=%s\n"
+		    "%s        config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
+		    "%s        JB delay   : cur=%d%s, max=%d%s, abs max=%d%s",
+		    indent,
+		    last_update,
+		    /* pakcets */
+		    indent,
+		    xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
+		    xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
+		    /* burst */
+		    indent,
+		    xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
+		    xr_stat.tx.voip_mtc.burst_dur, "ms",
+		    /* gap */
+		    indent,
+		    xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
+		    xr_stat.tx.voip_mtc.gap_dur, "ms",
+		    /* delay */
+		    indent,
+		    xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
+		    xr_stat.tx.voip_mtc.end_sys_delay, "ms",
+		    /* level */
+		    indent,
+		    signal_lvl, "dB",
+		    noise_lvl, "dB",
+		    rerl, "",
+		    /* quality */
+		    indent,
+		    r_factor, ext_r_factor,
+		    indent,
+		    mos_lq, mos_cq,
+		    /* config */
+		    indent,
+		    plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
+		    /* JB delay */
+		    indent,
+		    xr_stat.tx.voip_mtc.jb_nom, "ms",
+		    xr_stat.tx.voip_mtc.jb_max, "ms",
+		    xr_stat.tx.voip_mtc.jb_abs_max, "ms"
+		    );
+	    VALIDATE_PRINT_BUF();
+
+
+	    /* RTT delay (by receiver side) */
+	    len = pj_ansi_snprintf(p, end-p,
+		    "%s   RTT (from recv)      min     avg     max     last    dev",
+		    indent);
+	    VALIDATE_PRINT_BUF();
+	    len = pj_ansi_snprintf(p, end-p,
+		    "%s     RTT msec      : %7.3f %7.3f %7.3f %7.3f %7.3f",
+		    indent,
+		    xr_stat.rtt.min / 1000.0,
+		    xr_stat.rtt.mean / 1000.0,
+		    xr_stat.rtt.max / 1000.0,
+		    xr_stat.rtt.last / 1000.0,
+		    pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
+		   );
+	    VALIDATE_PRINT_BUF();
+	} /* if audio */;
+#endif
+
+    }
+}
+
+
+/* Print call info */
+void print_call(const char *title,
+	        int call_id,
+	        char *buf, pj_size_t size)
+{
+    int len;
+    pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
+    pjsip_dialog *dlg = inv->dlg;
+    char userinfo[128];
+
+    /* Dump invite sesion info. */
+
+    len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
+    if (len < 0)
+	pj_ansi_strcpy(userinfo, "<--uri too long-->");
+    else
+	userinfo[len] = '\0';
+
+    len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
+			   title,
+			   pjsip_inv_state_name(inv->state),
+			   userinfo);
+    if (len < 1 || len >= (int)size) {
+	pj_ansi_strcpy(buf, "<--uri too long-->");
+	len = 18;
+    } else
+	buf[len] = '\0';
+}
+
+
+/*
+ * Dump call and media statistics to string.
+ */
+PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id,
+				     pj_bool_t with_media,
+				     char *buffer,
+				     unsigned maxlen,
+				     const char *indent)
+{
+    pjsua_call *call;
+    pjsip_dialog *dlg;
+    pj_time_val duration, res_delay, con_delay;
+    char tmp[128];
+    char *p, *end;
+    pj_status_t status;
+    int len;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    *buffer = '\0';
+    p = buffer;
+    end = buffer + maxlen;
+    len = 0;
+
+    print_call(indent, call_id, tmp, sizeof(tmp));
+
+    len = pj_ansi_strlen(tmp);
+    pj_ansi_strcpy(buffer, tmp);
+
+    p += len;
+    *p++ = '\r';
+    *p++ = '\n';
+
+    /* Calculate call duration */
+    if (call->conn_time.sec != 0) {
+	pj_gettimeofday(&duration);
+	PJ_TIME_VAL_SUB(duration, call->conn_time);
+	con_delay = call->conn_time;
+	PJ_TIME_VAL_SUB(con_delay, call->start_time);
+    } else {
+	duration.sec = duration.msec = 0;
+	con_delay.sec = con_delay.msec = 0;
+    }
+
+    /* Calculate first response delay */
+    if (call->res_time.sec != 0) {
+	res_delay = call->res_time;
+	PJ_TIME_VAL_SUB(res_delay, call->start_time);
+    } else {
+	res_delay.sec = res_delay.msec = 0;
+    }
+
+    /* Print duration */
+    len = pj_ansi_snprintf(p, end-p,
+		           "%s  Call time: %02dh:%02dm:%02ds, "
+		           "1st res in %d ms, conn in %dms",
+			   indent,
+		           (int)(duration.sec / 3600),
+		           (int)((duration.sec % 3600)/60),
+		           (int)(duration.sec % 60),
+		           (int)PJ_TIME_VAL_MSEC(res_delay),
+		           (int)PJ_TIME_VAL_MSEC(con_delay));
+
+    if (len > 0 && len < end-p) {
+	p += len;
+	*p++ = '\n';
+	*p = '\0';
+    }
+
+    /* Dump session statistics */
+    if (with_media && pjsua_call_has_media(call_id))
+	dump_media_session(indent, p, end-p, call);
+
+    pjsip_dlg_dec_lock(dlg);
+
+    return PJ_SUCCESS;
+}
+
Index: /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_media.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_media.c	(revision 3608)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_media.c	(revision 3609)
@@ -276,43 +276,4 @@
 #endif	/* PJMEDIA_HAS_L16_CODEC */
 
-#if PJMEDIA_HAS_VIDEO
-    status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error creating PJMEDIA video format manager",
-		     status);
-	return status;
-    }
-
-    status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error creating PJMEDIA converter manager",
-		     status);
-	return status;
-    }
-
-    status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error creating PJMEDIA video codec manager",
-		     status);
-	return status;
-    }
-
-    status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error creating PJMEDIA video subsystem",
-		     status);
-	return status;
-    }
-#endif
-
-#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
-    /* Init ffmpeg video codecs */
-    status = pjmedia_codec_ffmpeg_init(NULL, &pjsua_var.cp.factory);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error initializing ffmpeg library",
-		     status);
-	return status;
-    }
-#endif
 
     /* Save additional conference bridge parameters for future
@@ -337,5 +298,4 @@
     }
 	
-
     /* Init conference bridge. */
     status = pjmedia_conf_create(pjsua_var.pool, 
@@ -375,4 +335,11 @@
 #endif
 
+    /* Video */
+#if PJMEDIA_HAS_VIDEO
+    status = pjsua_vid_subsys_init();
+    if (status != PJ_SUCCESS)
+	return status;
+#endif
+
     return PJ_SUCCESS;
 }
@@ -472,4 +439,11 @@
     pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
 			&close_snd_timer_cb);
+
+    /* Video */
+#if PJMEDIA_HAS_VIDEO
+    status = pjsua_vid_subsys_start();
+    if (status != PJ_SUCCESS)
+	return status;
+#endif
 
     /* Perform NAT detection */
@@ -539,14 +513,7 @@
     if (pjsua_var.med_endpt) {
 
-	/* Videodev */
 #	if PJMEDIA_HAS_VIDEO
-	    pjmedia_vid_dev_subsys_shutdown();
+	    pjsua_vid_subsys_destroy();
 #	endif
-
-	/* ffmpeg */
-#	if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
-	    pjmedia_codec_ffmpeg_deinit();
-#	endif
-
 	/* Shutdown all codecs: */
 #	if PJMEDIA_HAS_SPEEX_CODEC
@@ -3938,219 +3905,2 @@
 
 
-#if PJMEDIA_HAS_VIDEO
-
-/*****************************************************************************
- * Video codecs.
- */
-
-/*
- * Enum all supported video codecs in the system.
- */
-PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
-					   unsigned *p_count )
-{
-    pjmedia_vid_codec_info info[32];
-    unsigned i, j, count, prio[32];
-    pj_status_t status;
-
-    count = PJ_ARRAY_SIZE(info);
-    status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
-    if (status != PJ_SUCCESS) {
-	*p_count = 0;
-	return status;
-    }
-
-    for (i=0, j=0; i<count && j<*p_count; ++i) {
-	if (info[i].has_rtp_pack) {
-	    pj_bzero(&id[j], sizeof(pjsua_codec_info));
-
-	    pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
-	    id[j].codec_id = pj_str(id[j].buf_);
-	    id[j].priority = (pj_uint8_t) prio[i];
-	    
-	    if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
-		id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
-		pj_strncpy(&id[j].desc, &info[i].encoding_desc,
-			   sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
-	    }
-
-	    ++j;
-	}
-    }
-
-    *p_count = j;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Change video codec priority.
- */
-PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
-						  pj_uint8_t priority )
-{
-    const pj_str_t all = { NULL, 0 };
-
-    if (codec_id->slen==1 && *codec_id->ptr=='*')
-	codec_id = &all;
-
-    return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
-						    priority);
-}
-
-
-/*
- * Get video codec parameters.
- */
-PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
-					const pj_str_t *codec_id,
-					pjmedia_vid_codec_param *param)
-{
-    const pj_str_t all = { NULL, 0 };
-    const pjmedia_vid_codec_info *info;
-    unsigned count = 1;
-    pj_status_t status;
-
-    if (codec_id->slen==1 && *codec_id->ptr=='*')
-	codec_id = &all;
-
-    status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
-						     &count, &info, NULL);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    if (count != 1)
-	return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
-
-    status = pjmedia_vid_codec_mgr_get_default_param(NULL, info, param);
-    return status;
-}
-
-
-/*
- * Set video codec parameters.
- */
-PJ_DEF(pj_status_t) pjsua_vid_codec_set_param( 
-					const pj_str_t *codec_id,
-					const pjmedia_vid_codec_param *param)
-{
-    const pjmedia_vid_codec_info *info[2];
-    unsigned count = 2;
-    pj_status_t status;
-
-    status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
-						     &count, info, NULL);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    /* Codec ID should be specific */
-    if (count > 1) {
-	pj_assert(!"Codec ID is not specific");
-	return PJ_ETOOMANY;
-    }
-
-    status = pjmedia_vid_codec_mgr_set_default_param(NULL, pjsua_var.pool,
-						     info[0], param);
-    return status;
-}
-
-
-/*****************************************************************************
- * Video devices.
- */
-
-/*
- * Enum all video devices installed in the system.
- */
-PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
-					unsigned *count)
-{
-    unsigned i, dev_count;
-
-    dev_count = pjmedia_vid_dev_count();
-    
-    if (dev_count > *count) dev_count = *count;
-
-    for (i=0; i<dev_count; ++i) {
-	pj_status_t status;
-
-	status = pjmedia_vid_dev_get_info(i, &info[i]);
-	if (status != PJ_SUCCESS)
-	    return status;
-    }
-
-    *count = dev_count;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Get currently active video devices.
- */
-PJ_DEF(pj_status_t) pjsua_vid_get_dev(int *capture_dev, int *render_dev)
-{
-    if (capture_dev)
-	*capture_dev = pjsua_var.vcap_dev;
-    if (render_dev)
-	*render_dev = pjsua_var.vrdr_dev;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Select video device for the next video sessions.
- */
-PJ_DEF(pj_status_t) pjsua_vid_set_dev(int capture_dev, int render_dev)
-{
-    pjmedia_vid_dev_info info;
-    pj_status_t status;
-
-    if (capture_dev < 0)
-	capture_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
-    if (render_dev < 0)
-	render_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
-
-    status = pjmedia_vid_dev_get_info(capture_dev, &info);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    status = pjmedia_vid_dev_get_info(render_dev, &info);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    pjsua_var.vcap_dev = capture_dev;
-    pjsua_var.vrdr_dev = render_dev;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Configure video device setting to the video device being used.
- */
-PJ_DEF(pj_status_t) pjsua_vid_set_setting(pjmedia_vid_dev_cap cap,
-					  const void *pval,
-					  pj_bool_t keep)
-{
-    PJ_UNUSED_ARG(cap);
-    PJ_UNUSED_ARG(pval);
-    PJ_UNUSED_ARG(keep);
-    return PJ_ENOTSUP;
-}
-
-
-/*
- * Retrieve a video device setting.
- */
-PJ_DEF(pj_status_t) pjsua_vid_get_setting(pjmedia_vid_dev_cap cap,
-					  void *pval)
-{
-    PJ_UNUSED_ARG(cap);
-    PJ_UNUSED_ARG(pval);
-    return PJ_ENOTSUP;
-}
-
-#endif /* PJMEDIA_HAS_VIDEO */
Index: /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_vid.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_vid.c	(revision 3609)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_vid.c	(revision 3609)
@@ -0,0 +1,551 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2011-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 
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+#define THIS_FILE	"pjsua_vid.c"
+
+#if PJSUA_HAS_VIDEO
+
+/*****************************************************************************
+ * pjsua video subsystem.
+ */
+pj_status_t pjsua_vid_subsys_init(void)
+{
+    unsigned i;
+    pj_status_t status;
+
+    status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status,
+		     "Error creating PJMEDIA video format manager"));
+	return status;
+    }
+
+    status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status,
+		     "Error creating PJMEDIA converter manager"));
+	return status;
+    }
+
+    status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status,
+		     "Error creating PJMEDIA video codec manager"));
+	return status;
+    }
+
+    status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory);
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status,
+		     "Error creating PJMEDIA video subsystem"));
+	return status;
+    }
+
+#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
+    status = pjmedia_codec_ffmpeg_init(NULL, &pjsua_var.cp.factory);
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status,
+		     "Error initializing ffmpeg library"));
+	return status;
+    }
+#endif
+
+    for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+	if (pjsua_var.win[i].pool == NULL) {
+	    pjsua_var.win[i].pool = pjsua_pool_create("win%p", 512, 512);
+	    if (pjsua_var.win[i].pool == NULL)
+		return PJ_ENOMEM;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+pj_status_t pjsua_vid_subsys_start(void)
+{
+    return PJ_SUCCESS;
+}
+
+pj_status_t pjsua_vid_subsys_destroy(void)
+{
+    unsigned i;
+
+    for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+	if (pjsua_var.win[i].pool) {
+	    pj_pool_release(pjsua_var.win[i].pool);
+	    pjsua_var.win[i].pool = NULL;
+	}
+    }
+
+    pjmedia_vid_dev_subsys_shutdown();
+
+#if PJMEDIA_HAS_FFMPEG_CODEC
+	    pjmedia_codec_ffmpeg_deinit();
+#endif
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Devices.
+ */
+
+/*
+ * Get the number of video devices installed in the system.
+ */
+PJ_DEF(unsigned) pjsua_vid_dev_count(void)
+{
+    return pjmedia_vid_dev_count();
+}
+
+/*
+ * Retrieve the video device info for the specified device index.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,
+                                           pjmedia_vid_dev_info *vdi)
+{
+    return pjmedia_vid_dev_get_info(id, vdi);
+}
+
+/*
+ * Enum all video devices installed in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
+					unsigned *count)
+{
+    unsigned i, dev_count;
+
+    dev_count = pjmedia_vid_dev_count();
+
+    if (dev_count > *count) dev_count = *count;
+
+    for (i=0; i<dev_count; ++i) {
+	pj_status_t status;
+
+	status = pjmedia_vid_dev_get_info(i, &info[i]);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    *count = dev_count;
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Codecs.
+ */
+
+/*
+ * Enum all supported video codecs in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
+					   unsigned *p_count )
+{
+    pjmedia_vid_codec_info info[32];
+    unsigned i, j, count, prio[32];
+    pj_status_t status;
+
+    count = PJ_ARRAY_SIZE(info);
+    status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
+    if (status != PJ_SUCCESS) {
+	*p_count = 0;
+	return status;
+    }
+
+    for (i=0, j=0; i<count && j<*p_count; ++i) {
+	if (info[i].has_rtp_pack) {
+	    pj_bzero(&id[j], sizeof(pjsua_codec_info));
+
+	    pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
+	    id[j].codec_id = pj_str(id[j].buf_);
+	    id[j].priority = (pj_uint8_t) prio[i];
+
+	    if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
+		id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
+		pj_strncpy(&id[j].desc, &info[i].encoding_desc,
+			   sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
+	    }
+
+	    ++j;
+	}
+    }
+
+    *p_count = j;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Change video codec priority.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
+						  pj_uint8_t priority )
+{
+    const pj_str_t all = { NULL, 0 };
+
+    if (codec_id->slen==1 && *codec_id->ptr=='*')
+	codec_id = &all;
+
+    return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
+						    priority);
+}
+
+
+/*
+ * Get video codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
+					const pj_str_t *codec_id,
+					pjmedia_vid_codec_param *param)
+{
+    const pj_str_t all = { NULL, 0 };
+    const pjmedia_vid_codec_info *info;
+    unsigned count = 1;
+    pj_status_t status;
+
+    if (codec_id->slen==1 && *codec_id->ptr=='*')
+	codec_id = &all;
+
+    status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
+						     &count, &info, NULL);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    if (count != 1)
+	return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
+
+    status = pjmedia_vid_codec_mgr_get_default_param(NULL, info, param);
+    return status;
+}
+
+
+/*
+ * Set video codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_set_param(
+					const pj_str_t *codec_id,
+					const pjmedia_vid_codec_param *param)
+{
+    const pjmedia_vid_codec_info *info[2];
+    unsigned count = 2;
+    pj_status_t status;
+
+    status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
+						     &count, info, NULL);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Codec ID should be specific */
+    if (count > 1) {
+	pj_assert(!"Codec ID is not specific");
+	return PJ_ETOOMANY;
+    }
+
+    status = pjmedia_vid_codec_mgr_set_default_param(NULL, pjsua_var.pool,
+						     info[0], param);
+    return status;
+}
+
+
+/*****************************************************************************
+ * Preview
+ */
+
+/*
+ * Get the preview window handle associated with the capture device, if any.
+ */
+PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id)
+{
+    pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+    unsigned i;
+
+    PJSUA_LOCK();
+    for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+	pjsua_vid_win *w = &pjsua_var.win[i];
+	if (w->type == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) {
+	    wid = i;
+	    break;
+	}
+    }
+    PJSUA_UNLOCK();
+
+    return wid;
+}
+
+static pjsua_vid_win_id alloc_vid_win(pjsua_vid_win_type type)
+{
+    pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+    unsigned i;
+
+    for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+	pjsua_vid_win *w = &pjsua_var.win[i];
+	if (w->type == PJSUA_WND_TYPE_NONE) {
+	    wid = i;
+	    w->type = type;
+	    break;
+	}
+    }
+
+    return wid;
+}
+
+static void free_vid_win(pjsua_vid_win_id wid)
+{
+    pjsua_vid_win *w = &pjsua_var.win[wid];
+    if (w->vp_cap) {
+	pjmedia_vid_port_destroy(w->vp_cap);
+    }
+    if (w->vp_rend) {
+	pjmedia_vid_port_destroy(w->vp_rend);
+    }
+    pjsua_vid_win_reset(wid);
+}
+
+/*
+ * Start video preview window for the specified capture device.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id,
+                                            pjsua_vid_preview_param *prm)
+{
+    pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+    pjsua_vid_win *w = NULL;
+    pjmedia_vid_port_param cap_param, rend_param;
+    pjmedia_port *rend_port;
+    const pjmedia_video_format_detail *vfd;
+    pj_status_t status;
+
+    PJSUA_LOCK();
+
+    wid = pjsua_vid_preview_get_win(id);
+    if (wid != PJSUA_INVALID_ID) {
+	/* Preview already started for this device */
+	PJSUA_UNLOCK();
+	return PJ_SUCCESS;
+    }
+
+    wid = alloc_vid_win(PJSUA_WND_TYPE_PREVIEW);
+    if (wid != PJSUA_INVALID_ID) {
+	pjsua_var.win[wid].preview_cap_id = id;
+    }
+    if (wid == PJSUA_INVALID_ID) {
+	PJSUA_UNLOCK();
+	return PJ_ETOOMANY;
+    }
+    w = &pjsua_var.win[wid];
+
+    /* Create capture video port */
+    pjmedia_vid_port_param_default(&cap_param);
+    cap_param.active = PJ_TRUE;
+    cap_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+    status = pjmedia_vid_dev_default_param(w->pool, id,
+					   &cap_param.vidparam);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    status = pjmedia_vid_port_create(w->pool, &cap_param, &w->vp_cap);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    vfd = pjmedia_format_get_video_format_detail(&cap_param.vidparam.fmt,
+                                                 PJ_TRUE);
+    if (vfd == NULL) {
+	status = PJ_ENOTFOUND;
+	goto on_error;
+    }
+
+    /* Create renderer video port */
+    pjmedia_vid_port_param_default(&rend_param);
+    status = pjmedia_vid_dev_default_param(w->pool,
+					   PJMEDIA_VID_DEFAULT_RENDER_DEV,
+					   &rend_param.vidparam);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    rend_param.active = PJ_FALSE;
+    rend_param.vidparam.dir = PJMEDIA_DIR_RENDER;
+    rend_param.vidparam.fmt = cap_param.vidparam.fmt;
+    rend_param.vidparam.disp_size = vfd->size;
+
+    status = pjmedia_vid_port_create(w->pool, &rend_param, &w->vp_rend);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Connect capture dev to renderer */
+    rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend);
+    status = pjmedia_vid_port_connect(w->vp_cap, rend_port, PJ_FALSE);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Start devices */
+    status = pjmedia_vid_port_start(w->vp_rend);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    status = pjmedia_vid_port_start(w->vp_cap);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Done */
+    PJSUA_UNLOCK();
+    return PJ_SUCCESS;
+
+on_error:
+    if (wid != PJSUA_INVALID_ID) {
+	free_vid_win(wid);
+    }
+
+    PJSUA_UNLOCK();
+    return status;
+}
+
+/*
+ * Stop video preview.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id)
+{
+    pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+    pjsua_vid_win *w;
+    pj_status_t status;
+
+    PJSUA_LOCK();
+    wid = pjsua_vid_preview_get_win(id);
+    if (wid == PJSUA_INVALID_ID) {
+	PJSUA_UNLOCK();
+	return PJ_ENOTFOUND;
+    }
+
+    w = &pjsua_var.win[wid];
+
+    status = pjmedia_vid_port_stop(w->vp_cap);
+    status = pjmedia_vid_port_stop(w->vp_rend);
+
+    free_vid_win(wid);
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Window
+ */
+
+/*
+ * Get window info.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_get_info( pjsua_vid_win_id wid,
+                                            pjsua_vid_win_info *wi)
+{
+    pjsua_vid_win *w;
+
+    PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && wi, PJ_EINVAL);
+
+    PJSUA_LOCK();
+    w = &pjsua_var.win[wid];
+    if (w->vp_rend == NULL) {
+	PJSUA_UNLOCK();
+	return PJ_EINVAL;
+    }
+
+    PJ_TODO(vid_implement_pjsua_vid_win_get_info);
+    PJSUA_UNLOCK();
+
+    return PJ_ENOTSUP;
+}
+
+/*
+ * Show or hide window.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_show( pjsua_vid_win_id wid,
+                                            pj_bool_t show)
+{
+    pjsua_vid_win *w;
+
+    PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
+
+    PJSUA_LOCK();
+    w = &pjsua_var.win[wid];
+    if (w->vp_rend == NULL) {
+	PJSUA_UNLOCK();
+	return PJ_EINVAL;
+    }
+
+    PJ_TODO(vid_implement_pjsua_vid_win_set_show);
+    PJSUA_UNLOCK();
+
+    return PJ_ENOTSUP;
+}
+
+/*
+ * Set video window position.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_pos( pjsua_vid_win_id wid,
+                                           const pjmedia_coord *pos)
+{
+    pjsua_vid_win *w;
+
+    PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && pos, PJ_EINVAL);
+
+    PJSUA_LOCK();
+    w = &pjsua_var.win[wid];
+    if (w->vp_rend == NULL) {
+	PJSUA_UNLOCK();
+	return PJ_EINVAL;
+    }
+
+    PJ_TODO(vid_implement_pjsua_vid_win_set_pos);
+    PJSUA_UNLOCK();
+
+    return PJ_ENOTSUP;
+}
+
+/*
+ * Resize window.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_size( pjsua_vid_win_id wid,
+                                            const pjmedia_rect_size *size)
+{
+    pjsua_vid_win *w;
+
+    PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && size, PJ_EINVAL);
+
+    PJSUA_LOCK();
+    w = &pjsua_var.win[wid];
+    if (w->vp_rend == NULL) {
+	PJSUA_UNLOCK();
+	return PJ_EINVAL;
+    }
+
+    PJ_TODO(vid_implement_pjsua_vid_win_set_size);
+    PJSUA_UNLOCK();
+
+    return PJ_ENOTSUP;
+}
+
+
+
+#endif /* PJSUA_HAS_VIDEO */
+
