ICE (work in progress): integration with PJSUA

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1098 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h
index 10d81c0..6db9bbe 100644
--- a/pjmedia/include/pjmedia.h
+++ b/pjmedia/include/pjmedia.h
@@ -54,6 +54,7 @@
 #include <pjmedia/splitcomb.h>
 #include <pjmedia/tonegen.h>
 #include <pjmedia/transport.h>
+#include <pjmedia/transport_ice.h>
 #include <pjmedia/transport_udp.h>
 #include <pjmedia/wav_playlist.h>
 #include <pjmedia/wav_port.h>
diff --git a/pjmedia/include/pjmedia/transport.h b/pjmedia/include/pjmedia/transport.h
index 4b2450b..1e7d67d 100644
--- a/pjmedia/include/pjmedia/transport.h
+++ b/pjmedia/include/pjmedia/transport.h
@@ -178,6 +178,11 @@
  */
 struct pjmedia_transport_op
 {
+    /**
+     * Get media socket info from the specified transport.
+     *
+     * Application should call #pjmedia_transport_get_info() instead
+     */
     pj_status_t (*get_info)(pjmedia_transport *tp,
 			    pjmedia_sock_info *info);
 
@@ -254,6 +259,20 @@
 typedef struct pjmedia_transport_op pjmedia_transport_op;
 
 
+/** 
+ * Media transport type.
+ */
+typedef enum pjmedia_transport_type
+{
+    /** Media transport using standard UDP */
+    PJMEDIA_TRANSPORT_TYPE_UDP,
+
+    /** Media transport using ICE */
+    PJMEDIA_TRANSPORT_TYPE_ICE
+
+} pjmedia_transport_type;
+
+
 /**
  * This structure declares stream transport. A stream transport is called
  * by the stream to transmit a packet, and will notify stream when
@@ -262,13 +281,27 @@
 struct pjmedia_transport
 {
     /** Transport name (for logging purpose). */
-    char		  name[PJ_MAX_OBJ_NAME];
+    char		     name[PJ_MAX_OBJ_NAME];
+
+    /** Transport type. */
+    pjmedia_transport_type   type;
 
     /** Transport's "virtual" function table. */
-    pjmedia_transport_op *op;
+    pjmedia_transport_op    *op;
 };
 
 
