Index: /pjproject/branches/projects/2.0-dev/build.mak.in
===================================================================
--- /pjproject/branches/projects/2.0-dev/build.mak.in	(revision 3456)
+++ /pjproject/branches/projects/2.0-dev/build.mak.in	(revision 3457)
@@ -59,4 +59,34 @@
 @ac_build_mak_vars@
 
+#
+# Video
+# Note: there are duplicated macros in pjmedia/os-auto.mak.in (and that's not
+#       good!
+
+# SDL flags
+SDL_CFLAGS = @ac_sdl_cflags@
+SDL_LDFLAGS = @ac_sdl_ldflags@
+
+# FFMPEG dlags
+FFMPEG_CFLAGS = @ac_ffmpeg_cflags@ 
+FFMPEG_LDFLAGS =  @ac_ffmpeg_ldflags@
+
+# Video4Linux2
+V4L2_CFLAGS = @ac_v4l2_cflags@
+V4L2_LDFLAGS = @ac_v4l2_ldflags@
+
+# QT
+AC_PJMEDIA_VIDEO_HAS_QT = @ac_pjmedia_video_has_qt@
+QT_CFLAGS = @ac_qt_cflags@
+
+# iOS
+IOS_CFLAGS = @ac_ios_cflags@
+
+# PJMEDIA features exclusion
+PJ_VIDEO_CFLAGS += $(SDL_CFLAGS) $(FFMPEG_CFLAGS) $(V4L2_CFLAGS) $(QT_CFLAGS) \
+		   $(IOS_CFLAGS)
+PJ_VIDEO_LDFLAGS += $(SDL_LDFLAGS) $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS)
+
+
 # CFLAGS, LDFLAGS, and LIBS to be used by applications
 export PJDIR := @ac_pjdir@
@@ -65,4 +95,5 @@
 export APP_CFLAGS := -DPJ_AUTOCONF=1\
 	@CFLAGS@\
+	$(PJ_VIDEO_CFLAGS) \
 	-I$(PJDIR)/pjlib/include\
 	-I$(PJDIR)/pjlib-util/include\
@@ -77,4 +108,5 @@
 	-L$(PJDIR)/pjsip/lib\
 	-L$(PJDIR)/third_party/lib\
+	$(PJ_VIDEO_LDFLAGS) \
 	@LDFLAGS@
 export APP_LDLIBS := -lpjsua-$(TARGET_NAME)\
Index: /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/transport.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/transport.h	(revision 3456)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/transport.h	(revision 3457)
@@ -484,4 +484,7 @@
     /** Transport's "virtual" function table. */
     pjmedia_transport_op    *op;
+
+    /** Application/user data */
+    void		    *user_data;
 };
 
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 3456)
+++ /pjproject/branches/projects/2.0-dev/pjsip-apps/src/pjsua/pjsua_app.c	(revision 3457)
@@ -268,5 +268,5 @@
 
     puts  ("");
-    puts  ("Media Options:");
+    puts  ("Audio Options:");
     puts  ("  --add-codec=name    Manually add codec (default is to enable all)");
     puts  ("  --dis-codec=name    Disable codec (can be specified multiple times)");
@@ -302,4 +302,9 @@
     puts  ("  --no-tones          Disable audible tones");
     puts  ("  --jb-max-size       Specify jitter buffer maximum size, in frames (default=-1)");
+    puts  ("  --extra-audio       Add one more audio stream");
+
+    puts  ("");
+    puts  ("Video Options:");
+    puts  ("  --video             Enable video");
 
     puts  ("");
@@ -541,5 +546,6 @@
 	   OPT_AUTO_UPDATE_NAT,OPT_USE_COMPACT_FORM,OPT_DIS_CODEC,
 	   OPT_NO_FORCE_LR,
-	   OPT_TIMER, OPT_TIMER_SE, OPT_TIMER_MIN_SE
+	   OPT_TIMER, OPT_TIMER_SE, OPT_TIMER_MIN_SE,
+	   OPT_VIDEO, OPT_EXTRA_AUDIO
     };
     struct pj_getopt_option long_options[] = {
@@ -660,4 +666,6 @@
 	{ "timer-min-se", 1, 0, OPT_TIMER_MIN_SE},
 	{ "outb-rid",	1, 0, OPT_OUTB_RID},
+	{ "video",	0, 0, OPT_VIDEO},
+	{ "extra-audio",0, 0, OPT_EXTRA_AUDIO},
 	{ NULL, 0, 0, 0}
     };
@@ -1416,4 +1424,10 @@
 	    cfg->udp_cfg.qos_params.flags = PJ_QOS_PARAM_HAS_DSCP;
 	    cfg->udp_cfg.qos_params.dscp_val = 0x18;
+	    break;
+	case OPT_VIDEO:
+	    ++cur_acc->max_video_cnt;
+	    break;
+	case OPT_EXTRA_AUDIO:
+	    ++cur_acc->max_audio_cnt;
 	    break;
 	default:
@@ -1661,4 +1675,12 @@
     if (acc_cfg->mwi_enabled)
 	pj_strcat2(result, "--mwi\n");
+
+    /* Video & extra audio */
+    for (i=0; i<acc_cfg->max_video_cnt; ++i) {
+	pj_strcat2(result, "--video\n");
+    }
+    for (i=1; i<acc_cfg->max_audio_cnt; ++i) {
+	pj_strcat2(result, "--extra-audio\n");
+    }
 }
 
@@ -3892,4 +3914,6 @@
 		acc_cfg.cred_info[0].data = pj_str(passwd);
 
+		acc_cfg.rtp_cfg = app_config.rtp_cfg;
+
 		status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
 		if (status != PJ_SUCCESS) {
@@ -4929,4 +4953,5 @@
     /* Add accounts */
     for (i=0; i<app_config.acc_cnt; ++i) {
+	app_config.acc_cfg[i].rtp_cfg = app_config.rtp_cfg;
 	status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
 	if (status != PJ_SUCCESS)
@@ -4960,6 +4985,8 @@
     if (app_config.ipv6)
 	status = create_ipv6_media_transports();
+  #if DISABLED_FOR_TICKET_1185
     else
 	status = pjsua_media_transports_create(&app_config.rtp_cfg);
+  #endif
 #endif
     if (status != PJ_SUCCESS)
@@ -5296,5 +5323,9 @@
     }
 
+#if DISABLED_FOR_TICKET_1185
     return pjsua_media_transports_attach(tp, i, PJ_TRUE);
-}
-
+#else
+    return PJ_ENOTSUP;
+#endif
+}
+
Index: /pjproject/branches/projects/2.0-dev/pjsip/build/Makefile
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/build/Makefile	(revision 3456)
+++ /pjproject/branches/projects/2.0-dev/pjsip/build/Makefile	(revision 3457)
@@ -77,5 +77,5 @@
 			pjsua_acc.o pjsua_call.o pjsua_core.o \
 			pjsua_im.o pjsua_media.o pjsua_pres.o
-export PJSUA_LIB_CFLAGS += $(_CFLAGS)
+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 3456)
+++ /pjproject/branches/projects/2.0-dev/pjsip/include/pjsua-lib/pjsua.h	(revision 3457)
@@ -251,4 +251,7 @@
 #define PJSUA_INVALID_ID	    (-1)
 
+/** Disabled features temporarily for media reorganization */
+#define DISABLED_FOR_TICKET_1185	0
+
 /** Call identification */
 typedef int pjsua_call_id;
@@ -282,6 +285,4 @@
 #   define PJSUA_ACC_MAX_PROXIES    8
 #endif
-
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
 
 /**
@@ -304,6 +305,4 @@
 #endif
 
-#endif
-
 /**
  * Controls whether PJSUA-LIB should add ICE media feature tag
@@ -325,4 +324,42 @@
 #   define PJSUA_ACQUIRE_CALL_TIMEOUT 2000
 #endif
+
+
+/**
+ * This enumeration represents pjsua state.
+ */
+typedef enum pjsua_state
+{
+    /**
+     * The library has not been initialized.
+     */
+    PJSUA_STATE_NULL,
+
+    /**
+     * After pjsua_create() is called but before pjsua_init() is called.
+     */
+    PJSUA_STATE_CREATED,
+
+    /**
+     * After pjsua_init() is called but before pjsua_start() is called.
+     */
+    PJSUA_STATE_INIT,
+
+    /**
+     * After pjsua_start() is called but before everything is running.
+     */
+    PJSUA_STATE_STARTING,
+
+    /**
+     * After pjsua_start() is called and before pjsua_destroy() is called.
+     */
+    PJSUA_STATE_RUNNING,
+
+    /**
+     * After pjsua_destroy() is called but before the function returns.
+     */
+    PJSUA_STATE_CLOSING
+
+} pjsua_state;
 
 
@@ -484,5 +521,5 @@
      *
      * @param call_id	    Call identification.
-     * @param sess	    Media session for the call.
+     * @param strm	    Media stream.
      * @param stream_idx    Stream index in the media session.
      * @param p_port	    On input, it specifies the media port of the
@@ -492,5 +529,5 @@
      */
     void (*on_stream_created)(pjsua_call_id call_id, 
-			      pjmedia_session *sess,
+			      pjmedia_stream *strm,
                               unsigned stream_idx, 
 			      pjmedia_port **p_port);
@@ -501,9 +538,9 @@
      *
      * @param call_id	    Call identification.
-     * @param sess	    Media session for the call.
+     * @param strm	    Media stream.
      * @param stream_idx    Stream index in the media session.
      */
     void (*on_stream_destroyed)(pjsua_call_id call_id,
-                                pjmedia_session *sess, 
+                                pjmedia_stream *strm,
 				unsigned stream_idx);
 
@@ -1155,5 +1192,4 @@
     pj_str_t	    user_agent;
 
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
     /**
      * Specify default value of secure media transport usage. 
@@ -1185,7 +1221,7 @@
 
     /**
-     * Specify whether SRTP in PJMEDIA_SRTP_OPTIONAL mode should compose 
+     * Specify whether SRTP in PJMEDIA_SRTP_OPTIONAL mode should compose
      * duplicated media in SDP offer, i.e: unsecured and secured version.
-     * Otherwise, the SDP media will be composed as unsecured media but 
+     * Otherwise, the SDP media will be composed as unsecured media but
      * with SDP "crypto" attribute.
      *
@@ -1193,5 +1229,4 @@
      */
     pj_bool_t	     srtp_optional_dup_offer;
-#endif
 
     /**
@@ -1351,4 +1386,12 @@
  */
 PJ_DECL(pj_status_t) pjsua_destroy(void);
+
+
+/**
+ * Retrieve pjsua state.
+ *
+ * @return 	pjsua state.
+ */
+PJ_DECL(pjsua_state) pjsua_get_state(void);
 
 
@@ -2094,5 +2137,4 @@
 #endif
 
-
 /**
  * This structure describes account configuration to be specified when
@@ -2385,5 +2427,27 @@
     pj_str_t	     ka_data;
 
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+    /**
+     * Maximum number of simultaneous active audio streams to be allowed
+     * for calls on this account. Setting this to zero will disable audio
+     * in calls on this account.
+     *
+     * Default: 1
+     */
+    unsigned         max_audio_cnt;
+
+    /**
+     * 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
+     */
+    unsigned         max_video_cnt;
+
+    /**
+     * Media transport config.
+     */
+    pjsua_transport_config rtp_cfg;
+
     /**
      * Specify whether secure media transport should be used for this account.
@@ -2409,7 +2473,7 @@
 
     /**
-     * Specify whether SRTP in PJMEDIA_SRTP_OPTIONAL mode should compose 
+     * Specify whether SRTP in PJMEDIA_SRTP_OPTIONAL mode should compose
      * duplicated media in SDP offer, i.e: unsecured and secured version.
-     * Otherwise, the SDP media will be composed as unsecured media but 
+     * Otherwise, the SDP media will be composed as unsecured media but
      * with SDP "crypto" attribute.
      *
@@ -2417,5 +2481,4 @@
      */
     pj_bool_t	     srtp_optional_dup_offer;
-#endif
 
     /**
@@ -2921,17 +2984,27 @@
 typedef enum pjsua_call_media_status
 {
-    /** Call currently has no media */
+    /**
+     * Call currently has no media, or the media is not used.
+     */
     PJSUA_CALL_MEDIA_NONE,
 
-    /** The media is active */
+    /**
+     * The media is active
+     */
     PJSUA_CALL_MEDIA_ACTIVE,
 
-    /** The media is currently put on hold by local endpoint */
+    /**
+     * The media is currently put on hold by local endpoint
+     */
     PJSUA_CALL_MEDIA_LOCAL_HOLD,
 
