Ticket #610: Added sample to create a media transport adapter, similar to how SRTP media transport works

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@2262 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/build.symbian/pjmedia.mmp b/build.symbian/pjmedia.mmp
index 60ec642..ce2d279 100644
--- a/build.symbian/pjmedia.mmp
+++ b/build.symbian/pjmedia.mmp
@@ -67,6 +67,7 @@
 SOURCE		stereo_port.c
 SOURCE		stream.c
 SOURCE		tonegen.c
+SOURCE		transport_adapter_sample.c
 SOURCE		transport_ice.c
 SOURCE		transport_udp.c
 SOURCE		transport_srtp.c
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index 98e5db0..adf0bcf 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -57,7 +57,8 @@
 			resample_port.o rtcp.o rtcp_xr.o rtp.o \
 			sdp.o sdp_cmp.o sdp_neg.o \
 			session.o silencedet.o sound_port.o stereo_port.o \
-			stream.o tonegen.o transport_ice.o transport_loop.o \
+			stream.o tonegen.o transport_adapter_sample.o \
+			transport_ice.o transport_loop.o \
 			transport_srtp.o transport_udp.o \
 			wav_player.o wav_playlist.o wav_writer.o wave.o \
 			wsola.o $(SOUND_OBJS) $(NULLSOUND_OBJS)
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp
index 5c8a96b..5e608f2 100644
--- a/pjmedia/build/pjmedia.dsp
+++ b/pjmedia/build/pjmedia.dsp
@@ -257,6 +257,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\src\pjmedia\transport_adapter_sample.c

+# End Source File

+# Begin Source File

+

 SOURCE=..\src\pjmedia\transport_ice.c

 # End Source File

 # Begin Source File

@@ -441,6 +445,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\include\pjmedia\transport_adapter_sample.h

+# End Source File

+# Begin Source File

+

 SOURCE=..\include\pjmedia\transport_ice.h

 # End Source File

 # Begin Source File

diff --git a/pjmedia/build/pjmedia.vcproj b/pjmedia/build/pjmedia.vcproj
index 5bc900b..281ec6b 100644
--- a/pjmedia/build/pjmedia.vcproj
+++ b/pjmedia/build/pjmedia.vcproj
@@ -970,6 +970,10 @@
 				</FileConfiguration>

 			</File>

 			<File

+				RelativePath="..\src\pjmedia\transport_adapter_sample.c"

+				>

+			</File>

+			<File

 				RelativePath="..\src\pjmedia\transport_ice.c"

 				>

 			</File>

@@ -1245,6 +1249,10 @@
 				>

 			</File>

 			<File

+				RelativePath="..\include\pjmedia\transport_adapter_sample.h"

+				>

+			</File>

+			<File

 				RelativePath="..\include\pjmedia\transport_ice.h"

 				>

 			</File>

diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h
index f6179a9..a992a10 100644
--- a/pjmedia/include/pjmedia.h
+++ b/pjmedia/include/pjmedia.h
@@ -58,6 +58,7 @@
 #include <pjmedia/stream.h>
 #include <pjmedia/tonegen.h>
 #include <pjmedia/transport.h>
+#include <pjmedia/transport_adapter_sample.h>
 #include <pjmedia/transport_ice.h>
 #include <pjmedia/transport_loop.h>
 #include <pjmedia/transport_srtp.h>
diff --git a/pjmedia/include/pjmedia/transport.h b/pjmedia/include/pjmedia/transport.h
index c9c2909..6c5ffde 100644
--- a/pjmedia/include/pjmedia/transport.h
+++ b/pjmedia/include/pjmedia/transport.h
@@ -427,7 +427,12 @@
      * stacked with other transport to enable encryption on the underlying
      * transport.
      */
-    PJMEDIA_TRANSPORT_TYPE_SRTP
+    PJMEDIA_TRANSPORT_TYPE_SRTP,
+
+    /**
+     * Start of user defined transport.
+     */
+    PJMEDIA_TRANSPORT_TYPE_USER
 
 } pjmedia_transport_type;
 