+/**
+ * Get media socket info from the specified transport. The socket info
+ * contains information about the local address of this transport, and
+ * would be needed for example to fill in the "c=" and "m=" line of local 
+ * SDP.
+ *
+ * @param tp	    The transport.
+ * @param info	    Media socket info to be initialized.
+ *
+ * @return	    PJ_SUCCESS on success.
+ */
 PJ_INLINE(pj_status_t) pjmedia_transport_get_info(pjmedia_transport *tp,
 						  pjmedia_sock_info *info)
 {
diff --git a/pjmedia/include/pjmedia/transport_ice.h b/pjmedia/include/pjmedia/transport_ice.h
index fa4deeb..e676730 100644
--- a/pjmedia/include/pjmedia/transport_ice.h
+++ b/pjmedia/include/pjmedia/transport_ice.h
@@ -41,21 +41,21 @@
 
 PJ_DECL(pj_status_t) pjmedia_ice_create(pjmedia_endpt *endpt,
 					const char *name,
+					unsigned comp_cnt,
 					pj_stun_config *stun_cfg,
-					pj_dns_resolver *resolver,
-					pj_bool_t enable_relay,
-					const pj_str_t *stun_name,
 					pjmedia_transport **p_tp);
 PJ_DECL(pj_status_t) pjmedia_ice_destroy(pjmedia_transport *tp);
 
+PJ_DECL(pj_ice_st*) pjmedia_ice_get_ice_st(pjmedia_transport *tp);
+
 
 PJ_DECL(pj_status_t) pjmedia_ice_init_ice(pjmedia_transport *tp,
 					  pj_ice_role role,
 					  const pj_str_t *local_ufrag,
 					  const pj_str_t *local_passwd);
-PJ_DECL(pj_status_t) pjmedia_ice_build_sdp(pjmedia_transport *tp,
-					   pj_pool_t *pool,
-					   pjmedia_sdp_session *sdp);
+PJ_DECL(pj_status_t) pjmedia_ice_modify_sdp(pjmedia_transport *tp,
+					    pj_pool_t *pool,
+					    pjmedia_sdp_session *sdp);
 PJ_DECL(pj_status_t) pjmedia_ice_start_ice(pjmedia_transport *tp,
 					   pj_pool_t *pool,
 					   pjmedia_sdp_session *rem_sdp);
diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c
index 98e89fb..2023a1d 100644
--- a/pjmedia/src/pjmedia/transport_ice.c
+++ b/pjmedia/src/pjmedia/transport_ice.c
@@ -25,7 +25,7 @@
     pjmedia_transport	 base;
     pj_ice_st		*ice_st;
 
-    void  *user_data;
+    void  *stream;
     void (*rtp_cb)(void*,
 		   const void*,
 		   pj_ssize_t);
@@ -41,7 +41,7 @@
 static pj_status_t tp_get_info(pjmedia_transport *tp,
 			       pjmedia_sock_info *info);
 static pj_status_t tp_attach( pjmedia_transport *tp,
-			      void *user_data,
+			      void *stream,
 			      const pj_sockaddr_t *rem_addr,
 			      const pj_sockaddr_t *rem_rtcp,
 			      unsigned addr_len,
@@ -92,15 +92,14 @@
 
 PJ_DEF(pj_status_t) pjmedia_ice_create(pjmedia_endpt *endpt,
 				       const char *name,
+				       unsigned comp_cnt,
 				       pj_stun_config *stun_cfg,
-				       pj_dns_resolver *resolver,
-				       pj_bool_t enable_relay,
-				       const pj_str_t *stun_name,
-				       pjmedia_transport **p_tp)
+	    			       pjmedia_transport **p_tp)
 {
     pj_ice_st *ice_st;
     pj_ice_st_cb ice_st_cb;
     struct transport_ice *tp_ice;
+    unsigned i;
     pj_status_t status;
 
     PJ_UNUSED_ARG(endpt);
@@ -117,24 +116,11 @@
     if (status != PJ_SUCCESS)
 	return status;
 
-    /* Add component 1 (RTP) */
-    status = pj_ice_st_add_comp(ice_st, 1);
-    if (status != PJ_SUCCESS) 
-	goto on_error;
-
-    /* Add host candidates. */
-    status = pj_ice_st_add_all_host_interfaces(ice_st, 1, 0, PJ_FALSE, NULL);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    /* Configure STUN server for ICE */
-    if (stun_name) {
-	status = pj_ice_st_set_stun(ice_st, resolver, enable_relay, stun_name);
+    /* Add components */
+    for (i=0; i<comp_cnt; ++i) {
+	status = pj_ice_st_add_comp(ice_st, i+1);
 	if (status != PJ_SUCCESS) 
 	    goto on_error;
-
-	/* Add STUN candidates */
-	status = pj_ice_st_add_stun_interface(ice_st, 1, 0, PJ_FALSE, NULL);
     }
 
     /* Create transport instance and attach to ICE */
@@ -142,6 +128,7 @@
     tp_ice->ice_st = ice_st;
     pj_ansi_strcpy(tp_ice->base.name, ice_st->obj_name);
     tp_ice->base.op = &tp_ice_op;
+    tp_ice->base.type = PJMEDIA_TRANSPORT_TYPE_ICE;
 
     ice_st->user_data = (void*)tp_ice;
 
@@ -157,19 +144,28 @@
 }
 
 
-PJ_DEF(pj_status_t) pjmedia_ice_close(pjmedia_transport *tp)
+PJ_DEF(pj_status_t) pjmedia_ice_destroy(pjmedia_transport *tp)
 {
     struct transport_ice *tp_ice = (struct transport_ice*)tp;
 
     if (tp_ice->ice_st) {
 	pj_ice_st_destroy(tp_ice->ice_st);
-	tp_ice->ice_st = NULL;
+	//Must not touch tp_ice after ice_st is destroyed!
+	//(it has the pool)
+	//tp_ice->ice_st = NULL;
     }
 
     return PJ_SUCCESS;
 }
 
 
+PJ_DECL(pj_ice_st*) pjmedia_ice_get_ice_st(pjmedia_transport *tp)
+{
+    struct transport_ice *tp_ice = (struct transport_ice*)tp;
+    return tp_ice->ice_st;
+}
+
+
 PJ_DEF(pj_status_t) pjmedia_ice_init_ice(pjmedia_transport *tp,
 					 pj_ice_role role,
 					 const pj_str_t *local_ufrag,
@@ -180,9 +176,9 @@
 }
 
 
-PJ_DEF(pj_status_t) pjmedia_ice_build_sdp(pjmedia_transport *tp,
-					  pj_pool_t *pool,
-					  pjmedia_sdp_session *sdp)
+PJ_DEF(pj_status_t) pjmedia_ice_modify_sdp(pjmedia_transport *tp,
+					   pj_pool_t *pool,
+					   pjmedia_sdp_session *sdp)
 {
     struct transport_ice *tp_ice = (struct transport_ice*)tp;
     enum { MAXLEN = 256 };
@@ -202,7 +198,7 @@
 				   &tp_ice->ice_st->ice->rx_pass);
     sdp->attr[sdp->attr_count++] = attr;
 
-    /* Add all candidates */
+    /* Add all candidates (to media level) */
     cand_cnt = pj_ice_get_cand_cnt(tp_ice->ice_st->ice);
     for (i=0; i<cand_cnt; ++i) {
 	pj_ice_cand *cand;
@@ -255,7 +251,7 @@
 
 	value = pj_str(buffer);
 	attr = pjmedia_sdp_attr_create(pool, "candidate", &value);
-	sdp->attr[sdp->attr_count++] = attr;
+	sdp->media[0]->attr[sdp->media[0]->attr_count++] = attr;
     }
 
     /* Done */
@@ -377,10 +373,10 @@
 
     /* Get all candidates */
     cand_cnt = 0;
-    for (i=0; i<rem_sdp->attr_count; ++i) {
+    for (i=0; i<rem_sdp->media[0]->attr_count; ++i) {
 	pjmedia_sdp_attr *attr;
 
-	attr = rem_sdp->attr[i];
+	attr = rem_sdp->media[0]->attr[i];
 	if (pj_strcmp2(&attr->name, "candidate")!=0)
 	    continue;
 
@@ -441,7 +437,7 @@
 
 
 static pj_status_t tp_attach( pjmedia_transport *tp,
-			      void *user_data,
+			      void *stream,
 			      const pj_sockaddr_t *rem_addr,
 			      const pj_sockaddr_t *rem_rtcp,
 			      unsigned addr_len,
@@ -454,7 +450,7 @@
 {
     struct transport_ice *tp_ice = (struct transport_ice*)tp;
 
-    tp_ice->user_data = user_data;
+    tp_ice->stream = stream;
     tp_ice->rtp_cb = rtp_cb;
     tp_ice->rtcp_cb = rtcp_cb;
 
@@ -473,7 +469,7 @@
 
     tp_ice->rtp_cb = NULL;
     tp_ice->rtcp_cb = NULL;
-    tp_ice->user_data = NULL;
+    tp_ice->stream = NULL;
 
     PJ_UNUSED_ARG(strm);
 }
@@ -514,9 +510,9 @@
     struct transport_ice *tp_ice = (struct transport_ice*) ice_st->user_data;
 
     if (comp_id==1 && tp_ice->rtp_cb)
-	(*tp_ice->rtp_cb)(tp_ice->user_data, pkt, size);
+	(*tp_ice->rtp_cb)(tp_ice->stream, pkt, size);
     else if (comp_id==2 && tp_ice->rtcp_cb)
-	(*tp_ice->rtcp_cb)(tp_ice->user_data, pkt, size);
+	(*tp_ice->rtcp_cb)(tp_ice->stream, pkt, size);
 
     PJ_UNUSED_ARG(cand_id);
     PJ_UNUSED_ARG(src_addr);
diff --git a/pjmedia/src/pjmedia/transport_udp.c b/pjmedia/src/pjmedia/transport_udp.c
index 3325be0..2bf570e 100644
--- a/pjmedia/src/pjmedia/transport_udp.c
+++ b/pjmedia/src/pjmedia/transport_udp.c
@@ -239,6 +239,7 @@
     tp->options = options;
     pj_ansi_strcpy(tp->base.name, name);
     tp->base.op = &transport_udp_op;
+    tp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
 
     /* Copy socket infos */
     tp->rtp_sock = si->rtp_sock;
diff --git a/pjnath/include/pjnath/ice_stream_transport.h b/pjnath/include/pjnath/ice_stream_transport.h
index c85aff9..37ff568 100644
--- a/pjnath/include/pjnath/ice_stream_transport.h
+++ b/pjnath/include/pjnath/ice_stream_transport.h
@@ -71,6 +71,7 @@
 {
     pj_ice_st		*ice_st;
     pj_ice_cand_type	 type;
+    pj_status_t		 status;
     unsigned		 comp_id;
     int			 cand_id;
     pj_str_t		 foundation;
@@ -150,7 +151,7 @@
 						   unsigned local_port,
 						   pj_bool_t notify,
 						   void *notify_data);
-
+PJ_DECL(pj_status_t) pj_ice_st_get_interfaces_status(pj_ice_st *ice_st);
 
 PJ_DECL(pj_status_t) pj_ice_st_init_ice(pj_ice_st *ice_st,
 					pj_ice_role role,
diff --git a/pjnath/src/pjnath/ice.c b/pjnath/src/pjnath/ice.c
index e21e81f..d2d2474 100644
--- a/pjnath/src/pjnath/ice.c
+++ b/pjnath/src/pjnath/ice.c
@@ -888,12 +888,15 @@
 static void on_ice_complete(pj_ice *ice, pj_status_t status)
 {
     if (!ice->is_complete) {
+	char errmsg[PJ_ERR_MSG_SIZE];
 
 	ice->is_complete = PJ_TRUE;
 	ice->ice_status = status;
     
 	/* Log message */
-	LOG((ice->obj_name, "ICE process complete"));
+	LOG((ice->obj_name, "ICE process complete, status=%s", 
+	     pj_strerror(status, errmsg, sizeof(errmsg)).ptr));
+
 	dump_checklist("Dumping checklist", ice, &ice->clist);
 	dump_valid_list("Dumping valid list", ice);
 
@@ -1136,7 +1139,7 @@
     /* Log checklist */
     dump_checklist("Checklist created:", ice, clist);
 
-    pj_mutex_lock(ice->mutex);
+    pj_mutex_unlock(ice->mutex);
 
     return PJ_SUCCESS;
 }
diff --git a/pjnath/src/pjnath/ice_stream_transport.c b/pjnath/src/pjnath/ice_stream_transport.c
index 475aa35..e654fcf 100644
--- a/pjnath/src/pjnath/ice_stream_transport.c
+++ b/pjnath/src/pjnath/ice_stream_transport.c
@@ -151,7 +151,6 @@
     return status;
 }
 
-
 /* 
  * This is callback called by ioqueue on incoming packet 
  */
@@ -191,7 +190,6 @@
     }
 }
 
-
 /* 
  * Destroy an interface 
  */
@@ -207,7 +205,6 @@
     }
 }
 
-
 /* 
  * Create ICE stream transport 
  */
@@ -241,7 +238,6 @@
     return PJ_SUCCESS;
 }
 
-
 static void destroy_ice_st(pj_ice_st *ice_st, pj_status_t reason)
 {
     unsigned i;
@@ -273,7 +269,6 @@
     }
 }
 
-
 /*
  * Destroy ICE stream transport.
  */
@@ -283,7 +278,6 @@
     return PJ_SUCCESS;
 }
 
-
 /*
  * Resolve STUN server
  */