-    /** The media is currently put on hold by remote endpoint */
+    /**
+     * The media is currently put on hold by remote endpoint
+     */
     PJSUA_CALL_MEDIA_REMOTE_HOLD,
 
-    /** The media has reported error (e.g. ICE negotiation) */
+    /**
+     * The media has reported error (e.g. ICE negotiation)
+     */
     PJSUA_CALL_MEDIA_ERROR
 
@@ -2983,9 +3056,29 @@
     pjsua_call_media_status media_status;
 
-    /** Media direction */
+    /** Number of active audio streams in this call */
+    unsigned		audio_cnt;
+
+    /** Media direction of the first audio stream. */
     pjmedia_dir		media_dir;
 
-    /** The conference port number for the call */
+    /** The conference port number for the first audio stream. */
     pjsua_conf_port_id	conf_slot;
+
+    /** Array of audio media stream information */
+    struct
+    {
+	/** Media index in SDP. */
+	unsigned		index;
+
+	/** Call media status. */
+	pjsua_call_media_status media_status;
+
+	/** Media direction. */
+	pjmedia_dir		media_dir;
+
+	/** The conference port number for the call. */
+	pjsua_conf_port_id	conf_slot;
+
+    } audio[4];
 
     /** Up-to-date call connected duration (zero when call is not 
@@ -3083,4 +3176,5 @@
 
 
+#if DISABLED_FOR_TICKET_1185
 /**
  * Retrieve the media session associated with this call. Note that the media
@@ -3096,5 +3190,4 @@
 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. 
@@ -3107,5 +3200,5 @@
  */
 PJ_DECL(pjmedia_transport*) pjsua_call_get_media_transport(pjsua_call_id cid);
-
+#endif /* DISABLED_FOR_TICKET_1185 */
 
 /**
@@ -4883,7 +4976,5 @@
 					    const pjmedia_codec_param *param);
 
-
-
-
+#if DISABLED_FOR_TICKET_1185
 /**
  * Create UDP media transports for all the calls. This function creates
@@ -4917,4 +5008,5 @@
 			       unsigned count,
 			       pj_bool_t auto_delete);
+#endif
 
 
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 3456)
+++ /pjproject/branches/projects/2.0-dev/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 3457)
@@ -44,4 +44,57 @@
 } pjsua_med_tp_st;
 
+/** Forward decl of pjsua call */
+typedef struct pjsua_call pjsua_call;
+
+/**
+ * Call's media stream.
+ */
+typedef struct pjsua_call_media
+{
+    pjsua_call		*call;	    /**< Parent call.			    */
+    pjmedia_type	 type;	    /**< Media type.			    */
+    unsigned		 idx;       /**< This media index in parent call.   */
+    pjsua_call_media_status state;  /**< Media state.			    */
+    pjmedia_dir		 dir;       /**< Media direction.		    */
+
+    /** The stream */
+    union {
+	/** Audio stream */
+	struct {
+	    pjmedia_stream *stream;    /**< The media session.		    */
+	    int		    conf_slot; /**< Slot # in conference bridge.    */
+	} a;
+
+	/** Video stream */
+	struct {
+	} v;
+
+    } strm;
+
+    pj_uint32_t		 ssrc;	    /**< RTP SSRC			    */
+    pj_uint32_t		 rtp_tx_ts; /**< Initial RTP timestamp for sender.  */
+    pj_uint16_t		 rtp_tx_seq;/**< Initial RTP sequence for sender.   */
+    pj_uint8_t		 rtp_tx_seq_ts_set;
+				    /**< Bitmask flags if initial RTP sequence
+				         and/or timestamp for sender are set.
+					 bit 0/LSB : sequence flag
+					 bit 1     : timestamp flag 	    */
+
+    pjmedia_transport	*tp;        /**< Current media transport (can be 0) */
+    pj_status_t		 tp_ready;  /**< Media transport status.	    */
+    pjmedia_transport	*tp_orig;   /**< Original media transport	    */
+    pj_bool_t		 tp_auto_del; /**< May delete media transport   */
+    pjsua_med_tp_st	 tp_st;     /**< Media transport state		    */
+    pj_sockaddr		 rtp_addr;  /**< Current RTP source address
+					    (used to update ICE default
+					    address)			    */
+    pjmedia_srtp_use	 rem_srtp_use; /**< Remote's SRTP usage policy.	    */
+} pjsua_call_media;
+
+/**
+ * Maximum number of SDP "m=" lines to be supported.
+ */
+#define PJSUA_MAX_CALL_MEDIA		PJMEDIA_MAX_SDP_MEDIA
+
 /** 
  * Structure to be attached to invite dialog. 
@@ -49,5 +102,5 @@
  * by accessing dlg->mod_data[pjsua.mod.id].
  */
-typedef struct pjsua_call
+struct pjsua_call
 {
     unsigned		 index;	    /**< Index in pjsua array.		    */
@@ -64,29 +117,12 @@
     pjsua_call_hold_type call_hold_type; /**< How to do call hold.	    */
     pj_bool_t		 local_hold;/**< Flag for call-hold by local.	    */
-    pjsua_call_media_status media_st;/**< Media state.			    */
-    pjmedia_dir		 media_dir; /**< Media direction.		    */
-    pjmedia_session	*session;   /**< The media session.		    */
-    int			 audio_idx; /**< Index of m=audio in SDP.	    */
-    pj_uint32_t		 ssrc;	    /**< RTP SSRC			    */
-    pj_uint32_t		 rtp_tx_ts; /**< Initial RTP timestamp for sender.  */
-    pj_uint16_t		 rtp_tx_seq;/**< Initial RTP sequence for sender.   */
-    pj_uint8_t		 rtp_tx_seq_ts_set;
-				    /**< Bitmask flags if initial RTP sequence
-				         and/or timestamp for sender are set.
-					 bit 0/LSB : sequence flag 
-					 bit 1     : timestamp flag 	    */
-    int			 conf_slot; /**< Slot # in conference bridge.	    */
+
+    unsigned		 med_cnt;   /**< Number of media in SDP.	    */
+    pjsua_call_media     media[PJSUA_MAX_CALL_MEDIA]; /**< Array of media   */
+    unsigned		 audio_idx; /**< Selected audio media.		    */
+
     pjsip_evsub		*xfer_sub;  /**< Xfer server subscription, if this
 					 call was triggered by xfer.	    */
-    pjmedia_transport	*med_tp;    /**< Current media transport.	    */
-    pj_status_t		 med_tp_ready;/**< Media transport status.	    */
-    pjmedia_transport	*med_orig;  /**< Original media transport	    */
-    pj_bool_t		 med_tp_auto_del; /**< May delete media transport   */
-    pjsua_med_tp_st	 med_tp_st; /**< Media transport state		    */
-    pj_sockaddr		 med_rtp_addr; /**< Current RTP source address
-					    (used to update ICE default
-					    address)			    */
     pj_stun_nat_type	 rem_nat_type; /**< NAT type of remote endpoint.    */
-    pjmedia_srtp_use	 rem_srtp_use; /**< Remote's SRTP usage policy.	    */
 
     char    last_text_buf_[128];    /**< Buffer for last_text.		    */
@@ -100,5 +136,5 @@
 					  contains multiple codecs.	    */
 
-} pjsua_call;
+};
 
 
@@ -271,4 +307,5 @@
     pj_pool_t		*pool;	    /**< pjsua's private pool.		*/
     pj_mutex_t		*mutex;	    /**< Mutex protection for this data	*/
+    pjsua_state		 state;	    /**< Library state.			*/
 
     /* Logging: */
@@ -402,4 +439,7 @@
 #define PJSUA_UNLOCK()
 #endif
+
+/* Core */
+void pjsua_set_state(pjsua_state new_state);
 
 /******
Index: /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_acc.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_acc.c	(revision 3456)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_acc.c	(revision 3457)
@@ -119,5 +119,6 @@
     pjsip_auth_clt_pref_dup(pool, &dst->auth_pref, &src->auth_pref);
 
-    dst->ka_interval = src->ka_interval;
+    pjsua_transport_config_dup(pool, &dst->rtp_cfg, &src->rtp_cfg);
+
     pj_strdup(pool, &dst->ka_data, &src->ka_data);
 }
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 3456)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_call.c	(revision 3457)
@@ -102,27 +102,17 @@
 {
     pjsua_call *call = &pjsua_var.calls[id];
-
+    unsigned i;
+
+    pj_bzero(call, sizeof(*call));
     call->index = id;
-    call->inv = NULL;
-    call->user_data = NULL;
-    call->session = NULL;
-    call->audio_idx = -1;
-    call->ssrc = pj_rand();
-    call->rtp_tx_seq = 0;
-    call->rtp_tx_ts = 0;
-    call->rtp_tx_seq_ts_set = 0;
-    call->xfer_sub = NULL;
-    call->last_code = (pjsip_status_code) 0;
-    call->conf_slot = PJSUA_INVALID_ID;
     call->last_text.ptr = call->last_text_buf_;
-    call->last_text.slen = 0;
-    call->conn_time.sec = 0;
-    call->conn_time.msec = 0;
-    call->res_time.sec = 0;
-    call->res_time.msec = 0;
-    call->rem_nat_type = PJ_STUN_NAT_TYPE_UNKNOWN;
-    call->rem_srtp_use = PJMEDIA_SRTP_DISABLED;
-    call->local_hold = PJ_FALSE;
-    pj_bzero(&call->lock_codec, sizeof(call->lock_codec));
+    for (i=0; i<PJ_ARRAY_SIZE(call->media); ++i) {
+	pjsua_call_media *call_med = &call->media[i];
+	call_med->ssrc = pj_rand();
+	call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
+	call_med->call = call;
+	call_med->idx = i;
+	call_med->tp_auto_del = PJ_TRUE;
+    }
 }
 
@@ -824,5 +814,6 @@
     status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS, 
 				      call->secure_level, 
-				      rdata->tp_info.pool, offer,
+				      rdata->tp_info.pool,
+				      offer,
 				      &sip_err_code);
     if (status != PJ_SUCCESS) {
@@ -1119,10 +1110,12 @@
 PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
 {
+    pjsua_call *call = &pjsua_var.calls[call_id];
     PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, 
 		     PJ_EINVAL);
-    return pjsua_var.calls[call_id].session != NULL;
-}
-
-
+    return call->audio_idx >= 0 && call->media[call->audio_idx].strm.a.stream;
+}
+
+
+#if DISABLED_FOR_TICKET_1185
 /*
  * Retrieve the media session associated with this call.
@@ -1143,7 +1136,7 @@
     PJ_ASSERT_RETURN(cid>=0 && cid<(int)pjsua_var.ua_cfg.max_calls, 
 		     NULL);
-    return pjsua_var.calls[cid].med_tp;
-}
-
+    return pjsua_var.calls[cid].tp;
+}
+#endif /* Removed in 2.0 */
 
 /* Acquire lock to the specified call_id */
@@ -1239,5 +1232,5 @@
 	return PJSUA_INVALID_ID;
 
-    port_id = call->conf_slot;
+    port_id = call->media[call->audio_idx].strm.a.conf_slot;
 
     pjsip_dlg_dec_lock(dlg);
@@ -1256,4 +1249,5 @@
     pjsua_call *call;
     pjsip_dialog *dlg;
+    unsigned mi;
     pj_status_t status;
 
@@ -1330,11 +1324,26 @@
     }
     
-    /* media status and dir */
-    info->media_status = call->media_st;
-    info->media_dir = call->media_dir;
-
+    /* Build array of media status and dir */
+    info->audio_cnt = 0;
+    for (mi=0; mi < call->med_cnt &&
+	       info->audio_cnt < PJ_ARRAY_SIZE(info->audio); ++mi)
+    {
+	pjsua_call_media *call_med = &call->media[mi];
+	if (call_med->type != PJMEDIA_TYPE_AUDIO)
+	    continue;
+	info->audio[info->audio_cnt].index = mi;
+	info->audio[info->audio_cnt].media_status = call_med->state;
+	info->audio[info->audio_cnt].media_dir = call_med->dir;
+	info->audio[info->audio_cnt].conf_slot = call_med->strm.a.conf_slot;
+	++info->audio_cnt;
+    }
+
+    if (info->audio_cnt) {
+	info->media_status = info->audio[0].media_status;
+	info->media_dir = info->audio[0].media_dir;
+    }
 
     /* conference slot number */
-    info->conf_slot = call->conf_slot;
+    info->conf_slot = call->media[call->audio_idx].strm.a.conf_slot;
 
     /* calculate duration */
@@ -1960,5 +1969,5 @@
 	return status;
 