diff --git a/pjmedia/include/pjmedia/transport_adapter_sample.h b/pjmedia/include/pjmedia/transport_adapter_sample.h
new file mode 100644
index 0000000..803ba41
--- /dev/null
+++ b/pjmedia/include/pjmedia/transport_adapter_sample.h
@@ -0,0 +1,72 @@
+/* $Id:$ */
+/* 
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJMEDIA_TRANSPORT_ADAPTER_SAMPLE_H__
+#define __PJMEDIA_TRANSPORT_ADAPTER_SAMPLE_H__
+
+
+/**
+ * @file transport_adapter_sample.h
+ * @brief Sample Media Transport Adapter
+ */
+
+#include <pjmedia/transport.h>
+
+
+/**
+ * @defgroup PJMEDIA_TRANSPORT_ADAPTER_SAMPLE Sample Transport Adapter
+ * @ingroup PJMEDIA_TRANSPORT
+ * @brief Example on how to create transport adapter.
+ * @{
+ *
+ * This describes a sample implementation of transport adapter, similar to
+ * the way the SRTP transport adapter works.
+ */
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Create the transport adapter, specifying the underlying transport to be
+ * used to send and receive RTP/RTCP packets.
+ *
+ * @param endpt		The media endpoint.
+ * @param name		Optional name to identify this media transport
+ *			for logging purposes.
+ * @param transport	The underlying media transport to send and receive
+ *			RTP/RTCP packets.
+ * @param p_tp		Pointer to receive the media transport instance.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt,
+					        const char *name,
+					        pjmedia_transport *transport,
+						pjmedia_transport **p_tp);
+
+PJ_END_DECL
+
+
+/**
+ * @}
+ */
+
+
+#endif	/* __PJMEDIA_TRANSPORT_ADAPTER_SAMPLE_H__ */
+
+
diff --git a/pjmedia/src/pjmedia/transport_adapter_sample.c b/pjmedia/src/pjmedia/transport_adapter_sample.c
new file mode 100644
index 0000000..b4d4c9d
--- /dev/null
+++ b/pjmedia/src/pjmedia/transport_adapter_sample.c
@@ -0,0 +1,433 @@
+/* $Id:$ */
+/* 
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjmedia/transport_adapter_sample.h>
+#include <pjmedia/endpoint.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+
+
+/* Transport functions prototypes */
+static pj_status_t transport_get_info (pjmedia_transport *tp,
+				       pjmedia_transport_info *info);
+static pj_status_t transport_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 (*rtcp_cb)(void*,
+						       void*,
+						       pj_ssize_t));
+static void	   transport_detach   (pjmedia_transport *tp,
+				       void *strm);
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+				       const void *pkt,
+				       pj_size_t size);
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+				       const void *pkt,
+				       pj_size_t size);
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+				       const pj_sockaddr_t *addr,
+				       unsigned addr_len,
+				       const void *pkt,
+				       pj_size_t size);
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+				       pj_pool_t *sdp_pool,
+				       unsigned options,
+				       const pjmedia_sdp_session *rem_sdp,
+				       unsigned media_index);
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+				       pj_pool_t *sdp_pool,
+				       pjmedia_sdp_session *local_sdp,
+				       const pjmedia_sdp_session *rem_sdp,
+				       unsigned media_index);
+static pj_status_t transport_media_start (pjmedia_transport *tp,
+				       pj_pool_t *pool,
+				       const pjmedia_sdp_session *local_sdp,
+				       const pjmedia_sdp_session *rem_sdp,
+				       unsigned media_index);
+static pj_status_t transport_media_stop(pjmedia_transport *tp);
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+				       pjmedia_dir dir,
+				       unsigned pct_lost);
+static pj_status_t transport_destroy  (pjmedia_transport *tp);
+
+
+/* The transport operations */
+static struct pjmedia_transport_op tp_adapter_op = 
+{
+    &transport_get_info,
+    &transport_attach,
+    &transport_detach,
+    &transport_send_rtp,
+    &transport_send_rtcp,
+    &transport_send_rtcp2,
+    &transport_media_create,
+    &transport_encode_sdp,
+    &transport_media_start,
+    &transport_media_stop,
+    &transport_simulate_lost,
+    &transport_destroy
+};
+
+
+/* The transport adapter instance */
+struct tp_adapter
+{
+    pjmedia_transport	 base;
+
+    pj_pool_t		*pool;
+
+    /* Stream information. */
+    void		*stream_user_data;
+    void	       (*stream_rtp_cb)(void *user_data,
+					void *pkt,
+					pj_ssize_t);
+    void	       (*stream_rtcp_cb)(void *user_data,
+					 void *pkt,
+					 pj_ssize_t);
+
+
+    /* Add your own member here.. */
+    pjmedia_transport	*slave_tp;
+};
+
+
+/*
+ * Create the adapter.
+ */
+PJ_DEF(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt,
+					       const char *name,
+					       pjmedia_transport *transport,
+					       pjmedia_transport **p_tp)
+{
+    pj_pool_t *pool;
+    struct tp_adapter *adapter;
+
+    if (name == NULL)
+	name = "tpad%p";
+
+    /* Create the pool and initialize the adapter structure */
+    pool = pjmedia_endpt_create_pool(endpt, name, 512, 512);
+    adapter = PJ_POOL_ZALLOC_T(pool, struct tp_adapter);
+    adapter->pool = pool;
+    pj_ansi_strncpy(adapter->base.name, pool->obj_name, 
+		    sizeof(adapter->base.name));
+    adapter->base.type = PJMEDIA_TRANSPORT_TYPE_USER + 1;
+    adapter->base.op = &tp_adapter_op;
+
+    /* Save the transport as the slave transport */
+    adapter->slave_tp = transport;
+
+    /* Done */
+    *p_tp = &adapter->base;
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * get_info() is called to get the transport addresses to be put
+ * in SDP c= line and a=rtcp line.
+ */
+static pj_status_t transport_get_info(pjmedia_transport *tp,
+				      pjmedia_transport_info *info)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+    /* Since we don't have our own connection here, we just pass
+     * this function to the slave transport.
+     */
+    return pjmedia_transport_get_info(adapter->slave_tp, info);
+}
+
+
+/* This is our RTP callback, that is called by the slave transport when it
+ * receives RTP packet.
+ */
+static void transport_rtp_cb(void *user_data, void *pkt, pj_ssize_t size)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)user_data;
+
+    pj_assert(adapter->stream_rtp_cb != NULL);
+
+    /* Call stream's callback */
+    adapter->stream_rtp_cb(adapter->stream_user_data, pkt, size);
+}
+
+/* This is our RTCP callback, that is called by the slave transport when it
+ * receives RTCP packet.
+ */
+static void transport_rtcp_cb(void *user_data, void *pkt, pj_ssize_t size)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)user_data;
+
+    pj_assert(adapter->stream_rtcp_cb != NULL);
+
+    /* Call stream's callback */
+    adapter->stream_rtcp_cb(adapter->stream_user_data, pkt, size);
+}
+
+
+/*
+ * attach() is called by stream to register callbacks that we should
+ * call on receipt of RTP and RTCP packets.
+ */
+static pj_status_t transport_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 (*rtcp_cb)(void*,
+						    void*,
+						    pj_ssize_t))
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+    pj_status_t status;
+
+    /* In this example, we will save the stream information and callbacks
+     * to our structure, and we will register different RTP/RTCP callbacks
+     * instead.
+     */
+    pj_assert(adapter->stream_user_data == NULL);
+    adapter->stream_user_data = user_data;
+    adapter->stream_rtp_cb = rtp_cb;
+    adapter->stream_rtcp_cb = rtcp_cb;
+
+    status = pjmedia_transport_attach(adapter->slave_tp, adapter, rem_addr,
+				      rem_rtcp, addr_len, &transport_rtp_cb,
+				      &transport_rtcp_cb);
+    if (status != PJ_SUCCESS) {
+	adapter->stream_user_data = NULL;
+	adapter->stream_rtp_cb = NULL;
+	adapter->stream_rtcp_cb = NULL;
+	return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* 
+ * detach() is called when the media is terminated, and the stream is 
+ * to be disconnected from us.
+ */
+static void transport_detach(pjmedia_transport *tp, void *strm)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+    
+    PJ_UNUSED_ARG(strm);
+
+    if (adapter->stream_user_data != NULL) {
+	pjmedia_transport_detach(adapter->slave_tp, adapter);
+	adapter->stream_user_data = NULL;
+	adapter->stream_rtp_cb = NULL;
+	adapter->stream_rtcp_cb = NULL;
+    }
+}
+
+
+/*
+ * send_rtp() is called to send RTP packet. The "pkt" and "size" argument 
+ * contain both the RTP header and the payload.
+ */
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+				       const void *pkt,
+				       pj_size_t size)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+    /* You may do some processing to the RTP packet here if you want. */
+
+    /* Send the packet using the slave transport */
+    return pjmedia_transport_send_rtp(adapter->slave_tp, pkt, size);
+}
+
+
+/*
+ * send_rtcp() is called to send RTCP packet. The "pkt" and "size" argument
+ * contain the RTCP packet.
+ */
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+				       const void *pkt,
+				       pj_size_t size)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+    /* You may do some processing to the RTCP packet here if you want. */
+
+    /* Send the packet using the slave transport */
+    return pjmedia_transport_send_rtcp(adapter->slave_tp, pkt, size);
+}
+
+
+/*
+ * This is another variant of send_rtcp(), with the alternate destination
+ * address in the argument.
+ */
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+				        const pj_sockaddr_t *addr,
+				        unsigned addr_len,
+				        const void *pkt,
+				        pj_size_t size)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+    return pjmedia_transport_send_rtcp2(adapter->slave_tp, addr, addr_len, 
+					pkt, size);
+}
+
+/*
+ * The media_create() is called when the transport is about to be used for
+ * a new call.
+ */
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+				          pj_pool_t *sdp_pool,
+				          unsigned options,
+				          const pjmedia_sdp_session *rem_sdp,
+				          unsigned media_index)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+    /* if "rem_sdp" is not NULL, it means we are UAS. You may do some
+     * inspections on the incoming SDP to verify that the SDP is acceptable
+     * for us. If the SDP is not acceptable, we can reject the SDP by 
+     * returning non-PJ_SUCCESS.
+     */
+    if (rem_sdp) {
+	/* Do your stuff.. */
+    }
+
+    /* Once we're done with our initialization, pass the call to the
+     * slave transports to let it do it's own initialization too.
+     */
+    return pjmedia_transport_media_create(adapter->slave_tp, sdp_pool, options,
+					   rem_sdp, media_index);
+}
+
+/*
+ * The encode_sdp() is called when we're about to send SDP to remote party,
+ * either as SDP offer or as SDP answer.
+ */
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+				        pj_pool_t *sdp_pool,
+				        pjmedia_sdp_session *local_sdp,
+				        const pjmedia_sdp_session *rem_sdp,
+				        unsigned media_index)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+    /* If "rem_sdp" is not NULL, it means we're encoding SDP answer. You may
+     * do some more checking on the SDP's once again to make sure that
+     * everything is okay before we send SDP.
+     */
+    if (rem_sdp) {
+	/* Do checking stuffs here.. */
+    }
+
+    /* You may do anything to the local_sdp, e.g. adding new attributes, or
+     * even modifying the SDP if you want.
+     */
+    if (1) {
+	/* Say we add a proprietary attribute here.. */
+	pjmedia_sdp_attr *my_attr;
+
+	my_attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr);
+	pj_strdup2(sdp_pool, &my_attr->name, "X-adapter");
+	pj_strdup2(sdp_pool, &my_attr->value, "some value");
+
+	pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count,
+			     local_sdp->media[media_index]->attr,
+			     my_attr);
+    }
+
+    /* And then pass the call to slave transport to let it encode its 
+     * information in the SDP. You may choose to call encode_sdp() to slave
+     * first before adding your custom attributes if you want.
+     */
+    return pjmedia_transport_encode_sdp(adapter->slave_tp, sdp_pool, local_sdp,
+					rem_sdp, media_index);
+}
+
+/*
+ * The media_start() is called once both local and remote SDP have been
+ * negotiated successfully, and the media is ready to start. Here we can start
+ * committing our processing.
+ */
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+				         pj_pool_t *pool,
+				         const pjmedia_sdp_session *local_sdp,
+				         const pjmedia_sdp_session *rem_sdp,
+				         unsigned media_index)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+    /* Do something.. */
+
+    /* And pass the call to the slave transport */
+    return pjmedia_transport_media_start(adapter->slave_tp, pool, local_sdp,
+					 rem_sdp, media_index);
+}
+
+/*
+ * The media_stop() is called when media has been stopped.
+ */
+static pj_status_t transport_media_stop(pjmedia_transport *tp)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+    /* Do something.. */
+
+    /* And pass the call to the slave transport */
+    return pjmedia_transport_media_stop(adapter->slave_tp);
+}
+
+/*
+ * simulate_lost() is called to simulate packet lost
+ */
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+				           pjmedia_dir dir,
+				           unsigned pct_lost)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+    return pjmedia_transport_simulate_lost(adapter->slave_tp, dir, pct_lost);
+}
+
+/*
+ * destroy() is called when the transport is no longer needed.
+ */
+static pj_status_t transport_destroy  (pjmedia_transport *tp)
+{
+    struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+    /* Close the slave transport */
+    pjmedia_transport_close(adapter->slave_tp);
+
+    /* Self destruct.. */
+    pj_pool_release(adapter->pool);
+
+    return PJ_SUCCESS;
+}
+
+
+
+
+
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index d2c22e1..b5157ee 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -23,6 +23,7 @@
 #define NO_LIMIT	(int)0x7FFFFFFF
 
 //#define STEREO_DEMO