@@ -300,7 +294,6 @@
     return -1;
 }
 
-
 /*
  * Set STUN server address.
  */
@@ -319,7 +312,6 @@
     return PJ_SUCCESS;
 }
 
-
 /*
  * Add new component.
  */
@@ -350,7 +342,6 @@
     return PJ_SUCCESS;
 }
 
-
 /* Add interface */
 static void add_interface(pj_ice_st *ice_st, pj_ice_st_interface *is,
 			  unsigned *p_itf_id, pj_bool_t notify,
@@ -405,10 +396,12 @@
     /* Store this interface */
     add_interface(ice_st, is, p_itf_id, notify, notify_data);
 
+    /* Set interface status to SUCCESS */
+    is->status = PJ_SUCCESS;
+
     return PJ_SUCCESS;
 }
 
-
 /*
  * Enumerate and add all host interfaces.
  */
@@ -434,7 +427,6 @@
 					NULL, notify, notify_data);
 }
 
-
 /*
  * Add STUN mapping interface.
  */
@@ -453,7 +445,6 @@
     return -1;
 }
 
-
 /*
  * Add TURN mapping interface.
  */
@@ -472,6 +463,25 @@
     return -1;
 }
 
+PJ_DEF(pj_status_t) pj_ice_st_get_interfaces_status(pj_ice_st *ice_st)
+{
+    unsigned i;
+    pj_status_t worst = PJ_SUCCESS;
+
+    for (i=0; i<ice_st->itf_cnt; ++i) {
+	pj_ice_st_interface *itf = ice_st->itfs[i];
+
+	if (itf->status == PJ_SUCCESS) {
+	    /* okay */
+	} else if (itf->status == PJ_EPENDING && worst==PJ_SUCCESS) {
+	    worst = itf->status;
+	} else {
+	    worst = itf->status;
+	}
+    }
+
+    return worst;
+}
 
 /*
  * Create ICE!
@@ -537,7 +547,6 @@
     return status;
 }
 
-
 /*
  * Enum candidates
  */
@@ -568,7 +577,6 @@
     return PJ_SUCCESS;
 }
 
-
 /*
  * Start ICE processing !
  */
@@ -588,7 +596,6 @@
     return pj_ice_start_check(ice_st->ice);
 }
 
-
 /*
  * Stop ICE!
  */
@@ -602,7 +609,6 @@
     return PJ_SUCCESS;
 }
 
-
 /*
  * Send data to peer agent.
  */
@@ -617,8 +623,6 @@
     return pj_ice_send_data(ice_st->ice, comp_id, data, data_len);
 }
 
-
-
 /*
  * Callback called by ICE session when ICE processing is complete, either
  * successfully or with failure.
@@ -631,7 +635,6 @@
     }
 }
 
-
 /*
  * Callback called by ICE session when it wants to send outgoing packet.
  */
@@ -667,7 +670,6 @@
     return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
 }
 
-
 /*
  * Callback called by ICE session when it receives application data.
  */
diff --git a/pjsip-apps/build/pjsua.dsp b/pjsip-apps/build/pjsua.dsp
index 8939263..5ab3d3d 100644
--- a/pjsip-apps/build/pjsua.dsp
+++ b/pjsip-apps/build/pjsua.dsp
@@ -42,7 +42,7 @@
 # PROP Ignore_Export_Lib 0

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c

-# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../../pjsip/include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /FD /c

+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../../pjsip/include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /I "../../pjnath/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /FD /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "NDEBUG"

 # ADD RSC /l 0x409 /d "NDEBUG"

@@ -68,7 +68,7 @@
 # PROP Ignore_Export_Lib 0

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c

-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../../pjsip/include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /FD /GZ /c

+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../../pjsip/include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /I "../../pjnath/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /FD /GZ /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "_DEBUG"

 # ADD RSC /l 0x409 /d "_DEBUG"

diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index 50efe7f..ed4017d 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -144,8 +144,7 @@
     puts  ("                      This option can be specified multiple times.");
     puts  ("  --outbound=url      Set the URL of global outbound proxy server");
     puts  ("                      May be specified multiple times");
-    puts  ("  --use-stun1=FORMAT  where FORMAT=host[:port]");
-    puts  ("  --use-stun2=FORMAT  Resolve local IP with the specified STUN servers");
+    puts  ("  --stun-srv=name     Set STUN server host or domain");
     puts  ("");
     puts  ("TLS Options:");
     puts  ("  --use-tls           Enable TLS transport (default=no)");
@@ -159,6 +158,7 @@
 
     puts  ("");
     puts  ("Media Options:");
+    puts  ("  --use-ice           Enable ICE (default:no)");
     puts  ("  --add-codec=name    Manually add codec (default is to enable all)");
     puts  ("  --clock-rate=N      Override sound device clock rate");
     puts  ("  --null-audio        Use NULL audio device");
@@ -325,10 +325,10 @@
 	   OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY, 
 	   OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
 	   OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
-	   OPT_NAMESERVER, OPT_USE_STUN1, OPT_USE_STUN2, 
+	   OPT_NAMESERVER, OPT_STUN_SRV,
 	   OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
 	   OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
-	   OPT_AUTO_CONF, OPT_CLOCK_RATE,
+	   OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_USE_ICE,
 	   OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC, 
 	   OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
 	   OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
@@ -366,8 +366,7 @@
 	{ "username",	1, 0, OPT_USERNAME},
 	{ "password",	1, 0, OPT_PASSWORD},
 	{ "nameserver", 1, 0, OPT_NAMESERVER},
-	{ "use-stun1",  1, 0, OPT_USE_STUN1},
-	{ "use-stun2",  1, 0, OPT_USE_STUN2},
+	{ "stun-srv",   1, 0, OPT_STUN_SRV},
 	{ "add-buddy",  1, 0, OPT_ADD_BUDDY},
 	{ "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
 	{ "no-presence", 0, 0, OPT_NO_PRESENCE},
@@ -381,6 +380,7 @@
 	{ "play-tone",  1, 0, OPT_PLAY_TONE},
 	{ "rec-file",   1, 0, OPT_REC_FILE},
 	{ "rtp-port",	1, 0, OPT_RTP_PORT},
+	{ "use-ice",    0, 0, OPT_USE_ICE},
 	{ "add-codec",  1, 0, OPT_ADD_CODEC},
 	{ "complexity",	1, 0, OPT_COMPLEXITY},
 	{ "quality",	1, 0, OPT_QUALITY},
@@ -441,7 +441,6 @@
      */
     pj_optind = 0;
     while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
-	char *p;
 	pj_str_t tmp;
 	long lval;
 
@@ -632,41 +631,8 @@
 	    }
 	    break;
 
-	case OPT_USE_STUN1:   /* STUN server 1 */
-	    p = pj_ansi_strchr(pj_optarg, ':');
-	    if (p) {
-		*p = '\0';
-		cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
-		cfg->udp_cfg.stun_config.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
-		if (cfg->udp_cfg.stun_config.stun_port1 < 1 || cfg->udp_cfg.stun_config.stun_port1 > 65535) {
-		    PJ_LOG(1,(THIS_FILE, 
-			      "Error: expecting port number with "
-			      "option --use-stun1"));
-		    return PJ_EINVAL;
-		}
-	    } else {
-		cfg->udp_cfg.stun_config.stun_port1 = 3478;
-		cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
-	    }
-	    cfg->udp_cfg.use_stun = PJ_TRUE;
-	    break;
-
-	case OPT_USE_STUN2:   /* STUN server 2 */
-	    p = pj_ansi_strchr(pj_optarg, ':');
-	    if (p) {
-		*p = '\0';
-		cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
-		cfg->udp_cfg.stun_config.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
-		if (cfg->udp_cfg.stun_config.stun_port2 < 1 || cfg->udp_cfg.stun_config.stun_port2 > 65535) {
-		    PJ_LOG(1,(THIS_FILE, 
-			      "Error: expecting port number with "
-			      "option --use-stun2"));
-		    return PJ_EINVAL;
-		}
-	    } else {
-		cfg->udp_cfg.stun_config.stun_port2 = 3478;
-		cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
-	    }
+	case OPT_STUN_SRV:   /* STUN server */
+	    cfg->cfg.stun_srv = pj_str(pj_optarg);
 	    break;
 
 	case OPT_ADD_BUDDY: /* Add to buddy list. */
