Index: /pjproject/trunk/pjmedia/include/pjmedia/transport.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/transport.h	(revision 5988)
+++ /pjproject/trunk/pjmedia/include/pjmedia/transport.h	(revision 5989)
@@ -485,4 +485,7 @@
      */
     PJMEDIA_TRANSPORT_TYPE_SRTP,
+
+    /** Loopback media transport */
+    PJMEDIA_TRANSPORT_TYPE_LOOP,
 
     /**
Index: /pjproject/trunk/pjmedia/include/pjmedia/transport_loop.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/transport_loop.h	(revision 5988)
+++ /pjproject/trunk/pjmedia/include/pjmedia/transport_loop.h	(revision 5989)
@@ -51,4 +51,54 @@
 
 /**
+ * Settings to be given when creating loopback media transport. Application
+ * should call #pjmedia_loop_tp_setting_default() to initialize this
+ * structure with its default values.
+ */
+typedef struct pjmedia_loop_tp_setting
+{
+    /* Address family, which can be pj_AF_INET() for IPv4 or
+     * pj_AF_INET6() for IPv6. Default is IPv4 (pj_AF_INET()).
+     */
+    int 	af;
+
+    /* Optional local address which will be returned in the transport info.
+     * If the string is empty, the address will be the default loopback
+     * address (127.0.0.1 or ::1).
+     *
+     * Note that the address is used for info purpose only and no actual
+     * resource will be allocated.
+     *
+     * Default is empty string.
+     */
+    pj_str_t 	addr;
+
+    /* The port number for the RTP socket. The RTCP port number will be
+     * set to one above RTP port. If zero, it will use the default port
+     * number (4000).
+     *
+     * Note that no actual port will be allocated. Default is 4000.
+     */
+    int 	port;
+    
+    /* Setting whether attached streams will receive incoming packets.
+     * Application can further customize the setting of a particular setting
+     * using the API pjmedia_transport_loop_disable_rx().
+     *
+     * Default: PJ_FALSE;
+     */
+    pj_bool_t 	disable_rx;
+
+} pjmedia_loop_tp_setting;
+
+
+/**
+ * Initialize loopback media transport setting with its default values.
+ *
+ * @param opt	SRTP setting to be initialized.
+ */
+PJ_DECL(void) pjmedia_loop_tp_setting_default(pjmedia_loop_tp_setting *opt);
+
+
+/**
  * Create the loopback transport.
  *
@@ -63,5 +113,27 @@
 
 /**
- * Set this stream as the receiver of incoming packets.
+ * Create the loopback transport.
+ *
+ * @param endpt	    The media endpoint instance.
+ * @param opt       Optional settings. If NULL is given, default
+ *		    settings will be used.
+ * @param p_tp	    Pointer to receive the transport instance.
+ *
+ * @return	    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_transport_loop_create2(pjmedia_endpt *endpt,
+			       const pjmedia_loop_tp_setting *opt,
+			       pjmedia_transport **p_tp);
+
+
+/**
+ * Set the configuration of whether a stream will become the receiver of
+ * incoming packets.
+ *
+ * @param tp	    The transport.
+ * @param user	    The stream.
+ * @param disabled  PJ_TRUE to disable the receiving of packets, or
+ *		    PJ_FALSE to enable it.
  */
 PJ_DECL(pj_status_t) pjmedia_transport_loop_disable_rx(pjmedia_transport *tp,
Index: /pjproject/trunk/pjmedia/src/pjmedia/transport_loop.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/transport_loop.c	(revision 5988)
+++ /pjproject/trunk/pjmedia/src/pjmedia/transport_loop.c	(revision 5989)
@@ -36,4 +36,5 @@
 			void*,
 			pj_ssize_t);
+    void  (*rtp_cb2)(	pjmedia_tp_cb_param*);
     void  (*rtcp_cb)(	void*,		/**< To report incoming RTCP.	    */
 			void*,
@@ -48,4 +49,7 @@
     unsigned		user_cnt;	/**< Number of attachments	    */
     struct user		users[4];	/**< Array of users.		    */
+    pj_bool_t		disable_rx;	/**< Disable RX.		    */
+
+    pjmedia_loop_tp_setting setting;	/**< Setting.			    */
 
     unsigned		tx_drop_pct;	/**< Percent of tx pkts to drop.    */
@@ -72,4 +76,7 @@
 						       void*,
 						       pj_ssize_t));
+static pj_status_t transport_attach2  (pjmedia_transport *tp,
+				       pjmedia_transport_attach_param
+				           *att_param);
 static void	   transport_detach   (pjmedia_transport *tp,
 				       void *strm);
@@ -120,6 +127,18 @@
     &transport_media_stop,
     &transport_simulate_lost,
-    &transport_destroy
+    &transport_destroy,
+    &transport_attach2
 };