-    if (!call->session) {
+    if (!pjsua_call_has_media(call_id)) {
 	PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
 	pjsip_dlg_dec_lock(dlg);
@@ -1966,5 +1975,6 @@
     }
 
-    status = pjmedia_session_dial_dtmf( call->session, 0, digits);
+    status = pjmedia_stream_dial_dtmf(
+		call->media[call->audio_idx].strm.a.stream, digits);
 
     pjsip_dlg_dec_lock(dlg);
@@ -2191,14 +2201,10 @@
     char *p = buf, *end = buf+maxlen;
     int len;
-    pjmedia_session_info info;
-    pjmedia_session *session = call->session;
-    pjmedia_transport_info tp_info;
-
-    pjmedia_transport_info_init(&tp_info);
-
-    pjmedia_transport_get_info(call->med_tp, &tp_info);
-    pjmedia_session_get_info(session, &info);
-
-    for (i=0; i<info.stream_cnt; ++i) {
+
+    for (i=0; i<call->med_cnt; ++i) {
+	pjsua_call_media *call_med = &call->media[i];
+	pjmedia_stream_info info;
+	pjmedia_stream *stream = call_med->strm.a.stream;
+	pjmedia_transport_info tp_info;
 	pjmedia_rtcp_stat stat;
 	char rem_addr_buf[80];
@@ -2209,5 +2215,40 @@
 	pj_time_val media_duration, now;
 
-	pjmedia_session_get_stream_stat(session, i, &stat);
+	/* Check if the stream is deactivated */
+	if (call_med->tp == NULL || stream == NULL) {
+	    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;
+	    }
+	    len = pj_ansi_snprintf(p, end-p,
+		      "%s  #%d m=%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);
+
+	pjmedia_stream_get_info(stream, &info);
+	pjmedia_stream_get_stat(stream, &stat);
+
 	// rem_addr will contain actual address of RTP originator, instead of
 	// remote RTP address specified by stream which is fetched from the SDP.
@@ -2223,14 +2264,14 @@
 	}
 
-	if (call->media_dir == PJMEDIA_DIR_NONE) {
+	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 = "inactive";
-	} else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING)
+	} else if (info.dir == PJMEDIA_DIR_ENCODING)
 	    dir = "sendonly";
-	else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING)
+	else if (info.dir == PJMEDIA_DIR_DECODING)
 	    dir = "recvonly";
-	else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING)
+	else if (info.dir == PJMEDIA_DIR_ENCODING_DECODING)
 	    dir = "sendrecv";
 	else
@@ -2238,10 +2279,10 @@
 
 	
-	len = pj_ansi_snprintf(buf, end-p, 
+	len = pj_ansi_snprintf(p, end-p,
 		  "%s  #%d %.*s @%dKHz, %s, peer=%s",
 		  indent, i,
-		  (int)info.stream_info[i].fmt.encoding_name.slen,
-		  info.stream_info[i].fmt.encoding_name.ptr,
-		  info.stream_info[i].fmt.clock_rate / 1000,
+		  (int)info.fmt.encoding_name.slen,
+		  info.fmt.encoding_name.ptr,
+		  info.fmt.clock_rate / 1000,
 		  dir,
 		  rem_addr);
@@ -2294,5 +2335,5 @@
 #endif
 	       "%s",
-	       indent, info.stream_info[i].fmt.pt,
+	       indent, info.fmt.pt,
 	       last_update,
 	       indent,
@@ -2371,7 +2412,6 @@
 	       "%s        jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f%s",
 	       indent,
-	       info.stream_info[i].tx_pt,
-	       info.stream_info[i].param->info.frm_ptime *
-		info.stream_info[i].param->setting.frm_per_pkt,
+	       info.tx_pt,
+	       info.param->info.frm_ptime * info.param->setting.frm_per_pkt,
 	       last_update,
 
@@ -2416,5 +2456,5 @@
 
 	len = pj_ansi_snprintf(p, end-p,
-	       "%s    RTT msec       : %7.3f %7.3f %7.3f %7.3f %7.3f", 
+	       "%s     RTT msec      : %7.3f %7.3f %7.3f %7.3f %7.3f",
 	       indent,
 	       stat.rtt.min / 1000.0,
@@ -2471,5 +2511,5 @@
 	    }
 
-	    clock_rate = info.stream_info[i].fmt.clock_rate;
+	    clock_rate = info.fmt.clock_rate;
 
 	    len = pj_ansi_snprintf(p, end-p, "\n%s  Extended reports:", indent);
@@ -2944,6 +2984,7 @@
 
     /* Get and ICE SRTP status */
+#if DISABLED_FOR_TICKET_1185
     pjmedia_transport_info_init(&tp_info);
-    pjmedia_transport_get_info(call->med_tp, &tp_info);
+    pjmedia_transport_get_info(call->tp, &tp_info);
     if (tp_info.specific_info_cnt > 0) {
 	unsigned i;
@@ -2984,7 +3025,8 @@
 	}
     }
+#endif	/* DISABLED_FOR_TICKET_1185 */
 
     /* Dump session statistics */
-    if (with_media && call->session)
+    if (with_media && pjsua_call_has_media(call_id))
 	dump_media_session(indent, p, end-p, call);
 
@@ -3064,4 +3106,5 @@
     const pjmedia_sdp_media *ref_m;
     pjmedia_sdp_media *m;
+    pjsua_call_media *call_med = &call->media[call->audio_idx];
     unsigned i, codec_cnt = 0;
     pj_bool_t rem_can_update;
@@ -3098,7 +3141,7 @@
 
     /* Verify if media is deactivated */
-    if (call->media_st == PJSUA_CALL_MEDIA_NONE ||
-        call->media_st == PJSUA_CALL_MEDIA_ERROR ||
-        call->media_dir == PJMEDIA_DIR_NONE)
+    if (call_med->state == PJSUA_CALL_MEDIA_NONE ||
+	call_med->state == PJSUA_CALL_MEDIA_ERROR ||
+	call_med->dir == PJMEDIA_DIR_NONE)
     {
         return PJ_EINVALIDOP;
@@ -3190,4 +3233,5 @@
     const pjmedia_sdp_media *rem_m, *loc_m;
     unsigned codec_cnt=0, i;
+    pjsua_call_media *call_med = &call->media[call->audio_idx];
     pj_time_val delay = {0, 0};
     const pj_str_t st_update = {"UPDATE", 6};
@@ -3207,7 +3251,7 @@
 
     /* Skip this if the media is inactive or error */
-    if (call->media_st == PJSUA_CALL_MEDIA_NONE ||
-        call->media_st == PJSUA_CALL_MEDIA_ERROR ||
-        call->media_dir == PJMEDIA_DIR_NONE)
+    if (call_med->state == PJSUA_CALL_MEDIA_NONE ||
+	call_med->state == PJSUA_CALL_MEDIA_ERROR ||
+	call_med->dir == PJMEDIA_DIR_NONE)
     {
         return PJ_SUCCESS;
@@ -3525,5 +3569,6 @@
 
     /* Add SDP in 488 status */
-    if (call && call->med_tp && tdata->msg->type==PJSIP_RESPONSE_MSG && 
+#if DISABLED_FOR_TICKET_1185
+    if (call && call->tp && tdata->msg->type==PJSIP_RESPONSE_MSG &&
 	code==PJSIP_SC_NOT_ACCEPTABLE_HERE) 
     {
@@ -3540,4 +3585,5 @@
 	}
     }
+#endif
 
     pjsip_inv_send_msg(inv, tdata);
@@ -3655,5 +3701,5 @@
      */
     /* http://trac.pjsip.org/repos/ticket/880 
-       if (call->media_dir != PJMEDIA_DIR_ENCODING) {
+       if (call->dir != PJMEDIA_DIR_ENCODING) {
      */
     /* https://trac.pjsip.org/repos/ticket/1142:
@@ -3695,5 +3741,5 @@
 	pjmedia_sdp_media_remove_all_attr(m, "inactive");
 
-	if (call->media_dir & PJMEDIA_DIR_ENCODING) {
+	if (call->media[call->audio_idx].dir & PJMEDIA_DIR_ENCODING) {
 	    /* Add sendonly attribute */
 	    attr = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
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 3456)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_core.c	(revision 3457)
@@ -104,8 +104,6 @@
     cfg->force_lr = PJ_TRUE;
     cfg->enable_unsolicited_mwi = PJ_TRUE;
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
     cfg->use_srtp = PJSUA_DEFAULT_USE_SRTP;
     cfg->srtp_secure_signaling = PJSUA_DEFAULT_SRTP_SECURE_SIGNALING;
-#endif
     cfg->hangup_forked_call = PJ_TRUE;
 
@@ -177,9 +175,9 @@
     cfg->ka_interval = 15;
     cfg->ka_data = pj_str("\r\n");
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+    cfg->max_audio_cnt = 1;
+    pjsua_transport_config_default(&cfg->rtp_cfg);
     cfg->use_srtp = pjsua_var.ua_cfg.use_srtp;
     cfg->srtp_secure_signaling = pjsua_var.ua_cfg.srtp_secure_signaling;
     cfg->srtp_optional_dup_offer = pjsua_var.ua_cfg.srtp_optional_dup_offer;
-#endif
     cfg->reg_retry_interval = PJSUA_REG_RETRY_INTERVAL;
     cfg->contact_rewrite_method = PJSUA_CONTACT_REWRITE_METHOD;
@@ -224,5 +222,4 @@
 }
 
-
 /*****************************************************************************
  * This is a very simple PJSIP module, whose sole purpose is to display
@@ -379,4 +376,5 @@
 
     /* Get media socket info, make sure transport is ready */
+#if DISABLED_FOR_TICKET_1185
     if (pjsua_var.calls[0].med_tp) {
 	pjmedia_transport_info_init(&tpinfo);
@@ -390,6 +388,7 @@
 	}
     }
-
-    /* Send response statelessly */
+#endif
+
+    /* Send response */
     pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
     status = pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, tdata, NULL, NULL);
@@ -660,4 +659,5 @@
     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
 
+    pjsua_set_state(PJSUA_STATE_CREATED);
 
     return PJ_SUCCESS;
@@ -919,4 +919,6 @@
 			 pj_get_version(), PJ_OS_NAME));
 
+    pjsua_set_state(PJSUA_STATE_INIT);
+
     return PJ_SUCCESS;
 
@@ -1276,4 +1278,10 @@
     int i;  /* Must be signed */
 
+    if (pjsua_var.state > PJSUA_STATE_NULL &&
+	pjsua_var.state < PJSUA_STATE_CLOSING)
+    {
+	pjsua_set_state(PJSUA_STATE_CLOSING);
+    }
+
     /* Signal threads to quit: */
     pjsua_var.thread_quit_flag = 1;
@@ -1453,8 +1461,32 @@
     pj_bzero(&pjsua_var, sizeof(pjsua_var));
 
+    pjsua_set_state(PJSUA_STATE_NULL);
+
     /* Done. */
     return PJ_SUCCESS;
 }
 