@@ -728,6 +694,10 @@
 	    cfg->rec_file = pj_str(pj_optarg);
 	    break;
 
+	case OPT_USE_ICE:
+	    cfg->media_cfg.enable_ice = PJ_TRUE;
+	    break;
+
 	case OPT_RTP_PORT:
 	    cfg->rtp_cfg.port = my_atoi(pj_optarg);
 	    if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
@@ -1113,21 +1083,13 @@
     }
 
     /* STUN */
-    if (config->udp_cfg.stun_config.stun_port1) {
-	pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
-			(int)config->udp_cfg.stun_config.stun_srv1.slen, 
-			config->udp_cfg.stun_config.stun_srv1.ptr, 
-			config->udp_cfg.stun_config.stun_port1);
+    if (config->cfg.stun_srv.slen) {
+	pj_ansi_sprintf(line, "--stun-srv %.*s\n",
+			(int)config->cfg.stun_srv.slen, 
+			config->cfg.stun_srv.ptr);
 	pj_strcat2(&cfg, line);
     }
 
-    if (config->udp_cfg.stun_config.stun_port2) {
-	pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
-			(int)config->udp_cfg.stun_config.stun_srv2.slen, 
-			config->udp_cfg.stun_config.stun_srv2.ptr, 
-			config->udp_cfg.stun_config.stun_port2);
-	pj_strcat2(&cfg, line);
-    }
 
     /* TLS */
     if (config->use_tls)
@@ -1174,6 +1136,8 @@
 
 
     /* Media */
+    if (config->media_cfg.enable_ice)
+	pj_strcat2(&cfg, "--use-ice\n");
     if (config->null_audio)
 	pj_strcat2(&cfg, "--null-audio\n");
     if (config->auto_play)
@@ -2962,11 +2926,6 @@
     if (status != PJ_SUCCESS)
 	return status;
 
-    /* Copy udp_cfg STUN config to rtp_cfg */
-    app_config.rtp_cfg.use_stun = app_config.udp_cfg.use_stun;
-    app_config.rtp_cfg.stun_config = app_config.udp_cfg.stun_config;
-
-
     /* Initialize application callbacks */
     app_config.cfg.cb.on_call_state = &on_call_state;
     app_config.cfg.cb.on_call_media_state = &on_call_media_state;
diff --git a/pjsip/build/pjsua_lib.dsp b/pjsip/build/pjsua_lib.dsp
index b15a7f4..da7c163 100644
--- a/pjsip/build/pjsua_lib.dsp
+++ b/pjsip/build/pjsua_lib.dsp
@@ -41,7 +41,7 @@
 # PROP Intermediate_Dir ".\output\pjsua-lib-i386-win32-vc6-release"

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c

-# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /c

+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /I "../../pjnath/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "NDEBUG"

 # ADD RSC /l 0x409 /d "NDEBUG"

@@ -65,7 +65,7 @@
 # PROP Intermediate_Dir ".\output\pjsua-lib-i386-win32-vc6-debug"

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c

-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c

+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /I "../../pjnath/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "_DEBUG"

 # ADD RSC /l 0x409 /d "_DEBUG"

diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h
index 08c6793..f285ef8 100644
--- a/pjsip/include/pjsip/sip_endpoint.h
+++ b/pjsip/include/pjsip/sip_endpoint.h
@@ -156,6 +156,15 @@
 PJ_DECL(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt, 
 					pj_timer_entry *entry );
 
+/**
+ * Get the timer heap instance of the SIP endpoint.
+ *
+ * @param endpt	    The endpoint.
+ *
+ * @return	    The timer heap instance.
+ */
+PJ_DECL(pj_timer_heap_t*) pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt);
+
 
 /**
  * Register new module to the endpoint.
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 55cf626..fcd0549 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -902,6 +902,14 @@
      */
     pj_str_t	    outbound_proxy[4];
 
+    /**
+     * Specify STUN server. This server will be first resolved with DNS SRV
+     * to get the actual server address. If DNS SRV resolution failed, or
+     * when nameserver is not configured, the server will be resolved using
+     * DNS A resolution (i.e. gethostbyname()).
+     */
+    pj_str_t	    stun_srv;
+
     /** 
      * Number of credentials in the credential array.
      */
@@ -1001,6 +1009,7 @@
     }
 
     pj_strdup_with_null(pool, &dst->user_agent, &src->user_agent);
+    pj_strdup_with_null(pool, &dst->stun_srv, &src->stun_srv);
 }
 
 
@@ -1334,60 +1343,6 @@
 
 
 /**
- * This structure describes STUN configuration for SIP and media transport,
- * and is embedded inside pjsua_transport_config structure.
- */
-typedef struct pjsua_stun_config
-{
-    /**
-     * The first STUN server IP address or hostname.
-     */
-    pj_str_t	stun_srv1;
-
-    /**
-     * Port number of the first STUN server.
-     * If zero, default STUN port will be used.
-     */
-    unsigned	stun_port1;
-    
-    /**
-     * Optional second STUN server IP address or hostname, for which the
-     * result of the mapping request will be compared to. If the value
-     * is empty, only one STUN server will be used.
-     */
-    pj_str_t	stun_srv2;
-
-    /**
-     * Port number of the second STUN server.
-     * If zero, default STUN port will be used.
-     */
-    unsigned	stun_port2;
-
-} pjsua_stun_config;
-
-
-
-/**
- * Call this function to initialize STUN config with default values.
- * STUN config is normally embedded inside pjsua_transport_config, so
- * normally there is no need to call this function and rather just
- * call pjsua_transport_config_default() instead.
- *
- * @param cfg	    The STUN config to be initialized.
- *
- * \par Python:
- * The corresponding Python function creates and initialize the config:
- * \code
-    stun_cfg = py_pjsua.stun_config_default()
- * \endcode
- */
-PJ_INLINE(void) pjsua_stun_config_default(pjsua_stun_config *cfg)
-{
-    pj_bzero(cfg, sizeof(*cfg));
-}
-
-
-/**
  * Transport configuration for creating transports for both SIP
  * and media. Before setting some values to this structure, application
  * MUST call #pjsua_transport_config_default() to initialize its
@@ -1435,16 +1390,6 @@
     pj_str_t		bound_addr;
 
     /**
-     * Flag to indicate whether STUN should be used.
-     */
-    pj_bool_t		use_stun;
-
-    /**
-     * STUN configuration, must be specified when STUN is used.
-     */
-    pjsua_stun_config	stun_config;
-
-    /**
      * This specifies TLS settings for TLS transport. It is only be used
      * when this transport config is being used to create a SIP TLS
      * transport.
@@ -1468,46 +1413,11 @@
 PJ_INLINE(void) pjsua_transport_config_default(pjsua_transport_config *cfg)
 {
     pj_bzero(cfg, sizeof(*cfg));
-    pjsua_stun_config_default(&cfg->stun_config);
     pjsip_tls_setting_default(&cfg->tls_setting);
 }
 
 
 /**
- * This is a utility function to normalize STUN config. It's only
- * used internally by the library.
- *
- * @param cfg	    The STUN config to be initialized.
- *
- * \par Python:
- * \code
-    py_pjsua.normalize_stun_config(cfg)
- * \code
- */
-PJ_INLINE(void) pjsua_normalize_stun_config( pjsua_stun_config *cfg )
-{
-    if (cfg->stun_srv1.slen) {
-
-	if (cfg->stun_port1 == 0)
-	    cfg->stun_port1 = 3478;
-
-	if (cfg->stun_srv2.slen == 0) {
-	    cfg->stun_srv2 = cfg->stun_srv1;
-	    cfg->stun_port2 = cfg->stun_port1;
-	} else {
-	    if (cfg->stun_port2 == 0)
-		cfg->stun_port2 = 3478;
-	}
-
-    } else {
-	cfg->stun_port1 = 0;
-	cfg->stun_srv2.slen = 0;
-	cfg->stun_port2 = 0;
-    }
-}
-
-
-/**
  * Duplicate transport config.
  *
  * @param pool		The pool.
@@ -1522,19 +1432,8 @@
 					   pjsua_transport_config *dst,
 					   const pjsua_transport_config *src)
 {
+    PJ_UNUSED_ARG(pool);
     pj_memcpy(dst, src, sizeof(*src));
-
-    if (src->stun_config.stun_srv1.slen) {
-	pj_strdup_with_null(pool, &dst->stun_config.stun_srv1,
-			    &src->stun_config.stun_srv1);
-    }
-
-    if (src->stun_config.stun_srv2.slen) {
-	pj_strdup_with_null(pool, &dst->stun_config.stun_srv2,
-			    &src->stun_config.stun_srv2);
-    }
-
-    pjsua_normalize_stun_config(&dst->stun_config);
 }
 
 
@@ -3530,6 +3429,15 @@
      */
     int			jb_max;
 