+
+
+/**
+ * Initialize loopback media transport setting with its default values.
+ */
+PJ_DEF(void) pjmedia_loop_tp_setting_default(pjmedia_loop_tp_setting *opt)
+{
+    pj_bzero(opt, sizeof(pjmedia_loop_tp_setting));
+    
+    opt->af = pj_AF_INET();
+}
 
 
@@ -130,4 +149,18 @@
 						  pjmedia_transport **p_tp)
 {
+    pjmedia_loop_tp_setting opt;
+    
+    pj_bzero(&opt, sizeof(opt));
+    opt.af = pj_AF_INET();
+
+    return pjmedia_transport_loop_create2(endpt, &opt, p_tp);
+}
+
+
+PJ_DEF(pj_status_t)
+pjmedia_transport_loop_create2(pjmedia_endpt *endpt,
+			       const pjmedia_loop_tp_setting *opt,
+			       pjmedia_transport **p_tp)
+{
     struct transport_loop *tp;
     pj_pool_t *pool;
@@ -147,4 +180,18 @@
     tp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
 
+    if (opt) {
+    	tp->setting = *opt;
+    } else {
+    	pjmedia_loop_tp_setting_default(&tp->setting);
+    }
+    if (tp->setting.addr.slen) {
+    	pj_strdup(pool, &tp->setting.addr, &opt->addr);
+    } else {
+    	pj_strset2(&tp->setting.addr, (opt->af == pj_AF_INET())?
+    				       "127.0.0.1": "::1");
+    }
+    if (tp->setting.port == 0)
+    	tp->setting.port = 4000;
+
     /* Done */
     *p_tp = &tp->base;
@@ -190,10 +237,12 @@
 				      pjmedia_transport_info *info)
 {
-    PJ_ASSERT_RETURN(tp && info, PJ_EINVAL);
+    struct transport_loop *loop = (struct transport_loop*) tp;
 
     info->sock_info.rtp_sock = 1;
-    pj_sockaddr_in_init(&info->sock_info.rtp_addr_name.ipv4, 0, 0);
+    pj_sockaddr_init(loop->setting.af, &info->sock_info.rtp_addr_name, 
+		     &loop->setting.addr, loop->setting.port);
     info->sock_info.rtcp_sock = 2;
-    pj_sockaddr_in_init(&info->sock_info.rtcp_addr_name.ipv4, 0, 0);
+    pj_sockaddr_init(loop->setting.af, &info->sock_info.rtcp_addr_name,
+    		     &loop->setting.addr, loop->setting.port + 1);
 
     return PJ_SUCCESS;
@@ -202,4 +251,48 @@
 
 /* Called by application to initialize the transport */
+static pj_status_t tp_attach(   pjmedia_transport *tp,
+				       void *user_data,
+				       const pj_sockaddr_t *rem_addr,
+				       const pj_sockaddr_t *rem_rtcp,
+				       unsigned addr_len,
+				       void (*rtp_cb)(void*,
+						      void*,
+						      pj_ssize_t),
+				       void (*rtp_cb2)(pjmedia_tp_cb_param*),
+				       void (*rtcp_cb)(void*,
+						       void*,
+						       pj_ssize_t))
+{
+    struct transport_loop *loop = (struct transport_loop*) tp;
+    unsigned i;
+    const pj_sockaddr *rtcp_addr;
+
+    /* Validate arguments */
+    PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL);
+
+    /* Must not be "attached" to same user */
+    for (i=0; i<loop->user_cnt; ++i) {
+	PJ_ASSERT_RETURN(loop->users[i].user_data != user_data,
+			 PJ_EINVALIDOP);
+    }
+    PJ_ASSERT_RETURN(loop->user_cnt != PJ_ARRAY_SIZE(loop->users), 
+		     PJ_ETOOMANY);
+
+    PJ_UNUSED_ARG(rem_rtcp);
+    PJ_UNUSED_ARG(rtcp_addr);
+
+    /* "Attach" the application: */
+
+    /* Save the new user */
+    loop->users[loop->user_cnt].rtp_cb = rtp_cb;
+    loop->users[loop->user_cnt].rtp_cb2 = rtp_cb2;
+    loop->users[loop->user_cnt].rtcp_cb = rtcp_cb;
+    loop->users[loop->user_cnt].user_data = user_data;
+    loop->users[loop->user_cnt].rx_disabled = loop->disable_rx;
+    ++loop->user_cnt;
+
+    return PJ_SUCCESS;
+}
+
 static pj_status_t transport_attach(   pjmedia_transport *tp,
 				       void *user_data,
@@ -214,31 +307,17 @@
 						       pj_ssize_t))
 {
-    struct transport_loop *loop = (struct transport_loop*) tp;
-    unsigned i;
-    const pj_sockaddr *rtcp_addr;
-
-    /* Validate arguments */
-    PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL);
-
-    /* Must not be "attached" to same user */
-    for (i=0; i<loop->user_cnt; ++i) {
-	PJ_ASSERT_RETURN(loop->users[i].user_data != user_data,
-			 PJ_EINVALIDOP);
-    }
-    PJ_ASSERT_RETURN(loop->user_cnt != PJ_ARRAY_SIZE(loop->users), 
-		     PJ_ETOOMANY);
-
-    PJ_UNUSED_ARG(rem_rtcp);
-    PJ_UNUSED_ARG(rtcp_addr);
-
-    /* "Attach" the application: */
-
-    /* Save the new user */
-    loop->users[loop->user_cnt].rtp_cb = rtp_cb;
-    loop->users[loop->user_cnt].rtcp_cb = rtcp_cb;
-    loop->users[loop->user_cnt].user_data = user_data;
-    ++loop->user_cnt;
-
-    return PJ_SUCCESS;
+    return tp_attach(tp, user_data, rem_addr, rem_rtcp, addr_len,
+    		     rtp_cb, NULL, rtcp_cb);
+}
+
+static pj_status_t transport_attach2(pjmedia_transport *tp,
+				     pjmedia_transport_attach_param *att_param)
+{
+    return tp_attach(tp, att_param->user_data, 
+			    (pj_sockaddr_t*)&att_param->rem_addr, 
+			    (pj_sockaddr_t*)&att_param->rem_rtcp, 
+			    att_param->addr_len, att_param->rtp_cb,
+			    att_param->rtp_cb2, 
+			    att_param->rtcp_cb);
 }
 