+void pjsua_set_state(pjsua_state new_state)
+{
+    const char *state_name[] = {
+        "NULL",
+        "CREATED",
+        "INIT",
+        "STARTING",
+        "RUNNING",
+        "CLOSING"
+    };
+    pjsua_state old_state = pjsua_var.state;
+
+    pjsua_var.state = new_state;
+    PJ_LOG(4,(THIS_FILE, "PJSUA state changed: %s --> %s",
+	      state_name[old_state], state_name[new_state]));
+}
+
+/* Get state */
+PJ_DEF(pjsua_state) pjsua_get_state(void)
+{
+    return pjsua_var.state;
+}
 
 /**
@@ -1469,4 +1501,6 @@
     pj_status_t status;
 
+    pjsua_set_state(PJSUA_STATE_STARTING);
+
     status = pjsua_call_subsys_start();
     if (status != PJ_SUCCESS)
@@ -1480,4 +1514,6 @@
     if (status != PJ_SUCCESS)
 	return status;
+
+    pjsua_set_state(PJSUA_STATE_RUNNING);
 
     return PJ_SUCCESS;
@@ -2564,4 +2600,5 @@
 
     PJ_LOG(3,(THIS_FILE, "Dumping media transports:"));
+#if DISABLED_FOR_TICKET_1185
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
 	pjsua_call *call = &pjsua_var.calls[i];
@@ -2580,4 +2617,5 @@
 				    sizeof(addr_buf), 3)));
     }
+#endif
 
     pjsip_tsx_layer_dump(detail);
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 3456)
+++ /pjproject/branches/projects/2.0-dev/pjsip/src/pjsua-lib/pjsua_media.c	(revision 3457)
@@ -275,4 +275,43 @@
 #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_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
@@ -339,212 +378,4 @@
 
 
-/* 
- * Create RTP and RTCP socket pair, and possibly resolve their public
- * address via STUN.
- */
-static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
-					pjmedia_sock_info *skinfo)
-{
-    enum { 
-	RTP_RETRY = 100
-    };
-    int i;
-    pj_sockaddr_in bound_addr;
-    pj_sockaddr_in mapped_addr[2];
-    pj_status_t status = PJ_SUCCESS;
-    char addr_buf[PJ_INET6_ADDRSTRLEN+2];
-    pj_sock_t sock[2];
-
-    /* Make sure STUN server resolution has completed */
-    status = resolve_stun_server(PJ_TRUE);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
-	return status;
-    }
-
-    if (next_rtp_port == 0)
-	next_rtp_port = (pj_uint16_t)cfg->port;
-
-    for (i=0; i<2; ++i)
-	sock[i] = PJ_INVALID_SOCKET;
-
-    bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
-    if (cfg->bound_addr.slen) {
-	status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
-			 status);
-	    return status;
-	}
-    }
-
-    /* Loop retry to bind RTP and RTCP sockets. */
-    for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
-
-	/* Create RTP socket. */
-	status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "socket() error", status);
-	    return status;
-	}
-
-	/* Apply QoS to RTP socket, if specified */
-	status = pj_sock_apply_qos2(sock[0], cfg->qos_type, 
-				    &cfg->qos_params, 
-				    2, THIS_FILE, "RTP socket");
-
-	/* Bind RTP socket */
-	status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr), 
-			       next_rtp_port);
-	if (status != PJ_SUCCESS) {
-	    pj_sock_close(sock[0]); 
-	    sock[0] = PJ_INVALID_SOCKET;
-	    continue;
-	}
-
-	/* Create RTCP socket. */
-	status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "socket() error", status);
-	    pj_sock_close(sock[0]);
-	    return status;
-	}
-
-	/* Apply QoS to RTCP socket, if specified */
-	status = pj_sock_apply_qos2(sock[1], cfg->qos_type, 
-				    &cfg->qos_params, 
-				    2, THIS_FILE, "RTCP socket");
-
-	/* Bind RTCP socket */
-	status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr), 
-			       (pj_uint16_t)(next_rtp_port+1));
-	if (status != PJ_SUCCESS) {
-	    pj_sock_close(sock[0]); 
-	    sock[0] = PJ_INVALID_SOCKET;
-
-	    pj_sock_close(sock[1]); 
-	    sock[1] = PJ_INVALID_SOCKET;
-	    continue;
-	}
-
-	/*
-	 * If we're configured to use STUN, then find out the mapped address,
-	 * and make sure that the mapped RTCP port is adjacent with the RTP.
-	 */
-	if (pjsua_var.stun_srv.addr.sa_family != 0) {
-	    char ip_addr[32];
-	    pj_str_t stun_srv;
-
-	    pj_ansi_strcpy(ip_addr, 
-			   pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
-	    stun_srv = pj_str(ip_addr);
-
-	    status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
-					   &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
-					   &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
-					   mapped_addr);
-	    if (status != PJ_SUCCESS) {
-		pjsua_perror(THIS_FILE, "STUN resolve error", status);
-		goto on_error;
-	    }
-
-#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
-	    if (pj_ntohs(mapped_addr[1].sin_port) == 
-		pj_ntohs(mapped_addr[0].sin_port)+1)
-	    {
-		/* Success! */
-		break;
-	    }
-
-	    pj_sock_close(sock[0]); 
-	    sock[0] = PJ_INVALID_SOCKET;
-
-	    pj_sock_close(sock[1]); 
-	    sock[1] = PJ_INVALID_SOCKET;
-#else
-	    if (pj_ntohs(mapped_addr[1].sin_port) != 
-		pj_ntohs(mapped_addr[0].sin_port)+1)
-	    {
-		PJ_LOG(4,(THIS_FILE, 
-			  "Note: STUN mapped RTCP port %d is not adjacent"
-			  " to RTP port %d",
-			  pj_ntohs(mapped_addr[1].sin_port),
-			  pj_ntohs(mapped_addr[0].sin_port)));
-	    }
-	    /* Success! */
-	    break;
-#endif
-
-	} else if (cfg->public_addr.slen) {
-
-	    status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
-					 (pj_uint16_t)next_rtp_port);
-	    if (status != PJ_SUCCESS)
-		goto on_error;
-
-	    status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
-					 (pj_uint16_t)(next_rtp_port+1));
-	    if (status != PJ_SUCCESS)
-		goto on_error;
-
-	    break;
-
-	} else {
-
-	    if (bound_addr.sin_addr.s_addr == 0) {
-		pj_sockaddr addr;
-
-		/* Get local IP address. */
-		status = pj_gethostip(pj_AF_INET(), &addr);
-		if (status != PJ_SUCCESS)
-		    goto on_error;
-
-		bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
-	    }
-
-	    for (i=0; i<2; ++i) {
-		pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
-		mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
-	    }
-
-	    mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
-	    mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
-	    break;
-	}
-    }
-
-    if (sock[0] == PJ_INVALID_SOCKET) {
-	PJ_LOG(1,(THIS_FILE, 
-		  "Unable to find appropriate RTP/RTCP ports combination"));
-	goto on_error;
-    }
-
-
-    skinfo->rtp_sock = sock[0];
-    pj_memcpy(&skinfo->rtp_addr_name, 
-	      &mapped_addr[0], sizeof(pj_sockaddr_in));
-
-    skinfo->rtcp_sock = sock[1];
-    pj_memcpy(&skinfo->rtcp_addr_name, 
-	      &mapped_addr[1], sizeof(pj_sockaddr_in));
-
-    PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
-	      pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
-				sizeof(addr_buf), 3)));
-    PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
-	      pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
-				sizeof(addr_buf), 3)));
-
-    next_rtp_port += 2;
-    return PJ_SUCCESS;
-
-on_error:
-    for (i=0; i<2; ++i) {
-	if (sock[i] != PJ_INVALID_SOCKET)
-	    pj_sock_close(sock[i]);
-    }
-    return status;
-}
-
 /* Check if sound device is idle. */
 static void check_snd_dev_idle()
@@ -579,5 +410,5 @@
      * there is no active call.
      */
-    if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) && 
+    if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
 	pjsua_var.snd_idle_timer.id == PJ_FALSE &&
 	pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
@@ -591,5 +422,5 @@
 
 	pjsua_var.snd_idle_timer.id = PJ_TRUE;
-	pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer, 
+	pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
 				   &delay);
     }
@@ -605,5 +436,5 @@
     PJSUA_LOCK();
     if (entry->id) {
-	PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds", 
+	PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
 		  pjsua_var.media_cfg.snd_auto_close_time));
 
@@ -623,6 +454,7 @@
     pj_status_t status;
 
+#if DISABLED_FOR_TICKET_1185
     /* Create media for calls, if none is specified */
-    if (pjsua_var.calls[0].med_tp == NULL) {
+    if (pjsua_var.calls[0].media[0].tp == NULL) {
 	pjsua_transport_config transport_cfg;
 
@@ -635,10 +467,14 @@
 	    return status;
     }
-
-    pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL, 
+#endif
+
+    pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
 			&close_snd_timer_cb);
 
     /* Perform NAT detection */
-    pjsua_detect_nat_type();
+    status = pjsua_detect_nat_type();
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed"));
+    }
 
     return PJ_SUCCESS;
@@ -685,15 +521,30 @@
     /* Close media transports */
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-	if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
-	    pjsua_media_channel_deinit(i);
-	}
-	if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
-	    pjmedia_transport_close(pjsua_var.calls[i].med_tp);
-	}
-	pjsua_var.calls[i].med_tp = NULL;
+	unsigned strm_idx;
+	pjsua_call *call = &pjsua_var.calls[i];
+	for (strm_idx=0; strm_idx<call->med_cnt; ++strm_idx) {
+	    pjsua_call_media *call_med = &call->media[strm_idx];
+	    if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
+		pjsua_media_channel_deinit(i);
+	    }
+	    if (call_med->tp && call_med->tp_auto_del) {
+		pjmedia_transport_close(call_med->tp);
+	    }
+	    call_med->tp = NULL;
+	}
     }
 
     /* Destroy media endpoint. */
     if (pjsua_var.med_endpt) {
+
+	/* Videodev */
+#	if PJMEDIA_HAS_VIDEO
+	    pjmedia_vid_subsys_shutdown();
+#	endif
+
+	/* ffmpeg */
+#	if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
+	    pjmedia_codec_ffmpeg_deinit();
+#	endif
 
 	/* Shutdown all codecs: */
@@ -745,39 +596,270 @@
 }
 
-
+/*
+ * Create RTP and RTCP socket pair, and possibly resolve their public
+ * address via STUN.
+ */
+static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
+					pjmedia_sock_info *skinfo)
+{
+    enum {
+	RTP_RETRY = 100
+    };
+    int i;
+    pj_sockaddr_in bound_addr;
+    pj_sockaddr_in mapped_addr[2];
+    pj_status_t status = PJ_SUCCESS;
+    char addr_buf[PJ_INET6_ADDRSTRLEN+2];
+    pj_sock_t sock[2];
+
+    /* Make sure STUN server resolution has completed */
+    status = resolve_stun_server(PJ_TRUE);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
+	return status;
+    }
+
+    if (next_rtp_port == 0)
+	next_rtp_port = (pj_uint16_t)cfg->port;
+
+    if (next_rtp_port == 0)
+	next_rtp_port = (pj_uint16_t)40000;
+
+    for (i=0; i<2; ++i)
+	sock[i] = PJ_INVALID_SOCKET;
+
+    bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
+    if (cfg->bound_addr.slen) {
+	status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
+			 status);
+	    return status;
+	}
+    }
+
+    /* Loop retry to bind RTP and RTCP sockets. */
+    for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
+
+	/* Create RTP socket. */
+	status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "socket() error", status);
+	    return status;
+	}
+
+	/* Apply QoS to RTP socket, if specified */
+	status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
+				    &cfg->qos_params,
+				    2, THIS_FILE, "RTP socket");
+
+	/* Bind RTP socket */
+	status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
+			       next_rtp_port);
+	if (status != PJ_SUCCESS) {
+	    pj_sock_close(sock[0]);
+	    sock[0] = PJ_INVALID_SOCKET;
+	    continue;
+	}
+
+	/* Create RTCP socket. */
+	status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "socket() error", status);
+	    pj_sock_close(sock[0]);
+	    return status;
+	}
+
+	/* Apply QoS to RTCP socket, if specified */
+	status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
+				    &cfg->qos_params,
+				    2, THIS_FILE, "RTCP socket");
+
+	/* Bind RTCP socket */
+	status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
+			       (pj_uint16_t)(next_rtp_port+1));
+	if (status != PJ_SUCCESS) {
+	    pj_sock_close(sock[0]);
+	    sock[0] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[1]);
+	    sock[1] = PJ_INVALID_SOCKET;
+	    continue;
+	}
+
+	/*
+	 * If we're configured to use STUN, then find out the mapped address,
+	 * and make sure that the mapped RTCP port is adjacent with the RTP.
+	 */
+	if (pjsua_var.stun_srv.addr.sa_family != 0) {
+	    char ip_addr[32];
+	    pj_str_t stun_srv;
+
+	    pj_ansi_strcpy(ip_addr,
+			   pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
+	    stun_srv = pj_str(ip_addr);
+
+	    status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
+					   &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
+					   &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
+					   mapped_addr);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "STUN resolve error", status);
+		goto on_error;
+	    }
+
+#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
+	    if (pj_ntohs(mapped_addr[1].sin_port) ==
+		pj_ntohs(mapped_addr[0].sin_port)+1)
+	    {
+		/* Success! */
+		break;
+	    }
+
+	    pj_sock_close(sock[0]);
+	    sock[0] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[1]);
+	    sock[1] = PJ_INVALID_SOCKET;
+#else
+	    if (pj_ntohs(mapped_addr[1].sin_port) !=
+		pj_ntohs(mapped_addr[0].sin_port)+1)
+	    {
+		PJ_LOG(4,(THIS_FILE,
+			  "Note: STUN mapped RTCP port %d is not adjacent"
+			  " to RTP port %d",
+			  pj_ntohs(mapped_addr[1].sin_port),
+			  pj_ntohs(mapped_addr[0].sin_port)));
+	    }
+	    /* Success! */
+	    break;
+#endif
+
+	} else if (cfg->public_addr.slen) {
+
+	    status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
+					 (pj_uint16_t)next_rtp_port);
+	    if (status != PJ_SUCCESS)
+		goto on_error;
+
+	    status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
+					 (pj_uint16_t)(next_rtp_port+1));
+	    if (status != PJ_SUCCESS)
+		goto on_error;
+
+	    break;
+
+	} else {
+
+	    if (bound_addr.sin_addr.s_addr == 0) {
+		pj_sockaddr addr;
+
+		/* Get local IP address. */
+		status = pj_gethostip(pj_AF_INET(), &addr);
+		if (status != PJ_SUCCESS)
+		    goto on_error;
+
+		bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
+	    }
+
+	    for (i=0; i<2; ++i) {
+		pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
+		mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
+	    }
+
+	    mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
+	    mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
+	    break;
+	}
+    }
+
+    if (sock[0] == PJ_INVALID_SOCKET) {
+	PJ_LOG(1,(THIS_FILE,
+		  "Unable to find appropriate RTP/RTCP ports combination"));
+	goto on_error;
+    }
+
+
+    skinfo->rtp_sock = sock[0];
+    pj_memcpy(&skinfo->rtp_addr_name,
+	      &mapped_addr[0], sizeof(pj_sockaddr_in));
+
+    skinfo->rtcp_sock = sock[1];
+    pj_memcpy(&skinfo->rtcp_addr_name,
+	      &mapped_addr[1], sizeof(pj_sockaddr_in));
+
+    PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
+	      pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
+				sizeof(addr_buf), 3)));
+    PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
+	      pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
+				sizeof(addr_buf), 3)));
+
+    next_rtp_port += 2;
+    return PJ_SUCCESS;
+
+on_error:
+    for (i=0; i<2; ++i) {
+	if (sock[i] != PJ_INVALID_SOCKET)
+	    pj_sock_close(sock[i]);
+    }
+    return status;
+}
+
+/* Create normal UDP media transports */
+static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg,
+					      pjsua_call_media *call_med)
+{
+    pjmedia_sock_info skinfo;
+    pj_status_t status;
+
+    status = create_rtp_rtcp_sock(cfg, &skinfo);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
+		     status);
+	goto on_error;
+    }
+
+    status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
+					  &skinfo, 0, &call_med->tp);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create media transport",
+		     status);
+	goto on_error;
+    }
+
+    pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
+				    pjsua_var.media_cfg.tx_drop_pct);
+
+    pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
+				    pjsua_var.media_cfg.rx_drop_pct);
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (call_med->tp)
+	pjmedia_transport_close(call_med->tp);
+
+    return status;
+}
+
+#if DISABLED_FOR_TICKET_1185
 /* Create normal UDP media transports */
 static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
 {
     unsigned i;
-    pjmedia_sock_info skinfo;
     pj_status_t status;
 
-    /* Create each media transport */
-    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-
-	status = create_rtp_rtcp_sock(cfg, &skinfo);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
-		         status);
-	    goto on_error;
-	}
-
-	status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
-					      &skinfo, 0,
-					      &pjsua_var.calls[i].med_tp);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create media transport",
-		         status);
-	    goto on_error;
-	}
-
-	pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
-					PJMEDIA_DIR_ENCODING,
-					pjsua_var.media_cfg.tx_drop_pct);
-
-	pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
-					PJMEDIA_DIR_DECODING,
-					pjsua_var.media_cfg.rx_drop_pct);
-
+    for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+	pjsua_call *call = &pjsua_var.calls[i];
+	unsigned strm_idx;
+
+	for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+	    pjsua_call_media *call_med = &call->media[strm_idx];
+
+	    status = create_udp_media_transport(cfg, &call_med->tp);
+	    if (status != PJ_SUCCESS)
+		goto on_error;
+	}
     }
 