+    /**
+     * Enable ICE
+     */
+    pj_bool_t		enable_ice;
+
+    /**
+     * Enable ICE media relay.
+     */
+    pj_bool_t		enable_relay;
 };
 
 
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
index 4f6a49f..5365ff1 100644
--- a/pjsip/include/pjsua-lib/pjsua_internal.h
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -186,6 +186,12 @@
     pj_bool_t		 thread_quit_flag;  /**< Thread quit flag.	*/
     pj_thread_t		*thread[4];	    /**< Array of threads.	*/
 
+    /* STUN and resolver */
+    pj_stun_config	 stun_cfg;  /**< Global STUN settings.		*/
+    pj_sockaddr		 stun_srv;  /**< Resolved STUN server address	*/
+    pj_status_t		 stun_status; /**< STUN server status.		*/
+    pj_dns_resolver	*resolver;  /**< DNS resolver.			*/
+
     /* Account: */
     unsigned		 acc_cnt;	     /**< Number of accounts.	*/
     pjsua_acc_id	 default_acc;	     /**< Default account ID	*/
@@ -270,12 +276,30 @@
 #endif
 
 
+/**
+ * Resolve STUN server.
+ */
+pj_status_t pjsua_resolve_stun_server(pj_bool_t wait);
 
 /**
  * Handle incoming invite request.
  */
 pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata);
 
+/*
+ * Media channel.
+ */
+pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
+				    pjsip_role_e role);
+pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, 
+					   pj_pool_t *pool,
+					   pjmedia_sdp_session **p_sdp);
+pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
+				       pjmedia_sdp_session *local_sdp,
+				       pjmedia_sdp_session *remote_sdp);
+pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id);
+
+
 /**
  * Init presence.
  */
diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c
index b587ac1..1a509aa 100644
--- a/pjsip/src/pjsip/sip_endpoint.c
+++ b/pjsip/src/pjsip/sip_endpoint.c
@@ -758,6 +758,14 @@
 }
 
 /*
+ * Get the timer heap instance of the SIP endpoint.
+ */
+PJ_DEF(pj_timer_heap_t*) pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt)
+{
+    return endpt->timer_heap;
+}
+
+/*
  * This is the callback that is called by the transport manager when it 
  * receives a message from the network.
  */
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 68eba77..2441f8e 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -60,8 +60,6 @@
 					    pjsip_event *e);
 
 
-/* Destroy the call's media */
-static pj_status_t call_destroy_media(int call_id);
 
 /* Create inactive SDP for call hold. */
 static pj_status_t create_inactive_sdp(pjsua_call *call,
@@ -302,10 +300,15 @@
 	return status;
     }
 
-    /* Get media capability from media endpoint: */
+    /* Init media channel */
+    status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+	goto on_error;
+    }
 
-    status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, dlg->pool, 1, 
-				       &call->skinfo, &offer);
+    /* Create SDP offer */
+    status = pjsua_media_channel_create_sdp(call->index, dlg->pool, &offer);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status);
 	goto on_error;
@@ -403,6 +406,7 @@
 
     if (call_id != -1) {
 	reset_call(call_id);
+	pjsua_media_channel_deinit(call_id);
     }
 
     PJSUA_UNLOCK();
@@ -526,10 +530,8 @@
     }
 
 
-    /* Get media capability from media endpoint: */
-    status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, 
-				       rdata->tp_info.pool, 1,
-				       &call->skinfo, &answer );
+    /* Init media channel */
+    status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS);
     if (status != PJ_SUCCESS) {
 	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
 				      NULL, NULL);
@@ -538,6 +540,17 @@
     }
 
 
+    /* Get media capability from media endpoint: */
+    status = pjsua_media_channel_create_sdp(call->index, rdata->tp_info.pool, &answer);
+    if (status != PJ_SUCCESS) {
+	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
+				      NULL, NULL);
+	pjsua_media_channel_deinit(call->index);
+	PJSUA_UNLOCK();
+	return PJ_TRUE;
+    }
+
+
     /* Verify that we can handle the request. */
     status = pjsip_inv_verify_request(rdata, &options, answer, NULL,
 				      pjsua_var.endpt, &response);
@@ -561,6 +574,7 @@
 					  NULL, NULL);
 	}
 
+	pjsua_media_channel_deinit(call->index);
 	PJSUA_UNLOCK();
 	return PJ_TRUE;
     } 
@@ -580,6 +594,7 @@
 	pjsua_perror(THIS_FILE, "Unable to generate Contact header", status);
 	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
 				      NULL, NULL);
+	pjsua_media_channel_deinit(call->index);
 	PJSUA_UNLOCK();
 	return PJ_TRUE;
     }
@@ -590,6 +605,7 @@
     if (status != PJ_SUCCESS) {
 	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
 				      NULL, NULL);
+	pjsua_media_channel_deinit(call->index);
 	PJSUA_UNLOCK();
 	return PJ_TRUE;
     }
@@ -618,6 +634,7 @@
 	/* Can't terminate dialog because transaction is in progress.
 	pjsip_dlg_terminate(dlg);
 	 */
+	pjsua_media_channel_deinit(call->index);
 	PJSUA_UNLOCK();
 	return PJ_TRUE;
     }
@@ -649,13 +666,15 @@
 
 	pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL);
 	pjsip_inv_terminate(inv, 500, PJ_FALSE);
+	pjsua_media_channel_deinit(call->index);
 	PJSUA_UNLOCK();
 	return PJ_TRUE;
 
     } else {
 	status = pjsip_inv_send_msg(inv, response);
-	if (status != PJ_SUCCESS)
+	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, "Unable to send 100 response", status);
+	}
     }
 
     ++pjsua_var.call_cnt;
@@ -1191,8 +1210,7 @@
     /* Create SDP */
     PJ_UNUSED_ARG(unhold);
     PJ_TODO(create_active_inactive_sdp_based_on_unhold_arg);
-    status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, call->inv->pool, 
-				       1, &call->skinfo, &sdp);
+    status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, &sdp);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", 
 		     status);