@@ -297,7 +376,17 @@
     /* Distribute to users */
     for (i=0; i<loop->user_cnt; ++i) {
-	if (!loop->users[i].rx_disabled && loop->users[i].rtp_cb)
+	if (loop->users[i].rx_disabled) continue;
+	if (loop->users[i].rtp_cb2) {
+	    pjmedia_tp_cb_param param;
+
+	    pj_bzero(&param, sizeof(param));
+	    param.user_data = loop->users[i].user_data;
+	    param.pkt = (void *)pkt;
+	    param.size = size;
+	    (*loop->users[i].rtp_cb2)(&param);
+	} else if (loop->users[i].rtp_cb) {
 	    (*loop->users[i].rtp_cb)(loop->users[i].user_data, (void*)pkt, 
 				     size);
+	}
     }
 
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 5988)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 5989)
@@ -3859,4 +3859,22 @@
 
     /**
+     * Use loopback media transport. This may be useful if application
+     * doesn't want PJSIP to create real media transports/sockets, such as
+     * when using third party media.
+     *
+     * Default: PJ_FALSE
+     */
+    pj_bool_t			use_loop_med_tp;
+
+    /**
+     * Enable local loopback when loop_med_tp_use is set to PJ_TRUE.
+     * If enabled, packets sent to the transport will be sent back to
+     * the streams attached to the transport.
+     *
+     * Default: PJ_FALSE
+     */
+    pj_bool_t			enable_loopback;
+
+    /**
      * Control the use of ICE in the account. By default, the settings in the
      * \a pjsua_media_config will be used.
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 5988)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 5989)
@@ -630,4 +630,50 @@
     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);
+
+    call_med->tp_ready = PJ_SUCCESS;
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (call_med->tp)
+	pjmedia_transport_close(call_med->tp);
+
+    return status;
+}
+
+/* Create loop media transport */
+static pj_status_t create_loop_media_transport(
+		       const pjsua_transport_config *cfg,
+		       pjsua_call_media *call_med)
+{
+    pj_status_t status;
+    pjmedia_loop_tp_setting opt;
+    pj_bool_t use_ipv6, use_nat64;
+    int af;
+    pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
+
+    use_ipv6 = (acc->cfg.ipv6_media_use != PJSUA_IPV6_DISABLED);
+    use_nat64 = (acc->cfg.nat64_opt != PJSUA_NAT64_DISABLED);
+    af = (use_ipv6 || use_nat64) ? pj_AF_INET6() : pj_AF_INET();
+
+    pjmedia_loop_tp_setting_default(&opt);
+    opt.af = af;
+    if (cfg->bound_addr.slen)
+        opt.addr = cfg->bound_addr;
+    opt.port = cfg->port;
+    opt.disable_rx=!pjsua_var.acc[call_med->call->acc_id].cfg.enable_loopback;
+    status = pjmedia_transport_loop_create2(pjsua_var.med_endpt, &opt,
+    					    &call_med->tp);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create loop media transport",
 		     status);
 	goto on_error;
@@ -1755,4 +1801,6 @@
      */
     if (call_med->tp == NULL) {
+    	pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
+
         /* Initializations. If media transport creation completes immediately, 
          * we don't need to call the callbacks.
@@ -1772,5 +1820,7 @@
         pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);
 
-	if (pjsua_var.acc[call_med->call->acc_id].cfg.ice_cfg.enable_ice) {
+	if (acc->cfg.use_loop_med_tp) {
+	    status = create_loop_media_transport(tcfg, call_med);
+	} else if (acc->cfg.ice_cfg.enable_ice) {
 	    status = create_ice_media_transport(tcfg, call_med, async);
             if (async && status == PJ_EPENDING) {
@@ -3302,4 +3352,12 @@
 			goto on_check_med_status;
 		    }
+
+		    if (pjmedia_transport_info_get_spc_info(
+				    &tp_info, PJMEDIA_TRANSPORT_TYPE_LOOP))
+		    {
+			pjmedia_transport_loop_disable_rx(
+				call_med->tp, call_med->strm.a.stream,
+				!acc->cfg.enable_loopback);
+		    }
 		}
 	    }