@@ -785,14 +867,20 @@
 
 on_error:
-    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-	if (pjsua_var.calls[i].med_tp != NULL) {
-	    pjmedia_transport_close(pjsua_var.calls[i].med_tp);
-	    pjsua_var.calls[i].med_tp = NULL;
-	}
-    }
-
+    for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+	pjsua_call *call = &pjsua_var.calls[i];
+	unsigned strm_idx;
+
+	for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+	    pjsua_call_media *call_med = &call->media[strm_idx];
+
+	    if (call_med->tp) {
+		pjmedia_transport_close(call_med->tp);
+		call_med->tp = NULL;
+	    }
+	}
+    }
     return status;
 }
-
+#endif
 
 /* This callback is called when ICE negotiation completes */
@@ -801,38 +889,22 @@
 			    pj_status_t result)
 {
-    unsigned id;
-    pj_bool_t found = PJ_FALSE;
-
-    /* Find call which has this media transport */
-
-    PJSUA_LOCK();
-
-    for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
-	if (pjsua_var.calls[id].med_tp == tp ||
-	    pjsua_var.calls[id].med_orig == tp) 
-	{
-	    found = PJ_TRUE;
-	    break;
-	}
-    }
-
-    PJSUA_UNLOCK();
-
-    if (!found)
+    pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
+
+    if (!call_med)
 	return;
 
     switch (op) {
     case PJ_ICE_STRANS_OP_INIT:
-	pjsua_var.calls[id].med_tp_ready = result;
+	call_med->tp_ready = result;
 	break;
     case PJ_ICE_STRANS_OP_NEGOTIATION:
 	if (result != PJ_SUCCESS) {
-	    pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
-	    pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
-
-	    if (pjsua_var.ua_cfg.cb.on_call_media_state) {
-		pjsua_var.ua_cfg.cb.on_call_media_state(id);
+	    call_med->state = PJSUA_CALL_MEDIA_ERROR;
+	    call_med->dir = PJMEDIA_DIR_NONE;
+
+	    if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
+		pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
 	    }
-	} else {
+	} else if (call_med->call) {
 	    /* Send UPDATE if default transport address is different than
 	     * what was advertised (ticket #881)
@@ -854,5 +926,5 @@
 	    if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
 		pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
-				&pjsua_var.calls[id].med_rtp_addr))
+				&call_med->rtp_addr))
 	    {
 		pj_bool_t use_update;
@@ -861,5 +933,5 @@
 		pjsip_dialog *dlg;
 
-		dlg = pjsua_var.calls[id].inv->dlg;
+		dlg = call_med->call->inv->dlg;
 		support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
 							  NULL, &STR_UPDATE);
@@ -868,11 +940,11 @@
 		PJ_LOG(4,(THIS_FILE, 
 		          "ICE default transport address has changed for "
-			  "call %d, sending %s", id,
+			  "call %d, sending %s", call_med->call->index,
 			  (use_update ? "UPDATE" : "re-INVITE")));
 
 		if (use_update)
-		    pjsua_call_update(id, 0, NULL);
+		    pjsua_call_update(call_med->call->index, 0, NULL);
 		else
-		    pjsua_call_reinvite(id, 0, NULL);
+		    pjsua_call_reinvite(call_med->call->index, 0, NULL);
 	    }
 	}
@@ -881,7 +953,9 @@
 	if (result != PJ_SUCCESS) {
 	    PJ_PERROR(4,(THIS_FILE, result,
-		         "ICE keep alive failure for transport %d", id));
+		         "ICE keep alive failure for transport %d:%d",
+		         call_med->call->index, call_med->idx));
 	}
 	if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
+	    pjsua_call_id id = call_med->call->index;
 	    (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
 							  NULL);
@@ -919,9 +993,13 @@
 
 /* Create ICE media transports (when ice is enabled) */
-static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
+static pj_status_t create_ice_media_transport(
+				const pjsua_transport_config *cfg,
+				pjsua_call_media *call_med)
 {
     char stunip[PJ_INET6_ADDRSTRLEN];
     pj_ice_strans_cfg ice_cfg;
-    unsigned i;
+    pjmedia_ice_cb ice_cb;
+    char name[32];
+    unsigned comp_cnt;
     pj_status_t status;
 
@@ -980,58 +1058,46 @@
     }
 
-    /* Create each media transport */
-    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-	pjmedia_ice_cb ice_cb;
-	char name[32];
-	unsigned comp_cnt;
-
-	pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
-	ice_cb.on_ice_complete = &on_ice_complete;
-	pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
-	pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
-
-	comp_cnt = 1;
-	if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
-	    ++comp_cnt;
-
-	status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
-				    &ice_cfg, &ice_cb,
-				    &pjsua_var.calls[i].med_tp);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
-		         status);
-	    goto on_error;
-	}
-
-	/* Wait until transport is initialized, or time out */
-	PJSUA_UNLOCK();
-	while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
-	    pjsua_handle_events(100);
-	}
-	PJSUA_LOCK();
-	if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
-		         pjsua_var.calls[i].med_tp_ready);
-	    status = pjsua_var.calls[i].med_tp_ready;
-	    goto on_error;
-	}
-
-	pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
-				        PJMEDIA_DIR_ENCODING,
-				        pjsua_var.media_cfg.tx_drop_pct);
-
-	pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
-				        PJMEDIA_DIR_DECODING,
-				        pjsua_var.media_cfg.rx_drop_pct);
-    }
+    pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
+    ice_cb.on_ice_complete = &on_ice_complete;
+    pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
+    call_med->tp_ready = PJ_EPENDING;
+
+    comp_cnt = 1;
+    if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
+	++comp_cnt;
+
+    status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
+				&ice_cfg, &ice_cb, &call_med->tp);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
+		     status);
+	goto on_error;
+    }
+
+    /* Wait until transport is initialized, or time out */
+    PJSUA_UNLOCK();
+    while (call_med->tp_ready == PJ_EPENDING) {
+	pjsua_handle_events(100);
+    }
+    PJSUA_LOCK();
+    if (call_med->tp_ready != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
+		     call_med->tp_ready);
+	status = call_med->tp_ready;
+	goto on_error;
+    }
+
+    pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
+				    pjsua_var.media_cfg.tx_drop_pct);
+
+    pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
+				    pjsua_var.media_cfg.rx_drop_pct);
 
     return PJ_SUCCESS;
 
 on_error:
-    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-	if (pjsua_var.calls[i].med_tp != NULL) {
-	    pjmedia_transport_close(pjsua_var.calls[i].med_tp);
-	    pjsua_var.calls[i].med_tp = NULL;
-	}
+    if (call_med->tp != NULL) {
+	pjmedia_transport_close(call_med->tp);
+	call_med->tp = NULL;
     }
 
@@ -1039,7 +1105,47 @@
 }
 
-
-/*
- * Create UDP media transports for all the calls. This function creates
+#if DISABLED_FOR_TICKET_1185
+/* Create ICE media transports (when ice is enabled) */
+static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
+{
+    unsigned i;
+    pj_status_t status;
+
+    for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+	pjsua_call *call = &pjsua_var.calls[i];
+	unsigned strm_idx;
+
+	for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+	    pjsua_call_media *call_med = &call->media[strm_idx];
+
+	    status = create_ice_media_transport(cfg, call_med);
+	    if (status != PJ_SUCCESS)
+		goto on_error;
+	}
+    }
+
+    return PJ_SUCCESS;
+
+on_error:
+    for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+	pjsua_call *call = &pjsua_var.calls[i];
+	unsigned strm_idx;
+
+	for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+	    pjsua_call_media *call_med = &call->media[strm_idx];
+
+	    if (call_med->tp) {
+		pjmedia_transport_close(call_med->tp);
+		call_med->tp = NULL;
+	    }
+	}
+    }
+    return status;
+}
+#endif
+
+#if DISABLED_FOR_TICKET_1185
+/*
+ * Create media transports for all the calls. This function creates
  * one UDP media transport for each call.
  */
@@ -1059,10 +1165,15 @@
     /* Delete existing media transports */
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-	if (pjsua_var.calls[i].med_tp != NULL && 
-	    pjsua_var.calls[i].med_tp_auto_del) 
-	{
-	    pjmedia_transport_close(pjsua_var.calls[i].med_tp);
-	    pjsua_var.calls[i].med_tp = NULL;
-	    pjsua_var.calls[i].med_orig = NULL;
+	pjsua_call *call = &pjsua_var.calls[i];
+	unsigned strm_idx;
+
+	for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+	    pjsua_call_media *call_med = &call->media[strm_idx];
+
+	    if (call_med->tp && call_med->tp_auto_del) {
+		pjmedia_transport_close(call_med->tp);
+		call_med->tp = NULL;
+		call_med->tp_orig = NULL;
+	    }
 	}
     }
@@ -1080,5 +1191,12 @@
     /* Set media transport auto_delete to True */
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-	pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
+	pjsua_call *call = &pjsua_var.calls[i];
+	unsigned strm_idx;
+
+	for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+	    pjsua_call_media *call_med = &call->media[strm_idx];
+
+	    call_med->tp_auto_del = PJ_TRUE;
+	}
     }
 