@@ -1885,34 +1903,6 @@
 
 
 /*
- * Destroy the call's media
- */
-static pj_status_t call_destroy_media(int call_id)
-{
-    pjsua_call *call = &pjsua_var.calls[call_id];
-
-    if (call->conf_slot != PJSUA_INVALID_ID) {
-	pjmedia_conf_remove_port(pjsua_var.mconf, call->conf_slot);
-	call->conf_slot = PJSUA_INVALID_ID;
-    }
-
-    if (call->session) {
-	/* Destroy session (this will also close RTP/RTCP sockets). */
-	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;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
  * This callback receives notification from invite session when the
  * session state has changed.
  */
@@ -2034,7 +2024,7 @@
 	pj_assert(call != NULL);
 
 	if (call)
-	    call_destroy_media(call->index);
+	    pjsua_media_channel_deinit(call->index);
 
 	/* Free call */
 	call->inv = NULL;
@@ -2077,22 +2067,6 @@
 }
 
 /*
- * DTMF callback from the stream.
- */
-static void dtmf_callback(pjmedia_stream *strm, void *user_data,
-			  int digit)
-{
-    PJ_UNUSED_ARG(strm);
-
-    if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
-	pjsua_call_id call_id;
-
-	call_id = (pjsua_call_id)user_data;
-	pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
-    }
-}
-
-/*
  * Callback to be called when SDP offer/answer negotiation has just completed
  * in the session. This function will start/update media if negotiation
  * has succeeded.
@@ -2100,14 +2074,9 @@
 static void pjsua_call_on_media_update(pjsip_inv_session *inv,
 				       pj_status_t status)
 {
-    int prev_media_st = 0;
     pjsua_call *call;
-    pjmedia_session_info sess_info;
-    const pjmedia_sdp_session *local_sdp;
-    const pjmedia_sdp_session *remote_sdp;
-    pjmedia_port *media_port;
-    pj_str_t port_name;
-    char tmp[PJSIP_MAX_URL_SIZE];
+    pjmedia_sdp_session *local_sdp;
+    pjmedia_sdp_session *remote_sdp;
 
     PJSUA_LOCK();
 
@@ -2118,7 +2087,7 @@
 	pjsua_perror(THIS_FILE, "SDP negotiation has failed", status);
 
 	/* Stop/destroy media, if any */
-	call_destroy_media(call->index);
+	pjsua_media_channel_deinit(call->index);
 
 	/* Disconnect call if we're not in the middle of initializing an
 	 * UAS dialog and if this is not a re-INVITE 
@@ -2133,15 +2102,8 @@
 	return;
     }
 
-    /* Destroy existing media session, if any. */
-
-    if (call) {
-	prev_media_st = call->media_st;
-	call_destroy_media(call->index);
-    }
 
     /* Get local and remote SDP */
-
     status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, 
@@ -2163,155 +2125,16 @@
 	return;
     }
 
-    /* Create media session info based on SDP parameters. 
-     * We only support one stream per session at the moment
-     */    
-    status = pjmedia_session_info_from_sdp( call->inv->dlg->pool, 
-					    pjsua_var.med_endpt, 
-					    1,&sess_info, 
-					    local_sdp, remote_sdp);
+    status = pjsua_media_channel_update(call->index, local_sdp, remote_sdp);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create media session", 
 		     status);
 	call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+	pjsua_media_channel_deinit(call->index);
 	PJSUA_UNLOCK();
 	return;
     }
 
-    /* Check if media is put on-hold */
-    if (sess_info.stream_cnt == 0 || 
-	sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE)
-    {
-
-	/* Determine who puts the call on-hold */
-	if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) {
-	    if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) {
-		/* It was local who offer hold */
-		call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
-	    } else {
-		call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
-	    }
-	}
-
-	call->media_dir = PJMEDIA_DIR_NONE;
-
-    } else {
-
-	/* Override ptime, if this option is specified. */
-	if (pjsua_var.media_cfg.ptime != 0) {
-	    sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t)
-		(pjsua_var.media_cfg.ptime / sess_info.stream_info[0].param->info.frm_ptime);
-	    if (sess_info.stream_info[0].param->setting.frm_per_pkt == 0)
-		sess_info.stream_info[0].param->setting.frm_per_pkt = 1;
-	}
-
-	/* Disable VAD, if this option is specified. */
-	if (pjsua_var.media_cfg.no_vad) {
-	    sess_info.stream_info[0].param->setting.vad = 0;
-	}
-
-
-	/* Optionally, application may modify other stream settings here
-	 * (such as jitter buffer parameters, codec ptime, etc.)
-	 */
-	sess_info.stream_info[0].jb_init = pjsua_var.media_cfg.jb_init;
-	sess_info.stream_info[0].jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
-	sess_info.stream_info[0].jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
-	sess_info.stream_info[0].jb_max = pjsua_var.media_cfg.jb_max;
-
-	/* Create session based on session info. */
-	status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
-					 &call->med_tp,
-					 call, &call->session );
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create media session", 
-			 status);
-	    //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
-	    PJSUA_UNLOCK();
-	    return;
-	}
-
-	/* If DTMF callback is installed by application, install our
-	 * callback to the session.
-	 */
-	if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
-	    pjmedia_session_set_dtmf_callback(call->session, 0, 
-					      &dtmf_callback, 
-					      (void*)(call->index));
-	}
-
-	/* Get the port interface of the first stream in the session.
-	 * We need the port interface to add to the conference bridge.
-	 */
-	pjmedia_session_get_port(call->session, 0, &media_port);
-
-
-	/*
-	 * Add the call to conference bridge.
-	 */
-	port_name.ptr = tmp;
-	port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
-					 call->inv->dlg->remote.info->uri,
-					 tmp, sizeof(tmp));
-	if (port_name.slen < 1) {
-	    port_name = pj_str("call");
-	}
-	status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
-					media_port, 
-					&port_name,
-					(unsigned*)&call->conf_slot);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create conference slot", 
-			 status);
-	    call_destroy_media(call->index);
-	    //call_disconnect(inv, PJSIP_SC_INTERNAL_SERVER_ERROR);
-	    PJSUA_UNLOCK();
-	    return;
-	}
-
-	/* Call's media state is active */
-	call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
-	call->media_dir = sess_info.stream_info[0].dir;
-    }
-
-    /* Print info. */
-    {
-	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;
-	    }
-	    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));
-    }
 
     /* Call application callback, if any */
     if (pjsua_var.ua_cfg.cb.on_call_media_state)
@@ -2413,9 +2236,7 @@
     } else {
 	PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer",
 		  call->index));
-	status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, 
-					   call->inv->pool, 1,
-					   &call->skinfo, &answer);	
+	status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, &answer);
     }
 
     if (status != PJ_SUCCESS) {
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 46b8e0e..116a51f 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -42,11 +42,15 @@
 {
     unsigned i;
 
+    pj_bzero(&pjsua_var, sizeof(pjsua_var));
+
     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i)
 	pjsua_var.acc[i].index = i;
     
     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata); ++i)
 	pjsua_var.tpdata[i].index = i;
+
+    pjsua_var.stun_status = PJ_EUNKNOWN;
 }
 
 
@@ -466,15 +470,16 @@
      */
     if (ua_cfg->nameserver_count) {
 #if PJSIP_HAS_RESOLVER
-	pj_dns_resolver *resv;
 	unsigned i;
 
 	/* Create DNS resolver */
-	status = pjsip_endpt_create_resolver(pjsua_var.endpt, &resv);
+	status = pjsip_endpt_create_resolver(pjsua_var.endpt, 
+					     &pjsua_var.resolver);
 	PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
 
 	/* Configure nameserver for the DNS resolver */
-	status = pj_dns_resolver_set_ns(resv, ua_cfg->nameserver_count,
+	status = pj_dns_resolver_set_ns(pjsua_var.resolver, 
+					ua_cfg->nameserver_count,
 					ua_cfg->nameserver, NULL);
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, "Error setting nameserver", status);
@@ -482,7 +487,7 @@
 	}
 
 	/* Set this DNS resolver to be used by the SIP resolver */
-	status = pjsip_endpt_set_resolver(pjsua_var.endpt, resv);
+	status = pjsip_endpt_set_resolver(pjsua_var.endpt, pjsua_var.resolver);
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, "Error setting DNS resolver", status);
 	    return status;
@@ -550,6 +555,13 @@
 	goto on_error;
 
 
+    /* Start resolving STUN server */
+    status = pjsua_resolve_stun_server(PJ_FALSE);
+    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+	pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
+	return status;
+    }
+
     /* Initialize PJSUA media subsystem */
     status = pjsua_media_subsys_init(media_cfg);
     if (status != PJ_SUCCESS)