+//#define TRANSPORT_ADAPTER_SAMPLE
 
 /* Ringtones		    US	       UK  */
 #define RINGBACK_FREQ1	    440	    /* 400 */
@@ -132,6 +133,9 @@
 #ifdef STEREO_DEMO
 static void stereo_demo();
 #endif
+#ifdef TRANSPORT_ADAPTER_SAMPLE
+static pj_status_t transport_adapter_sample(void);
+#endif
 pj_status_t app_destroy(void);
 
 static void ringback_start(pjsua_call_id call_id);
@@ -4206,7 +4210,12 @@
     }
 
     /* Add RTP transports */
+#ifdef TRANSPORT_ADAPTER_SAMPLE
+    status = transport_adapter_sample();
+
+#else
     status = pjsua_media_transports_create(&app_config.rtp_cfg);
+#endif
     if (status != PJ_SUCCESS)
 	goto on_error;
 
@@ -4391,3 +4400,44 @@
 }
 #endif
 
+#ifdef TRANSPORT_ADAPTER_SAMPLE
+static pj_status_t create_transport_adapter(pjmedia_endpt *med_endpt, int port,
+					    pjmedia_transport **p_tp)
+{
+    pjmedia_transport *udp;
+    pj_status_t status;
+
+    /* Create the UDP media transport */
+    status = pjmedia_transport_udp_create(med_endpt, NULL, port, 0, &udp);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Create the adapter */
+    status = pjmedia_tp_adapter_create(med_endpt, NULL, udp, p_tp);
+    if (status != PJ_SUCCESS) {
+	pjmedia_transport_close(udp);
+	return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t transport_adapter_sample(void)
+{
+    pjsua_media_transport tp[PJSUA_MAX_CALLS];
+    pj_status_t status;
+    int port = 7000;
+    unsigned i;
+
+    for (i=0; i<app_config.cfg.max_calls; ++i) {
+	status = create_transport_adapter(pjsua_get_pjmedia_endpt(), 
+					  port + i*10,
+					  &tp[i].transport);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    return pjsua_media_transports_attach(tp, i, PJ_TRUE);
+}
+#endif
+
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
index 3839fa8..d52b6d5 100644
--- a/pjsip/include/pjsua-lib/pjsua_internal.h
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -79,6 +79,7 @@
     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_timer_entry	 refresh_tm;/**< Timer to send re-INVITE.	    */
     pj_timer_entry	 hangup_tm; /**< Timer to hangup call.		    */
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index 02f32a3..eab2985 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -543,10 +543,10 @@
 	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->op->destroy)(pjsua_var.calls[i].med_tp);
-	    pjsua_var.calls[i].med_tp = NULL;
+	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;
     }
 
     /* Destroy media endpoint. */
@@ -841,7 +841,9 @@
 
     /* Delete existing media transports */
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
-	if (pjsua_var.calls[i].med_tp != NULL) {
+	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;
 	}
@@ -850,18 +852,49 @@
     /* Copy config */
     pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
 
+    /* Create the transports */
     if (pjsua_var.media_cfg.enable_ice) {
 	status = create_ice_media_transports();
     } else {
 	status = create_udp_media_transports(&cfg);
     }
 
+    /* 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_UNLOCK();
 
     return status;
 }
 
+/*
+ * Attach application's created media transports.
+ */
+PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
+						  unsigned count,
+						  pj_bool_t auto_delete)
+{
+    unsigned i;
+
+    PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
+
+    /* 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;
+    }
+
+    return PJ_SUCCESS;
+}
+
 
 pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
 				     pjsip_role_e role,