@@ -1101,68 +1219,204 @@
     /* Assign the media transports */
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-	if (pjsua_var.calls[i].med_tp != NULL && 
-	    pjsua_var.calls[i].med_tp_auto_del) 
-	{
-	    pjmedia_transport_close(pjsua_var.calls[i].med_tp);
-	}
-
-	pjsua_var.calls[i].med_tp = tp[i].transport;
-	pjsua_var.calls[i].med_tp_auto_del = auto_delete;
+	pjsua_call *call = &pjsua_var.calls[i];
+	unsigned strm_idx;
+
+	for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+	    pjsua_call_media *call_med = &call->media[strm_idx];
+
+	    if (call_med->tp && call_med->tp_auto_del) {
+		pjmedia_transport_close(call_med->tp);
+		call_med->tp = NULL;
+		call_med->tp_orig = NULL;
+	    }
+	}
+
+	PJ_TODO(remove_pjsua_media_transports_attach);
+
+	call->media[0].tp = tp[i].transport;
+	call->media[0].tp_auto_del = auto_delete;
     }
 
     return PJ_SUCCESS;
 }
-
-
-static int find_audio_index(const pjmedia_sdp_session *sdp, 
-			    pj_bool_t prefer_srtp)
+#endif
+
+/* Go through the list of media in the SDP, find acceptable media, and
+ * sort them based on the "quality" of the media, and store the indexes
+ * in the specified array. Media with the best quality will be listed
+ * first in the array. The quality factors considered currently is
+ * encryption.
+ */
+static void sort_media(const pjmedia_sdp_session *sdp,
+		       const pj_str_t *type,
+		       pjmedia_srtp_use	use_srtp,
+		       pj_uint8_t midx[],
+		       unsigned *p_count)
 {
     unsigned i;
-    int audio_idx = -1;
-
-    for (i=0; i<sdp->media_count; ++i) {
+    unsigned count = 0;
+    int score[PJSUA_MAX_CALL_MEDIA];
+
+    pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
+
+    *p_count = 0;
+    pj_bzero(score, sizeof(score));
+
+    /* Score each media */
+    for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
 	const pjmedia_sdp_media *m = sdp->media[i];
 
-	/* Skip if media is not audio */
-	if (pj_stricmp2(&m->desc.media, "audio") != 0)
+	/* Skip different media */
+	if (pj_stricmp(&m->desc.media, type) != 0) {
+	    score[count++] = -22000;
 	    continue;
-
-	/* Skip if media is disabled */
+	}
+
+	/* Supported transports */
+	if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
+	    switch (use_srtp) {
+	    case PJMEDIA_SRTP_MANDATORY:
+	    case PJMEDIA_SRTP_OPTIONAL:
+		++score[i];
+		break;
+	    case PJMEDIA_SRTP_DISABLED:
+		--score[i];
+		break;
+	    }
+	} else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
+	    switch (use_srtp) {
+	    case PJMEDIA_SRTP_MANDATORY:
+		--score[i];
+		break;
+	    case PJMEDIA_SRTP_OPTIONAL:
+		/* No change in score */
+		break;
+	    case PJMEDIA_SRTP_DISABLED:
+		++score[i];
+		break;
+	    }
+	} else {
+	    score[i] -= 10;
+	}
+
+	/* Is media disabled? */
 	if (m->desc.port == 0)
-	    continue;
-
-	/* Skip if transport is not supported */
-	if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
-	    pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
-	{
-	    continue;
-	}
-
-	if (audio_idx == -1) {
-	    audio_idx = i;
+	    score[i] -= 10;
+
+	/* Is media inactive? */
+	if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL))
+	    score[i] -= 10;
+
+	++count;
+    }
+
+    /* Created sorted list based on quality */
+    for (i=0; i<count; ++i) {
+	unsigned j;
+	int best = 0;
+
+	for (j=1; j<count; ++j) {
+	    if (score[j] > score[best])
+		best = j;
+	}
+	/* Don't put media with negative score, that media is unacceptable
+	 * for us.
+	 */
+	if (score[best] >= 0) {
+	    midx[*p_count] = (pj_uint8_t)best;
+	    (*p_count)++;
+	}
+
+	score[best] = -22000;
+
+    }
+}
+
+/* Initialize the media line */
+static pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
+                                         pjmedia_type type,
+					 const pjsua_transport_config *tcfg,
+					 int security_level,
+					 int *sip_err_code)
+{
+    pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
+    pj_status_t status;
+
+    /*
+     * Note: this function may be called when the media already exists
+     * (e.g. in reinvites, updates, etc.)
+     */
+    call_med->type = type;
+
+    /* Create the media transport for initial call. This is blocking for now */
+    if (call_med->tp == NULL) {
+	if (pjsua_var.media_cfg.enable_ice) {
+	    status = create_ice_media_transport(tcfg, call_med);
 	} else {
-	    /* We've found multiple candidates. This could happen
-	     * e.g. when remote is offering both RTP/SAVP and RTP/AVP,
-	     * or when remote for some reason offers two audio.
-	     */
-
-	    if (prefer_srtp &&
-		pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
-	    {
-		/* Prefer RTP/SAVP when our media transport is SRTP */
-		audio_idx = i;
-		break;
-	    } else if (!prefer_srtp &&
-		       pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
-	    {
-		/* Prefer RTP/AVP when our media transport is NOT SRTP */
-		audio_idx = i;
+	    status = create_udp_media_transport(tcfg, call_med);
+	}
+
+	if (status != PJ_SUCCESS) {
+	    PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
+	    return status;
+	}
+    }
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+    /* This function may be called when SRTP transport already exists
+     * (e.g: in re-invite, update), don't need to destroy/re-create.
+     */
+    if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) {
+	pjmedia_srtp_setting srtp_opt;
+	pjmedia_transport *srtp = NULL;
+
+	/* Check if SRTP requires secure signaling */
+	if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
+	    if (security_level < acc->cfg.srtp_secure_signaling) {
+		if (sip_err_code)
+		    *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
+		status = PJSIP_ESESSIONINSECURE;
+		goto on_error;
 	    }
 	}
-    }
-
-    return audio_idx;
-}
-
+
+	/* Always create SRTP adapter */
+	pjmedia_srtp_setting_default(&srtp_opt);
+	srtp_opt.close_member_tp = PJ_FALSE;
+	/* If media session has been ever established, let's use remote's
+	 * preference in SRTP usage policy, especially when it is stricter.
+	 */
+	if (call_med->rem_srtp_use > acc->cfg.use_srtp)
+	    srtp_opt.use = call_med->rem_srtp_use;
+	else
+	    srtp_opt.use = acc->cfg.use_srtp;
+
+	status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
+					       call_med->tp,
+					       &srtp_opt, &srtp);
+	if (status != PJ_SUCCESS) {
+	    if (sip_err_code)
+		*sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
+	    goto on_error;
+	}
+
+	/* Set SRTP as current media transport */
+	call_med->tp_orig = call_med->tp;
+	call_med->tp = srtp;
+    }
+#else
+    call->tp_orig = call->tp;
+    PJ_UNUSED_ARG(security_level);
+#endif
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (call_med->tp) {
+	pjmedia_transport_close(call_med->tp);
+	call_med->tp = NULL;
+    }
+    return status;
+}
 
 pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
@@ -1173,92 +1427,64 @@
 				     int *sip_err_code)
 {
+    const pj_str_t STR_AUDIO = { "audio", 5 };
+    const pj_str_t STR_VIDEO = { "video", 5 };
     pjsua_call *call = &pjsua_var.calls[call_id];
+    pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
+    pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
+    unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
+    pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
+    unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
+    pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA];
+    unsigned mi;
     pj_status_t status;
 
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
-    pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
-    pjmedia_srtp_setting srtp_opt;
-    pjmedia_transport *srtp = NULL;
-#endif
-
     PJ_UNUSED_ARG(role);
 
+    if (pjsua_get_state() != PJSUA_STATE_RUNNING)
+	return PJ_EBUSY;
+
+#if DISABLED_FOR_TICKET_1185
     /* Return error if media transport has not been created yet
      * (e.g. application is starting)
      */
-    if (call->med_tp == NULL) {
-	if (sip_err_code)
-	    *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
-	return PJ_EBUSY;
-    }
-
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
-    /* This function may be called when SRTP transport already exists 
-     * (e.g: in re-invite, update), don't need to destroy/re-create.
-     */
-    if (!call->med_orig || call->med_tp == call->med_orig) {
-
-	/* Check if SRTP requires secure signaling */
-	if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
-	    if (security_level < acc->cfg.srtp_secure_signaling) {
-		if (sip_err_code)
-		    *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
-		return PJSIP_ESESSIONINSECURE;
-	    }
-	}
-
-	/* Always create SRTP adapter */
-	pjmedia_srtp_setting_default(&srtp_opt);
-	srtp_opt.close_member_tp = PJ_FALSE;
-	/* If media session has been ever established, let's use remote's 
-	 * preference in SRTP usage policy, especially when it is stricter.
-	 */
-	if (call->rem_srtp_use > acc->cfg.use_srtp)
-	    srtp_opt.use = call->rem_srtp_use;
-	else
-	    srtp_opt.use = acc->cfg.use_srtp;
-
-	status = pjmedia_transport_srtp_create(pjsua_var.med_endpt, 
-					       call->med_tp,
-					       &srtp_opt, &srtp);
-	if (status != PJ_SUCCESS) {
-	    if (sip_err_code)
-		*sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
-	    return status;
-	}
-
-	/* Set SRTP as current media transport */
-	call->med_orig = call->med_tp;
-	call->med_tp = srtp;
-    }
-#else
-    call->med_orig = call->med_tp;
-    PJ_UNUSED_ARG(security_level);
+    for (i=0; i<call->med_cnt; ++i) {
+	if (call->media[i].tp == NULL) {
+	    return PJ_EBUSY;
+	}
+    }
 #endif
 
-    /* Find out which media line in SDP that we support. If we are offerer,
-     * audio will be initialized at index 0 in SDP. 
-     */
-    if (rem_sdp == NULL) {
-	call->audio_idx = 0;
-    } 
-    /* Otherwise find out the candidate audio media line in SDP */
-    else {
-	pj_bool_t srtp_active;
-
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
-	srtp_active = acc->cfg.use_srtp;
-#else
-	srtp_active = PJ_FALSE;
-#endif
-
-	/* Media count must have been checked */
-	pj_assert(rem_sdp->media_count != 0);
-
-	call->audio_idx = find_audio_index(rem_sdp, srtp_active);
-    }
-
-    /* Reject offer if we couldn't find a good m=audio line in offer */
-    if (call->audio_idx < 0) {
+    if (rem_sdp) {
+	sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
+		   maudidx, &maudcnt);
+
+	if (maudcnt==0) {
+	    /* Expecting audio in the offer */
+	    if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+	    pjsua_media_channel_deinit(call_id);
+	    return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
+	}
+
+	sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
+		   mvididx, &mvidcnt);
+	mvidcnt = (mvidcnt < acc->cfg.max_video_cnt) ?
+			mvidcnt : acc->cfg.max_video_cnt;
+
+    } else {
+	maudcnt = acc->cfg.max_audio_cnt;
+	for (mi=0; mi<maudcnt; ++mi) {
+	    maudidx[mi] = mi;
+	    media_types[mi] = PJMEDIA_TYPE_AUDIO;
+	}
+	mvidcnt = acc->cfg.max_video_cnt;
+	for (mi=0; mi<mvidcnt; ++mi) {
+	    media_types[maudcnt + mi] = PJMEDIA_TYPE_VIDEO;
+	}
+    }
+
+    call->med_cnt = maudcnt + mvidcnt;
+
+    if (call->med_cnt == 0) {
+	/* Expecting at least one media */
 	if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
 	pjsua_media_channel_deinit(call_id);
@@ -1266,17 +1492,70 @@
     }
 
-    PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
+    /* Initialize each media line */
+    for (mi=0; mi < call->med_cnt; ++mi) {
+	pjsua_call_media *call_med = &call->media[mi];
+	pj_bool_t enabled = PJ_FALSE;
+	pjmedia_type media_type = PJMEDIA_TYPE_NONE;
+
+	if (rem_sdp) {
+	    if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_AUDIO)) {
+		media_type = PJMEDIA_TYPE_AUDIO;
+		if (pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) {
+		    enabled = PJ_TRUE;
+		}
+	    }
+	    else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_VIDEO)) {
+		media_type = PJMEDIA_TYPE_VIDEO;
+		if (pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) {
+		    enabled = PJ_TRUE;
+		}
+	    }
+
+	} else {
+	    enabled = PJ_TRUE;
+	    media_type = media_types[mi];
+	}
+
+	if (enabled) {
+	    status = pjsua_call_media_init(call_med, media_type,
+	                                   &acc->cfg.rtp_cfg,
+					   security_level, sip_err_code);
+	    if (status != PJ_SUCCESS) {
+		pjsua_media_channel_deinit(call_id);
+		return status;
+	    }
+	} else {
+	    /* By convention, the media is inactive if transport is NULL */
+	    if (call_med->tp) {
+		pjmedia_transport_close(call_med->tp);
+		call_med->tp = NULL;
+	    }
+	}
+    }
+
+    call->audio_idx = maudidx[0];
+
+    PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
 	      call->audio_idx, call->index));
 