@@ -638,6 +650,49 @@
 }
 
 /*
+ * Resolve STUN server.
+ */
+pj_status_t pjsua_resolve_stun_server(pj_bool_t wait)
+{
+    if (pjsua_var.stun_status == PJ_EUNKNOWN) {
+	/* Initialize STUN configuration */
+	pj_stun_config_init(&pjsua_var.stun_cfg, &pjsua_var.cp.factory, 0,
+			    pjsip_endpt_get_ioqueue(pjsua_var.endpt),
+			    pjsip_endpt_get_timer_heap(pjsua_var.endpt));
+
+	/* Start STUN server resolution */
+	/* For now just do DNS A resolution */
+
+	if (pjsua_var.ua_cfg.stun_srv.slen == 0) {
+	    pjsua_var.stun_status = PJ_SUCCESS;
+	} else {
+	    pj_hostent he;
+
+	    pjsua_var.stun_status = 
+		pj_gethostbyname(&pjsua_var.ua_cfg.stun_srv, &he);
+
+	    if (pjsua_var.stun_status == PJ_SUCCESS) {
+		pj_sockaddr_in_init(&pjsua_var.stun_srv.ipv4, NULL, 0);
+		pjsua_var.stun_srv.ipv4.sin_addr = *(pj_in_addr*)he.h_addr;
+		pjsua_var.stun_srv.ipv4.sin_port = pj_htons((pj_uint16_t)3478);
+	    }
+	}
+	return pjsua_var.stun_status;
+
+    } else if (pjsua_var.stun_status == PJ_EPENDING) {
+	/* STUN server resolution has been started, wait for the
+	 * result.
+	 */
+	pj_assert(!"Should not happen");
+	return PJ_EBUG;
+
+    } else {
+	/* STUN server has been resolved, return the status */
+	return pjsua_var.stun_status;
+    }
+}
+
+/*
  * Destroy pjsua.
  */
 PJ_DEF(pj_status_t) pjsua_destroy(void)
@@ -819,15 +874,21 @@
  */
 static pj_status_t create_sip_udp_sock(pj_in_addr bound_addr,
 				       int port,
-				       pj_bool_t use_stun,
-				       const pjsua_stun_config *stun_param,
 				       pj_sock_t *p_sock,
 				       pj_sockaddr_in *p_pub_addr)
 {
-    pjsua_stun_config stun;
+    char ip_addr[32];
+    pj_str_t stun_srv;
     pj_sock_t sock;
     pj_status_t status;
 
+    /* Make sure STUN server resolution has completed */
+    status = pjsua_resolve_stun_server(PJ_TRUE);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
+	return status;
+    }
+
     status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "socket() error", status);
@@ -856,27 +917,24 @@
 	port = pj_ntohs(bound_addr.sin_port);
     }
 
-    /* Copy and normalize STUN param */
-    if (use_stun) {
-	pj_memcpy(&stun, stun_param, sizeof(*stun_param));
-	pjsua_normalize_stun_config(&stun);
+    if (pjsua_var.stun_srv.addr.sa_family != 0) {
+	pj_ansi_strcpy(ip_addr,pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
+	stun_srv = pj_str(ip_addr);
     } else {
-	pj_bzero(&stun, sizeof(pjsua_stun_config));
+	stun_srv.slen = 0;
     }
 
     /* Get the published address, either by STUN or by resolving
      * the name of local host.
      */
-    if (stun.stun_srv1.slen) {
+    if (stun_srv.slen) {
 	/*
 	 * STUN is specified, resolve the address with STUN.
 	 */
 	status = pjstun_get_mapped_addr(&pjsua_var.cp.factory, 1, &sock,
-				         &stun.stun_srv1, 
-					  stun.stun_port1,
-					 &stun.stun_srv2, 
-					  stun.stun_port2,
-				          p_pub_addr);
+				         &stun_srv, 3478,
+					 &stun_srv, 3478,
+				         p_pub_addr);
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, "Error resolving with STUN", status);
 	    pj_sock_close(sock);
@@ -986,7 +1044,6 @@
 	 * (only when public address is not specified).
 	 */
 	status = create_sip_udp_sock(bound_addr.sin_addr, cfg->port, 
-				     cfg->use_stun, &cfg->stun_config, 
 				     &sock, &pub_addr);
 	if (status != PJ_SUCCESS)
 	    goto on_return;
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index 668d3b9..6c81583 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -215,6 +215,14 @@
     pj_status_t status = PJ_SUCCESS;
     pj_sock_t sock[2];
 
+    /* Make sure STUN server resolution has completed */
+    status = pjsua_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;
 
@@ -272,12 +280,17 @@
 	 * 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 (cfg->stun_config.stun_srv1.slen) {
+	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,
-					   &cfg->stun_config.stun_srv1, 
-					   cfg->stun_config.stun_port1,
-					   &cfg->stun_config.stun_srv2, 
-					   cfg->stun_config.stun_port2,
+					   &stun_srv, 3478,
+					   &stun_srv, 3478,
 					   mapped_addr);
 	    if (status != PJ_SUCCESS) {
 		pjsua_perror(THIS_FILE, "STUN resolve error", status);
@@ -488,39 +501,16 @@
 }
 
 
-/*
- * Create UDP media transports for all the calls. This function creates
- * one UDP media transport for each call.
- */
-PJ_DEF(pj_status_t) 
-pjsua_media_transports_create(const pjsua_transport_config *app_cfg)
+/* Create normal UDP media transports */
+static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
 {
-    pjsua_transport_config cfg;
     unsigned i;
     pj_status_t status;
 
-
-    /* Make sure pjsua_init() has been called */
-    PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
-
-    PJSUA_LOCK();
-
-    /* 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->op->destroy(pjsua_var.calls[i].med_tp);
-	    pjsua_var.calls[i].med_tp = NULL;
-	}
-    }
-
-    /* Copy config */
-    pj_memcpy(&cfg, app_cfg, sizeof(*app_cfg));
-    pjsua_normalize_stun_config(&cfg.stun_config);
-
     /* Create each media transport */
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
 
-	status = create_rtp_rtcp_sock(&cfg, &pjsua_var.calls[i].skinfo);
+	status = create_rtp_rtcp_sock(cfg, &pjsua_var.calls[i].skinfo);
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
 		         status);
@@ -545,24 +535,428 @@
 
     }
 
-    PJSUA_UNLOCK();
+    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;
+	}
+    }
+
+    return status;
+}
+
+
+/* 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;
+
+    /* Create each media transport */
+    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+	pj_ice_st *ice_st;
+
+	status = pjmedia_ice_create(pjsua_var.med_endpt, NULL, 1,
+				    &pjsua_var.stun_cfg, 
+				    &pjsua_var.calls[i].med_tp);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
+		         status);
+	    goto on_error;
+	}
+
+	ice_st = pjmedia_ice_get_ice_st(pjsua_var.calls[i].med_tp);
+
+	/* Add host candidates for RTP */
+	status = pj_ice_st_add_all_host_interfaces(ice_st, 1, 0, 
+						   PJ_FALSE, NULL);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Error adding ICE host candidates",
+		         status);
+	    goto on_error;
+	}
+
+	/* Configure STUN server */
+	if (pjsua_var.stun_srv.addr.sa_family != 0) {
+	
+	    status = pj_ice_st_set_stun_addr(ice_st, 
+					     pjsua_var.media_cfg.enable_relay,
+					     &pjsua_var.stun_srv.ipv4);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Error setting ICE's STUN server",
+			     status);
+		goto on_error;
+	    }
+
+	    /* Add STUN server reflexive candidate for RTP */
+	    status = pj_ice_st_add_stun_interface(ice_st, 1, 0, 
+						  PJ_FALSE, NULL);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Error adding ICE address",
+			     status);
+		goto on_error;
+	    }
+	}
+    }
+
+    /* Wait until all ICE transports are ready */
+    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+	pj_ice_st *ice_st;
+
+	ice_st = pjmedia_ice_get_ice_st(pjsua_var.calls[i].med_tp);
+
+	/* Wait until interface status is PJ_SUCCESS */
+	for (;;) {
+	    status = pj_ice_st_get_interfaces_status(ice_st);
+	    if (status == PJ_EPENDING)
+		pjsua_handle_events(100);
+	    else
+		break;
+	}
+
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, 
+			 "Error adding STUN address to ICE media transport",
+		         status);
+	    goto on_error;
+	}
+
+	/* Get transport info */
+	pjmedia_transport_get_info(pjsua_var.calls[i].med_tp,
+				   &pjsua_var.calls[i].skinfo);
+
+    }
 
     return PJ_SUCCESS;
 
 on_error:
     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->op->destroy(pjsua_var.calls[i].med_tp);
+	    pjmedia_transport_close(pjsua_var.calls[i].med_tp);
 	    pjsua_var.calls[i].med_tp = NULL;
 	}
     }
 
+    return status;
+}
+
+
+/*
+ * Create UDP media transports for all the calls. This function creates
+ * one UDP media transport for each call.
+ */
+PJ_DEF(pj_status_t) 
+pjsua_media_transports_create(const pjsua_transport_config *app_cfg)
+{
+    pjsua_transport_config cfg;
+    unsigned i;
+    pj_status_t status;
+
+
+    /* Make sure pjsua_init() has been called */
+    PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
+
+    PJSUA_LOCK();
+
+    /* Delete existing media transports */
+    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;
+	}
+    }
+
+    /* Copy config */
+    pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
+
+    if (pjsua_var.media_cfg.enable_ice) {
+	status = create_ice_media_transports(&cfg);
+    } else {
+	status = create_udp_media_transports(&cfg);
+    }
+
+
     PJSUA_UNLOCK();
 
     return status;
 }
 
 
+pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
+				     pjsip_role_e role)
+{
+    pjsua_call *call = &pjsua_var.calls[call_id];
+
+    if (pjsua_var.media_cfg.enable_ice) {
+	pj_ice_role ice_role;
+	pj_status_t status;
+
+	ice_role = (role==PJSIP_ROLE_UAC ? PJ_ICE_ROLE_CONTROLLING : 
+				           PJ_ICE_ROLE_CONTROLLED);
+
+	/* Restart ICE */
+	pjmedia_ice_stop_ice(call->med_tp);
+
+	status = pjmedia_ice_init_ice(call->med_tp, ice_role, NULL, NULL);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, 
+					   pj_pool_t *pool,
+					   pjmedia_sdp_session **p_sdp)
+{
+    pjmedia_sdp_session *sdp;
+    pjsua_call *call = &pjsua_var.calls[call_id];
+    pj_status_t status;
+
+    status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, 1,
+				      &call->skinfo, &sdp);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    if (pjsua_var.media_cfg.enable_ice) {
+	status = pjmedia_ice_modify_sdp(call->med_tp, pool, sdp);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    *p_sdp = sdp;
+    return PJ_SUCCESS;
+
+on_error:
+    pjsua_media_channel_deinit(call_id);
+    return status;
+
+}
+
+
+static void stop_media_session(pjsua_call_id call_id)
+{
+    pjsua_call *call = &pjsua_var.calls[call_id];
+
+    if (call->conf_slot != PJSUA_INVALID_ID) {
+	pjmedia_conf_remove_port(pjsua_var.mconf, call->conf_slot);
+	call->conf_slot = PJSUA_INVALID_ID;
+    }
+
+    if (call->session) {
+	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 (pjsua_var.media_cfg.enable_ice) {
+	pjmedia_ice_stop_ice(call->med_tp);
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * DTMF callback from the stream.
+ */
+static void dtmf_callback(pjmedia_stream *strm, void *user_data,
+			  int digit)
+{
+    PJ_UNUSED_ARG(strm);
+
+    if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+	pjsua_call_id call_id;
+
+	call_id = (pjsua_call_id)user_data;
+	pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
+    }
+}
+
+
+pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
+				       pjmedia_sdp_session *local_sdp,
+				       pjmedia_sdp_session *remote_sdp)
+{
+    int prev_media_st = 0;
+    pjsua_call *call = &pjsua_var.calls[call_id];
+    pjmedia_session_info sess_info;
+    pjmedia_port *media_port;
+    pj_str_t port_name;
+    char tmp[PJSIP_MAX_URL_SIZE];
+    pj_status_t status;
+
+    /* 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. 
+     * We only support one stream per session at the moment
+     */    
+    status = pjmedia_session_info_from_sdp( call->inv->dlg->pool, 
+					    pjsua_var.med_endpt, 
+					    1,&sess_info, 
+					    local_sdp, remote_sdp);
+    if (status != PJ_SUCCESS)
+	return status;
+
+
+    /* Check if media is put on-hold */
+    if (sess_info.stream_cnt == 0 || 
+	sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE)
+    {
+
+	/* Determine who puts the call on-hold */
+	if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) {
+	    if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) {
+		/* It was local who offer hold */
+		call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
+	    } else {
+		call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
+	    }
+	}
+
+	call->media_dir = PJMEDIA_DIR_NONE;
+
+	/* Shutdown transport */
+	/* No need because we need keepalive? */
+
+    } else {
+
+	/* Start ICE */
+	if (pjsua_var.media_cfg.enable_ice) {
+	    status = pjmedia_ice_start_ice(call->med_tp, call->inv->pool, 
+					   remote_sdp);
+	    if (status != PJ_SUCCESS)
+		return status;
+	}
+
+	/* Override ptime, if this option is specified. */
+	if (pjsua_var.media_cfg.ptime != 0) {
+	    sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t)
+		(pjsua_var.media_cfg.ptime / sess_info.stream_info[0].param->info.frm_ptime);
+	    if (sess_info.stream_info[0].param->setting.frm_per_pkt == 0)
+		sess_info.stream_info[0].param->setting.frm_per_pkt = 1;
+	}
+
+	/* Disable VAD, if this option is specified. */
+	if (pjsua_var.media_cfg.no_vad) {
+	    sess_info.stream_info[0].param->setting.vad = 0;
+	}
+
+
+	/* Optionally, application may modify other stream settings here
+	 * (such as jitter buffer parameters, codec ptime, etc.)
+	 */
+	sess_info.stream_info[0].jb_init = pjsua_var.media_cfg.jb_init;
+	sess_info.stream_info[0].jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
+	sess_info.stream_info[0].jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
+	sess_info.stream_info[0].jb_max = pjsua_var.media_cfg.jb_max;
+
+	/* Create session based on session info. */
+	status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
+					 &call->med_tp,
+					 call, &call->session );
+	if (status != PJ_SUCCESS) {
+	    return status;
+	}
+
+	/* If DTMF callback is installed by application, install our
+	 * callback to the session.
+	 */
+	if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+	    pjmedia_session_set_dtmf_callback(call->session, 0, 
+					      &dtmf_callback, 
+					      (void*)(call->index));
+	}
+
+	/* Get the port interface of the first stream in the session.
+	 * We need the port interface to add to the conference bridge.
+	 */
+	pjmedia_session_get_port(call->session, 0, &media_port);
+
+
+	/*
+	 * Add the call to conference bridge.
+	 */
+	port_name.ptr = tmp;
+	port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
+					 call->inv->dlg->remote.info->uri,
+					 tmp, sizeof(tmp));
+	if (port_name.slen < 1) {
+	    port_name = pj_str("call");
+	}
+	status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
+					media_port, 
+					&port_name,
+					(unsigned*)&call->conf_slot);
+	if (status != PJ_SUCCESS) {
+	    return status;
+	}
+
+	/* Call's media state is active */
+	call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
+	call->media_dir = sess_info.stream_info[0].dir;
+    }
+
+    /* Print info. */
+    {
+	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;
+	    }
+	    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));
+    }
+
+    return PJ_SUCCESS;
+}
+
+
 /*
  * Get maxinum number of conference ports.
  */