-    /* Create the media transport */
-    status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
-					    rem_sdp, call->audio_idx);
-    if (status != PJ_SUCCESS) {
-	if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
-	pjsua_media_channel_deinit(call_id);
-	return status;
-    }
-
-    call->med_tp_st = PJSUA_MED_TP_INIT;
+    /* Tell the media transport of a new offer/answer session */
+    for (mi=0; mi < call->med_cnt; ++mi) {
+	pjsua_call_media *call_med = &call->media[mi];
+
+	/* Note: tp may be NULL if this media line is inactive */
+	if (call_med->tp) {
+	    status = pjmedia_transport_media_create(call_med->tp,
+						    tmp_pool, 0,
+						    rem_sdp, mi);
+	    if (status != PJ_SUCCESS) {
+		if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
+		pjsua_media_channel_deinit(call_id);
+		return status;
+	    }
+
+	    call_med->tp_st = PJSUA_MED_TP_INIT;
+	}
+    }
+
     return PJ_SUCCESS;
 }
@@ -1286,50 +1565,47 @@
 					   const pjmedia_sdp_session *rem_sdp,
 					   pjmedia_sdp_session **p_sdp,
-					   int *sip_status_code)
-{
-    enum { MAX_MEDIA = 1 };
+					   int *sip_err_code)
+{
+    const pj_str_t STR_AUDIO = { "audio", 5 };
+    enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
     pjmedia_sdp_session *sdp;
-    pjmedia_transport_info tpinfo;
+    pj_sockaddr origin;
     pjsua_call *call = &pjsua_var.calls[call_id];
+    pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
     pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
+    unsigned mi;
     pj_status_t status;
 
-    /* Return error if media transport has not been created yet
-     * (e.g. application is starting)
+    if (pjsua_get_state() != PJSUA_STATE_RUNNING)
+	return PJ_EBUSY;
+
+    if (rem_sdp) {
+	pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
+	unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
+
+	sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
+	           maudidx, &maudcnt);
+
+	if (maudcnt==0) {
+	    /* Expecting audio in the offer */
+	    if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+	    pjsua_media_channel_deinit(call_id);
+	    return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
+	}
+
+	call->audio_idx = maudidx[0];
+    } else {
+	/* Audio is first in our offer, by convention */
+	call->audio_idx = 0;
+    }
+
+    /* Create media if it's not created. This could happen when call is
+     * currently on-hold (with the old style hold)
      */
-    if (call->med_tp == NULL) {
-	return PJ_EBUSY;
-    }
-
-    if (rem_sdp && rem_sdp->media_count != 0) {
-	pj_bool_t srtp_active;
-
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
-        srtp_active = pjsua_var.acc[call->acc_id].cfg.use_srtp;
-#else
-	srtp_active = PJ_FALSE;
-#endif
-
-	call->audio_idx = find_audio_index(rem_sdp, srtp_active);
-	if (call->audio_idx == -1) {
-	    /* No audio in the offer. We can't accept this */
-	    PJ_LOG(4,(THIS_FILE,
-		      "Unable to accept SDP offer without audio for call %d",
-		      call_id));
-	    return PJMEDIA_SDP_EINMEDIA;
-	}
-    }
-
-    /* Media index must have been determined before */
-    pj_assert(call->audio_idx != -1);
-
-    /* Create media if it's not created. This could happen when call is
-     * currently on-hold
-     */
-    if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
+    if (call->media[call->audio_idx].tp == NULL) {
 	pjsip_role_e role;
 	role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
 	status = pjsua_media_channel_init(call_id, role, call->secure_level, 
-					  pool, rem_sdp, sip_status_code);
+					  pool, rem_sdp, sip_err_code);
 	if (status != PJ_SUCCESS)
 	    return status;
@@ -1340,50 +1616,98 @@
 	sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
 
-    /* Get media socket info */
-    pjmedia_transport_info_init(&tpinfo);
-    pjmedia_transport_get_info(call->med_tp, &tpinfo);
-
-    /* Create SDP */
-    status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
-				      &tpinfo.sock_info, &sdp);
-    if (status != PJ_SUCCESS) {
-	if (sip_status_code) *sip_status_code = 500;
+    /* Get one address to use in the origin field */
+    pj_bzero(&origin, sizeof(origin));
+    for (mi=0; mi<call->med_cnt; ++mi) {
+	pjmedia_transport_info tpinfo;
+
+	if (call->media[mi].tp == NULL)
+	    continue;
+
+	pjmedia_transport_info_init(&tpinfo);
+	pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
+	pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
+	break;
+    }
+
+    /* Create the base (blank) SDP */
+    status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
+                                           &origin, &sdp);
+    if (status != PJ_SUCCESS)
 	return status;
-    }
-
-    /* If we're answering or updating the session with a new offer,
-     * and the selected media is not the first media
-     * in SDP, then fill in the unselected media with with zero port. 
-     * Otherwise we'll crash in transport_encode_sdp() because the media
-     * lines are not aligned between offer and answer.
-     */
-    if (call->audio_idx != 0 && 
-	(rem_sdp || sdp_neg_state==PJMEDIA_SDP_NEG_STATE_DONE))
-    {
-	unsigned i;
-	const pjmedia_sdp_session *ref_sdp = rem_sdp;
-
-	if (!ref_sdp) {
-	    /* We are updating session with a new offer */
-	    status = pjmedia_sdp_neg_get_active_local(call->inv->neg,
-						      &ref_sdp);
-	    pj_assert(status == PJ_SUCCESS);
-	}
-
-	for (i=0; i<ref_sdp->media_count; ++i) {
-	    const pjmedia_sdp_media *ref_m = ref_sdp->media[i];
-	    pjmedia_sdp_media *m;
-
-	    if ((int)i == call->audio_idx)
-		continue;
-
-	    m = pjmedia_sdp_media_clone_deactivate(pool, ref_m);
-	    if (i==sdp->media_count)
-		sdp->media[sdp->media_count++] = m;
-	    else {
-		pj_array_insert(sdp->media, sizeof(sdp->media[0]),
-				sdp->media_count, i, &m);
-		++sdp->media_count;
+
+    /* Process each media line */
+    for (mi=0; mi<call->med_cnt; ++mi) {
+	pjsua_call_media *call_med = &call->media[mi];
+	pjmedia_sdp_media *m = NULL;
+	pjmedia_transport_info tpinfo;
+
+	if (call_med->tp == NULL) {
+	    /*
+	     * This media is deactivated. Just create a valid SDP with zero
+	     * port.
+	     */
+	    m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
+	    m->desc.transport = pj_str("RTP/AVP");
+	    m->desc.fmt_count = 1;
+	    m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
+	    m->conn->net_type = pj_str("IN");
+	    m->conn->addr_type = pj_str("IP4");
+	    m->conn->addr = pj_str("127.0.0.1");
+
+	    switch (call_med->type) {
+	    case PJMEDIA_TYPE_AUDIO:
+		m->desc.media = pj_str("audio");
+		m->desc.fmt[0] = pj_str("0");
+		break;
+	    case PJMEDIA_TYPE_VIDEO:
+		m->desc.media = pj_str("video");
+		m->desc.fmt[0] = pj_str("31");
+		break;
+	    default:
+		if (rem_sdp && mi < rem_sdp->media_count) {
+		    pj_strdup(pool, &m->desc.media,
+		              &rem_sdp->media[mi]->desc.media);
+		    pj_strdup(pool, &m->desc.fmt[0],
+		              &rem_sdp->media[mi]->desc.fmt[0]);
+		} else {
+		    pj_assert(!"Invalid call_med media type");
+		    return PJ_EBUG;
+		}
 	    }
+
+	    sdp->media[sdp->media_count++] = m;
+	    continue;
+	}
+
+	/* Get transport address info */
+	pjmedia_transport_info_init(&tpinfo);
+	pjmedia_transport_get_info(call_med->tp, &tpinfo);
+
+	/* Ask pjmedia endpoint to create SDP media line */
+	switch (call_med->type) {
+	case PJMEDIA_TYPE_AUDIO:
+	    status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
+                                                    &tpinfo.sock_info, 0, &m);
+	    break;
+	case PJMEDIA_TYPE_VIDEO:
+	    status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
+	                                            &tpinfo.sock_info, 0, &m);
+	    break;
+	default:
+	    pj_assert(!"Invalid call_med media type");
+	    return PJ_EBUG;
+	}
+
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	sdp->media[sdp->media_count++] = m;
+
+	/* Give to transport */
+	status = pjmedia_transport_encode_sdp(call_med->tp, pool,
+					      sdp, rem_sdp, mi);
+	if (status != PJ_SUCCESS) {
+	    if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
+	    return status;
 	}
     }
@@ -1413,13 +1737,6 @@
     }
 
-    /* Give the SDP to media transport */
-    status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp, 
-					  call->audio_idx);
-    if (status != PJ_SUCCESS) {
-	if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
-	return status;
-    }
-
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+
+#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
     /* Check if SRTP is in optional mode and configured to use duplicated
      * media, i.e: secured and unsecured version, in the SDP offer.
@@ -1434,5 +1751,5 @@
 	    pjmedia_sdp_media *m = sdp->media[i];
 
-	    /* Check if this media is unsecured but has SDP "crypto" 
+	    /* Check if this media is unsecured but has SDP "crypto"
 	     * attribute.
 	     */
@@ -1440,5 +1757,5 @@
 		pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
 	    {
-		if (i == (unsigned)call->audio_idx && 
+		if (i == (unsigned)call->audio_idx &&
 		    sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
 		{
@@ -1463,5 +1780,5 @@
 		    /* Insert the new media before the unsecured media */
 		    if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
-			pj_array_insert(sdp->media, sizeof(new_m), 
+			pj_array_insert(sdp->media, sizeof(new_m),
 					sdp->media_count, i, &new_m);
 			++sdp->media_count;
@@ -1474,8 +1791,4 @@
 #endif
 
-    /* Update currently advertised RTP source address */
-    pj_memcpy(&call->med_rtp_addr, &tpinfo.sock_info.rtp_addr_name, 
-	      sizeof(pj_sockaddr));
-
     *p_sdp = sdp;
     return PJ_SUCCESS;
@@ -1486,59 +1799,71 @@
 {
     pjsua_call *call = &pjsua_var.calls[call_id];
-
-    if (call->conf_slot != PJSUA_INVALID_ID) {
-	if (pjsua_var.mconf) {
-	    pjsua_conf_remove_port(call->conf_slot);
-	}
-	call->conf_slot = PJSUA_INVALID_ID;
-    }
-
-    if (call->session) {
-	pjmedia_rtcp_stat stat;
-
-	if ((call->media_dir & PJMEDIA_DIR_ENCODING) &&
-	    (pjmedia_session_get_stream_stat(call->session, 0, &stat) 
-	     == PJ_SUCCESS))
+    unsigned mi;
+
+    for (mi=0; mi<call->med_cnt; ++mi) {
+	pjsua_call_media *call_med = &call->media[mi];
+
+	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+	    pjmedia_stream *strm = call_med->strm.a.stream;
+	    pjmedia_rtcp_stat stat;
+
+	    if (strm) {
+		if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
+		    if (pjsua_var.mconf) {
+			pjsua_conf_remove_port(call_med->strm.a.conf_slot);
+		    }
+		    call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
+		}
+
+		if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
+		    (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
+		{
+		    /* Save RTP timestamp & sequence, so when media session is
+		     * restarted, those values will be restored as the initial
+		     * RTP timestamp & sequence of the new media session. So in
+		     * the same call session, RTP timestamp and sequence are
+		     * guaranteed to be contigue.
+		     */
+		    call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
+		    call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
+		    call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
+		}
+
+		if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
+		    pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
+		}
+
+		pjmedia_stream_destroy(strm);
+		call_med->strm.a.stream = NULL;
+
+		PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
+				     call_id, mi));
+	    }
+	}
+	call_med->state = PJSUA_CALL_MEDIA_NONE;
+    }
+}
+
+pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
+{
+    pjsua_call *call = &pjsua_var.calls[call_id];
+    unsigned mi;
+
+    stop_media_session(call_id);
+
+    for (mi=0; mi<call->med_cnt; ++mi) {
+	pjsua_call_media *call_med = &call->media[mi];
+
+	if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
+	    pjmedia_transport_media_stop(call_med->tp);
+	    call_med->tp_st = PJSUA_MED_TP_IDLE;
+	}
+
+	if (call_med->tp_orig && call_med->tp &&
+		call_med->tp != call_med->tp_orig)
 	{
-	    /* Save RTP timestamp & sequence, so when media session is 
-	     * restarted, those values will be restored as the initial 
-	     * RTP timestamp & sequence of the new media session. So in 
-	     * the same call session, RTP timestamp and sequence are 
-	     * guaranteed to be contigue.
-	     */
-	    call->rtp_tx_seq_ts_set = 1 | (1 << 1);
-	    call->rtp_tx_seq = stat.rtp_tx_last_seq;
-	    call->rtp_tx_ts = stat.rtp_tx_last_ts;
-	}
-
-	if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
-	    pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
-	}
-
-	pjmedia_session_destroy(call->session);
-	call->session = NULL;
-
-	PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed", 
-			     call_id));
-
-    }
-
-    call->media_st = PJSUA_CALL_MEDIA_NONE;
-}
-
-pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
-{
-    pjsua_call *call = &pjsua_var.calls[call_id];
-
-    stop_media_session(call_id);
-
-    if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
-	pjmedia_transport_media_stop(call->med_tp);
-	call->med_tp_st = PJSUA_MED_TP_IDLE;
-    }
-
-    if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
-	pjmedia_transport_close(call->med_tp);
-	call->med_tp = call->med_orig;
+	    pjmedia_transport_close(call_med->tp);
+	    call_med->tp = call_med->tp_orig;
+	}
     }
 
@@ -1570,74 +1895,27 @@
 
 
-pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
-				       const pjmedia_sdp_session *local_sdp,
-				       const pjmedia_sdp_session *remote_sdp)
-{
-    int prev_media_st = 0;
-    pjsua_call *call = &pjsua_var.calls[call_id];
-    pjmedia_session_info sess_info;
-    pjmedia_stream_info *si = NULL;
+static pj_status_t audio_channel_update(pjsua_call_media *call_med,
+                                        pj_pool_t *tmp_pool,
+				        const pjmedia_sdp_session *local_sdp,
+				        const pjmedia_sdp_session *remote_sdp)
+{
+    pjsua_call *call = call_med->call;
+    pjmedia_stream_info the_si, *si = &the_si;
     pjmedia_port *media_port;
+    unsigned strm_idx = call_med->idx;
     pj_status_t status;
-
-    if (!pjsua_var.med_endpt) {
-	/* We're being shutdown */
-	return PJ_EBUSY;
-    }
-
-    /* Destroy existing media session, if any. */
-    prev_media_st = call->media_st;
-    stop_media_session(call->index);
-
-    /* Create media session info based on SDP parameters. 
-     */    
-    status = pjmedia_session_info_from_sdp( call->inv->pool_prov, 
-					    pjsua_var.med_endpt, 
-					    PJMEDIA_MAX_SDP_MEDIA, &sess_info,
-					    local_sdp, remote_sdp);
+    
+    status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
+                                          local_sdp, remote_sdp, strm_idx);
     if (status != PJ_SUCCESS)
 	return status;
 
-    /* Update audio index from the negotiated SDP */
-    call->audio_idx = find_audio_index(local_sdp, PJ_TRUE);
-
-    /* Find which session is audio */
-    PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
-    PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
-    si = &sess_info.stream_info[call->audio_idx];
-    
-    /* Reset session info with only one media stream */
-    sess_info.stream_cnt = 1;
-    if (si != &sess_info.stream_info[0]) {
-	pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
-	si = &sess_info.stream_info[0];
-    }
-
     /* Check if no media is active */
-    if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
-    {
+    if (si->dir == PJMEDIA_DIR_NONE) {
 	/* Call media state */
-	call->media_st = PJSUA_CALL_MEDIA_NONE;
+	call_med->state = PJSUA_CALL_MEDIA_NONE;
 
 	/* Call media direction */
-	call->media_dir = PJMEDIA_DIR_NONE;
-
-	/* Don't stop transport because we need to transmit keep-alives, and
-	 * also to prevent restarting ICE negotiation. See
-	 *  http://trac.pjsip.org/repos/ticket/1094
-	 */
-#if 0
-	/* Shutdown transport's session */
-	pjmedia_transport_media_stop(call->med_tp);
-	call->med_tp_st = PJSUA_MED_TP_IDLE;
-
-	/* No need because we need keepalive? */
-
-	/* Close upper entry of transport stack */
-	if (call->med_orig && (call->med_tp != call->med_orig)) {
-	    pjmedia_transport_close(call->med_tp);
-	    call->med_tp = call->med_orig;
-	}
-#endif
+	call_med->dir = PJMEDIA_DIR_NONE;
 
     } else {
@@ -1645,16 +1923,15 @@
 
 	/* Start/restart media transport */
-	status = pjmedia_transport_media_start(call->med_tp, 
-					       call->inv->pool_prov,
-					       local_sdp, remote_sdp,
-					       call->audio_idx);
+	status = pjmedia_transport_media_start(call_med->tp,
+					       tmp_pool, local_sdp,
+					       remote_sdp, strm_idx);
 	if (status != PJ_SUCCESS)
 	    return status;
 
-	call->med_tp_st = PJSUA_MED_TP_RUNNING;
+	call_med->tp_st = PJSUA_MED_TP_RUNNING;
 
 	/* Get remote SRTP usage policy */
 	pjmedia_transport_info_init(&tp_info);
-	pjmedia_transport_get_info(call->med_tp, &tp_info);
+	pjmedia_transport_get_info(call_med->tp, &tp_info);
 	if (tp_info.specific_info_cnt > 0) {
 	    unsigned i;
@@ -1665,5 +1942,5 @@
 				(pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
 
-		    call->rem_srtp_use = srtp_info->peer_use;
+		    call_med->rem_srtp_use = srtp_info->peer_use;
 		    break;
 		}
@@ -1694,5 +1971,5 @@
 
 	/* Set SSRC */
-	si->ssrc = call->ssrc;
+	si->ssrc = call_med->ssrc;
 
 	/* Set RTP timestamp & sequence, normally these value are intialized
@@ -1701,7 +1978,7 @@
 	 * contigue.
 	 */
-	si->rtp_ts = call->rtp_tx_ts;
-	si->rtp_seq = call->rtp_tx_seq;
-	si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
+	si->rtp_ts = call_med->rtp_tx_ts;
+	si->rtp_seq = call_med->rtp_tx_seq;
+	si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
 
 #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
@@ -1711,7 +1988,13 @@
 
 	/* Create session based on session info. */
-	status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
-					 &call->med_tp,
-					 call, &call->session );
+	status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
+				       call_med->tp, NULL,
+				       &call_med->strm.a.stream);
+	if (status != PJ_SUCCESS) {
+	    return status;
+	}
+
+	/* Start stream */
+	status = pjmedia_stream_start(call_med->strm.a.stream);
 	if (status != PJ_SUCCESS) {
 	    return status;
@@ -1722,7 +2005,7 @@
 	 */
 	if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
-	    pjmedia_session_set_dtmf_callback(call->session, 0, 
-					      &dtmf_callback, 
-					      (void*)(long)(call->index));
+	    pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
+					     &dtmf_callback,
+					     (void*)(long)(call->index));
 	}
 
@@ -1730,5 +2013,5 @@
 	 * We need the port interface to add to the conference bridge.
 	 */
-	pjmedia_session_get_port(call->session, 0, &media_port);
+	pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
 
 	/* Notify application about stream creation.
@@ -1737,6 +2020,7 @@
 	 */
 	if (pjsua_var.ua_cfg.cb.on_stream_created) {
-	    pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
-						  0, &media_port);
+	    pjsua_var.ua_cfg.cb.on_stream_created(call->index,
+						  call_med->strm.a.stream,
+						  strm_idx, &media_port);
 	}
 
@@ -1759,5 +2043,6 @@
 					    media_port, 
 					    &port_name,
-					    (unsigned*)&call->conf_slot);
+					    (unsigned*)
+					    &call_med->strm.a.conf_slot);
 	    if (status != PJ_SUCCESS) {
 		return status;
@@ -1766,13 +2051,13 @@
 
 	/* Call media direction */
-	call->media_dir = si->dir;
+	call_med->dir = si->dir;
 
 	/* Call media state */
 	if (call->local_hold)
-	    call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
-	else if (call->media_dir == PJMEDIA_DIR_DECODING)
-	    call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
+	    call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
+	else if (call_med->dir == PJMEDIA_DIR_DECODING)
+	    call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
 	else
-	    call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
+	    call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
     }
 
@@ -1781,37 +2066,89 @@
 	char info[80];
 	int info_len = 0;
-	unsigned i;
-
-	for (i=0; i<sess_info.stream_cnt; ++i) {
-	    int len;
-	    const char *dir;
-	    pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
-
-	    switch (strm_info->dir) {
-	    case PJMEDIA_DIR_NONE:
-		dir = "inactive";
-		break;
-	    case PJMEDIA_DIR_ENCODING:
-		dir = "sendonly";
-		break;
-	    case PJMEDIA_DIR_DECODING:
-		dir = "recvonly";
-		break;
-	    case PJMEDIA_DIR_ENCODING_DECODING:
-		dir = "sendrecv";
-		break;
-	    default:
-		dir = "unknown";
-		break;
+	int len;
+	const char *dir;
+
+	switch (si->dir) {
+	case PJMEDIA_DIR_NONE:
+	    dir = "inactive";
+	    break;
+	case PJMEDIA_DIR_ENCODING:
+	    dir = "sendonly";
+	    break;
+	case PJMEDIA_DIR_DECODING:
+	    dir = "recvonly";
+	    break;
+	case PJMEDIA_DIR_ENCODING_DECODING:
+	    dir = "sendrecv";
+	    break;
+	default:
+	    dir = "unknown";
+	    break;
+	}
+	len = pj_ansi_sprintf( info+info_len,
+			       ", stream #%d: %.*s (%s)", strm_idx,
+			       (int)si->fmt.encoding_name.slen,
+			       si->fmt.encoding_name.ptr,
+			       dir);
+	if (len > 0)
+	    info_len += len;
+	PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
+    }
+
+    return PJ_SUCCESS;
+}
+
+pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
+				       const pjmedia_sdp_session *local_sdp,
+				       const pjmedia_sdp_session *remote_sdp)
+{
+    pjsua_call *call = &pjsua_var.calls[call_id];
+    pj_pool_t *tmp_pool = call->inv->pool_prov;
+    unsigned mi;
+    pj_status_t status;
+
+    if (pjsua_get_state() != PJSUA_STATE_RUNNING)
+	return PJ_EBUSY;
+
+    /* Destroy existing media session, if any. */
+    stop_media_session(call->index);
+
+    /* Reset audio_idx first */
+    call->audio_idx = -1;
+
+    /* Process each media stream */
+    for (mi=0; mi < call->med_cnt; ++mi) {
+	pjsua_call_media *call_med = &call->media[mi];
+
+	if (mi > local_sdp->media_count ||
+	    mi > remote_sdp->media_count)
+	{
+	    /* Something is wrong */
+	    PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
+		      "invalid media index %d in SDP", call_id, mi));
+	    return PJMEDIA_SDP_EINSDP;
+	}
+
+	switch (call_med->type) {
+	case PJMEDIA_TYPE_AUDIO:
+	    status = audio_channel_update(call_med, tmp_pool,
+	                                  local_sdp, remote_sdp);
+	    if (call->audio_idx==-1 && status==PJ_SUCCESS &&
+		    call_med->strm.a.stream)
+	    {
+		call->audio_idx = mi;
 	    }
-	    len = pj_ansi_sprintf( info+info_len,
-				   ", stream #%d: %.*s (%s)", i,
-				   (int)strm_info->fmt.encoding_name.slen,
-				   strm_info->fmt.encoding_name.ptr,
-				   dir);
-	    if (len > 0)
-		info_len += len;
-	}
-	PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
+	    break;
+	case PJMEDIA_TYPE_VIDEO:
+	    PJ_LOG(4,(THIS_FILE, "-x-x-x-x-  Updating video for stream %d", mi));
+	    break;
+	default:
+	    break;
+	}
+
+	if (status != PJ_SUCCESS) {
+	    PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02:%d",
+		         call_id, mi));
+	}
     }
 
