Index: pjproject/trunk/Makefile
===================================================================
--- pjproject/trunk/Makefile	(revision 3981)
+++ pjproject/trunk/Makefile	(revision 3982)
@@ -4,9 +4,21 @@
 include version.mak
 
-DIRS = pjlib/build pjlib-util/build pjnath/build third_party/build pjmedia/build pjsip/build pjsip-apps/build $(EXTRA_DIRS)
+LIB_DIRS = pjlib/build pjlib-util/build pjnath/build third_party/build pjmedia/build pjsip/build
+DIRS = $(LIB_DIRS) pjsip-apps/build $(EXTRA_DIRS)
 
 ifdef MINSIZE
 MAKE_FLAGS := MINSIZE=1
 endif
+
+lib:
+	for dir in $(LIB_DIRS); do \
+		if $(MAKE) $(MAKE_FLAGS) -C $$dir all; then \
+		    true; \
+		else \
+		    exit 1; \
+		fi; \
+	done; \
+	make -C pjsip-apps/src/third_party_media
+
 
 all clean dep depend distclean print realclean:
@@ -18,4 +30,6 @@
 		fi; \
 	done
+
+.PHONY: lib 
 
 doc:
Index: pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- pjproject/trunk/pjmedia/build/Makefile	(revision 3981)
+++ pjproject/trunk/pjmedia/build/Makefile	(revision 3982)
@@ -65,9 +65,9 @@
 			resample_port.o rtcp.o rtcp_xr.o rtp.o \
 			sdp.o sdp_cmp.o sdp_neg.o session.o silencedet.o \
-			sound_legacy.o sound_port.o stereo_port.o \
-			stream_common.o stream.o tonegen.o transport_adapter_sample.o \
+			sound_legacy.o sound_port.o stereo_port.o stream_common.o \
+			stream.o stream_info.o tonegen.o transport_adapter_sample.o \
 			transport_ice.o transport_loop.o transport_srtp.o transport_udp.o \
 			types.o vid_codec.o vid_codec_util.o \
-			vid_port.o vid_stream.o vid_tee.o \
+			vid_port.o vid_stream.o vid_stream_info.o vid_tee.o \
 			wav_player.o wav_playlist.o wav_writer.o wave.o \
 			wsola.o
Index: pjproject/trunk/pjmedia/src/pjmedia/stream.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/stream.c	(revision 3981)
+++ pjproject/trunk/pjmedia/src/pjmedia/stream.c	(revision 3982)
@@ -2794,529 +2794,4 @@
 }
 
-
-static const pj_str_t ID_AUDIO = { "audio", 5};
-static const pj_str_t ID_IN = { "IN", 2 };
-static const pj_str_t ID_IP4 = { "IP4", 3};
-static const pj_str_t ID_IP6 = { "IP6", 3};
-static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
-static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
-//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
-static const pj_str_t ID_RTPMAP = { "rtpmap", 6 };
-static const pj_str_t ID_TELEPHONE_EVENT = { "telephone-event", 15 };
-
-static const pj_str_t STR_INACTIVE = { "inactive", 8 };
-static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
-static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
-static const pj_str_t STR_RECVONLY = { "recvonly", 8 };
-
-
-/*
- * Internal function for collecting codec info and param from the SDP media.
- */
-static pj_status_t get_audio_codec_info_param(pjmedia_stream_info *si,
-					      pj_pool_t *pool,
-					      pjmedia_codec_mgr *mgr,
-					      const pjmedia_sdp_media *local_m,
-					      const pjmedia_sdp_media *rem_m)
-{
-    const pjmedia_sdp_attr *attr;
-    pjmedia_sdp_rtpmap *rtpmap;
-    unsigned i, fmti, pt = 0;
-    pj_status_t status;
-
-    /* Find the first codec which is not telephone-event */
-    for ( fmti = 0; fmti < local_m->desc.fmt_count; ++fmti ) {
-	pjmedia_sdp_rtpmap r;
-
-	if ( !pj_isdigit(*local_m->desc.fmt[fmti].ptr) )
-	    return PJMEDIA_EINVALIDPT;
-	pt = pj_strtoul(&local_m->desc.fmt[fmti]);
-
-	if (pt < 96) {
-	    /* This is known static PT. Skip rtpmap checking because it is
-	     * optional. */
-	    break;
-	}
-
-	attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
-					   &local_m->desc.fmt[fmti]);
-	if (attr == NULL)
-	    continue;
-
-	status = pjmedia_sdp_attr_get_rtpmap(attr, &r);
-	if (status != PJ_SUCCESS)
-	    continue;
-
-	if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) != 0)
-	    break;
-    }
-    if ( fmti >= local_m->desc.fmt_count )
-	return PJMEDIA_EINVALIDPT;
-
-    /* Get payload type for receiving direction */
-    si->rx_pt = pt;
-
-    /* Get codec info.
-     * For static payload types, get the info from codec manager.
-     * For dynamic payload types, MUST get the rtpmap.
-     */
-    if (pt < 96) {
-	pj_bool_t has_rtpmap;
-
-	rtpmap = NULL;
-	has_rtpmap = PJ_TRUE;
-
-	attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, 
-					   &local_m->desc.fmt[fmti]);
-	if (attr == NULL) {
-	    has_rtpmap = PJ_FALSE;
-	}
-	if (attr != NULL) {
-	    status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
-	    if (status != PJ_SUCCESS)
-		has_rtpmap = PJ_FALSE;
-	}
-
-	/* Build codec format info: */
-	if (has_rtpmap) {
-	    si->fmt.type = si->type;
-	    si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]);
-	    pj_strdup(pool, &si->fmt.encoding_name, &rtpmap->enc_name);
-	    si->fmt.clock_rate = rtpmap->clock_rate;
-	    
-#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG != 0)
-	    /* The session info should have the actual clock rate, because 
-	     * this info is used for calculationg buffer size, etc in stream 
-	     */
-	    if (si->fmt.pt == PJMEDIA_RTP_PT_G722)
-		si->fmt.clock_rate = 16000;
-#endif
-
-	    /* For audio codecs, rtpmap parameters denotes the number of
-	     * channels.
-	     */
-	    if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) {
-		si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param);
-	    } else {
-		si->fmt.channel_cnt = 1;
-	    }
-
-	} else {	    
-	    const pjmedia_codec_info *p_info;
-
-	    status = pjmedia_codec_mgr_get_codec_info( mgr, pt, &p_info);
-	    if (status != PJ_SUCCESS)
-		return status;
-
-	    pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info));
-	}
-
-	/* For static payload type, pt's are symetric */
-	si->tx_pt = pt;
-
-    } else {
-	pjmedia_codec_id codec_id;
-	pj_str_t codec_id_st;
-	const pjmedia_codec_info *p_info;
-
-	attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, 
-					   &local_m->desc.fmt[fmti]);
-	if (attr == NULL)
-	    return PJMEDIA_EMISSINGRTPMAP;
-
-	status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
-	if (status != PJ_SUCCESS)
-	    return status;
-
-	/* Build codec format info: */
-
-	si->fmt.type = si->type;
-	si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]);
-	si->fmt.encoding_name = rtpmap->enc_name;
-	si->fmt.clock_rate = rtpmap->clock_rate;
-
-	/* For audio codecs, rtpmap parameters denotes the number of
-	 * channels.
-	 */
-	if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) {
-	    si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param);
-	} else {
-	    si->fmt.channel_cnt = 1;
-	}
-
-	/* Normalize the codec info from codec manager. Note that the
-	 * payload type will be resetted to its default (it might have
-	 * been rewritten by the SDP negotiator to match to the remote
-	 * offer), this is intentional as currently some components may
-	 * prefer (or even require) the default PT in codec info.
-	 */
-	pjmedia_codec_info_to_id(&si->fmt, codec_id, sizeof(codec_id));
-
-	i = 1;
-	codec_id_st = pj_str(codec_id);
-	status = pjmedia_codec_mgr_find_codecs_by_id(mgr, &codec_id_st,
-						     &i, &p_info, NULL);
-	if (status != PJ_SUCCESS)
-	    return status;
-
-	pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info));
-
-	/* Determine payload type for outgoing channel, by finding
-	 * dynamic payload type in remote SDP that matches the answer.
-	 */
-	si->tx_pt = 0xFFFF;
-	for (i=0; i<rem_m->desc.fmt_count; ++i) {
-	    unsigned rpt;
-	    pjmedia_sdp_attr *r_attr;
-	    pjmedia_sdp_rtpmap r_rtpmap;
-
-	    rpt = pj_strtoul(&rem_m->desc.fmt[i]);
-	    if (rpt < 96)
-		continue;
-
-	    r_attr = pjmedia_sdp_media_find_attr(rem_m, &ID_RTPMAP,
-						 &rem_m->desc.fmt[i]);
-	    if (!r_attr)
-		continue;
-
-	    if (pjmedia_sdp_attr_get_rtpmap(r_attr, &r_rtpmap) != PJ_SUCCESS)
-		continue;
-
-	    if (!pj_stricmp(&rtpmap->enc_name, &r_rtpmap.enc_name) &&
-		rtpmap->clock_rate == r_rtpmap.clock_rate)
-	    {
-		/* Found matched codec. */
-		si->tx_pt = rpt;
-
-		break;
-	    }
-	}
-
-	if (si->tx_pt == 0xFFFF)
-	    return PJMEDIA_EMISSINGRTPMAP;
-    }
-
-  
-    /* Now that we have codec info, get the codec param. */
-    si->param = PJ_POOL_ALLOC_T(pool, pjmedia_codec_param);
-    status = pjmedia_codec_mgr_get_default_param(mgr, &si->fmt,
-					         si->param);
-
-    /* Get remote fmtp for our encoder. */
-    pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt,
-				   &si->param->setting.enc_fmtp);
-
-    /* Get local fmtp for our decoder. */
-    pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt,
-				   &si->param->setting.dec_fmtp);
-
-    /* Get the remote ptime for our encoder. */
-    attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
-				  "ptime", NULL);
-    if (attr) {
-	pj_str_t tmp_val = attr->value;
-	unsigned frm_per_pkt;
- 
-	pj_strltrim(&tmp_val);
-
-	/* Round up ptime when the specified is not multiple of frm_ptime */
-	frm_per_pkt = (pj_strtoul(&tmp_val) + 
-		      si->param->info.frm_ptime/2) /
-		      si->param->info.frm_ptime;
-	if (frm_per_pkt != 0) {
-            si->param->setting.frm_per_pkt = (pj_uint8_t)frm_per_pkt;
-        }
-    }
-
-    /* Get remote maxptime for our encoder. */
-    attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
-				  "maxptime", NULL);
-    if (attr) {
-	pj_str_t tmp_val = attr->value;
-
-	pj_strltrim(&tmp_val);
-	si->tx_maxptime = pj_strtoul(&tmp_val);
-    }
-
-    /* When direction is NONE (it means SDP negotiation has failed) we don't
-     * need to return a failure here, as returning failure will cause
-     * the whole SDP to be rejected. See ticket #:
-     *	http://
-     *
-     * Thanks Alain Totouom 
-     */
-    if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE)
-	return status;
-
-
-    /* Get incomming payload type for telephone-events */
-    si->rx_event_pt = -1;
-    for (i=0; i<local_m->attr_count; ++i) {
-	pjmedia_sdp_rtpmap r;
-
-	attr = local_m->attr[i];
-	if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0)
-	    continue;
-	if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS)
-	    continue;
-	if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) {
-	    si->rx_event_pt = pj_strtoul(&r.pt);
-	    break;
-	}
-    }
-
-    /* Get outgoing payload type for telephone-events */
-    si->tx_event_pt = -1;
-    for (i=0; i<rem_m->attr_count; ++i) {
-	pjmedia_sdp_rtpmap r;
-
-	attr = rem_m->attr[i];
-	if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0)
-	    continue;
-	if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS)
-	    continue;
-	if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) {
-	    si->tx_event_pt = pj_strtoul(&r.pt);
-	    break;
-	}
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-
-/*
- * Create stream info from SDP media line.
- */
-PJ_DEF(pj_status_t) pjmedia_stream_info_from_sdp(
-					   pjmedia_stream_info *si,
-					   pj_pool_t *pool,
-					   pjmedia_endpt *endpt,
-					   const pjmedia_sdp_session *local,
-					   const pjmedia_sdp_session *remote,
-					   unsigned stream_idx)
-{
-    pjmedia_codec_mgr *mgr;
-    const pjmedia_sdp_attr *attr;
-    const pjmedia_sdp_media *local_m;
-    const pjmedia_sdp_media *rem_m;
-    const pjmedia_sdp_conn *local_conn;
-    const pjmedia_sdp_conn *rem_conn;
-    int rem_af, local_af;
-    pj_sockaddr local_addr;
-    pj_status_t status;
-
-    
-    /* Validate arguments: */
-    PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL);
-    PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL);
-    PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL);
-
-    /* Keep SDP shortcuts */
-    local_m = local->media[stream_idx];
-    rem_m = remote->media[stream_idx];
-
-    local_conn = local_m->conn ? local_m->conn : local->conn;
-    if (local_conn == NULL)
-	return PJMEDIA_SDP_EMISSINGCONN;
-
-    rem_conn = rem_m->conn ? rem_m->conn : remote->conn;
-    if (rem_conn == NULL)
-	return PJMEDIA_SDP_EMISSINGCONN;
-
-    /* Media type must be audio */
-    if (pj_stricmp(&local_m->desc.media, &ID_AUDIO) != 0)
-	return PJMEDIA_EINVALIMEDIATYPE;
-
-    /* Get codec manager. */
-    mgr = pjmedia_endpt_get_codec_mgr(endpt);
-
-    /* Reset: */
-
-    pj_bzero(si, sizeof(*si));
-
-#if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
-    /* Set default RTCP XR enabled/disabled */
-    si->rtcp_xr_enabled = PJ_TRUE;
-#endif
-
-    /* Media type: */
-    si->type = PJMEDIA_TYPE_AUDIO;
-
-    /* Transport protocol */
-
-    /* At this point, transport type must be compatible, 
-     * the transport instance will do more validation later.
-     */
-    status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport, 
-				       &local_m->desc.transport);
-    if (status != PJ_SUCCESS)
-	return PJMEDIA_SDPNEG_EINVANSTP;
-
-    if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) {
-
-	si->proto = PJMEDIA_TP_PROTO_RTP_AVP;
-
-    } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) {
-
-	si->proto = PJMEDIA_TP_PROTO_RTP_SAVP;
-
-    } else {
-
-	si->proto = PJMEDIA_TP_PROTO_UNKNOWN;
-	return PJ_SUCCESS;
-    }
-
-
-    /* Check address family in remote SDP */
-    rem_af = pj_AF_UNSPEC();
-    if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) {
-	if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) {
-	    rem_af = pj_AF_INET();
-	} else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) {
-	    rem_af = pj_AF_INET6();
-	}
-    }
-
-    if (rem_af==pj_AF_UNSPEC()) {
-	/* Unsupported address family */
-	return PJ_EAFNOTSUP;
-    }
-
-    /* Set remote address: */
-    status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr, 
-			      rem_m->desc.port);
-    if (status != PJ_SUCCESS) {
-	/* Invalid IP address. */
-	return PJMEDIA_EINVALIDIP;
-    }
-
-    /* Check address family of local info */
-    local_af = pj_AF_UNSPEC();
-    if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) {
-	if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) {
-	    local_af = pj_AF_INET();
-	} else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) {
-	    local_af = pj_AF_INET6();
-	}
-    }
-
-    if (local_af==pj_AF_UNSPEC()) {
-	/* Unsupported address family */
-	return PJ_SUCCESS;
-    }
-
-    /* Set remote address: */
-    status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr, 
-			      local_m->desc.port);
-    if (status != PJ_SUCCESS) {
-	/* Invalid IP address. */
-	return PJMEDIA_EINVALIDIP;
-    }
-
-    /* Local and remote address family must match */
-    if (local_af != rem_af)
-	return PJ_EAFNOTSUP;
-
-    /* Media direction: */
-
-    if (local_m->desc.port == 0 || 
-	pj_sockaddr_has_addr(&local_addr)==PJ_FALSE ||
-	pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE ||
-	pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL)
-    {
-	/* Inactive stream. */
-
-	si->dir = PJMEDIA_DIR_NONE;
-
-    } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) {
-
-	/* Send only stream. */
-
-	si->dir = PJMEDIA_DIR_ENCODING;
-
-    } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) {
-
-	/* Recv only stream. */
-
-	si->dir = PJMEDIA_DIR_DECODING;
-
-    } else {
-
-	/* Send and receive stream. */
-
-	si->dir = PJMEDIA_DIR_ENCODING_DECODING;
-
-    }
-
-    /* No need to do anything else if stream is rejected */
-    if (local_m->desc.port == 0) {
-	return PJ_SUCCESS;
-    }
-
-    /* If "rtcp" attribute is present in the SDP, set the RTCP address
-     * from that attribute. Otherwise, calculate from RTP address.
-     */
-    attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
-				  "rtcp", NULL);
-    if (attr) {
-	pjmedia_sdp_rtcp_attr rtcp;
-	status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
-	if (status == PJ_SUCCESS) {
-	    if (rtcp.addr.slen) {
-		status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr,
-					  (pj_uint16_t)rtcp.port);
-	    } else {
-		pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL, 
-				 (pj_uint16_t)rtcp.port);
-		pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp),
-		          pj_sockaddr_get_addr(&si->rem_addr),
-			  pj_sockaddr_get_addr_len(&si->rem_addr));
-	    }
-	}
-    }
-    
-    if (!pj_sockaddr_has_addr(&si->rem_rtcp)) {
-	int rtcp_port;
-
-	pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr));
-	rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1;
-	pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port);
-    }
-
-
-    /* Get the payload number for receive channel. */
-    /*
-       Previously we used to rely on fmt[0] being the selected codec,
-       but some UA sends telephone-event as fmt[0] and this would
-       cause assert failure below.
-
-       Thanks Chris Hamilton <chamilton .at. cs.dal.ca> for this patch.
-
-    // And codec must be numeric!
-    if (!pj_isdigit(*local_m->desc.fmt[0].ptr) || 
-	!pj_isdigit(*rem_m->desc.fmt[0].ptr))
-    {
-	return PJMEDIA_EINVALIDPT;
-    }
-
-    pt = pj_strtoul(&local_m->desc.fmt[0]);
-    pj_assert(PJMEDIA_RTP_PT_TELEPHONE_EVENTS==0 ||
-	      pt != PJMEDIA_RTP_PT_TELEPHONE_EVENTS);
-    */
-
-    /* Get codec info and param */
-    status = get_audio_codec_info_param(si, pool, mgr, local_m, rem_m);
-
-    /* Leave SSRC to random. */
-    si->ssrc = pj_rand();
-
-    /* Set default jitter buffer parameter. */
-    si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1;
-
-    return status;
-}
-
 /*
  * Send RTCP SDES.
Index: pjproject/trunk/pjmedia/src/pjmedia/stream_info.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/stream_info.c	(revision 3982)
+++ pjproject/trunk/pjmedia/src/pjmedia/stream_info.c	(revision 3982)
@@ -0,0 +1,548 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * 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/stream.h>
+#include <pjmedia/stream_common.h>
+#include <pj/ctype.h>
+#include <pj/rand.h>
+
+static const pj_str_t ID_AUDIO = { "audio", 5};
+static const pj_str_t ID_IN = { "IN", 2 };
+static const pj_str_t ID_IP4 = { "IP4", 3};
+static const pj_str_t ID_IP6 = { "IP6", 3};
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
+static const pj_str_t ID_RTPMAP = { "rtpmap", 6 };
+static const pj_str_t ID_TELEPHONE_EVENT = { "telephone-event", 15 };
+
+static const pj_str_t STR_INACTIVE = { "inactive", 8 };
+static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
+static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
+static const pj_str_t STR_RECVONLY = { "recvonly", 8 };
+
+
+/*
+ * Internal function for collecting codec info and param from the SDP media.
+ */
+static pj_status_t get_audio_codec_info_param(pjmedia_stream_info *si,
+					      pj_pool_t *pool,
+					      pjmedia_codec_mgr *mgr,
+					      const pjmedia_sdp_media *local_m,
+					      const pjmedia_sdp_media *rem_m)
+{
+    const pjmedia_sdp_attr *attr;
+    pjmedia_sdp_rtpmap *rtpmap;
+    unsigned i, fmti, pt = 0;
+    pj_status_t status;
+
+    /* Find the first codec which is not telephone-event */
+    for ( fmti = 0; fmti < local_m->desc.fmt_count; ++fmti ) {
+	pjmedia_sdp_rtpmap r;
+
+	if ( !pj_isdigit(*local_m->desc.fmt[fmti].ptr) )
+	    return PJMEDIA_EINVALIDPT;
+	pt = pj_strtoul(&local_m->desc.fmt[fmti]);
+
+	if (pt < 96) {
+	    /* This is known static PT. Skip rtpmap checking because it is
+	     * optional. */
+	    break;
+	}
+
+	attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
+					   &local_m->desc.fmt[fmti]);
+	if (attr == NULL)
+	    continue;
+
+	status = pjmedia_sdp_attr_get_rtpmap(attr, &r);
+	if (status != PJ_SUCCESS)
+	    continue;
+
+	if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) != 0)
+	    break;
+    }
+    if ( fmti >= local_m->desc.fmt_count )
+	return PJMEDIA_EINVALIDPT;
+
+    /* Get payload type for receiving direction */
+    si->rx_pt = pt;
+
+    /* Get codec info.
+     * For static payload types, get the info from codec manager.
+     * For dynamic payload types, MUST get the rtpmap.
+     */
+    if (pt < 96) {
+	pj_bool_t has_rtpmap;
+
+	rtpmap = NULL;
+	has_rtpmap = PJ_TRUE;
+
+	attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
+					   &local_m->desc.fmt[fmti]);
+	if (attr == NULL) {
+	    has_rtpmap = PJ_FALSE;
+	}
+	if (attr != NULL) {
+	    status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
+	    if (status != PJ_SUCCESS)
+		has_rtpmap = PJ_FALSE;
+	}
+
+	/* Build codec format info: */
+	if (has_rtpmap) {
+	    si->fmt.type = si->type;
+	    si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]);
+	    pj_strdup(pool, &si->fmt.encoding_name, &rtpmap->enc_name);
+	    si->fmt.clock_rate = rtpmap->clock_rate;
+
+#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG != 0)
+	    /* The session info should have the actual clock rate, because
+	     * this info is used for calculationg buffer size, etc in stream
+	     */
+	    if (si->fmt.pt == PJMEDIA_RTP_PT_G722)
+		si->fmt.clock_rate = 16000;
+#endif
+
+	    /* For audio codecs, rtpmap parameters denotes the number of
+	     * channels.
+	     */
+	    if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) {
+		si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param);
+	    } else {
+		si->fmt.channel_cnt = 1;
+	    }
+
+	} else {
+	    const pjmedia_codec_info *p_info;
+
+	    status = pjmedia_codec_mgr_get_codec_info( mgr, pt, &p_info);
+	    if (status != PJ_SUCCESS)
+		return status;
+
+	    pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info));
+	}
+
+	/* For static payload type, pt's are symetric */
+	si->tx_pt = pt;
+
+    } else {
+	pjmedia_codec_id codec_id;
+	pj_str_t codec_id_st;
+	const pjmedia_codec_info *p_info;
+
+	attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
+					   &local_m->desc.fmt[fmti]);
+	if (attr == NULL)
+	    return PJMEDIA_EMISSINGRTPMAP;
+
+	status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Build codec format info: */
+
+	si->fmt.type = si->type;
+	si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]);
+	si->fmt.encoding_name = rtpmap->enc_name;
+	si->fmt.clock_rate = rtpmap->clock_rate;
+
+	/* For audio codecs, rtpmap parameters denotes the number of
+	 * channels.
+	 */
+	if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) {
+	    si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param);
+	} else {
+	    si->fmt.channel_cnt = 1;
+	}
+
+	/* Normalize the codec info from codec manager. Note that the
+	 * payload type will be resetted to its default (it might have
+	 * been rewritten by the SDP negotiator to match to the remote
+	 * offer), this is intentional as currently some components may
+	 * prefer (or even require) the default PT in codec info.
+	 */
+	pjmedia_codec_info_to_id(&si->fmt, codec_id, sizeof(codec_id));
+
+	i = 1;
+	codec_id_st = pj_str(codec_id);
+	status = pjmedia_codec_mgr_find_codecs_by_id(mgr, &codec_id_st,
+						     &i, &p_info, NULL);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info));
+
+	/* Determine payload type for outgoing channel, by finding
+	 * dynamic payload type in remote SDP that matches the answer.
+	 */
+	si->tx_pt = 0xFFFF;
+	for (i=0; i<rem_m->desc.fmt_count; ++i) {
+	    unsigned rpt;
+	    pjmedia_sdp_attr *r_attr;
+	    pjmedia_sdp_rtpmap r_rtpmap;
+
+	    rpt = pj_strtoul(&rem_m->desc.fmt[i]);
+	    if (rpt < 96)
+		continue;
+
+	    r_attr = pjmedia_sdp_media_find_attr(rem_m, &ID_RTPMAP,
+						 &rem_m->desc.fmt[i]);
+	    if (!r_attr)
+		continue;
+
+	    if (pjmedia_sdp_attr_get_rtpmap(r_attr, &r_rtpmap) != PJ_SUCCESS)
+		continue;
+
+	    if (!pj_stricmp(&rtpmap->enc_name, &r_rtpmap.enc_name) &&
+		rtpmap->clock_rate == r_rtpmap.clock_rate)
+	    {
+		/* Found matched codec. */
+		si->tx_pt = rpt;
+
+		break;
+	    }
+	}
+
+	if (si->tx_pt == 0xFFFF)
+	    return PJMEDIA_EMISSINGRTPMAP;
+    }
+
+
+    /* Now that we have codec info, get the codec param. */
+    si->param = PJ_POOL_ALLOC_T(pool, pjmedia_codec_param);
+    status = pjmedia_codec_mgr_get_default_param(mgr, &si->fmt,
+					         si->param);
+
+    /* Get remote fmtp for our encoder. */
+    pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt,
+				   &si->param->setting.enc_fmtp);
+
+    /* Get local fmtp for our decoder. */
+    pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt,
+				   &si->param->setting.dec_fmtp);
+
+    /* Get the remote ptime for our encoder. */
+    attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
+				  "ptime", NULL);
+    if (attr) {
+	pj_str_t tmp_val = attr->value;
+	unsigned frm_per_pkt;
+
+	pj_strltrim(&tmp_val);
+
+	/* Round up ptime when the specified is not multiple of frm_ptime */
+	frm_per_pkt = (pj_strtoul(&tmp_val) +
+		      si->param->info.frm_ptime/2) /
+		      si->param->info.frm_ptime;
+	if (frm_per_pkt != 0) {
+            si->param->setting.frm_per_pkt = (pj_uint8_t)frm_per_pkt;
+        }
+    }
+
+    /* Get remote maxptime for our encoder. */
+    attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
+				  "maxptime", NULL);
+    if (attr) {
+	pj_str_t tmp_val = attr->value;
+
+	pj_strltrim(&tmp_val);
+	si->tx_maxptime = pj_strtoul(&tmp_val);
+    }
+
+    /* When direction is NONE (it means SDP negotiation has failed) we don't
+     * need to return a failure here, as returning failure will cause
+     * the whole SDP to be rejected. See ticket #:
+     *	http://
+     *
+     * Thanks Alain Totouom
+     */
+    if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE)
+	return status;
+
+
+    /* Get incomming payload type for telephone-events */
+    si->rx_event_pt = -1;
+    for (i=0; i<local_m->attr_count; ++i) {
+	pjmedia_sdp_rtpmap r;
+
+	attr = local_m->attr[i];
+	if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0)
+	    continue;
+	if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS)
+	    continue;
+	if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) {
+	    si->rx_event_pt = pj_strtoul(&r.pt);
+	    break;
+	}
+    }
+
+    /* Get outgoing payload type for telephone-events */
+    si->tx_event_pt = -1;
+    for (i=0; i<rem_m->attr_count; ++i) {
+	pjmedia_sdp_rtpmap r;
+
+	attr = rem_m->attr[i];
+	if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0)
+	    continue;
+	if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS)
+	    continue;
+	if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) {
+	    si->tx_event_pt = pj_strtoul(&r.pt);
+	    break;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Create stream info from SDP media line.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_info_from_sdp(
+					   pjmedia_stream_info *si,
+					   pj_pool_t *pool,
+					   pjmedia_endpt *endpt,
+					   const pjmedia_sdp_session *local,
+					   const pjmedia_sdp_session *remote,
+					   unsigned stream_idx)
+{
+    pjmedia_codec_mgr *mgr;
+    const pjmedia_sdp_attr *attr;
+    const pjmedia_sdp_media *local_m;
+    const pjmedia_sdp_media *rem_m;
+    const pjmedia_sdp_conn *local_conn;
+    const pjmedia_sdp_conn *rem_conn;
+    int rem_af, local_af;
+    pj_sockaddr local_addr;
+    pj_status_t status;
+
+
+    /* Validate arguments: */
+    PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL);
+    PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL);
+    PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL);
+
+    /* Keep SDP shortcuts */
+    local_m = local->media[stream_idx];
+    rem_m = remote->media[stream_idx];
+
+    local_conn = local_m->conn ? local_m->conn : local->conn;
+    if (local_conn == NULL)
+	return PJMEDIA_SDP_EMISSINGCONN;
+
+    rem_conn = rem_m->conn ? rem_m->conn : remote->conn;
+    if (rem_conn == NULL)
+	return PJMEDIA_SDP_EMISSINGCONN;
+
+    /* Media type must be audio */
+    if (pj_stricmp(&local_m->desc.media, &ID_AUDIO) != 0)
+	return PJMEDIA_EINVALIMEDIATYPE;
+
+    /* Get codec manager. */
+    mgr = pjmedia_endpt_get_codec_mgr(endpt);
+
+    /* Reset: */
+
+    pj_bzero(si, sizeof(*si));
+
+#if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
+    /* Set default RTCP XR enabled/disabled */
+    si->rtcp_xr_enabled = PJ_TRUE;
+#endif
+
+    /* Media type: */
+    si->type = PJMEDIA_TYPE_AUDIO;
+
+    /* Transport protocol */
+
+    /* At this point, transport type must be compatible,
+     * the transport instance will do more validation later.
+     */
+    status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport,
+				       &local_m->desc.transport);
+    if (status != PJ_SUCCESS)
+	return PJMEDIA_SDPNEG_EINVANSTP;
+
+    if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) {
+
+	si->proto = PJMEDIA_TP_PROTO_RTP_AVP;
+
+    } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) {
+
+	si->proto = PJMEDIA_TP_PROTO_RTP_SAVP;
+
+    } else {
+
+	si->proto = PJMEDIA_TP_PROTO_UNKNOWN;
+	return PJ_SUCCESS;
+    }
+
+
+    /* Check address family in remote SDP */
+    rem_af = pj_AF_UNSPEC();
+    if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) {
+	if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) {
+	    rem_af = pj_AF_INET();
+	} else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) {
+	    rem_af = pj_AF_INET6();
+	}
+    }
+
+    if (rem_af==pj_AF_UNSPEC()) {
+	/* Unsupported address family */
+	return PJ_EAFNOTSUP;
+    }
+
+    /* Set remote address: */
+    status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr,
+			      rem_m->desc.port);
+    if (status != PJ_SUCCESS) {
+	/* Invalid IP address. */
+	return PJMEDIA_EINVALIDIP;
+    }
+
+    /* Check address family of local info */
+    local_af = pj_AF_UNSPEC();
+    if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) {
+	if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) {
+	    local_af = pj_AF_INET();
+	} else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) {
+	    local_af = pj_AF_INET6();
+	}
+    }
+
+    if (local_af==pj_AF_UNSPEC()) {
+	/* Unsupported address family */
+	return PJ_SUCCESS;
+    }
+
+    /* Set remote address: */
+    status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr,
+			      local_m->desc.port);
+    if (status != PJ_SUCCESS) {
+	/* Invalid IP address. */
+	return PJMEDIA_EINVALIDIP;
+    }
+
+    /* Local and remote address family must match */
+    if (local_af != rem_af)
+	return PJ_EAFNOTSUP;
+
+    /* Media direction: */
+
+    if (local_m->desc.port == 0 ||
+	pj_sockaddr_has_addr(&local_addr)==PJ_FALSE ||
+	pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE ||
+	pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL)
+    {
+	/* Inactive stream. */
+
+	si->dir = PJMEDIA_DIR_NONE;
+
+    } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) {
+
+	/* Send only stream. */
+
+	si->dir = PJMEDIA_DIR_ENCODING;
+
+    } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) {
+
+	/* Recv only stream. */
+
+	si->dir = PJMEDIA_DIR_DECODING;
+
+    } else {
+
+	/* Send and receive stream. */
+
+	si->dir = PJMEDIA_DIR_ENCODING_DECODING;
+
+    }
+
+    /* No need to do anything else if stream is rejected */
+    if (local_m->desc.port == 0) {
+	return PJ_SUCCESS;
+    }
+
+    /* If "rtcp" attribute is present in the SDP, set the RTCP address
+     * from that attribute. Otherwise, calculate from RTP address.
+     */
+    attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
+				  "rtcp", NULL);
+    if (attr) {
+	pjmedia_sdp_rtcp_attr rtcp;
+	status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
+	if (status == PJ_SUCCESS) {
+	    if (rtcp.addr.slen) {
+		status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr,
+					  (pj_uint16_t)rtcp.port);
+	    } else {
+		pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL,
+				 (pj_uint16_t)rtcp.port);
+		pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp),
+		          pj_sockaddr_get_addr(&si->rem_addr),
+			  pj_sockaddr_get_addr_len(&si->rem_addr));
+	    }
+	}
+    }
+
+    if (!pj_sockaddr_has_addr(&si->rem_rtcp)) {
+	int rtcp_port;
+
+	pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr));
+	rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1;
+	pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port);
+    }
+
+
+    /* Get the payload number for receive channel. */
+    /*
+       Previously we used to rely on fmt[0] being the selected codec,
+       but some UA sends telephone-event as fmt[0] and this would
+       cause assert failure below.
+
+       Thanks Chris Hamilton <chamilton .at. cs.dal.ca> for this patch.
+
+    // And codec must be numeric!
+    if (!pj_isdigit(*local_m->desc.fmt[0].ptr) ||
+	!pj_isdigit(*rem_m->desc.fmt[0].ptr))
+    {
+	return PJMEDIA_EINVALIDPT;
+    }
+
+    pt = pj_strtoul(&local_m->desc.fmt[0]);
+    pj_assert(PJMEDIA_RTP_PT_TELEPHONE_EVENTS==0 ||
+	      pt != PJMEDIA_RTP_PT_TELEPHONE_EVENTS);
+    */
+
+    /* Get codec info and param */
+    status = get_audio_codec_info_param(si, pool, mgr, local_m, rem_m);
+
+    /* Leave SSRC to random. */
+    si->ssrc = pj_rand();
+
+    /* Set default jitter buffer parameter. */
+    si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1;
+
+    return status;
+}
+
Index: pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c	(revision 3981)
+++ pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c	(revision 3982)
@@ -23,9 +23,7 @@
 #include <pjmedia/rtcp.h>
 #include <pjmedia/jbuf.h>
-#include <pjmedia/sdp_neg.h>
 #include <pjmedia/stream_common.h>
 #include <pj/array.h>
 #include <pj/assert.h>
-#include <pj/ctype.h>
 #include <pj/compat/socket.h>
 #include <pj/errno.h>
@@ -1791,365 +1789,4 @@
 }
 
-
-static const pj_str_t ID_VIDEO = { "video", 5};
-static const pj_str_t ID_IN = { "IN", 2 };
-static const pj_str_t ID_IP4 = { "IP4", 3};
-static const pj_str_t ID_IP6 = { "IP6", 3};
-static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
-static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
-//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
-static const pj_str_t ID_RTPMAP = { "rtpmap", 6 };
-
-static const pj_str_t STR_INACTIVE = { "inactive", 8 };
-static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
-static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
-static const pj_str_t STR_RECVONLY = { "recvonly", 8 };
-
-
-/*
- * Internal function for collecting codec info and param from the SDP media.
- */
-static pj_status_t get_video_codec_info_param(pjmedia_vid_stream_info *si,
-					      pj_pool_t *pool,
-					      pjmedia_vid_codec_mgr *mgr,
-					      const pjmedia_sdp_media *local_m,
-					      const pjmedia_sdp_media *rem_m)
-{
-    unsigned pt = 0;
-    const pjmedia_vid_codec_info *p_info;
-    pj_status_t status;
-
-    pt = pj_strtoul(&local_m->desc.fmt[0]);
-
-    /* Get payload type for receiving direction */
-    si->rx_pt = pt;
-
-    /* Get codec info and payload type for transmitting direction. */
-    if (pt < 96) {
-	/* For static payload types, get the codec info from codec manager. */
-	status = pjmedia_vid_codec_mgr_get_codec_info(mgr, pt, &p_info);
-	if (status != PJ_SUCCESS)
-	    return status;
-
-	si->codec_info = *p_info;
-
-	/* Get payload type for transmitting direction.
-	 * For static payload type, pt's are symetric.
-	 */
-	si->tx_pt = pt;
-    } else {
-	const pjmedia_sdp_attr *attr;
-	pjmedia_sdp_rtpmap *rtpmap;
-	pjmedia_codec_id codec_id;
-	pj_str_t codec_id_st;
-	unsigned i;
-
-	/* Determine payload type for outgoing channel, by finding
-	 * dynamic payload type in remote SDP that matches the answer.
-	 */
-	si->tx_pt = 0xFFFF;
-	for (i=0; i<rem_m->desc.fmt_count; ++i) {
-	    if (pjmedia_sdp_neg_fmt_match(NULL,
-					  (pjmedia_sdp_media*)local_m, 0,
-					  (pjmedia_sdp_media*)rem_m, i, 0) ==
-		PJ_SUCCESS)
-	    {
-		/* Found matched codec. */
-		si->tx_pt = pj_strtoul(&rem_m->desc.fmt[i]);
-		break;
-	    }
-	}
-
-	if (si->tx_pt == 0xFFFF)
-	    return PJMEDIA_EMISSINGRTPMAP;
-
-	/* For dynamic payload types, get codec name from the rtpmap */
-	attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, 
-					   &local_m->desc.fmt[0]);
-	if (attr == NULL)
-	    return PJMEDIA_EMISSINGRTPMAP;
-
-	status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
-	if (status != PJ_SUCCESS)
-	    return status;
-
-	/* Then get the codec info from the codec manager */
-	pj_ansi_snprintf(codec_id, sizeof(codec_id), "%.*s/", 
-			 (int)rtpmap->enc_name.slen, rtpmap->enc_name.ptr);
-	codec_id_st = pj_str(codec_id);
-	i = 1;
-	status = pjmedia_vid_codec_mgr_find_codecs_by_id(mgr, &codec_id_st,
-							 &i, &p_info, NULL);
-	if (status != PJ_SUCCESS)
-	    return status;
-
-	si->codec_info = *p_info;
-    }
-
-
-    /* Request for codec with the correct packing for streaming */
-    si->codec_info.packings = PJMEDIA_VID_PACKING_PACKETS;
-
-    /* Now that we have codec info, get the codec param. */
-    si->codec_param = PJ_POOL_ALLOC_T(pool, pjmedia_vid_codec_param);
-    status = pjmedia_vid_codec_mgr_get_default_param(mgr, 
-						     &si->codec_info,
-						     si->codec_param);
-
-    /* Adjust encoding bitrate, if higher than remote preference. The remote
-     * bitrate preference is read from SDP "b=TIAS" line in media level.
-     */
-    if ((si->dir & PJMEDIA_DIR_ENCODING) && rem_m->bandw_count) {
-	unsigned i, bandw = 0;
-
-	for (i = 0; i < rem_m->bandw_count; ++i) {
-	    const pj_str_t STR_BANDW_MODIFIER_TIAS = { "TIAS", 4 };
-	    if (!pj_stricmp(&rem_m->bandw[i]->modifier, 
-		&STR_BANDW_MODIFIER_TIAS))
-	    {
-		bandw = rem_m->bandw[i]->value;
-		break;
-	    }
-	}
-
-	if (bandw) {
-	    pjmedia_video_format_detail *enc_vfd;
-	    enc_vfd = pjmedia_format_get_video_format_detail(
-					&si->codec_param->enc_fmt, PJ_TRUE);
-	    if (!enc_vfd->avg_bps || enc_vfd->avg_bps > bandw)
-		enc_vfd->avg_bps = bandw * 3 / 4;
-	    if (!enc_vfd->max_bps || enc_vfd->max_bps > bandw)
-		enc_vfd->max_bps = bandw;
-	}
-    }
-
-    /* Get remote fmtp for our encoder. */
-    pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt,
-				   &si->codec_param->enc_fmtp);
-
-    /* Get local fmtp for our decoder. */
-    pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt,
-				   &si->codec_param->dec_fmtp);
-
-    /* When direction is NONE (it means SDP negotiation has failed) we don't
-     * need to return a failure here, as returning failure will cause
-     * the whole SDP to be rejected. See ticket #:
-     *	http://
-     *
-     * Thanks Alain Totouom 
-     */
-    if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE)
-	return status;
-
-    return PJ_SUCCESS;
-}
-
-
-
-/*
- * Create stream info from SDP media line.
- */
-PJ_DEF(pj_status_t) pjmedia_vid_stream_info_from_sdp(
-					   pjmedia_vid_stream_info *si,
-					   pj_pool_t *pool,
-					   pjmedia_endpt *endpt,
-					   const pjmedia_sdp_session *local,
-					   const pjmedia_sdp_session *remote,
-					   unsigned stream_idx)
-{
-    const pjmedia_sdp_attr *attr;
-    const pjmedia_sdp_media *local_m;
-    const pjmedia_sdp_media *rem_m;
-    const pjmedia_sdp_conn *local_conn;
-    const pjmedia_sdp_conn *rem_conn;
-    int rem_af, local_af;
-    pj_sockaddr local_addr;
-    pj_status_t status;
-
-    PJ_UNUSED_ARG(endpt);
-
-    /* Validate arguments: */
-    PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL);
-    PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL);
-    PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL);
-
-    /* Keep SDP shortcuts */
-    local_m = local->media[stream_idx];
-    rem_m = remote->media[stream_idx];
-
-    local_conn = local_m->conn ? local_m->conn : local->conn;
-    if (local_conn == NULL)
-	return PJMEDIA_SDP_EMISSINGCONN;
-
-    rem_conn = rem_m->conn ? rem_m->conn : remote->conn;
-    if (rem_conn == NULL)
-	return PJMEDIA_SDP_EMISSINGCONN;
-
-    /* Media type must be video */
-    if (pj_stricmp(&local_m->desc.media, &ID_VIDEO) != 0)
-	return PJMEDIA_EINVALIMEDIATYPE;
-
-
-    /* Reset: */
-
-    pj_bzero(si, sizeof(*si));
-
-    /* Media type: */
-    si->type = PJMEDIA_TYPE_VIDEO;
-
-    /* Transport protocol */
-
-    /* At this point, transport type must be compatible, 
-     * the transport instance will do more validation later.
-     */
-    status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport, 
-				       &local_m->desc.transport);
-    if (status != PJ_SUCCESS)
-	return PJMEDIA_SDPNEG_EINVANSTP;
-
-    if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) {
-
-	si->proto = PJMEDIA_TP_PROTO_RTP_AVP;
-
-    } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) {
-
-	si->proto = PJMEDIA_TP_PROTO_RTP_SAVP;
-
-    } else {
-
-	si->proto = PJMEDIA_TP_PROTO_UNKNOWN;
-	return PJ_SUCCESS;
-    }
-
-
-    /* Check address family in remote SDP */
-    rem_af = pj_AF_UNSPEC();
-    if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) {
-	if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) {
-	    rem_af = pj_AF_INET();
-	} else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) {
-	    rem_af = pj_AF_INET6();
-	}
-    }
-
-    if (rem_af==pj_AF_UNSPEC()) {
-	/* Unsupported address family */
-	return PJ_EAFNOTSUP;
-    }
-
-    /* Set remote address: */
-    status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr, 
-			      rem_m->desc.port);
-    if (status != PJ_SUCCESS) {
-	/* Invalid IP address. */
-	return PJMEDIA_EINVALIDIP;
-    }
-
-    /* Check address family of local info */
-    local_af = pj_AF_UNSPEC();
-    if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) {
-	if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) {
-	    local_af = pj_AF_INET();
-	} else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) {
-	    local_af = pj_AF_INET6();
-	}
-    }
-
-    if (local_af==pj_AF_UNSPEC()) {
-	/* Unsupported address family */
-	return PJ_SUCCESS;
-    }
-
-    /* Set remote address: */
-    status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr, 
-			      local_m->desc.port);
-    if (status != PJ_SUCCESS) {
-	/* Invalid IP address. */
-	return PJMEDIA_EINVALIDIP;
-    }
-
-    /* Local and remote address family must match */
-    if (local_af != rem_af)
-	return PJ_EAFNOTSUP;
-
-    /* Media direction: */
-
-    if (local_m->desc.port == 0 || 
-	pj_sockaddr_has_addr(&local_addr)==PJ_FALSE ||
-	pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE ||
-	pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL)
-    {
-	/* Inactive stream. */
-
-	si->dir = PJMEDIA_DIR_NONE;
-
-    } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) {
-
-	/* Send only stream. */
-
-	si->dir = PJMEDIA_DIR_ENCODING;
-
-    } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) {
-
-	/* Recv only stream. */
-
-	si->dir = PJMEDIA_DIR_DECODING;
-
-    } else {
-
-	/* Send and receive stream. */
-
-	si->dir = PJMEDIA_DIR_ENCODING_DECODING;
-
-    }
-
-    /* No need to do anything else if stream is rejected */
-    if (local_m->desc.port == 0) {
-	return PJ_SUCCESS;
-    }
-
-    /* If "rtcp" attribute is present in the SDP, set the RTCP address
-     * from that attribute. Otherwise, calculate from RTP address.
-     */
-    attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
-				  "rtcp", NULL);
-    if (attr) {
-	pjmedia_sdp_rtcp_attr rtcp;
-	status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
-	if (status == PJ_SUCCESS) {
-	    if (rtcp.addr.slen) {
-		status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr,
-					  (pj_uint16_t)rtcp.port);
-	    } else {
-		pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL, 
-				 (pj_uint16_t)rtcp.port);
-		pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp),
-		          pj_sockaddr_get_addr(&si->rem_addr),
-			  pj_sockaddr_get_addr_len(&si->rem_addr));
-	    }
-	}
-    }
-    
-    if (!pj_sockaddr_has_addr(&si->rem_rtcp)) {
-	int rtcp_port;
-
-	pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr));
-	rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1;
-	pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port);
-    }
-
-    /* Get codec info and param */
-    status = get_video_codec_info_param(si, pool, NULL, local_m, rem_m);
-
-    /* Leave SSRC to random. */
-    si->ssrc = pj_rand();
-
-    /* Set default jitter buffer parameter. */
-    si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1;
-
-    return status;
-}
-
-
 /*
  * Force stream to send video keyframe.
Index: pjproject/trunk/pjmedia/src/pjmedia/vid_stream_info.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/vid_stream_info.c	(revision 3982)
+++ pjproject/trunk/pjmedia/src/pjmedia/vid_stream_info.c	(revision 3982)
@@ -0,0 +1,385 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjmedia/vid_stream.h>
+#include <pjmedia/sdp_neg.h>
+#include <pjmedia/stream_common.h>
+#include <pj/ctype.h>
+#include <pj/rand.h>
+
+
+static const pj_str_t ID_VIDEO = { "video", 5};
+static const pj_str_t ID_IN = { "IN", 2 };
+static const pj_str_t ID_IP4 = { "IP4", 3};
+static const pj_str_t ID_IP6 = { "IP6", 3};
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
+static const pj_str_t ID_RTPMAP = { "rtpmap", 6 };
+
+static const pj_str_t STR_INACTIVE = { "inactive", 8 };
+static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
+static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
+static const pj_str_t STR_RECVONLY = { "recvonly", 8 };
+
+
+/*
+ * Internal function for collecting codec info and param from the SDP media.
+ */
+static pj_status_t get_video_codec_info_param(pjmedia_vid_stream_info *si,
+					      pj_pool_t *pool,
+					      pjmedia_vid_codec_mgr *mgr,
+					      const pjmedia_sdp_media *local_m,
+					      const pjmedia_sdp_media *rem_m)
+{
+    unsigned pt = 0;
+    const pjmedia_vid_codec_info *p_info;
+    pj_status_t status;
+
+    pt = pj_strtoul(&local_m->desc.fmt[0]);
+
+    /* Get payload type for receiving direction */
+    si->rx_pt = pt;
+
+    /* Get codec info and payload type for transmitting direction. */
+    if (pt < 96) {
+	/* For static payload types, get the codec info from codec manager. */
+	status = pjmedia_vid_codec_mgr_get_codec_info(mgr, pt, &p_info);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	si->codec_info = *p_info;
+
+	/* Get payload type for transmitting direction.
+	 * For static payload type, pt's are symetric.
+	 */
+	si->tx_pt = pt;
+    } else {
+	const pjmedia_sdp_attr *attr;
+	pjmedia_sdp_rtpmap *rtpmap;
+	pjmedia_codec_id codec_id;
+	pj_str_t codec_id_st;
+	unsigned i;
+
+	/* Determine payload type for outgoing channel, by finding
+	 * dynamic payload type in remote SDP that matches the answer.
+	 */
+	si->tx_pt = 0xFFFF;
+	for (i=0; i<rem_m->desc.fmt_count; ++i) {
+	    if (pjmedia_sdp_neg_fmt_match(NULL,
+					  (pjmedia_sdp_media*)local_m, 0,
+					  (pjmedia_sdp_media*)rem_m, i, 0) ==
+		PJ_SUCCESS)
+	    {
+		/* Found matched codec. */
+		si->tx_pt = pj_strtoul(&rem_m->desc.fmt[i]);
+		break;
+	    }
+	}
+
+	if (si->tx_pt == 0xFFFF)
+	    return PJMEDIA_EMISSINGRTPMAP;
+
+	/* For dynamic payload types, get codec name from the rtpmap */
+	attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
+					   &local_m->desc.fmt[0]);
+	if (attr == NULL)
+	    return PJMEDIA_EMISSINGRTPMAP;
+
+	status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Then get the codec info from the codec manager */
+	pj_ansi_snprintf(codec_id, sizeof(codec_id), "%.*s/",
+			 (int)rtpmap->enc_name.slen, rtpmap->enc_name.ptr);
+	codec_id_st = pj_str(codec_id);
+	i = 1;
+	status = pjmedia_vid_codec_mgr_find_codecs_by_id(mgr, &codec_id_st,
+							 &i, &p_info, NULL);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	si->codec_info = *p_info;
+    }
+
+
+    /* Request for codec with the correct packing for streaming */
+    si->codec_info.packings = PJMEDIA_VID_PACKING_PACKETS;
+
+    /* Now that we have codec info, get the codec param. */
+    si->codec_param = PJ_POOL_ALLOC_T(pool, pjmedia_vid_codec_param);
+    status = pjmedia_vid_codec_mgr_get_default_param(mgr,
+						     &si->codec_info,
+						     si->codec_param);
+
+    /* Adjust encoding bitrate, if higher than remote preference. The remote
+     * bitrate preference is read from SDP "b=TIAS" line in media level.
+     */
+    if ((si->dir & PJMEDIA_DIR_ENCODING) && rem_m->bandw_count) {
+	unsigned i, bandw = 0;
+
+	for (i = 0; i < rem_m->bandw_count; ++i) {
+	    const pj_str_t STR_BANDW_MODIFIER_TIAS = { "TIAS", 4 };
+	    if (!pj_stricmp(&rem_m->bandw[i]->modifier,
+		&STR_BANDW_MODIFIER_TIAS))
+	    {
+		bandw = rem_m->bandw[i]->value;
+		break;
+	    }
+	}
+
+	if (bandw) {
+	    pjmedia_video_format_detail *enc_vfd;
+	    enc_vfd = pjmedia_format_get_video_format_detail(
+					&si->codec_param->enc_fmt, PJ_TRUE);
+	    if (!enc_vfd->avg_bps || enc_vfd->avg_bps > bandw)
+		enc_vfd->avg_bps = bandw * 3 / 4;
+	    if (!enc_vfd->max_bps || enc_vfd->max_bps > bandw)
+		enc_vfd->max_bps = bandw;
+	}
+    }
+
+    /* Get remote fmtp for our encoder. */
+    pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt,
+				   &si->codec_param->enc_fmtp);
+
+    /* Get local fmtp for our decoder. */
+    pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt,
+				   &si->codec_param->dec_fmtp);
+
+    /* When direction is NONE (it means SDP negotiation has failed) we don't
+     * need to return a failure here, as returning failure will cause
+     * the whole SDP to be rejected. See ticket #:
+     *	http://
+     *
+     * Thanks Alain Totouom
+     */
+    if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE)
+	return status;
+
+    return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Create stream info from SDP media line.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_info_from_sdp(
+					   pjmedia_vid_stream_info *si,
+					   pj_pool_t *pool,
+					   pjmedia_endpt *endpt,
+					   const pjmedia_sdp_session *local,
+					   const pjmedia_sdp_session *remote,
+					   unsigned stream_idx)
+{
+    const pjmedia_sdp_attr *attr;
+    const pjmedia_sdp_media *local_m;
+    const pjmedia_sdp_media *rem_m;
+    const pjmedia_sdp_conn *local_conn;
+    const pjmedia_sdp_conn *rem_conn;
+    int rem_af, local_af;
+    pj_sockaddr local_addr;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(endpt);
+
+    /* Validate arguments: */
+    PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL);
+    PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL);
+    PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL);
+
+    /* Keep SDP shortcuts */
+    local_m = local->media[stream_idx];
+    rem_m = remote->media[stream_idx];
+
+    local_conn = local_m->conn ? local_m->conn : local->conn;
+    if (local_conn == NULL)
+	return PJMEDIA_SDP_EMISSINGCONN;
+
+    rem_conn = rem_m->conn ? rem_m->conn : remote->conn;
+    if (rem_conn == NULL)
+	return PJMEDIA_SDP_EMISSINGCONN;
+
+    /* Media type must be video */
+    if (pj_stricmp(&local_m->desc.media, &ID_VIDEO) != 0)
+	return PJMEDIA_EINVALIMEDIATYPE;
+
+
+    /* Reset: */
+
+    pj_bzero(si, sizeof(*si));
+
+    /* Media type: */
+    si->type = PJMEDIA_TYPE_VIDEO;
+
+    /* Transport protocol */
+
+    /* At this point, transport type must be compatible,
+     * the transport instance will do more validation later.
+     */
+    status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport,
+				       &local_m->desc.transport);
+    if (status != PJ_SUCCESS)
+	return PJMEDIA_SDPNEG_EINVANSTP;
+
+    if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) {
+
+	si->proto = PJMEDIA_TP_PROTO_RTP_AVP;
+
+    } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) {
+
+	si->proto = PJMEDIA_TP_PROTO_RTP_SAVP;
+
+    } else {
+
+	si->proto = PJMEDIA_TP_PROTO_UNKNOWN;
+	return PJ_SUCCESS;
+    }
+
+
+    /* Check address family in remote SDP */
+    rem_af = pj_AF_UNSPEC();
+    if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) {
+	if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) {
+	    rem_af = pj_AF_INET();
+	} else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) {
+	    rem_af = pj_AF_INET6();
+	}
+    }
+
+    if (rem_af==pj_AF_UNSPEC()) {
+	/* Unsupported address family */
+	return PJ_EAFNOTSUP;
+    }
+
+    /* Set remote address: */
+    status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr,
+			      rem_m->desc.port);
+    if (status != PJ_SUCCESS) {
+	/* Invalid IP address. */
+	return PJMEDIA_EINVALIDIP;
+    }
+
+    /* Check address family of local info */
+    local_af = pj_AF_UNSPEC();
+    if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) {
+	if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) {
+	    local_af = pj_AF_INET();
+	} else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) {
+	    local_af = pj_AF_INET6();
+	}
+    }
+
+    if (local_af==pj_AF_UNSPEC()) {
+	/* Unsupported address family */
+	return PJ_SUCCESS;
+    }
+
+    /* Set remote address: */
+    status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr,
+			      local_m->desc.port);
+    if (status != PJ_SUCCESS) {
+	/* Invalid IP address. */
+	return PJMEDIA_EINVALIDIP;
+    }
+
+    /* Local and remote address family must match */
+    if (local_af != rem_af)
+	return PJ_EAFNOTSUP;
+
+    /* Media direction: */
+
+    if (local_m->desc.port == 0 ||
+	pj_sockaddr_has_addr(&local_addr)==PJ_FALSE ||
+	pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE ||
+	pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL)
+    {
+	/* Inactive stream. */
+
+	si->dir = PJMEDIA_DIR_NONE;
+
+    } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) {
+
+	/* Send only stream. */
+
+	si->dir = PJMEDIA_DIR_ENCODING;
+
+    } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) {
+
+	/* Recv only stream. */
+
+	si->dir = PJMEDIA_DIR_DECODING;
+
+    } else {
+
+	/* Send and receive stream. */
+
+	si->dir = PJMEDIA_DIR_ENCODING_DECODING;
+
+    }
+
+    /* No need to do anything else if stream is rejected */
+    if (local_m->desc.port == 0) {
+	return PJ_SUCCESS;
+    }
+
+    /* If "rtcp" attribute is present in the SDP, set the RTCP address
+     * from that attribute. Otherwise, calculate from RTP address.
+     */
+    attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
+				  "rtcp", NULL);
+    if (attr) {
+	pjmedia_sdp_rtcp_attr rtcp;
+	status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
+	if (status == PJ_SUCCESS) {
+	    if (rtcp.addr.slen) {
+		status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr,
+					  (pj_uint16_t)rtcp.port);
+	    } else {
+		pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL,
+				 (pj_uint16_t)rtcp.port);
+		pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp),
+		          pj_sockaddr_get_addr(&si->rem_addr),
+			  pj_sockaddr_get_addr_len(&si->rem_addr));
+	    }
+	}
+    }
+
+    if (!pj_sockaddr_has_addr(&si->rem_rtcp)) {
+	int rtcp_port;
+
+	pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr));
+	rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1;
+	pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port);
+    }
+
+    /* Get codec info and param */
+    status = get_video_codec_info_param(si, pool, NULL, local_m, rem_m);
+
+    /* Leave SSRC to random. */
+    si->ssrc = pj_rand();
+
+    /* Set default jitter buffer parameter. */
+    si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1;
+
+    return status;
+}
+
+
Index: pjproject/trunk/pjmedia/src/test/mips_test.c
===================================================================
--- pjproject/trunk/pjmedia/src/test/mips_test.c	(revision 3981)
+++ pjproject/trunk/pjmedia/src/test/mips_test.c	(revision 3982)
@@ -794,4 +794,5 @@
 }
 
+#if PJMEDIA_HAS_G711_CODEC
 /* G.711 benchmark */
 static pjmedia_port* g711_encode_decode(  pj_pool_t *pool,
@@ -807,6 +808,8 @@
 			       samples_per_frame, flags, te);
 }
+#endif
 
 /* GSM benchmark */
+#if PJMEDIA_HAS_GSM_CODEC
 static pjmedia_port* gsm_encode_decode(  pj_pool_t *pool,
 					 unsigned clock_rate,
@@ -821,5 +824,7 @@
 			       samples_per_frame, flags, te);
 }
-
+#endif
+
+#if PJMEDIA_HAS_ILBC_CODEC
 static pj_status_t ilbc_init(pjmedia_endpt *endpt)
 {
@@ -840,5 +845,7 @@
 			       channel_count, samples_per_frame, flags, te);
 }
-
+#endif
+
+#if PJMEDIA_HAS_SPEEX_CODEC
 /* Speex narrowband benchmark */
 static pjmedia_port* speex8_encode_decode(pj_pool_t *pool,
@@ -870,5 +877,7 @@
 			       samples_per_frame, flags, te);
 }
-
+#endif
+
+#if PJMEDIA_HAS_G722_CODEC
 /* G.722 benchmark benchmark */
 static pjmedia_port* g722_encode_decode(pj_pool_t *pool,
@@ -884,4 +893,5 @@
 			       samples_per_frame, flags, te);
 }
+#endif
 
 #if PJMEDIA_HAS_G7221_CODEC
@@ -1797,4 +1807,5 @@
 }
 
+#if PJMEDIA_HAS_G711_CODEC
 /* G.711 stream, no SRTP */
 static pjmedia_port* create_stream_pcmu( pj_pool_t *pool,
@@ -1871,5 +1882,7 @@
 			 samples_per_frame, flags, te);
 }
-
+#endif
+
+#if PJMEDIA_HAS_GSM_CODEC
 /* GSM stream */
 static pjmedia_port* create_stream_gsm(  pj_pool_t *pool,
@@ -1946,5 +1959,7 @@
 			 samples_per_frame, flags, te);
 }
-
+#endif
+
+#if PJMEDIA_HAS_G722_CODEC
 /* G722 stream */
 static pjmedia_port* create_stream_g722( pj_pool_t *pool,
@@ -1961,7 +1976,8 @@
 			 samples_per_frame, flags, te);
 }
-
+#endif
+
+#if PJMEDIA_HAS_G7221_CODEC
 /* G722.1 stream */
-#if PJMEDIA_HAS_G7221_CODEC
 static pjmedia_port* create_stream_g7221( pj_pool_t *pool,
 					  unsigned clock_rate,
Index: pjproject/trunk/pjsip/build/Makefile
===================================================================
--- pjproject/trunk/pjsip/build/Makefile	(revision 3981)
+++ pjproject/trunk/pjsip/build/Makefile	(revision 3982)
@@ -78,5 +78,5 @@
 			pjsua_acc.o pjsua_call.o pjsua_core.o \
 			pjsua_im.o pjsua_media.o pjsua_pres.o \
-			pjsua_dump.o pjsua_vid.o
+			pjsua_dump.o pjsua_aud.o pjsua_vid.o
 export PJSUA_LIB_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS)
 
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 3981)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 3982)
@@ -4915,4 +4915,13 @@
 
 /**
+ * Use PJMEDIA for media? Set this to zero when using third party media
+ * stack.
+ */
+#ifndef PJSUA_MEDIA_HAS_PJMEDIA
+#  define PJSUA_MEDIA_HAS_PJMEDIA	1
+#endif	/* PJSUA_MEDIA_HAS_PJMEDIA */
+
+
+/**
  * Max ports in the conference bridge. This setting is the default value
  * for pjsua_media_config.max_media_ports.
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 3981)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 3982)
@@ -602,10 +602,5 @@
                                   pj_bool_t async,
                                   pjsua_med_tp_state_cb cb);
-pj_status_t video_channel_update(pjsua_call_media *call_med,
-                                 pj_pool_t *tmp_pool,
-			         const pjmedia_sdp_session *local_sdp,
-			         const pjmedia_sdp_session *remote_sdp);
-void stop_video_stream(pjsua_call_media *call_med);
-void set_media_tp_state(pjsua_call_media *call_med, pjsua_med_tp_st tp_st);
+void pjsua_set_media_tp_state(pjsua_call_media *call_med, pjsua_med_tp_st tp_st);
 
 /* Callback to receive media events */
@@ -754,4 +749,17 @@
 
 /*
+ * Audio
+ */
+pj_status_t pjsua_aud_subsys_init(void);
+pj_status_t pjsua_aud_subsys_start(void);
+pj_status_t pjsua_aud_subsys_destroy(void);
+void pjsua_aud_stop_stream(pjsua_call_media *call_med);
+pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med,
+                                     pj_pool_t *tmp_pool,
+                                     pjmedia_stream_info *si,
+				     const pjmedia_sdp_session *local_sdp,
+				     const pjmedia_sdp_session *remote_sdp);
+
+/*
  * Video
  */
@@ -759,4 +767,11 @@
 pj_status_t pjsua_vid_subsys_start(void);
 pj_status_t pjsua_vid_subsys_destroy(void);
+void pjsua_vid_stop_stream(pjsua_call_media *call_med);
+pj_status_t pjsua_vid_channel_init(pjsua_call_media *call_med);
+pj_status_t pjsua_vid_channel_update(pjsua_call_media *call_med,
+				     pj_pool_t *tmp_pool,
+				     pjmedia_vid_stream_info *si,
+				     const pjmedia_sdp_session *local_sdp,
+				     const pjmedia_sdp_session *remote_sdp);
 
 #if PJSUA_HAS_VIDEO
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_aud.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_aud.c	(revision 3982)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_aud.c	(revision 3982)
@@ -0,0 +1,2104 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * 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 <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0
+
+#define THIS_FILE		"pjsua_aud.c"
+#define NULL_SND_DEV_ID		-99
+
+/*****************************************************************************
+/*
+ * Prototypes
+ */
+/* Open sound dev */
+static pj_status_t open_snd_dev(pjmedia_snd_port_param *param);
+/* Close existing sound device */
+static void close_snd_dev(void);
+/* Create audio device param */
+static pj_status_t create_aud_param(pjmedia_aud_param *param,
+				    pjmedia_aud_dev_index capture_dev,
+				    pjmedia_aud_dev_index playback_dev,
+				    unsigned clock_rate,
+				    unsigned channel_count,
+				    unsigned samples_per_frame,
+				    unsigned bits_per_sample);
+
+/*****************************************************************************
+/*
+ * Call API that are closely tied to PJMEDIA
+ */
+/*
+ * Check if call has an active media session.
+ */
+PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
+{
+    pjsua_call *call = &pjsua_var.calls[call_id];
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+    return call->audio_idx >= 0 && call->media[call->audio_idx].strm.a.stream;
+}
+
+
+/*
+ * Get the conference port identification associated with the call.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
+{
+    pjsua_call *call;
+    pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    /* Use PJSUA_LOCK() instead of acquire_call():
+     *  https://trac.pjsip.org/repos/ticket/1371
+     */
+    PJSUA_LOCK();
+
+    if (!pjsua_call_is_active(call_id))
+	goto on_return;
+
+    call = &pjsua_var.calls[call_id];
+    port_id = call->media[call->audio_idx].strm.a.conf_slot;
+
+on_return:
+    PJSUA_UNLOCK();
+
+    return port_id;
+}
+
+
+/*
+ * Get media stream info for the specified media index.
+ */
+PJ_DEF(pj_status_t) pjsua_call_get_stream_info( pjsua_call_id call_id,
+                                                unsigned med_idx,
+                                                pjsua_stream_info *psi)
+{
+    pjsua_call *call;
+    pjsua_call_media *call_med;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(psi, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (med_idx >= call->med_cnt) {
+	PJSUA_UNLOCK();
+	return PJ_EINVAL;
+    }
+
+    call_med = &call->media[med_idx];
+    psi->type = call_med->type;
+    switch (call_med->type) {
+    case PJMEDIA_TYPE_AUDIO:
+	status = pjmedia_stream_get_info(call_med->strm.a.stream,
+					 &psi->info.aud);
+	break;
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+    case PJMEDIA_TYPE_VIDEO:
+	status = pjmedia_vid_stream_get_info(call_med->strm.v.stream,
+					     &psi->info.vid);
+	break;
+#endif
+    default:
+	status = PJMEDIA_EINVALIMEDIATYPE;
+	break;
+    }
+
+    PJSUA_UNLOCK();
+    return status;
+}
+
+
+/*
+ *  Get media stream statistic for the specified media index.
+ */
+PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id,
+                                                unsigned med_idx,
+                                                pjsua_stream_stat *stat)
+{
+    pjsua_call *call;
+    pjsua_call_media *call_med;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(stat, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (med_idx >= call->med_cnt) {
+	PJSUA_UNLOCK();
+	return PJ_EINVAL;
+    }
+
+    call_med = &call->media[med_idx];
+    switch (call_med->type) {
+    case PJMEDIA_TYPE_AUDIO:
+	status = pjmedia_stream_get_stat(call_med->strm.a.stream,
+					 &stat->rtcp);
+	if (status == PJ_SUCCESS)
+	    status = pjmedia_stream_get_stat_jbuf(call_med->strm.a.stream,
+						  &stat->jbuf);
+	break;
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+    case PJMEDIA_TYPE_VIDEO:
+	status = pjmedia_vid_stream_get_stat(call_med->strm.v.stream,
+					     &stat->rtcp);
+	if (status == PJ_SUCCESS)
+	    status = pjmedia_vid_stream_get_stat_jbuf(call_med->strm.v.stream,
+						  &stat->jbuf);
+	break;
+#endif
+    default:
+	status = PJMEDIA_EINVALIMEDIATYPE;
+	break;
+    }
+
+    PJSUA_UNLOCK();
+    return status;
+}
+
+/*
+ * Send DTMF digits to remote using RFC 2833 payload formats.
+ */
+PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id,
+					  const pj_str_t *digits)
+{
+    pjsua_call *call;
+    pjsip_dialog *dlg = NULL;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    PJ_LOG(4,(THIS_FILE, "Call %d dialing DTMF %.*s",
+    			 call_id, (int)digits->slen, digits->ptr));
+    pj_log_push_indent();
+
+    status = acquire_call("pjsua_call_dial_dtmf()", call_id, &call, &dlg);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    if (!pjsua_call_has_media(call_id)) {
+	PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
+	status = PJ_EINVALIDOP;
+	goto on_return;
+    }
+
+    status = pjmedia_stream_dial_dtmf(
+		call->media[call->audio_idx].strm.a.stream, digits);
+
+on_return:
+    if (dlg) pjsip_dlg_dec_lock(dlg);
+    pj_log_pop_indent();
+    return status;
+}
+
+
+/*****************************************************************************
+/*
+ * Audio media with PJMEDIA backend
+ */
+
+/* Init pjmedia audio subsystem */
+pj_status_t pjsua_aud_subsys_init()
+{
+    pj_str_t codec_id = {NULL, 0};
+    unsigned opt;
+    pjmedia_audio_codec_config codec_cfg;
+    pj_status_t status;
+
+    /* To suppress warning about unused var when all codecs are disabled */
+    PJ_UNUSED_ARG(codec_id);
+
+    /*
+     * Register all codecs
+     */
+    pjmedia_audio_codec_config_default(&codec_cfg);
+    codec_cfg.speex.quality = pjsua_var.media_cfg.quality;
+    codec_cfg.speex.complexity = -1;
+    codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode;
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODECS
+    /* Register passthrough codecs */
+    {
+	unsigned aud_idx;
+	unsigned ext_fmt_cnt = 0;
+	pjmedia_format ext_fmts[32];
+
+	/* List extended formats supported by audio devices */
+	for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
+	    pjmedia_aud_dev_info aud_info;
+	    unsigned i;
+
+	    status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Error querying audio device info",
+			     status);
+		goto on_error;
+	    }
+
+	    /* Collect extended formats supported by this audio device */
+	    for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
+		unsigned j;
+		pj_bool_t is_listed = PJ_FALSE;
+
+		/* See if this extended format is already in the list */
+		for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
+		    if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
+			ext_fmts[j].det.aud.avg_bps ==
+			aud_info.ext_fmt[i].det.aud.avg_bps)
+		    {
+			is_listed = PJ_TRUE;
+		    }
+		}
+
+		/* Put this format into the list, if it is not in the list */
+		if (!is_listed)
+		    ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
+
+		pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
+	    }
+	}
+
+	/* Init the passthrough codec with supported formats only */
+	codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt;
+	codec_cfg.passthrough.setting.fmts = ext_fmts;
+	codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode;
+    }
+#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
+
+    /* Register all codecs */
+    status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt,
+                                                 &codec_cfg);
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs"));
+	goto on_error;
+    }
+
+    /* Set speex/16000 to higher priority*/
+    codec_id = pj_str("speex/16000");
+    pjmedia_codec_mgr_set_codec_priority(
+	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
+	&codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
+
+    /* Set speex/8000 to next higher priority*/
+    codec_id = pj_str("speex/8000");
+    pjmedia_codec_mgr_set_codec_priority(
+	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
+	&codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
+
+    /* Disable ALL L16 codecs */
+    codec_id = pj_str("L16");
+    pjmedia_codec_mgr_set_codec_priority(
+	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
+	&codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
+
+
+    /* Save additional conference bridge parameters for future
+     * reference.
+     */
+    pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
+    pjsua_var.mconf_cfg.bits_per_sample = 16;
+    pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
+					    pjsua_var.mconf_cfg.channel_count *
+					    pjsua_var.media_cfg.audio_frame_ptime /
+					    1000;
+
+    /* Init options for conference bridge. */
+    opt = PJMEDIA_CONF_NO_DEVICE;
+    if (pjsua_var.media_cfg.quality >= 3 &&
+	pjsua_var.media_cfg.quality <= 4)
+    {
+	opt |= PJMEDIA_CONF_SMALL_FILTER;
+    }
+    else if (pjsua_var.media_cfg.quality < 3) {
+	opt |= PJMEDIA_CONF_USE_LINEAR;
+    }
+
+    /* Init conference bridge. */
+    status = pjmedia_conf_create(pjsua_var.pool,
+				 pjsua_var.media_cfg.max_media_ports,
+				 pjsua_var.media_cfg.clock_rate,
+				 pjsua_var.mconf_cfg.channel_count,
+				 pjsua_var.mconf_cfg.samples_per_frame,
+				 pjsua_var.mconf_cfg.bits_per_sample,
+				 opt, &pjsua_var.mconf);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error creating conference bridge",
+		     status);
+	goto on_error;
+    }
+
+    /* Are we using the audio switchboard (a.k.a APS-Direct)? */
+    pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
+			    ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
+
+    /* Create null port just in case user wants to use null sound. */
+    status = pjmedia_null_port_create(pjsua_var.pool,
+				      pjsua_var.media_cfg.clock_rate,
+				      pjsua_var.mconf_cfg.channel_count,
+				      pjsua_var.mconf_cfg.samples_per_frame,
+				      pjsua_var.mconf_cfg.bits_per_sample,
+				      &pjsua_var.null_port);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    return status;
+
+on_error:
+    return status;
+}
+
+/* Check if sound device is idle. */
+static void check_snd_dev_idle()
+{
+    unsigned call_cnt;
+
+    /* Check if the sound device auto-close feature is disabled. */
+    if (pjsua_var.media_cfg.snd_auto_close_time < 0)
+	return;
+
+    /* Check if the sound device is currently closed. */
+    if (!pjsua_var.snd_is_on)
+	return;
+
+    /* Get the call count, we shouldn't close the sound device when there is
+     * any calls active.
+     */
+    call_cnt = pjsua_call_get_count();
+
+    /* When this function is called from pjsua_media_channel_deinit() upon
+     * disconnecting call, actually the call count hasn't been updated/
+     * decreased. So we put additional check here, if there is only one
+     * call and it's in DISCONNECTED state, there is actually no active
+     * call.
+     */
+    if (call_cnt == 1) {
+	pjsua_call_id call_id;
+	pj_status_t status;
+
+	status = pjsua_enum_calls(&call_id, &call_cnt);
+	if (status == PJ_SUCCESS && call_cnt > 0 &&
+	    !pjsua_call_is_active(call_id))
+	{
+	    call_cnt = 0;
+	}
+    }
+
+    /* Activate sound device auto-close timer if sound device is idle.
+     * It is idle when there is no port connection in the bridge and
+     * there is no active call.
+     */
+    if (pjsua_var.snd_idle_timer.id == PJ_FALSE &&
+	call_cnt == 0 &&
+	pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0)
+    {
+	pj_time_val delay;
+
+	delay.msec = 0;
+	delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
+
+	pjsua_var.snd_idle_timer.id = PJ_TRUE;
+	pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
+				   &delay);
+    }
+}
+
+/* Timer callback to close sound device */
+static void close_snd_timer_cb( pj_timer_heap_t *th,
+				pj_timer_entry *entry)
+{
+    PJ_UNUSED_ARG(th);
+
+    PJSUA_LOCK();
+    if (entry->id) {
+	PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
+		  pjsua_var.media_cfg.snd_auto_close_time));
+
+	entry->id = PJ_FALSE;
+
+	close_snd_dev();
+    }
+    PJSUA_UNLOCK();
+}
+
+pj_status_t pjsua_aud_subsys_start(void)
+{
+    pj_status_t status = PJ_SUCCESS;
+
+    pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
+			&close_snd_timer_cb);
+
+    return status;
+}
+
+pj_status_t pjsua_aud_subsys_destroy()
+{
+    unsigned i;
+
+    close_snd_dev();
+
+    if (pjsua_var.mconf) {
+	pjmedia_conf_destroy(pjsua_var.mconf);
+	pjsua_var.mconf = NULL;
+    }
+
+    if (pjsua_var.null_port) {
+	pjmedia_port_destroy(pjsua_var.null_port);
+	pjsua_var.null_port = NULL;
+    }
+
+    /* Destroy file players */
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
+	if (pjsua_var.player[i].port) {
+	    pjmedia_port_destroy(pjsua_var.player[i].port);
+	    pjsua_var.player[i].port = NULL;
+	}
+    }
+
+    /* Destroy file recorders */
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
+	if (pjsua_var.recorder[i].port) {
+	    pjmedia_port_destroy(pjsua_var.recorder[i].port);
+	    pjsua_var.recorder[i].port = NULL;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+void pjsua_aud_stop_stream(pjsua_call_media *call_med)
+{
+    pjmedia_stream *strm = call_med->strm.a.stream;
+    pjmedia_rtcp_stat stat;
+
+    if (strm) {
+	pjmedia_stream_send_rtcp_bye(strm);
+
+	if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
+	    if (pjsua_var.mconf) {
+		pjsua_conf_remove_port(call_med->strm.a.conf_slot);
+	    }
+	    call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
+	}
+
+	if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
+	    (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
+	{
+	    /* Save RTP timestamp & sequence, so when media session is
+	     * restarted, those values will be restored as the initial
+	     * RTP timestamp & sequence of the new media session. So in
+	     * the same call session, RTP timestamp and sequence are
+	     * guaranteed to be contigue.
+	     */
+	    call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
+	    call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
+	    call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
+	}
+
+	if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
+	    pjsua_var.ua_cfg.cb.on_stream_destroyed(call_med->call->index,
+	                                            strm, call_med->idx);
+	}
+
+	pjmedia_stream_destroy(strm);
+	call_med->strm.a.stream = NULL;
+    }
+
+    check_snd_dev_idle();
+}
+
+/*
+ * DTMF callback from the stream.
+ */
+static void dtmf_callback(pjmedia_stream *strm, void *user_data,
+			  int digit)
+{
+    PJ_UNUSED_ARG(strm);
+
+    pj_log_push_indent();
+
+    /* For discussions about call mutex protection related to this
+     * callback, please see ticket #460:
+     *	http://trac.pjsip.org/repos/ticket/460#comment:4
+     */
+    if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+	pjsua_call_id call_id;
+
+	call_id = (pjsua_call_id)(long)user_data;
+	pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
+    }
+
+    pj_log_pop_indent();
+}
+
+
+pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med,
+                                     pj_pool_t *tmp_pool,
+                                     pjmedia_stream_info *si,
+				     const pjmedia_sdp_session *local_sdp,
+				     const pjmedia_sdp_session *remote_sdp)
+{
+    pjsua_call *call = call_med->call;
+    pjmedia_port *media_port;
+    unsigned strm_idx = call_med->idx;
+    pj_status_t status;
+
+    PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
+    pj_log_push_indent();
+
+    si->rtcp_sdes_bye_disabled = PJ_TRUE;
+
+    /* Check if no media is active */
+    if (si->dir != PJMEDIA_DIR_NONE) {
+
+	/* Override ptime, if this option is specified. */
+	if (pjsua_var.media_cfg.ptime != 0) {
+	    si->param->setting.frm_per_pkt = (pj_uint8_t)
+		(pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
+	    if (si->param->setting.frm_per_pkt == 0)
+		si->param->setting.frm_per_pkt = 1;
+	}
+
+	/* Disable VAD, if this option is specified. */
+	if (pjsua_var.media_cfg.no_vad) {
+	    si->param->setting.vad = 0;
+	}
+
+
+	/* Optionally, application may modify other stream settings here
+	 * (such as jitter buffer parameters, codec ptime, etc.)
+	 */
+	si->jb_init = pjsua_var.media_cfg.jb_init;
+	si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
+	si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
+	si->jb_max = pjsua_var.media_cfg.jb_max;
+
+	/* Set SSRC */
+	si->ssrc = call_med->ssrc;
+
+	/* Set RTP timestamp & sequence, normally these value are intialized
+	 * automatically when stream session created, but for some cases (e.g:
+	 * call reinvite, call update) timestamp and sequence need to be kept
+	 * contigue.
+	 */
+	si->rtp_ts = call_med->rtp_tx_ts;
+	si->rtp_seq = call_med->rtp_tx_seq;
+	si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+	/* Enable/disable stream keep-alive and NAT hole punch. */
+	si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
+#endif
+
+	/* Create session based on session info. */
+	status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
+				       call_med->tp, NULL,
+				       &call_med->strm.a.stream);
+	if (status != PJ_SUCCESS) {
+	    goto on_return;
+	}
+
+	/* Start stream */
+	status = pjmedia_stream_start(call_med->strm.a.stream);
+	if (status != PJ_SUCCESS) {
+	    goto on_return;
+	}
+
+        if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
+            pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
+
+	/* If DTMF callback is installed by application, install our
+	 * callback to the session.
+	 */
+	if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+	    pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
+					     &dtmf_callback,
+					     (void*)(long)(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_stream_get_port(call_med->strm.a.stream, &media_port);
+
+	/* Notify application about stream creation.
+	 * Note: application may modify media_port to point to different
+	 * media port
+	 */
+	if (pjsua_var.ua_cfg.cb.on_stream_created) {
+	    pjsua_var.ua_cfg.cb.on_stream_created(call->index,
+						  call_med->strm.a.stream,
+						  strm_idx, &media_port);
+	}
+
+	/*
+	 * Add the call to conference bridge.
+	 */
+	{
+	    char tmp[PJSIP_MAX_URL_SIZE];
+	    pj_str_t port_name;
+
+	    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_prov,
+					    media_port,
+					    &port_name,
+					    (unsigned*)
+					    &call_med->strm.a.conf_slot);
+	    if (status != PJ_SUCCESS) {
+		goto on_return;
+	    }
+	}
+    }
+
+on_return:
+    pj_log_pop_indent();
+    return status;
+}
+
+
+/*
+ * Get maxinum number of conference ports.
+ */
+PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
+{
+    return pjsua_var.media_cfg.max_media_ports;
+}
+
+
+/*
+ * Get current number of active ports in the bridge.
+ */
+PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
+{
+    unsigned ports[PJSUA_MAX_CONF_PORTS];
+    unsigned count = PJ_ARRAY_SIZE(ports);
+    pj_status_t status;
+
+    status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
+    if (status != PJ_SUCCESS)
+	count = 0;
+
+    return count;
+}
+
+
+/*
+ * Enumerate all conference ports.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
+					  unsigned *count)
+{
+    return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
+}
+
+
+/*
+ * Get information about the specified conference port
+ */
+PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
+					      pjsua_conf_port_info *info)
+{
+    pjmedia_conf_port_info cinfo;
+    unsigned i;
+    pj_status_t status;
+
+    status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_bzero(info, sizeof(*info));
+    info->slot_id = id;
+    info->name = cinfo.name;
+    info->clock_rate = cinfo.clock_rate;
+    info->channel_count = cinfo.channel_count;
+    info->samples_per_frame = cinfo.samples_per_frame;
+    info->bits_per_sample = cinfo.bits_per_sample;
+
+    /* Build array of listeners */
+    info->listener_cnt = cinfo.listener_cnt;
+    for (i=0; i<cinfo.listener_cnt; ++i) {
+	info->listeners[i] = cinfo.listener_slots[i];
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Add arbitrary media port to PJSUA's conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
+					 pjmedia_port *port,
+					 pjsua_conf_port_id *p_id)
+{
+    pj_status_t status;
+
+    status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
+				   port, NULL, (unsigned*)p_id);
+    if (status != PJ_SUCCESS) {
+	if (p_id)
+	    *p_id = PJSUA_INVALID_ID;
+    }
+
+    return status;
+}
+
+
+/*
+ * Remove arbitrary slot from the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
+{
+    pj_status_t status;
+
+    status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
+    check_snd_dev_idle();
+
+    return status;
+}
+
+
+/*
+ * Establish unidirectional media flow from souce to sink.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
+					pjsua_conf_port_id sink)
+{
+    pj_status_t status = PJ_SUCCESS;
+
+    PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
+	      (pjsua_var.is_mswitch ? "Switch" : "Conf"),
+	      source, sink));
+    pj_log_push_indent();
+
+    /* If sound device idle timer is active, cancel it first. */
+    PJSUA_LOCK();
+    if (pjsua_var.snd_idle_timer.id) {
+	pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
+	pjsua_var.snd_idle_timer.id = PJ_FALSE;
+    }
+    PJSUA_UNLOCK();
+
+
+    /* For audio switchboard (i.e. APS-Direct):
+     * Check if sound device need to be reopened, i.e: its attributes
+     * (format, clock rate, channel count) must match to peer's.
+     * Note that sound device can be reopened only if it doesn't have
+     * any connection.
+     */
+    if (pjsua_var.is_mswitch) {
+	pjmedia_conf_port_info port0_info;
+	pjmedia_conf_port_info peer_info;
+	unsigned peer_id;
+	pj_bool_t need_reopen = PJ_FALSE;
+
+	peer_id = (source!=0)? source : sink;
+	status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
+					    &peer_info);
+	pj_assert(status == PJ_SUCCESS);
+
+	status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
+	pj_assert(status == PJ_SUCCESS);
+
+	/* Check if sound device is instantiated. */
+	need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
+		      !pjsua_var.no_snd);
+
+	/* Check if sound device need to reopen because it needs to modify
+	 * settings to match its peer. Sound device must be idle in this case
+	 * though.
+	 */
+	if (!need_reopen &&
+	    port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
+	{
+	    need_reopen = (peer_info.format.id != port0_info.format.id ||
+			   peer_info.format.det.aud.avg_bps !=
+				   port0_info.format.det.aud.avg_bps ||
+			   peer_info.clock_rate != port0_info.clock_rate ||
+			   peer_info.channel_count!=port0_info.channel_count);
+	}
+
+	if (need_reopen) {
+	    if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
+		pjmedia_snd_port_param param;
+
+		/* Create parameter based on peer info */
+		status = create_aud_param(&param.base, pjsua_var.cap_dev,
+					  pjsua_var.play_dev,
+					  peer_info.clock_rate,
+					  peer_info.channel_count,
+					  peer_info.samples_per_frame,
+					  peer_info.bits_per_sample);
+		if (status != PJ_SUCCESS) {
+		    pjsua_perror(THIS_FILE, "Error opening sound device",
+				 status);
+		    goto on_return;
+		}
+
+		/* And peer format */
+		if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
+		    param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+		    param.base.ext_fmt = peer_info.format;
+		}
+
+		param.options = 0;
+		status = open_snd_dev(&param);
+		if (status != PJ_SUCCESS) {
+		    pjsua_perror(THIS_FILE, "Error opening sound device",
+				 status);
+		    goto on_return;
+		}
+	    } else {
+		/* Null-audio */
+		status = pjsua_set_snd_dev(pjsua_var.cap_dev,
+					   pjsua_var.play_dev);
+		if (status != PJ_SUCCESS) {
+		    pjsua_perror(THIS_FILE, "Error opening sound device",
+				 status);
+		    goto on_return;
+		}
+	    }
+	} else if (pjsua_var.no_snd) {
+	    if (!pjsua_var.snd_is_on) {
+		pjsua_var.snd_is_on = PJ_TRUE;
+	    	/* Notify app */
+	    	if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+	    	    (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
+	    	}
+	    }
+	}
+
+    } else {
+	/* The bridge version */
+
+	/* Create sound port if none is instantiated */
+	if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
+	    !pjsua_var.no_snd)
+	{
+	    pj_status_t status;
+
+	    status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Error opening sound device", status);
+		goto on_return;
+	    }
+	} else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
+	    pjsua_var.snd_is_on = PJ_TRUE;
+	    /* Notify app */
+	    if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+		(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
+	    }
+	}
+    }
+
+    status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
+
+on_return:
+    pj_log_pop_indent();
+    return status;
+}
+
+
+/*
+ * Disconnect media flow from the source to destination port.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
+					   pjsua_conf_port_id sink)
+{
+    pj_status_t status;
+
+    PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
+	      (pjsua_var.is_mswitch ? "Switch" : "Conf"),
+	      source, sink));
+    pj_log_push_indent();
+
+    status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
+    check_snd_dev_idle();
+
+    pj_log_pop_indent();
+    return status;
+}
+
+
+/*
+ * Adjust the signal level to be transmitted from the bridge to the
+ * specified port by making it louder or quieter.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
+					       float level)
+{
+    return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
+					(int)((level-1) * 128));
+}
+
+/*
+ * Adjust the signal level to be received from the specified port (to
+ * the bridge) by making it louder or quieter.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
+					       float level)
+{
+    return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
+					(int)((level-1) * 128));
+}
+
+
+/*
+ * Get last signal level transmitted to or received from the specified port.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
+						unsigned *tx_level,
+						unsigned *rx_level)
+{
+    return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
+					 tx_level, rx_level);
+}
+
+/*****************************************************************************
+ * File player.
+ */
+
+static char* get_basename(const char *path, unsigned len)
+{
+    char *p = ((char*)path) + len;
+
+    if (len==0)
+	return p;
+
+    for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
+
+    return (p==path) ? p : p+1;
+}
+
+
+/*
+ * Create a file player, and automatically connect this player to
+ * the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
+					 unsigned options,
+					 pjsua_player_id *p_id)
+{
+    unsigned slot, file_id;
+    char path[PJ_MAXPATH];
+    pj_pool_t *pool = NULL;
+    pjmedia_port *port;
+    pj_status_t status = PJ_SUCCESS;
+
+    if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
+	return PJ_ETOOMANY;
+
+    PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
+	      (int)filename->slen, filename->ptr));
+    pj_log_push_indent();
+
+    PJSUA_LOCK();
+
+    for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
+	if (pjsua_var.player[file_id].port == NULL)
+	    break;
+    }
+
+    if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
+	/* This is unexpected */
+	pj_assert(0);
+	status = PJ_EBUG;
+	goto on_error;
+    }
+
+    pj_memcpy(path, filename->ptr, filename->slen);
+    path[filename->slen] = '\0';
+
+    pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
+    if (!pool) {
+	status = PJ_ENOMEM;
+	goto on_error;
+    }
+
+    status = pjmedia_wav_player_port_create(
+				    pool, path,
+				    pjsua_var.mconf_cfg.samples_per_frame *
+				    1000 / pjsua_var.media_cfg.channel_count /
+				    pjsua_var.media_cfg.clock_rate,
+				    options, 0, &port);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
+	goto on_error;
+    }
+
+    status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
+				   port, filename, &slot);
+    if (status != PJ_SUCCESS) {
+	pjmedia_port_destroy(port);
+	pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
+		     status);
+	goto on_error;
+    }
+
+    pjsua_var.player[file_id].type = 0;
+    pjsua_var.player[file_id].pool = pool;
+    pjsua_var.player[file_id].port = port;
+    pjsua_var.player[file_id].slot = slot;
+
+    if (p_id) *p_id = file_id;
+
+    ++pjsua_var.player_cnt;
+
+    PJSUA_UNLOCK();
+
+    PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
+
+    pj_log_pop_indent();
+    return PJ_SUCCESS;
+
+on_error:
+    PJSUA_UNLOCK();
+    if (pool) pj_pool_release(pool);
+    pj_log_pop_indent();
+    return status;
+}
+
+
+/*
+ * Create a file playlist media port, and automatically add the port
+ * to the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
+					   unsigned file_count,
+					   const pj_str_t *label,
+					   unsigned options,
+					   pjsua_player_id *p_id)
+{
+    unsigned slot, file_id, ptime;
+    pj_pool_t *pool = NULL;
+    pjmedia_port *port;
+    pj_status_t status = PJ_SUCCESS;
+
+    if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
+	return PJ_ETOOMANY;
+
+    PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
+    pj_log_push_indent();
+
+    PJSUA_LOCK();
+
+    for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
+	if (pjsua_var.player[file_id].port == NULL)
+	    break;
+    }
+
+    if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
+	/* This is unexpected */
+	pj_assert(0);
+	status = PJ_EBUG;
+	goto on_error;
+    }
+
+
+    ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
+	    pjsua_var.media_cfg.clock_rate;
+
+    pool = pjsua_pool_create("playlist", 1000, 1000);
+    if (!pool) {
+	status = PJ_ENOMEM;
+	goto on_error;
+    }
+
+    status = pjmedia_wav_playlist_create(pool, label,
+					 file_names, file_count,
+					 ptime, options, 0, &port);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create playlist", status);
+	goto on_error;
+    }
+
+    status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
+				   port, &port->info.name, &slot);
+    if (status != PJ_SUCCESS) {
+	pjmedia_port_destroy(port);
+	pjsua_perror(THIS_FILE, "Unable to add port", status);
+	goto on_error;
+    }
+
+    pjsua_var.player[file_id].type = 1;
+    pjsua_var.player[file_id].pool = pool;
+    pjsua_var.player[file_id].port = port;
+    pjsua_var.player[file_id].slot = slot;
+
+    if (p_id) *p_id = file_id;
+
+    ++pjsua_var.player_cnt;
+
+    PJSUA_UNLOCK();
+
+    PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
+
+    pj_log_pop_indent();
+
+    return PJ_SUCCESS;
+
+on_error:
+    PJSUA_UNLOCK();
+    if (pool) pj_pool_release(pool);
+    pj_log_pop_indent();
+
+    return status;
+}
+
+
+/*
+ * Get conference port ID associated with player.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
+{
+    PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+
+    return pjsua_var.player[id].slot;
+}
+
+/*
+ * Get the media port for the player.
+ */
+PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
+					   pjmedia_port **p_port)
+{
+    PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+    PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
+
+    *p_port = pjsua_var.player[id].port;
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Set playback position.
+ */
+PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
+					  pj_uint32_t samples)
+{
+    PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
+
+    return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
+}
+
+
+/*
+ * Close the file, remove the player from the bridge, and free
+ * resources associated with the file player.
+ */
+PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
+{
+    PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+
+    PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
+    pj_log_push_indent();
+
+    PJSUA_LOCK();
+
+    if (pjsua_var.player[id].port) {
+	pjsua_conf_remove_port(pjsua_var.player[id].slot);
+	pjmedia_port_destroy(pjsua_var.player[id].port);
+	pjsua_var.player[id].port = NULL;
+	pjsua_var.player[id].slot = 0xFFFF;
+	pj_pool_release(pjsua_var.player[id].pool);
+	pjsua_var.player[id].pool = NULL;
+	pjsua_var.player_cnt--;
+    }
+
+    PJSUA_UNLOCK();
+    pj_log_pop_indent();
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * File recorder.
+ */
+
+/*
+ * Create a file recorder, and automatically connect this recorder to
+ * the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
+					   unsigned enc_type,
+					   void *enc_param,
+					   pj_ssize_t max_size,
+					   unsigned options,
+					   pjsua_recorder_id *p_id)
+{
+    enum Format
+    {
+	FMT_UNKNOWN,
+	FMT_WAV,
+	FMT_MP3,
+    };
+    unsigned slot, file_id;
+    char path[PJ_MAXPATH];
+    pj_str_t ext;
+    int file_format;
+    pj_pool_t *pool = NULL;
+    pjmedia_port *port;
+    pj_status_t status = PJ_SUCCESS;
+
+    /* Filename must present */
+    PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
+
+    /* Don't support max_size at present */
+    PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
+
+    /* Don't support encoding type at present */
+    PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
+
+    PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
+	      (int)filename->slen, filename->ptr));
+    pj_log_push_indent();
+
+    if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
+	pj_log_pop_indent();
+	return PJ_ETOOMANY;
+    }
+
+    /* Determine the file format */
+    ext.ptr = filename->ptr + filename->slen - 4;
+    ext.slen = 4;
+
+    if (pj_stricmp2(&ext, ".wav") == 0)
+	file_format = FMT_WAV;
+    else if (pj_stricmp2(&ext, ".mp3") == 0)
+	file_format = FMT_MP3;
+    else {
+	PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
+			     "determine file format for %.*s",
+			     (int)filename->slen, filename->ptr));
+	pj_log_pop_indent();
+	return PJ_ENOTSUP;
+    }
+
+    PJSUA_LOCK();
+
+    for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
+	if (pjsua_var.recorder[file_id].port == NULL)
+	    break;
+    }
+
+    if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
+	/* This is unexpected */
+	pj_assert(0);
+	status = PJ_EBUG;
+	goto on_return;
+    }
+
+    pj_memcpy(path, filename->ptr, filename->slen);
+    path[filename->slen] = '\0';
+
+    pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
+    if (!pool) {
+	status = PJ_ENOMEM;
+	goto on_return;
+    }
+
+    if (file_format == FMT_WAV) {
+	status = pjmedia_wav_writer_port_create(pool, path,
+						pjsua_var.media_cfg.clock_rate,
+						pjsua_var.mconf_cfg.channel_count,
+						pjsua_var.mconf_cfg.samples_per_frame,
+						pjsua_var.mconf_cfg.bits_per_sample,
+						options, 0, &port);
+    } else {
+	PJ_UNUSED_ARG(enc_param);
+	port = NULL;
+	status = PJ_ENOTSUP;
+    }
+
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
+	goto on_return;
+    }
+
+    status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
+				   port, filename, &slot);
+    if (status != PJ_SUCCESS) {
+	pjmedia_port_destroy(port);
+	goto on_return;
+    }
+
+    pjsua_var.recorder[file_id].port = port;
+    pjsua_var.recorder[file_id].slot = slot;
+    pjsua_var.recorder[file_id].pool = pool;
+
+    if (p_id) *p_id = file_id;
+
+    ++pjsua_var.rec_cnt;
+
+    PJSUA_UNLOCK();
+
+    PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
+
+    pj_log_pop_indent();
+    return PJ_SUCCESS;
+
+on_return:
+    PJSUA_UNLOCK();
+    if (pool) pj_pool_release(pool);
+    pj_log_pop_indent();
+    return status;
+}
+
+
+/*
+ * Get conference port associated with recorder.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
+{
+    PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
+
+    return pjsua_var.recorder[id].slot;
+}
+
+/*
+ * Get the media port for the recorder.
+ */
+PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
+					     pjmedia_port **p_port)
+{
+    PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
+    PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
+
+    *p_port = pjsua_var.recorder[id].port;
+    return PJ_SUCCESS;
+}
+
+/*
+ * Destroy recorder (this will complete recording).
+ */
+PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
+{
+    PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
+
+    PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
+    pj_log_push_indent();
+
+    PJSUA_LOCK();
+
+    if (pjsua_var.recorder[id].port) {
+	pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
+	pjmedia_port_destroy(pjsua_var.recorder[id].port);
+	pjsua_var.recorder[id].port = NULL;
+	pjsua_var.recorder[id].slot = 0xFFFF;
+	pj_pool_release(pjsua_var.recorder[id].pool);
+	pjsua_var.recorder[id].pool = NULL;
+	pjsua_var.rec_cnt--;
+    }
+
+    PJSUA_UNLOCK();
+    pj_log_pop_indent();
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Sound devices.
+ */
+
+/*
+ * Enum sound devices.
+ */
+
+PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
+					 unsigned *count)
+{
+    unsigned i, dev_count;
+
+    dev_count = pjmedia_aud_dev_count();
+
+    if (dev_count > *count) dev_count = *count;
+
+    for (i=0; i<dev_count; ++i) {
+	pj_status_t status;
+
+	status = pjmedia_aud_dev_get_info(i, &info[i]);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    *count = dev_count;
+
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
+					 unsigned *count)
+{
+    unsigned i, dev_count;
+
+    dev_count = pjmedia_aud_dev_count();
+
+    if (dev_count > *count) dev_count = *count;
+    pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
+
+    for (i=0; i<dev_count; ++i) {
+	pjmedia_aud_dev_info ai;
+	pj_status_t status;
+
+	status = pjmedia_aud_dev_get_info(i, &ai);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	strncpy(info[i].name, ai.name, sizeof(info[i].name));
+	info[i].name[sizeof(info[i].name)-1] = '\0';
+	info[i].input_count = ai.input_count;
+	info[i].output_count = ai.output_count;
+	info[i].default_samples_per_sec = ai.default_samples_per_sec;
+    }
+
+    *count = dev_count;
+
+    return PJ_SUCCESS;
+}
+
+/* Create audio device parameter to open the device */
+static pj_status_t create_aud_param(pjmedia_aud_param *param,
+				    pjmedia_aud_dev_index capture_dev,
+				    pjmedia_aud_dev_index playback_dev,
+				    unsigned clock_rate,
+				    unsigned channel_count,
+				    unsigned samples_per_frame,
+				    unsigned bits_per_sample)
+{
+    pj_status_t status;
+
+    /* Normalize device ID with new convention about default device ID */
+    if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
+	playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+
+    /* Create default parameters for the device */
+    status = pjmedia_aud_dev_default_param(capture_dev, param);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error retrieving default audio "
+				"device parameters", status);
+	return status;
+    }
+    param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    param->rec_id = capture_dev;
+    param->play_id = playback_dev;
+    param->clock_rate = clock_rate;
+    param->channel_count = channel_count;
+    param->samples_per_frame = samples_per_frame;
+    param->bits_per_sample = bits_per_sample;
+
+    /* Update the setting with user preference */
+#define update_param(cap, field)    \
+	if (pjsua_var.aud_param.flags & cap) { \
+	    param->flags |= cap; \
+	    param->field = pjsua_var.aud_param.field; \
+	}
+    update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
+    update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
+    update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
+    update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
+#undef update_param
+
+    /* Latency settings */
+    param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+		     PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
+    param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
+    param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
+
+    /* EC settings */
+    if (pjsua_var.media_cfg.ec_tail_len) {
+	param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+	param->ec_enabled = PJ_TRUE;
+	param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
+    } else {
+	param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* Internal: the first time the audio device is opened (during app
+ *   startup), retrieve the audio settings such as volume level
+ *   so that aud_get_settings() will work.
+ */
+static pj_status_t update_initial_aud_param()
+{
+    pjmedia_aud_stream *strm;
+    pjmedia_aud_param param;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
+
+    strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+
+    status = pjmedia_aud_stream_get_param(strm, &param);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error audio stream "
+				"device parameters", status);
+	return status;
+    }
+
+#define update_saved_param(cap, field)  \
+	if (param.flags & cap) { \
+	    pjsua_var.aud_param.flags |= cap; \
+	    pjsua_var.aud_param.field = param.field; \
+	}
+
+    update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
+    update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
+    update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
+    update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
+#undef update_saved_param
+
+    return PJ_SUCCESS;
+}
+
+/* Get format name */
+static const char *get_fmt_name(pj_uint32_t id)
+{
+    static char name[8];
+
+    if (id == PJMEDIA_FORMAT_L16)
+	return "PCM";
+    pj_memcpy(name, &id, 4);
+    name[4] = '\0';
+    return name;
+}
+
+/* Open sound device with the setting. */
+static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
+{
+    pjmedia_port *conf_port;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(param, PJ_EINVAL);
+
+    /* Check if NULL sound device is used */
+    if (NULL_SND_DEV_ID==param->base.rec_id ||
+	NULL_SND_DEV_ID==param->base.play_id)
+    {
+	return pjsua_set_null_snd_dev();
+    }
+
+    /* Close existing sound port */
+    close_snd_dev();
+
+    /* Notify app */
+    if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+	(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
+    }
+
+    /* Create memory pool for sound device. */
+    pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
+    PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
+
+
+    PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
+	      get_fmt_name(param->base.ext_fmt.id),
+	      param->base.clock_rate, param->base.channel_count,
+	      param->base.samples_per_frame / param->base.channel_count *
+	      1000 / param->base.clock_rate));
+    pj_log_push_indent();
+
+    status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
+				       param, &pjsua_var.snd_port);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Get the port0 of the conference bridge. */
+    conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
+    pj_assert(conf_port != NULL);
+
+    /* For conference bridge, resample if necessary if the bridge's
+     * clock rate is different than the sound device's clock rate.
+     */
+    if (!pjsua_var.is_mswitch &&
+	param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
+	PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
+    {
+	pjmedia_port *resample_port;
+	unsigned resample_opt = 0;
+
+	if (pjsua_var.media_cfg.quality >= 3 &&
+	    pjsua_var.media_cfg.quality <= 4)
+	{
+	    resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
+	}
+	else if (pjsua_var.media_cfg.quality < 3) {
+	    resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
+	}
+
+	status = pjmedia_resample_port_create(pjsua_var.snd_pool,
+					      conf_port,
+					      param->base.clock_rate,
+					      resample_opt,
+					      &resample_port);
+	if (status != PJ_SUCCESS) {
+	    char errmsg[PJ_ERR_MSG_SIZE];
+	    pj_strerror(status, errmsg, sizeof(errmsg));
+	    PJ_LOG(4, (THIS_FILE,
+		       "Error creating resample port: %s",
+		       errmsg));
+	    close_snd_dev();
+	    goto on_error;
+	}
+
+	conf_port = resample_port;
+    }
+
+    /* Otherwise for audio switchboard, the switch's port0 setting is
+     * derived from the sound device setting, so update the setting.
+     */
+    if (pjsua_var.is_mswitch) {
+	if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
+	    conf_port->info.fmt = param->base.ext_fmt;
+	} else {
+	    unsigned bps, ptime_usec;
+	    bps = param->base.clock_rate * param->base.bits_per_sample;
+	    ptime_usec = param->base.samples_per_frame /
+			 param->base.channel_count * 1000000 /
+			 param->base.clock_rate;
+	    pjmedia_format_init_audio(&conf_port->info.fmt,
+				      PJMEDIA_FORMAT_PCM,
+				      param->base.clock_rate,
+				      param->base.channel_count,
+				      param->base.bits_per_sample,
+				      ptime_usec,
+				      bps, bps);
+	}
+    }
+
+
+    /* Connect sound port to the bridge */
+    status = pjmedia_snd_port_connect(pjsua_var.snd_port,
+				      conf_port );
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to connect conference port to "
+			        "sound device", status);
+	pjmedia_snd_port_destroy(pjsua_var.snd_port);
+	pjsua_var.snd_port = NULL;
+	goto on_error;
+    }
+
+    /* Save the device IDs */
+    pjsua_var.cap_dev = param->base.rec_id;
+    pjsua_var.play_dev = param->base.play_id;
+
+    /* Update sound device name. */
+    {
+	pjmedia_aud_dev_info rec_info;
+	pjmedia_aud_stream *strm;
+	pjmedia_aud_param si;
+        pj_str_t tmp;
+
+	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+	status = pjmedia_aud_stream_get_param(strm, &si);
+	if (status == PJ_SUCCESS)
+	    status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
+
+	if (status==PJ_SUCCESS) {
+	    if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
+		char tmp_buf[128];
+		int tmp_buf_len = sizeof(tmp_buf);
+
+		tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
+					       "%s (%dKHz)",
+					       rec_info.name,
+					       param->base.clock_rate/1000);
+		pj_strset(&tmp, tmp_buf, tmp_buf_len);
+		pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
+	    } else {
+		pjmedia_conf_set_port0_name(pjsua_var.mconf,
+					    pj_cstr(&tmp, rec_info.name));
+	    }
+	}
+
+	/* Any error is not major, let it through */
+	status = PJ_SUCCESS;
+    }
+
+    /* If this is the first time the audio device is open, retrieve some
+     * settings from the device (such as volume settings) so that the
+     * pjsua_snd_get_setting() work.
+     */
+    if (pjsua_var.aud_open_cnt == 0) {
+	update_initial_aud_param();
+	++pjsua_var.aud_open_cnt;
+    }
+
+    pjsua_var.snd_is_on = PJ_TRUE;
+
+    pj_log_pop_indent();
+    return PJ_SUCCESS;
+
+on_error:
+    pj_log_pop_indent();
+    return status;
+}
+
+
+/* Close existing sound device */
+static void close_snd_dev(void)
+{
+    pj_log_push_indent();
+
+    /* Notify app */
+    if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+	(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
+    }
+
+    /* Close sound device */
+    if (pjsua_var.snd_port) {
+	pjmedia_aud_dev_info cap_info, play_info;
+	pjmedia_aud_stream *strm;
+	pjmedia_aud_param param;
+
+	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+	pjmedia_aud_stream_get_param(strm, &param);
+
+	if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
+	    cap_info.name[0] = '\0';
+	if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
+	    play_info.name[0] = '\0';
+
+	PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
+			     "%s sound capture device",
+			     play_info.name, cap_info.name));
+
+	pjmedia_snd_port_disconnect(pjsua_var.snd_port);
+	pjmedia_snd_port_destroy(pjsua_var.snd_port);
+	pjsua_var.snd_port = NULL;
+    }
+
+    /* Close null sound device */
+    if (pjsua_var.null_snd) {
+	PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
+	pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
+	pjsua_var.null_snd = NULL;
+    }
+
+    if (pjsua_var.snd_pool)
+	pj_pool_release(pjsua_var.snd_pool);
+
+    pjsua_var.snd_pool = NULL;
+    pjsua_var.snd_is_on = PJ_FALSE;
+
+    pj_log_pop_indent();
+}
+
+
+/*
+ * Select or change sound device. Application may call this function at
+ * any time to replace current sound device.
+ */
+PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
+				       int playback_dev)
+{
+    unsigned alt_cr_cnt = 1;
+    unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
+    unsigned i;
+    pj_status_t status = -1;
+
+    PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
+	      capture_dev, playback_dev));
+    pj_log_push_indent();
+
+    /* Null-sound */
+    if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
+	status = pjsua_set_null_snd_dev();
+	pj_log_pop_indent();
+	return status;
+    }
+
+    /* Set default clock rate */
+    alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
+    if (alt_cr[0] == 0)
+	alt_cr[0] = pjsua_var.media_cfg.clock_rate;
+
+    /* Allow retrying of different clock rate if we're using conference
+     * bridge (meaning audio format is always PCM), otherwise lock on
+     * to one clock rate.
+     */
+    if (pjsua_var.is_mswitch) {
+	alt_cr_cnt = 1;
+    } else {
+	alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
+    }
+
+    /* Attempts to open the sound device with different clock rates */
+    for (i=0; i<alt_cr_cnt; ++i) {
+	pjmedia_snd_port_param param;
+	unsigned samples_per_frame;
+
+	/* Create the default audio param */
+	samples_per_frame = alt_cr[i] *
+			    pjsua_var.media_cfg.audio_frame_ptime *
+			    pjsua_var.media_cfg.channel_count / 1000;
+	status = create_aud_param(&param.base, capture_dev, playback_dev,
+				  alt_cr[i], pjsua_var.media_cfg.channel_count,
+				  samples_per_frame, 16);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+
+	/* Open! */
+	param.options = 0;
+	status = open_snd_dev(&param);
+	if (status == PJ_SUCCESS)
+	    break;
+    }
+
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to open sound device", status);
+	goto on_error;
+    }
+
+    pjsua_var.no_snd = PJ_FALSE;
+    pjsua_var.snd_is_on = PJ_TRUE;
+
+    pj_log_pop_indent();
+    return PJ_SUCCESS;
+
+on_error:
+    pj_log_pop_indent();
+    return status;
+}
+
+
+/*
+ * Get currently active sound devices. If sound devices has not been created
+ * (for example when pjsua_start() is not called), it is possible that
+ * the function returns PJ_SUCCESS with -1 as device IDs.
+ */
+PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
+				      int *playback_dev)
+{
+    if (capture_dev) {
+	*capture_dev = pjsua_var.cap_dev;
+    }
+    if (playback_dev) {
+	*playback_dev = pjsua_var.play_dev;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Use null sound device.
+ */
+PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
+{
+    pjmedia_port *conf_port;
+    pj_status_t status;
+
+    PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
+    pj_log_push_indent();
+
+
+    /* Close existing sound device */
+    close_snd_dev();
+
+    /* Notify app */
+    if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+	(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
+    }
+
+    /* Create memory pool for sound device. */
+    pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
+    PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
+
+    PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
+
+    /* Get the port0 of the conference bridge. */
+    conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
+    pj_assert(conf_port != NULL);
+
+    /* Create master port, connecting port0 of the conference bridge to
+     * a null port.
+     */
+    status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
+					conf_port, 0, &pjsua_var.null_snd);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create null sound device",
+		     status);
+	pj_log_pop_indent();
+	return status;
+    }
+
+    /* Start the master port */
+    status = pjmedia_master_port_start(pjsua_var.null_snd);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    pjsua_var.cap_dev = NULL_SND_DEV_ID;
+    pjsua_var.play_dev = NULL_SND_DEV_ID;
+
+    pjsua_var.no_snd = PJ_FALSE;
+    pjsua_var.snd_is_on = PJ_TRUE;
+
+    pj_log_pop_indent();
+    return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Use no device!
+ */
+PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
+{
+    /* Close existing sound device */
+    close_snd_dev();
+
+    pjsua_var.no_snd = PJ_TRUE;
+    return pjmedia_conf_get_master_port(pjsua_var.mconf);
+}
+
+
+/*
+ * Configure the AEC settings of the sound port.
+ */
+PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
+{
+    pjsua_var.media_cfg.ec_tail_len = tail_ms;
+
+    if (pjsua_var.snd_port)
+	return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
+					tail_ms, options);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get current AEC tail length.
+ */
+PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
+{
+    *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Check whether the sound device is currently active.
+ */
+PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
+{
+    return pjsua_var.snd_port != NULL;
+}
+
+
+/*
+ * Configure sound device setting to the sound device being used.
+ */
+PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
+					   const void *pval,
+					   pj_bool_t keep)
+{
+    pj_status_t status;
+
+    /* Check if we are allowed to set the cap */
+    if ((cap & pjsua_var.aud_svmask) == 0) {
+	return PJMEDIA_EAUD_INVCAP;
+    }
+
+    /* If sound is active, set it immediately */
+    if (pjsua_snd_is_active()) {
+	pjmedia_aud_stream *strm;
+
+	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+	status = pjmedia_aud_stream_set_cap(strm, cap, pval);
+    } else {
+	status = PJ_SUCCESS;
+    }
+
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Save in internal param for later device open */
+    if (keep) {
+	status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
+					   cap, pval);
+    }
+
+    return status;
+}
+
+/*
+ * Retrieve a sound device setting.
+ */
+PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
+					   void *pval)
+{
+    /* If sound device has never been opened before, open it to
+     * retrieve the initial setting from the device (e.g. audio
+     * volume)
+     */
+    if (pjsua_var.aud_open_cnt==0) {
+	PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
+	pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+	close_snd_dev();
+    }
+
+    if (pjsua_snd_is_active()) {
+	/* Sound is active, retrieve from device directly */
+	pjmedia_aud_stream *strm;
+
+	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+	return pjmedia_aud_stream_get_cap(strm, cap, pval);
+    } else {
+	/* Otherwise retrieve from internal param */
+	return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
+					 cap, pval);
+    }
+}
+
+#endif /* PJSUA_MEDIA_HAS_PJMEDIA */
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 3981)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 3982)
@@ -1390,16 +1390,4 @@
 
 
-/*
- * Check if call has an active media session.
- */
-PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
-{
-    pjsua_call *call = &pjsua_var.calls[call_id];
-    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, 
-		     PJ_EINVAL);
-    return call->audio_idx >= 0 && call->media[call->audio_idx].strm.a.stream;
-}
-
-
 /* Acquire lock to the specified call_id */
 pj_status_t acquire_call(const char *title,
@@ -1476,34 +1464,4 @@
     return PJ_SUCCESS;
 }
-
-
-/*
- * Get the conference port identification associated with the call.
- */
-PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
-{
-    pjsua_call *call;
-    pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
-
-    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, 
-		     PJ_EINVAL);
-
-    /* Use PJSUA_LOCK() instead of acquire_call():
-     *  https://trac.pjsip.org/repos/ticket/1371
-     */
-    PJSUA_LOCK();
-
-    if (!pjsua_call_is_active(call_id))
-	goto on_return;
-
-    call = &pjsua_var.calls[call_id];
-    port_id = call->media[call->audio_idx].strm.a.conf_slot;
-
-on_return:
-    PJSUA_UNLOCK();
-
-    return port_id;
-}
-
 
 
@@ -1736,103 +1694,4 @@
     *p_type = pjsua_var.calls[call_id].rem_nat_type;
     return PJ_SUCCESS;
-}
-
-
-/*
- * Get media stream info for the specified media index.
- */
-PJ_DEF(pj_status_t) pjsua_call_get_stream_info( pjsua_call_id call_id,
-                                                unsigned med_idx,
-                                                pjsua_stream_info *psi)
-{
-    pjsua_call *call;
-    pjsua_call_media *call_med;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(psi, PJ_EINVAL);
-
-    PJSUA_LOCK();
-
-    call = &pjsua_var.calls[call_id];
-    
-    if (med_idx >= call->med_cnt) {
-	PJSUA_UNLOCK();
-	return PJ_EINVAL;
-    }
-
-    call_med = &call->media[med_idx];
-    psi->type = call_med->type;
-    switch (call_med->type) {
-    case PJMEDIA_TYPE_AUDIO:
-	status = pjmedia_stream_get_info(call_med->strm.a.stream,
-					 &psi->info.aud);
-	break;
-#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
-    case PJMEDIA_TYPE_VIDEO:
-	status = pjmedia_vid_stream_get_info(call_med->strm.v.stream,
-					     &psi->info.vid);
-	break;
-#endif
-    default:
-	status = PJMEDIA_EINVALIMEDIATYPE;
-	break;
-    }
-    
-    PJSUA_UNLOCK();
-    return status;
-}
-
-
-/*
- *  Get media stream statistic for the specified media index.
- */
-PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id,
-                                                unsigned med_idx,
-                                                pjsua_stream_stat *stat)
-{
-    pjsua_call *call;
-    pjsua_call_media *call_med;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(stat, PJ_EINVAL);
-
-    PJSUA_LOCK();
-
-    call = &pjsua_var.calls[call_id];
-    
-    if (med_idx >= call->med_cnt) {
-	PJSUA_UNLOCK();
-	return PJ_EINVAL;
-    }
-
-    call_med = &call->media[med_idx];
-    switch (call_med->type) {
-    case PJMEDIA_TYPE_AUDIO:
-	status = pjmedia_stream_get_stat(call_med->strm.a.stream,
-					 &stat->rtcp);
-	if (status == PJ_SUCCESS)
-	    status = pjmedia_stream_get_stat_jbuf(call_med->strm.a.stream,
-						  &stat->jbuf);
-	break;
-#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
-    case PJMEDIA_TYPE_VIDEO:
-	status = pjmedia_vid_stream_get_stat(call_med->strm.v.stream,
-					     &stat->rtcp);
-	if (status == PJ_SUCCESS)
-	    status = pjmedia_vid_stream_get_stat_jbuf(call_med->strm.v.stream,
-						  &stat->jbuf);
-	break;
-#endif
-    default:
-	status = PJMEDIA_EINVALIMEDIATYPE;
-	break;
-    }
-    
-    PJSUA_UNLOCK();
-    return status;
 }
 
@@ -2526,41 +2385,4 @@
 on_error:
     if (dest_dlg) pjsip_dlg_dec_lock(dest_dlg);
-    pj_log_pop_indent();
-    return status;
-}
-
-
-/*
- * Send DTMF digits to remote using RFC 2833 payload formats.
- */
-PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, 
-					  const pj_str_t *digits)
-{
-    pjsua_call *call;
-    pjsip_dialog *dlg = NULL;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
-		     PJ_EINVAL);
-    
-    PJ_LOG(4,(THIS_FILE, "Call %d dialing DTMF %.*s",
-    			 call_id, (int)digits->slen, digits->ptr));
-    pj_log_push_indent();
-
-    status = acquire_call("pjsua_call_dial_dtmf()", call_id, &call, &dlg);
-    if (status != PJ_SUCCESS)
-	goto on_return;
-
-    if (!pjsua_call_has_media(call_id)) {
-	PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
-	status = PJ_EINVALIDOP;
-	goto on_return;
-    }
-
-    status = pjmedia_stream_dial_dtmf(
-		call->media[call->audio_idx].strm.a.stream, digits);
-
-on_return:
-    if (dlg) pjsip_dlg_dec_lock(dlg);
     pj_log_pop_indent();
     return status;
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 3981)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 3982)
@@ -26,27 +26,10 @@
 #define DEFAULT_RTP_PORT	4000
 
-#define NULL_SND_DEV_ID		-99
-
 #ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
 #   define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT	0
 #endif
 
-
 /* Next RTP port to be used */
 static pj_uint16_t next_rtp_port;
-
-/* Open sound dev */
-static pj_status_t open_snd_dev(pjmedia_snd_port_param *param);
-/* Close existing sound device */
-static void close_snd_dev(void);
-/* Create audio device param */
-static pj_status_t create_aud_param(pjmedia_aud_param *param,
-				    pjmedia_aud_dev_index capture_dev,
-				    pjmedia_aud_dev_index playback_dev,
-				    unsigned clock_rate,
-				    unsigned channel_count,
-				    unsigned samples_per_frame,
-				    unsigned bits_per_sample);
-
 
 static void pjsua_media_config_dup(pj_pool_t *pool,
@@ -65,11 +48,5 @@
 pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
 {
-    pj_str_t codec_id = {NULL, 0};
-    unsigned opt;
-    pjmedia_audio_codec_config codec_cfg;
     pj_status_t status;
-
-    /* To suppress warning about unused var when all codecs are disabled */
-    PJ_UNUSED_ARG(codec_id);
 
     pj_log_push_indent();
@@ -116,135 +93,7 @@
     }
 
-    /*
-     * Register all codecs
-     */
-    pjmedia_audio_codec_config_default(&codec_cfg);
-    codec_cfg.speex.quality = pjsua_var.media_cfg.quality;
-    codec_cfg.speex.complexity = -1;
-    codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode;
-
-#if PJMEDIA_HAS_PASSTHROUGH_CODECS
-    /* Register passthrough codecs */
-    {
-	unsigned aud_idx;
-	unsigned ext_fmt_cnt = 0;
-	pjmedia_format ext_fmts[32];
-
-	/* List extended formats supported by audio devices */
-	for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
-	    pjmedia_aud_dev_info aud_info;
-	    unsigned i;
-	    
-	    status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
-	    if (status != PJ_SUCCESS) {
-		pjsua_perror(THIS_FILE, "Error querying audio device info",
-			     status);
-		goto on_error;
-	    }
-	    
-	    /* Collect extended formats supported by this audio device */
-	    for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
-		unsigned j;
-		pj_bool_t is_listed = PJ_FALSE;
-
-		/* See if this extended format is already in the list */
-		for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
-		    if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
-			ext_fmts[j].det.aud.avg_bps ==
-			aud_info.ext_fmt[i].det.aud.avg_bps)
-		    {
-			is_listed = PJ_TRUE;
-		    }
-		}
-		
-		/* Put this format into the list, if it is not in the list */
-		if (!is_listed)
-		    ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
-
-		pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
-	    }
-	}
-
-	/* Init the passthrough codec with supported formats only */
-	codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt;
-	codec_cfg.passthrough.setting.fmts = ext_fmts;
-	codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode;
-    }
-#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
-
-    /* Register all codecs */
-    status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt,
-                                                 &codec_cfg);
-    if (status != PJ_SUCCESS) {
-	PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs"));
+    status = pjsua_aud_subsys_init();
+    if (status != PJ_SUCCESS)
 	goto on_error;
-    }
-
-    /* Set speex/16000 to higher priority*/
-    codec_id = pj_str("speex/16000");
-    pjmedia_codec_mgr_set_codec_priority(
-	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
-	&codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
-
-    /* Set speex/8000 to next higher priority*/
-    codec_id = pj_str("speex/8000");
-    pjmedia_codec_mgr_set_codec_priority(
-	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
-	&codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
-
-    /* Disable ALL L16 codecs */
-    codec_id = pj_str("L16");
-    pjmedia_codec_mgr_set_codec_priority( 
-	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
-	&codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
-
-
-    /* Save additional conference bridge parameters for future
-     * reference.
-     */
-    pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
-    pjsua_var.mconf_cfg.bits_per_sample = 16;
-    pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate * 
-					    pjsua_var.mconf_cfg.channel_count *
-					    pjsua_var.media_cfg.audio_frame_ptime / 
-					    1000;
-
-    /* Init options for conference bridge. */
-    opt = PJMEDIA_CONF_NO_DEVICE;
-    if (pjsua_var.media_cfg.quality >= 3 &&
-	pjsua_var.media_cfg.quality <= 4)
-    {
-	opt |= PJMEDIA_CONF_SMALL_FILTER;
-    }
-    else if (pjsua_var.media_cfg.quality < 3) {
-	opt |= PJMEDIA_CONF_USE_LINEAR;
-    }
-	
-    /* Init conference bridge. */
-    status = pjmedia_conf_create(pjsua_var.pool, 
-				 pjsua_var.media_cfg.max_media_ports,
-				 pjsua_var.media_cfg.clock_rate, 
-				 pjsua_var.mconf_cfg.channel_count,
-				 pjsua_var.mconf_cfg.samples_per_frame, 
-				 pjsua_var.mconf_cfg.bits_per_sample, 
-				 opt, &pjsua_var.mconf);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error creating conference bridge", 
-		     status);
-	goto on_error;
-    }
-
-    /* Are we using the audio switchboard (a.k.a APS-Direct)? */
-    pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
-			    ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
-
-    /* Create null port just in case user wants to use null sound. */
-    status = pjmedia_null_port_create(pjsua_var.pool, 
-				      pjsua_var.media_cfg.clock_rate,
-				      pjsua_var.mconf_cfg.channel_count,
-				      pjsua_var.mconf_cfg.samples_per_frame,
-				      pjsua_var.mconf_cfg.bits_per_sample,
-				      &pjsua_var.null_port);
-    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
 
 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
@@ -273,80 +122,4 @@
 }
 
-
-/* Check if sound device is idle. */
-static void check_snd_dev_idle()
-{
-    unsigned call_cnt;
-
-    /* Check if the sound device auto-close feature is disabled. */
-    if (pjsua_var.media_cfg.snd_auto_close_time < 0)
-	return;
-
-    /* Check if the sound device is currently closed. */
-    if (!pjsua_var.snd_is_on)
-	return;
-
-    /* Get the call count, we shouldn't close the sound device when there is
-     * any calls active.
-     */
-    call_cnt = pjsua_call_get_count();
-
-    /* When this function is called from pjsua_media_channel_deinit() upon
-     * disconnecting call, actually the call count hasn't been updated/
-     * decreased. So we put additional check here, if there is only one
-     * call and it's in DISCONNECTED state, there is actually no active
-     * call.
-     */
-    if (call_cnt == 1) {
-	pjsua_call_id call_id;
-	pj_status_t status;
-
-	status = pjsua_enum_calls(&call_id, &call_cnt);
-	if (status == PJ_SUCCESS && call_cnt > 0 &&
-	    !pjsua_call_is_active(call_id))
-	{
-	    call_cnt = 0;
-	}
-    }
-
-    /* Activate sound device auto-close timer if sound device is idle.
-     * It is idle when there is no port connection in the bridge and
-     * there is no active call.
-     */
-    if (pjsua_var.snd_idle_timer.id == PJ_FALSE &&
-	call_cnt == 0 &&
-	pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0)
-    {
-	pj_time_val delay;
-
-	delay.msec = 0;
-	delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
-
-	pjsua_var.snd_idle_timer.id = PJ_TRUE;
-	pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
-				   &delay);
-    }
-}
-
-
-/* Timer callback to close sound device */
-static void close_snd_timer_cb( pj_timer_heap_t *th,
-				pj_timer_entry *entry)
-{
-    PJ_UNUSED_ARG(th);
-
-    PJSUA_LOCK();
-    if (entry->id) {
-	PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
-		  pjsua_var.media_cfg.snd_auto_close_time));
-
-	entry->id = PJ_FALSE;
-
-	close_snd_dev();
-    }
-    PJSUA_UNLOCK();
-}
-
-
 /*
  * Start pjsua media subsystem.
@@ -375,6 +148,10 @@
 #endif
 
-    pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
-			&close_snd_timer_cb);
+    /* Audio */
+    status = pjsua_aud_subsys_start();
+    if (status != PJ_SUCCESS) {
+	pj_log_pop_indent();
+	return status;
+    }
 
     /* Video */
@@ -382,4 +159,5 @@
     status = pjsua_vid_subsys_start();
     if (status != PJ_SUCCESS) {
+	pjsua_aud_subsys_destroy();
 	pj_log_pop_indent();
 	return status;
@@ -408,30 +186,6 @@
     pj_log_push_indent();
 
-    close_snd_dev();
-
-    if (pjsua_var.mconf) {
-	pjmedia_conf_destroy(pjsua_var.mconf);
-	pjsua_var.mconf = NULL;
-    }
-
-    if (pjsua_var.null_port) {
-	pjmedia_port_destroy(pjsua_var.null_port);
-	pjsua_var.null_port = NULL;
-    }
-
-    /* Destroy file players */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
-	if (pjsua_var.player[i].port) {
-	    pjmedia_port_destroy(pjsua_var.player[i].port);
-	    pjsua_var.player[i].port = NULL;
-	}
-    }
-
-    /* Destroy file recorders */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
-	if (pjsua_var.recorder[i].port) {
-	    pjmedia_port_destroy(pjsua_var.recorder[i].port);
-	    pjsua_var.recorder[i].port = NULL;
-	}
+    if (pjsua_var.med_endpt) {
+	pjsua_aud_subsys_destroy();
     }
 
@@ -1328,6 +1082,6 @@
 
 /* Set media transport state and notify the application via the callback. */
-void set_media_tp_state(pjsua_call_media *call_med,
-                        pjsua_med_tp_st tp_st)
+void pjsua_set_media_tp_state(pjsua_call_media *call_med,
+                              pjsua_med_tp_st tp_st)
 {
     if (pjsua_var.ua_cfg.cb.on_call_media_transport_state &&
@@ -1363,5 +1117,5 @@
 
     if (call_med->tp_st == PJSUA_MED_TP_CREATING)
-        set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
+        pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
 
     if (!call_med->tp_orig &&
@@ -1467,24 +1221,13 @@
     if (call_med->tp == NULL) {
 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
-	pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
-
 	/* While in initial call, set default video devices */
 	if (type == PJMEDIA_TYPE_VIDEO) {
-	    call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
-	    call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
-	    if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
-		pjmedia_vid_dev_info info;
-		pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
-		call_med->strm.v.rdr_dev = info.id;
-	    }
-	    if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
-		pjmedia_vid_dev_info info;
-		pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
-		call_med->strm.v.cap_dev = info.id;
-	    }
+	    status = pjsua_vid_channel_init(call_med);
+	    if (status != PJ_SUCCESS)
+		return status;
 	}
 #endif
 
-        set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);
+        pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);
 
 	if (pjsua_var.media_cfg.enable_ice) {
@@ -1515,5 +1258,5 @@
     } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
 	/* Media is being reenabled. */
-	set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
+	pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
     }
 
@@ -1616,5 +1359,5 @@
 	    }
 
-	    set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
+	    pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
 	}
     }
@@ -1881,5 +1624,5 @@
 		pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT || 
 			  call_med->tp_st == PJSUA_MED_TP_RUNNING);
-		set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
+		pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
 	    }
 
@@ -2243,41 +1986,10 @@
 
 	if (call_med->type == PJMEDIA_TYPE_AUDIO) {
-	    pjmedia_stream *strm = call_med->strm.a.stream;
-	    pjmedia_rtcp_stat stat;
-
-	    if (strm) {
-		if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
-		    if (pjsua_var.mconf) {
-			pjsua_conf_remove_port(call_med->strm.a.conf_slot);
-		    }
-		    call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
-		}
-
-		if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
-		    (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
-		{
-		    /* Save RTP timestamp & sequence, so when media session is
-		     * restarted, those values will be restored as the initial
-		     * RTP timestamp & sequence of the new media session. So in
-		     * the same call session, RTP timestamp and sequence are
-		     * guaranteed to be contigue.
-		     */
-		    call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
-		    call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
-		    call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
-		}
-
-		if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
-		    pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
-		}
-
-		pjmedia_stream_destroy(strm);
-		call_med->strm.a.stream = NULL;
-	    }
+	    pjsua_aud_stop_stream(call_med);
 	}
 
 #if PJMEDIA_HAS_VIDEO
 	else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
-	    stop_video_stream(call_med);
+	    pjsua_vid_stop_stream(call_med);
 	}
 #endif
@@ -2315,19 +2027,12 @@
     pj_log_push_indent();
 
+    stop_media_session(call_id);
+
     for (mi=0; mi<call->med_cnt; ++mi) {
 	pjsua_call_media *call_med = &call->media[mi];
 
-        if (call_med->type == PJMEDIA_TYPE_AUDIO && call_med->strm.a.stream)
-            pjmedia_stream_send_rtcp_bye(call_med->strm.a.stream);
-    }
-
-    stop_media_session(call_id);
-
-    for (mi=0; mi<call->med_cnt; ++mi) {
-	pjsua_call_media *call_med = &call->media[mi];
-
         if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
 	    pjmedia_transport_media_stop(call_med->tp);
-	    set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
+	    pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
 	}
 
@@ -2345,5 +2050,4 @@
     }
 
-    check_snd_dev_idle();
     pj_log_pop_indent();
 
@@ -2351,243 +2055,4 @@
 }
 
-
-/*
- * DTMF callback from the stream.
- */
-static void dtmf_callback(pjmedia_stream *strm, void *user_data,
-			  int digit)
-{
-    PJ_UNUSED_ARG(strm);
-
-    pj_log_push_indent();
-
-    /* For discussions about call mutex protection related to this 
-     * callback, please see ticket #460:
-     *	http://trac.pjsip.org/repos/ticket/460#comment:4
-     */
-    if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
-	pjsua_call_id call_id;
-
-	call_id = (pjsua_call_id)(long)user_data;
-	pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
-    }
-
-    pj_log_pop_indent();
-}
-
-
-static pj_status_t audio_channel_update(pjsua_call_media *call_med,
-                                        pj_pool_t *tmp_pool,
-				        const pjmedia_sdp_session *local_sdp,
-				        const pjmedia_sdp_session *remote_sdp)
-{
-    pjsua_call *call = call_med->call;
-    pjmedia_stream_info the_si, *si = &the_si;
-    pjmedia_port *media_port;
-    unsigned strm_idx = call_med->idx;
-    pj_status_t status;
-
-    PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
-    pj_log_push_indent();
-    
-    status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
-                                          local_sdp, remote_sdp, strm_idx);
-    if (status != PJ_SUCCESS)
-	goto on_return;
-
-    si->rtcp_sdes_bye_disabled = PJ_TRUE;
-
-    /* Check if no media is active */
-    if (si->dir == PJMEDIA_DIR_NONE) {
-	/* Call media state */
-	call_med->state = PJSUA_CALL_MEDIA_NONE;
-
-	/* Call media direction */
-	call_med->dir = PJMEDIA_DIR_NONE;
-
-    } else {
-	pjmedia_transport_info tp_info;
-
-	/* Start/restart media transport */
-	status = pjmedia_transport_media_start(call_med->tp,
-					       tmp_pool, local_sdp,
-					       remote_sdp, strm_idx);
-	if (status != PJ_SUCCESS)
-	    goto on_return;
-
-	set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
-
-	/* Get remote SRTP usage policy */
-	pjmedia_transport_info_init(&tp_info);
-	pjmedia_transport_get_info(call_med->tp, &tp_info);
-	if (tp_info.specific_info_cnt > 0) {
-	    unsigned i;
-	    for (i = 0; i < tp_info.specific_info_cnt; ++i) {
-		if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP) 
-		{
-		    pjmedia_srtp_info *srtp_info = 
-				(pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
-
-		    call_med->rem_srtp_use = srtp_info->peer_use;
-		    break;
-		}
-	    }
-	}
-
-	/* Override ptime, if this option is specified. */
-	if (pjsua_var.media_cfg.ptime != 0) {
-	    si->param->setting.frm_per_pkt = (pj_uint8_t)
-		(pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
-	    if (si->param->setting.frm_per_pkt == 0)
-		si->param->setting.frm_per_pkt = 1;
-	}
-
-	/* Disable VAD, if this option is specified. */
-	if (pjsua_var.media_cfg.no_vad) {
-	    si->param->setting.vad = 0;
-	}
-
-
-	/* Optionally, application may modify other stream settings here
-	 * (such as jitter buffer parameters, codec ptime, etc.)
-	 */
-	si->jb_init = pjsua_var.media_cfg.jb_init;
-	si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
-	si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
-	si->jb_max = pjsua_var.media_cfg.jb_max;
-
-	/* Set SSRC */
-	si->ssrc = call_med->ssrc;
-
-	/* Set RTP timestamp & sequence, normally these value are intialized
-	 * automatically when stream session created, but for some cases (e.g:
-	 * call reinvite, call update) timestamp and sequence need to be kept
-	 * contigue.
-	 */
-	si->rtp_ts = call_med->rtp_tx_ts;
-	si->rtp_seq = call_med->rtp_tx_seq;
-	si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
-
-#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
-	/* Enable/disable stream keep-alive and NAT hole punch. */
-	si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
-#endif
-
-	/* Create session based on session info. */
-	status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
-				       call_med->tp, NULL,
-				       &call_med->strm.a.stream);
-	if (status != PJ_SUCCESS) {
-	    goto on_return;
-	}
-
-	/* Start stream */
-	status = pjmedia_stream_start(call_med->strm.a.stream);
-	if (status != PJ_SUCCESS) {
-	    goto on_return;
-	}
-
-        if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
-            pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
-
-	/* If DTMF callback is installed by application, install our
-	 * callback to the session.
-	 */
-	if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
-	    pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
-					     &dtmf_callback,
-					     (void*)(long)(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_stream_get_port(call_med->strm.a.stream, &media_port);
-
-	/* Notify application about stream creation.
-	 * Note: application may modify media_port to point to different
-	 * media port
-	 */
-	if (pjsua_var.ua_cfg.cb.on_stream_created) {
-	    pjsua_var.ua_cfg.cb.on_stream_created(call->index,
-						  call_med->strm.a.stream,
-						  strm_idx, &media_port);
-	}
-
-	/*
-	 * Add the call to conference bridge.
-	 */
-	{
-	    char tmp[PJSIP_MAX_URL_SIZE];
-	    pj_str_t port_name;
-
-	    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_prov,
-					    media_port, 
-					    &port_name,
-					    (unsigned*)
-					    &call_med->strm.a.conf_slot);
-	    if (status != PJ_SUCCESS) {
-		goto on_return;
-	    }
-	}
-
-	/* Call media direction */
-	call_med->dir = si->dir;
-
-	/* Call media state */
-	if (call->local_hold)
-	    call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
-	else if (call_med->dir == PJMEDIA_DIR_DECODING)
-	    call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
-	else
-	    call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
-    }
-
-    /* Print info. */
-    {
-	char info[80];
-	int info_len = 0;
-	int len;
-	const char *dir;
-
-	switch (si->dir) {
-	case PJMEDIA_DIR_NONE:
-	    dir = "inactive";
-	    break;
-	case PJMEDIA_DIR_ENCODING:
-	    dir = "sendonly";
-	    break;
-	case PJMEDIA_DIR_DECODING:
-	    dir = "recvonly";
-	    break;
-	case PJMEDIA_DIR_ENCODING_DECODING:
-	    dir = "sendrecv";
-	    break;
-	default:
-	    dir = "unknown";
-	    break;
-	}
-	len = pj_ansi_sprintf( info+info_len,
-			       ", stream #%d: %.*s (%s)", strm_idx,
-			       (int)si->fmt.encoding_name.slen,
-			       si->fmt.encoding_name.ptr,
-			       dir);
-	if (len > 0)
-	    info_len += len;
-	PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));
-    }
-
-on_return:
-    pj_log_pop_indent();
-    return status;
-}
 
 pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
@@ -2690,23 +2155,236 @@
 	}
 
-	switch (call_med->type) {
-	case PJMEDIA_TYPE_AUDIO:
-	    status = audio_channel_update(call_med, tmp_pool,
-	                                  local_sdp, remote_sdp);
+	if (call_med->type==PJMEDIA_TYPE_AUDIO) {
+	    pjmedia_stream_info the_si, *si = &the_si;
+
+	    status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
+	                                          local_sdp, remote_sdp, mi);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(1,(THIS_FILE, status,
+			     "pjmedia_stream_info_from_sdp() failed "
+			         "for call_id %d media %d",
+			     call_id, mi));
+		continue;
+	    }
+
+	    /* Check if no media is active */
+	    if (si->dir == PJMEDIA_DIR_NONE) {
+		/* Update call media state and direction */
+		call_med->state = PJSUA_CALL_MEDIA_NONE;
+		call_med->dir = PJMEDIA_DIR_NONE;
+
+	    } else {
+		pjmedia_transport_info tp_info;
+
+		/* Start/restart media transport based on info in SDP */
+		status = pjmedia_transport_media_start(call_med->tp,
+						       tmp_pool, local_sdp,
+						       remote_sdp, mi);
+		if (status != PJ_SUCCESS) {
+		    PJ_PERROR(1,(THIS_FILE, status,
+				 "pjmedia_transport_media_start() failed "
+				     "for call_id %d media %d",
+				 call_id, mi));
+		    continue;
+		}
+
+		pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
+
+		/* Get remote SRTP usage policy */
+		pjmedia_transport_info_init(&tp_info);
+		pjmedia_transport_get_info(call_med->tp, &tp_info);
+		if (tp_info.specific_info_cnt > 0) {
+		    unsigned i;
+		    for (i = 0; i < tp_info.specific_info_cnt; ++i) {
+			if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
+			{
+			    pjmedia_srtp_info *srtp_info =
+					(pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
+
+			    call_med->rem_srtp_use = srtp_info->peer_use;
+			    break;
+			}
+		    }
+		}
+
+		/* Call media direction */
+		call_med->dir = si->dir;
+
+		/* Call media state */
+		if (call->local_hold)
+		    call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
+		else if (call_med->dir == PJMEDIA_DIR_DECODING)
+		    call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
+		else
+		    call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
+	    }
+
+	    /* Call implementation */
+	    status = pjsua_aud_channel_update(call_med, tmp_pool, si,
+	                                      local_sdp, remote_sdp);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(1,(THIS_FILE, status,
+			     "pjsua_aud_channel_update() failed "
+				 "for call_id %d media %d",
+			     call_id, mi));
+		continue;
+	    }
+
+	    /* Print info. */
+	    if (status == PJ_SUCCESS) {
+		char info[80];
+		int info_len = 0;
+		int len;
+		const char *dir;
+
+		switch (si->dir) {
+		case PJMEDIA_DIR_NONE:
+		    dir = "inactive";
+		    break;
+		case PJMEDIA_DIR_ENCODING:
+		    dir = "sendonly";
+		    break;
+		case PJMEDIA_DIR_DECODING:
+		    dir = "recvonly";
+		    break;
+		case PJMEDIA_DIR_ENCODING_DECODING:
+		    dir = "sendrecv";
+		    break;
+		default:
+		    dir = "unknown";
+		    break;
+		}
+		len = pj_ansi_sprintf( info+info_len,
+				       ", stream #%d: %.*s (%s)", mi,
+				       (int)si->fmt.encoding_name.slen,
+				       si->fmt.encoding_name.ptr,
+				       dir);
+		if (len > 0)
+		    info_len += len;
+		PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));
+	    }
+
+
 	    if (call->audio_idx==-1 && status==PJ_SUCCESS &&
-		call_med->strm.a.stream)
+		si->dir != PJMEDIA_DIR_NONE)
 	    {
 		call->audio_idx = mi;
 	    }
-	    break;
+
 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
-	case PJMEDIA_TYPE_VIDEO:
-	    status = video_channel_update(call_med, tmp_pool,
-	                                  local_sdp, remote_sdp);
-	    break;
+	} else if (call_med->type==PJMEDIA_TYPE_VIDEO) {
+	    pjmedia_vid_stream_info the_si, *si = &the_si;
+
+	    status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
+						      local_sdp, remote_sdp, mi);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(1,(THIS_FILE, status,
+			     "pjmedia_vid_stream_info_from_sdp() failed "
+			         "for call_id %d media %d",
+			     call_id, mi));
+		continue;
+	    }
+
+	    /* Check if no media is active */
+	    if (si->dir == PJMEDIA_DIR_NONE) {
+		/* Call media state */
+		call_med->state = PJSUA_CALL_MEDIA_NONE;
+
+		/* Call media direction */
+		call_med->dir = PJMEDIA_DIR_NONE;
+
+	    } else {
+		pjmedia_transport_info tp_info;
+
+		/* Start/restart media transport */
+		status = pjmedia_transport_media_start(call_med->tp,
+						       tmp_pool, local_sdp,
+						       remote_sdp, mi);
+		if (status != PJ_SUCCESS) {
+		    PJ_PERROR(1,(THIS_FILE, status,
+				 "pjmedia_transport_media_start() failed "
+				     "for call_id %d media %d",
+				 call_id, mi));
+		    continue;
+		}
+
+		pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
+
+		/* Get remote SRTP usage policy */
+		pjmedia_transport_info_init(&tp_info);
+		pjmedia_transport_get_info(call_med->tp, &tp_info);
+		if (tp_info.specific_info_cnt > 0) {
+		    unsigned i;
+		    for (i = 0; i < tp_info.specific_info_cnt; ++i) {
+			if (tp_info.spc_info[i].type ==
+				PJMEDIA_TRANSPORT_TYPE_SRTP)
+			{
+			    pjmedia_srtp_info *sri;
+			    sri=(pjmedia_srtp_info*)tp_info.spc_info[i].buffer;
+			    call_med->rem_srtp_use = sri->peer_use;
+			    break;
+			}
+		    }
+		}
+
+		/* Call media direction */
+		call_med->dir = si->dir;
+
+		/* Call media state */
+		if (call->local_hold)
+		    call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
+		else if (call_med->dir == PJMEDIA_DIR_DECODING)
+		    call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
+		else
+		    call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
+	    }
+
+	    status = pjsua_vid_channel_update(call_med, tmp_pool, si,
+	                                      local_sdp, remote_sdp);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(1,(THIS_FILE, status,
+			     "pjmedia_transport_media_start() failed "
+				 "for call_id %d media %d",
+			     call_id, mi));
+		continue;
+	    }
+
+	    /* Print info. */
+	    {
+		char info[80];
+		int info_len = 0;
+		int len;
+		const char *dir;
+
+		switch (si->dir) {
+		case PJMEDIA_DIR_NONE:
+		    dir = "inactive";
+		    break;
+		case PJMEDIA_DIR_ENCODING:
+		    dir = "sendonly";
+		    break;
+		case PJMEDIA_DIR_DECODING:
+		    dir = "recvonly";
+		    break;
+		case PJMEDIA_DIR_ENCODING_DECODING:
+		    dir = "sendrecv";
+		    break;
+		default:
+		    dir = "unknown";
+		    break;
+		}
+		len = pj_ansi_sprintf( info+info_len,
+				       ", stream #%d: %.*s (%s)", mi,
+				       (int)si->codec_info.encoding_name.slen,
+				       si->codec_info.encoding_name.ptr,
+				       dir);
+		if (len > 0)
+		    info_len += len;
+		PJ_LOG(4,(THIS_FILE,"Video updated%s", info));
+	    }
+
 #endif
-	default:
+	} else {
 	    status = PJMEDIA_EINVALIMEDIATYPE;
-	    break;
 	}
 
@@ -2718,5 +2396,5 @@
 	    pjmedia_transport_close(call_med->tp);
 	    call_med->tp = call_med->tp_orig = NULL;
-	    set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
+	    pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
 	}
 
@@ -2758,1414 +2436,4 @@
 }
 
-/*
- * Get maxinum number of conference ports.
- */
-PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
-{
-    return pjsua_var.media_cfg.max_media_ports;
-}
-
-
-/*
- * Get current number of active ports in the bridge.
- */
-PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
-{
-    unsigned ports[PJSUA_MAX_CONF_PORTS];
-    unsigned count = PJ_ARRAY_SIZE(ports);
-    pj_status_t status;
-
-    status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
-    if (status != PJ_SUCCESS)
-	count = 0;
-
-    return count;
-}
-
-
-/*
- * Enumerate all conference ports.
- */
-PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
-					  unsigned *count)
-{
-    return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
-}
-
-
-/*
- * Get information about the specified conference port
- */
-PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
-					      pjsua_conf_port_info *info)
-{
-    pjmedia_conf_port_info cinfo;
-    unsigned i;
-    pj_status_t status;
-
-    status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    pj_bzero(info, sizeof(*info));
-    info->slot_id = id;
-    info->name = cinfo.name;
-    info->clock_rate = cinfo.clock_rate;
-    info->channel_count = cinfo.channel_count;
-    info->samples_per_frame = cinfo.samples_per_frame;
-    info->bits_per_sample = cinfo.bits_per_sample;
-
-    /* Build array of listeners */
-    info->listener_cnt = cinfo.listener_cnt;
-    for (i=0; i<cinfo.listener_cnt; ++i) {
-	info->listeners[i] = cinfo.listener_slots[i];
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Add arbitrary media port to PJSUA's conference bridge.
- */
-PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
-					 pjmedia_port *port,
-					 pjsua_conf_port_id *p_id)
-{
-    pj_status_t status;
-
-    status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
-				   port, NULL, (unsigned*)p_id);
-    if (status != PJ_SUCCESS) {
-	if (p_id)
-	    *p_id = PJSUA_INVALID_ID;
-    }
-
-    return status;
-}
-
-
-/*
- * Remove arbitrary slot from the conference bridge.
- */
-PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
-{
-    pj_status_t status;
-
-    status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
-    check_snd_dev_idle();
-
-    return status;
-}
-
-
-/*
- * Establish unidirectional media flow from souce to sink. 
- */
-PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
-					pjsua_conf_port_id sink)
-{
-    pj_status_t status = PJ_SUCCESS;
-
-    PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
-	      (pjsua_var.is_mswitch ? "Switch" : "Conf"),
-	      source, sink));
-    pj_log_push_indent();
-
-    /* If sound device idle timer is active, cancel it first. */
-    PJSUA_LOCK();
-    if (pjsua_var.snd_idle_timer.id) {
-	pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
-	pjsua_var.snd_idle_timer.id = PJ_FALSE;
-    }
-    PJSUA_UNLOCK();
-
-
-    /* For audio switchboard (i.e. APS-Direct):
-     * Check if sound device need to be reopened, i.e: its attributes
-     * (format, clock rate, channel count) must match to peer's. 
-     * Note that sound device can be reopened only if it doesn't have
-     * any connection.
-     */
-    if (pjsua_var.is_mswitch) {
-	pjmedia_conf_port_info port0_info;
-	pjmedia_conf_port_info peer_info;
-	unsigned peer_id;
-	pj_bool_t need_reopen = PJ_FALSE;
-
-	peer_id = (source!=0)? source : sink;
-	status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id, 
-					    &peer_info);
-	pj_assert(status == PJ_SUCCESS);
-
-	status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
-	pj_assert(status == PJ_SUCCESS);
-
-	/* Check if sound device is instantiated. */
-	need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && 
-		      !pjsua_var.no_snd);
-
-	/* Check if sound device need to reopen because it needs to modify 
-	 * settings to match its peer. Sound device must be idle in this case 
-	 * though.
-	 */
-	if (!need_reopen && 
-	    port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0) 
-	{
-	    need_reopen = (peer_info.format.id != port0_info.format.id ||
-			   peer_info.format.det.aud.avg_bps !=
-				   port0_info.format.det.aud.avg_bps ||
-			   peer_info.clock_rate != port0_info.clock_rate ||
-			   peer_info.channel_count!=port0_info.channel_count);
-	}
-
-	if (need_reopen) {
-	    if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
-		pjmedia_snd_port_param param;
-
-		/* Create parameter based on peer info */
-		status = create_aud_param(&param.base, pjsua_var.cap_dev, 
-					  pjsua_var.play_dev,
-					  peer_info.clock_rate,
-					  peer_info.channel_count,
-					  peer_info.samples_per_frame,
-					  peer_info.bits_per_sample);
-		if (status != PJ_SUCCESS) {
-		    pjsua_perror(THIS_FILE, "Error opening sound device",
-				 status);
-		    goto on_return;
-		}
-
-		/* And peer format */
-		if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
-		    param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
-		    param.base.ext_fmt = peer_info.format;
-		}
-
-		param.options = 0;
-		status = open_snd_dev(&param);
-		if (status != PJ_SUCCESS) {
-		    pjsua_perror(THIS_FILE, "Error opening sound device",
-				 status);
-		    goto on_return;
-		}
-	    } else {
-		/* Null-audio */
-		status = pjsua_set_snd_dev(pjsua_var.cap_dev,
-					   pjsua_var.play_dev);
-		if (status != PJ_SUCCESS) {
-		    pjsua_perror(THIS_FILE, "Error opening sound device",
-				 status);
-		    goto on_return;
-		}
-	    }
-	} else if (pjsua_var.no_snd) {
-	    if (!pjsua_var.snd_is_on) {
-		pjsua_var.snd_is_on = PJ_TRUE;
-	    	/* Notify app */
-	    	if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
-	    	    (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
-	    	}
-	    }
-	}
-
-    } else {
-	/* The bridge version */
-
-	/* Create sound port if none is instantiated */
-	if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && 
-	    !pjsua_var.no_snd) 
-	{
-	    pj_status_t status;
-
-	    status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
-	    if (status != PJ_SUCCESS) {
-		pjsua_perror(THIS_FILE, "Error opening sound device", status);
-		goto on_return;
-	    }
-	} else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
-	    pjsua_var.snd_is_on = PJ_TRUE;
-	    /* Notify app */
-	    if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
-		(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
-	    }
-	}
-    }
-
-    status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
-
-on_return:
-    pj_log_pop_indent();
-    return status;
-}
-
-
-/*
- * Disconnect media flow from the source to destination port.
- */
-PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
-					   pjsua_conf_port_id sink)
-{
-    pj_status_t status;
-
-    PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
-	      (pjsua_var.is_mswitch ? "Switch" : "Conf"),
-	      source, sink));
-    pj_log_push_indent();
-
-    status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
-    check_snd_dev_idle();
-
-    pj_log_pop_indent();
-    return status;
-}
-
-
-/*
- * Adjust the signal level to be transmitted from the bridge to the 
- * specified port by making it louder or quieter.
- */
-PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
-					       float level)
-{
-    return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
-					(int)((level-1) * 128));
-}
-
-/*
- * Adjust the signal level to be received from the specified port (to
- * the bridge) by making it louder or quieter.
- */
-PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
-					       float level)
-{
-    return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
-					(int)((level-1) * 128));
-}
-
-
-/*
- * Get last signal level transmitted to or received from the specified port.
- */
-PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
-						unsigned *tx_level,
-						unsigned *rx_level)
-{
-    return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot, 
-					 tx_level, rx_level);
-}
-
-/*****************************************************************************
- * File player.
- */
-
-static char* get_basename(const char *path, unsigned len)
-{
-    char *p = ((char*)path) + len;
-
-    if (len==0)
-	return p;
-
-    for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
-
-    return (p==path) ? p : p+1;
-}
-
-
-/*
- * Create a file player, and automatically connect this player to
- * the conference bridge.
- */
-PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
-					 unsigned options,
-					 pjsua_player_id *p_id)
-{
-    unsigned slot, file_id;
-    char path[PJ_MAXPATH];
-    pj_pool_t *pool = NULL;
-    pjmedia_port *port;
-    pj_status_t status = PJ_SUCCESS;
-
-    if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
-	return PJ_ETOOMANY;
-
-    PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
-	      (int)filename->slen, filename->ptr));
-    pj_log_push_indent();
-
-    PJSUA_LOCK();
-
-    for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
-	if (pjsua_var.player[file_id].port == NULL)
-	    break;
-    }
-
-    if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
-	/* This is unexpected */
-	pj_assert(0);
-	status = PJ_EBUG;
-	goto on_error;
-    }
-
-    pj_memcpy(path, filename->ptr, filename->slen);
-    path[filename->slen] = '\0';
-
-    pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
-    if (!pool) {
-	status = PJ_ENOMEM;
-	goto on_error;
-    }
-
-    status = pjmedia_wav_player_port_create(
-				    pool, path,
-				    pjsua_var.mconf_cfg.samples_per_frame *
-				    1000 / pjsua_var.media_cfg.channel_count /
-				    pjsua_var.media_cfg.clock_rate, 
-				    options, 0, &port);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
-	goto on_error;
-    }
-
-    status = pjmedia_conf_add_port(pjsua_var.mconf, pool, 
-				   port, filename, &slot);
-    if (status != PJ_SUCCESS) {
-	pjmedia_port_destroy(port);
-	pjsua_perror(THIS_FILE, "Unable to add file to conference bridge", 
-		     status);
-	goto on_error;
-    }
-
-    pjsua_var.player[file_id].type = 0;
-    pjsua_var.player[file_id].pool = pool;
-    pjsua_var.player[file_id].port = port;
-    pjsua_var.player[file_id].slot = slot;
-
-    if (p_id) *p_id = file_id;
-
-    ++pjsua_var.player_cnt;
-
-    PJSUA_UNLOCK();
-
-    PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
-
-    pj_log_pop_indent();
-    return PJ_SUCCESS;
-
-on_error:
-    PJSUA_UNLOCK();
-    if (pool) pj_pool_release(pool);
-    pj_log_pop_indent();
-    return status;
-}
-
-
-/*
- * Create a file playlist media port, and automatically add the port
- * to the conference bridge.
- */
-PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
-					   unsigned file_count,
-					   const pj_str_t *label,
-					   unsigned options,
-					   pjsua_player_id *p_id)
-{
-    unsigned slot, file_id, ptime;
-    pj_pool_t *pool = NULL;
-    pjmedia_port *port;
-    pj_status_t status = PJ_SUCCESS;
-
-    if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
-	return PJ_ETOOMANY;
-
-    PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
-    pj_log_push_indent();
-
-    PJSUA_LOCK();
-
-    for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
-	if (pjsua_var.player[file_id].port == NULL)
-	    break;
-    }
-
-    if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
-	/* This is unexpected */
-	pj_assert(0);
-	status = PJ_EBUG;
-	goto on_error;
-    }
-
-
-    ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 / 
-	    pjsua_var.media_cfg.clock_rate;
-
-    pool = pjsua_pool_create("playlist", 1000, 1000);
-    if (!pool) {
-	status = PJ_ENOMEM;
-	goto on_error;
-    }
-
-    status = pjmedia_wav_playlist_create(pool, label, 
-					 file_names, file_count,
-					 ptime, options, 0, &port);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create playlist", status);
-	goto on_error;
-    }
-
-    status = pjmedia_conf_add_port(pjsua_var.mconf, pool, 
-				   port, &port->info.name, &slot);
-    if (status != PJ_SUCCESS) {
-	pjmedia_port_destroy(port);
-	pjsua_perror(THIS_FILE, "Unable to add port", status);
-	goto on_error;
-    }
-
-    pjsua_var.player[file_id].type = 1;
-    pjsua_var.player[file_id].pool = pool;
-    pjsua_var.player[file_id].port = port;
-    pjsua_var.player[file_id].slot = slot;
-
-    if (p_id) *p_id = file_id;
-
-    ++pjsua_var.player_cnt;
-
-    PJSUA_UNLOCK();
-
-    PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
-
-    pj_log_pop_indent();
-
-    return PJ_SUCCESS;
-
-on_error:
-    PJSUA_UNLOCK();
-    if (pool) pj_pool_release(pool);
-    pj_log_pop_indent();
-
-    return status;
-}
-
-
-/*
- * Get conference port ID associated with player.
- */
-PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
-{
-    PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
-
-    return pjsua_var.player[id].slot;
-}
-
-/*
- * Get the media port for the player.
- */
-PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
-					   pjmedia_port **p_port)
-{
-    PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
-    PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
-    
-    *p_port = pjsua_var.player[id].port;
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Set playback position.
- */
-PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
-					  pj_uint32_t samples)
-{
-    PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
-
-    return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
-}
-
-
-/*
- * Close the file, remove the player from the bridge, and free
- * resources associated with the file player.
- */
-PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
-{
-    PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
-
-    PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
-    pj_log_push_indent();
-
-    PJSUA_LOCK();
-
-    if (pjsua_var.player[id].port) {
-	pjsua_conf_remove_port(pjsua_var.player[id].slot);
-	pjmedia_port_destroy(pjsua_var.player[id].port);
-	pjsua_var.player[id].port = NULL;
-	pjsua_var.player[id].slot = 0xFFFF;
-	pj_pool_release(pjsua_var.player[id].pool);
-	pjsua_var.player[id].pool = NULL;
-	pjsua_var.player_cnt--;
-    }
-
-    PJSUA_UNLOCK();
-    pj_log_pop_indent();
-
-    return PJ_SUCCESS;
-}
-
-
-/*****************************************************************************
- * File recorder.
- */
-
-/*
- * Create a file recorder, and automatically connect this recorder to
- * the conference bridge.
- */
-PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
-					   unsigned enc_type,
-					   void *enc_param,
-					   pj_ssize_t max_size,
-					   unsigned options,
-					   pjsua_recorder_id *p_id)
-{
-    enum Format
-    {
-	FMT_UNKNOWN,
-	FMT_WAV,
-	FMT_MP3,
-    };
-    unsigned slot, file_id;
-    char path[PJ_MAXPATH];
-    pj_str_t ext;
-    int file_format;
-    pj_pool_t *pool = NULL;
-    pjmedia_port *port;
-    pj_status_t status = PJ_SUCCESS;
-
-    /* Filename must present */
-    PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
-
-    /* Don't support max_size at present */
-    PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
-
-    /* Don't support encoding type at present */
-    PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
-
-    PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
-	      (int)filename->slen, filename->ptr));
-    pj_log_push_indent();
-
-    if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
-	pj_log_pop_indent();
-	return PJ_ETOOMANY;
-    }
-
-    /* Determine the file format */
-    ext.ptr = filename->ptr + filename->slen - 4;
-    ext.slen = 4;
-
-    if (pj_stricmp2(&ext, ".wav") == 0)
-	file_format = FMT_WAV;
-    else if (pj_stricmp2(&ext, ".mp3") == 0)
-	file_format = FMT_MP3;
-    else {
-	PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
-			     "determine file format for %.*s",
-			     (int)filename->slen, filename->ptr));
-	pj_log_pop_indent();
-	return PJ_ENOTSUP;
-    }
-
-    PJSUA_LOCK();
-
-    for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
-	if (pjsua_var.recorder[file_id].port == NULL)
-	    break;
-    }
-
-    if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
-	/* This is unexpected */
-	pj_assert(0);
-	status = PJ_EBUG;
-	goto on_return;
-    }
-
-    pj_memcpy(path, filename->ptr, filename->slen);
-    path[filename->slen] = '\0';
-
-    pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
-    if (!pool) {
-	status = PJ_ENOMEM;
-	goto on_return;
-    }
-
-    if (file_format == FMT_WAV) {
-	status = pjmedia_wav_writer_port_create(pool, path, 
-						pjsua_var.media_cfg.clock_rate, 
-						pjsua_var.mconf_cfg.channel_count,
-						pjsua_var.mconf_cfg.samples_per_frame,
-						pjsua_var.mconf_cfg.bits_per_sample, 
-						options, 0, &port);
-    } else {
-	PJ_UNUSED_ARG(enc_param);
-	port = NULL;
-	status = PJ_ENOTSUP;
-    }
-
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
-	goto on_return;
-    }
-
-    status = pjmedia_conf_add_port(pjsua_var.mconf, pool, 
-				   port, filename, &slot);
-    if (status != PJ_SUCCESS) {
-	pjmedia_port_destroy(port);
-	goto on_return;
-    }
-
-    pjsua_var.recorder[file_id].port = port;
-    pjsua_var.recorder[file_id].slot = slot;
-    pjsua_var.recorder[file_id].pool = pool;
-
-    if (p_id) *p_id = file_id;
-
-    ++pjsua_var.rec_cnt;
-
-    PJSUA_UNLOCK();
-
-    PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
-
-    pj_log_pop_indent();
-    return PJ_SUCCESS;
-
-on_return:
-    PJSUA_UNLOCK();
-    if (pool) pj_pool_release(pool);
-    pj_log_pop_indent();
-    return status;
-}
-
-
-/*
- * Get conference port associated with recorder.
- */
-PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
-{
-    PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder), 
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
-
-    return pjsua_var.recorder[id].slot;
-}
-
-/*
- * Get the media port for the recorder.
- */
-PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
-					     pjmedia_port **p_port)
-{
-    PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder), 
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
-    PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
-
-    *p_port = pjsua_var.recorder[id].port;
-    return PJ_SUCCESS;
-}
-
-/*
- * Destroy recorder (this will complete recording).
- */
-PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
-{
-    PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder), 
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
-
-    PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
-    pj_log_push_indent();
-
-    PJSUA_LOCK();
-
-    if (pjsua_var.recorder[id].port) {
-	pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
-	pjmedia_port_destroy(pjsua_var.recorder[id].port);
-	pjsua_var.recorder[id].port = NULL;
-	pjsua_var.recorder[id].slot = 0xFFFF;
-	pj_pool_release(pjsua_var.recorder[id].pool);
-	pjsua_var.recorder[id].pool = NULL;
-	pjsua_var.rec_cnt--;
-    }
-
-    PJSUA_UNLOCK();
-    pj_log_pop_indent();
-
-    return PJ_SUCCESS;
-}
-
-
-/*****************************************************************************
- * Sound devices.
- */
-
-/*
- * Enum sound devices.
- */
-
-PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
-					 unsigned *count)
-{
-    unsigned i, dev_count;
-
-    dev_count = pjmedia_aud_dev_count();
-    
-    if (dev_count > *count) dev_count = *count;
-
-    for (i=0; i<dev_count; ++i) {
-	pj_status_t status;
-
-	status = pjmedia_aud_dev_get_info(i, &info[i]);
-	if (status != PJ_SUCCESS)
-	    return status;
-    }
-
-    *count = dev_count;
-
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
-					 unsigned *count)
-{
-    unsigned i, dev_count;
-
-    dev_count = pjmedia_aud_dev_count();
-    
-    if (dev_count > *count) dev_count = *count;
-    pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
-
-    for (i=0; i<dev_count; ++i) {
-	pjmedia_aud_dev_info ai;
-	pj_status_t status;
-
-	status = pjmedia_aud_dev_get_info(i, &ai);
-	if (status != PJ_SUCCESS)
-	    return status;
-
-	strncpy(info[i].name, ai.name, sizeof(info[i].name));
-	info[i].name[sizeof(info[i].name)-1] = '\0';
-	info[i].input_count = ai.input_count;
-	info[i].output_count = ai.output_count;
-	info[i].default_samples_per_sec = ai.default_samples_per_sec;
-    }
-
-    *count = dev_count;
-
-    return PJ_SUCCESS;
-}
-
-/* Create audio device parameter to open the device */
-static pj_status_t create_aud_param(pjmedia_aud_param *param,
-				    pjmedia_aud_dev_index capture_dev,
-				    pjmedia_aud_dev_index playback_dev,
-				    unsigned clock_rate,
-				    unsigned channel_count,
-				    unsigned samples_per_frame,
-				    unsigned bits_per_sample)
-{
-    pj_status_t status;
-
-    /* Normalize device ID with new convention about default device ID */
-    if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
-	playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
-
-    /* Create default parameters for the device */
-    status = pjmedia_aud_dev_default_param(capture_dev, param);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error retrieving default audio "
-				"device parameters", status);
-	return status;
-    }
-    param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
-    param->rec_id = capture_dev;
-    param->play_id = playback_dev;
-    param->clock_rate = clock_rate;
-    param->channel_count = channel_count;
-    param->samples_per_frame = samples_per_frame;
-    param->bits_per_sample = bits_per_sample;
-
-    /* Update the setting with user preference */
-#define update_param(cap, field)    \
-	if (pjsua_var.aud_param.flags & cap) { \
-	    param->flags |= cap; \
-	    param->field = pjsua_var.aud_param.field; \
-	}
-    update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
-    update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
-    update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
-    update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
-#undef update_param
-
-    /* Latency settings */
-    param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | 
-		     PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
-    param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
-    param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
-
-    /* EC settings */
-    if (pjsua_var.media_cfg.ec_tail_len) {
-	param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
-	param->ec_enabled = PJ_TRUE;
-	param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
-    } else {
-	param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
-    }
-
-    return PJ_SUCCESS;
-}
-
-/* Internal: the first time the audio device is opened (during app
- *   startup), retrieve the audio settings such as volume level
- *   so that aud_get_settings() will work.
- */
-static pj_status_t update_initial_aud_param()
-{
-    pjmedia_aud_stream *strm;
-    pjmedia_aud_param param;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
-
-    strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
-
-    status = pjmedia_aud_stream_get_param(strm, &param);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error audio stream "
-				"device parameters", status);
-	return status;
-    }
-
-#define update_saved_param(cap, field)  \
-	if (param.flags & cap) { \
-	    pjsua_var.aud_param.flags |= cap; \
-	    pjsua_var.aud_param.field = param.field; \
-	}
-
-    update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
-    update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
-    update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
-    update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
-#undef update_saved_param
-
-    return PJ_SUCCESS;
-}
-
-/* Get format name */
-static const char *get_fmt_name(pj_uint32_t id)
-{
-    static char name[8];
-
-    if (id == PJMEDIA_FORMAT_L16)
-	return "PCM";
-    pj_memcpy(name, &id, 4);
-    name[4] = '\0';
-    return name;
-}
-
-/* Open sound device with the setting. */
-static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
-{
-    pjmedia_port *conf_port;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN(param, PJ_EINVAL);
-
-    /* Check if NULL sound device is used */
-    if (NULL_SND_DEV_ID==param->base.rec_id ||
-	NULL_SND_DEV_ID==param->base.play_id)
-    {
-	return pjsua_set_null_snd_dev();
-    }
-
-    /* Close existing sound port */
-    close_snd_dev();
-
-    /* Notify app */
-    if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
-	(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
-    }
-
-    /* Create memory pool for sound device. */
-    pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
-    PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
-
-
-    PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
-	      get_fmt_name(param->base.ext_fmt.id),
-	      param->base.clock_rate, param->base.channel_count,
-	      param->base.samples_per_frame / param->base.channel_count *
-	      1000 / param->base.clock_rate));
-    pj_log_push_indent();
-
-    status = pjmedia_snd_port_create2( pjsua_var.snd_pool, 
-				       param, &pjsua_var.snd_port);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    /* Get the port0 of the conference bridge. */
-    conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
-    pj_assert(conf_port != NULL);
-
-    /* For conference bridge, resample if necessary if the bridge's
-     * clock rate is different than the sound device's clock rate.
-     */
-    if (!pjsua_var.is_mswitch &&
-	param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
-	PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
-    {
-	pjmedia_port *resample_port;
-	unsigned resample_opt = 0;
-
-	if (pjsua_var.media_cfg.quality >= 3 &&
-	    pjsua_var.media_cfg.quality <= 4)
-	{
-	    resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
-	}
-	else if (pjsua_var.media_cfg.quality < 3) {
-	    resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
-	}
-	
-	status = pjmedia_resample_port_create(pjsua_var.snd_pool, 
-					      conf_port,
-					      param->base.clock_rate,
-					      resample_opt, 
-					      &resample_port);
-	if (status != PJ_SUCCESS) {
-	    char errmsg[PJ_ERR_MSG_SIZE];
-	    pj_strerror(status, errmsg, sizeof(errmsg));
-	    PJ_LOG(4, (THIS_FILE, 
-		       "Error creating resample port: %s", 
-		       errmsg));
-	    close_snd_dev();
-	    goto on_error;
-	} 
-	    
-	conf_port = resample_port;
-    }
-
-    /* Otherwise for audio switchboard, the switch's port0 setting is
-     * derived from the sound device setting, so update the setting.
-     */
-    if (pjsua_var.is_mswitch) {
-	if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
-	    conf_port->info.fmt = param->base.ext_fmt;
-	} else {
-	    unsigned bps, ptime_usec;
-	    bps = param->base.clock_rate * param->base.bits_per_sample;
-	    ptime_usec = param->base.samples_per_frame /
-			 param->base.channel_count * 1000000 /
-			 param->base.clock_rate;
-	    pjmedia_format_init_audio(&conf_port->info.fmt,
-				      PJMEDIA_FORMAT_PCM,
-				      param->base.clock_rate,
-				      param->base.channel_count,
-				      param->base.bits_per_sample,
-				      ptime_usec,
-				      bps, bps);
-	}
-    }
-
-
-    /* Connect sound port to the bridge */
-    status = pjmedia_snd_port_connect(pjsua_var.snd_port, 	 
-				      conf_port ); 	 
-    if (status != PJ_SUCCESS) { 	 
-	pjsua_perror(THIS_FILE, "Unable to connect conference port to "
-			        "sound device", status); 	 
-	pjmedia_snd_port_destroy(pjsua_var.snd_port); 	 
-	pjsua_var.snd_port = NULL; 	 
-	goto on_error;
-    }
-
-    /* Save the device IDs */
-    pjsua_var.cap_dev = param->base.rec_id;
-    pjsua_var.play_dev = param->base.play_id;
-
-    /* Update sound device name. */
-    {
-	pjmedia_aud_dev_info rec_info;
-	pjmedia_aud_stream *strm;
-	pjmedia_aud_param si;
-        pj_str_t tmp;
-
-	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
-	status = pjmedia_aud_stream_get_param(strm, &si);
-	if (status == PJ_SUCCESS)
-	    status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
-
-	if (status==PJ_SUCCESS) {
-	    if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
-		char tmp_buf[128];
-		int tmp_buf_len = sizeof(tmp_buf);
-
-		tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, 
-					       "%s (%dKHz)",
-					       rec_info.name, 
-					       param->base.clock_rate/1000);
-		pj_strset(&tmp, tmp_buf, tmp_buf_len);
-		pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp); 
-	    } else {
-		pjmedia_conf_set_port0_name(pjsua_var.mconf, 
-					    pj_cstr(&tmp, rec_info.name));
-	    }
-	}
-
-	/* Any error is not major, let it through */
-	status = PJ_SUCCESS;
-    }
-
-    /* If this is the first time the audio device is open, retrieve some
-     * settings from the device (such as volume settings) so that the
-     * pjsua_snd_get_setting() work.
-     */
-    if (pjsua_var.aud_open_cnt == 0) {
-	update_initial_aud_param();
-	++pjsua_var.aud_open_cnt;
-    }
-
-    pjsua_var.snd_is_on = PJ_TRUE;
-
-    pj_log_pop_indent();
-    return PJ_SUCCESS;
-
-on_error:
-    pj_log_pop_indent();
-    return status;
-}
-
-
-/* Close existing sound device */
-static void close_snd_dev(void)
-{
-    pj_log_push_indent();
-
-    /* Notify app */
-    if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
-	(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
-    }
-
-    /* Close sound device */
-    if (pjsua_var.snd_port) {
-	pjmedia_aud_dev_info cap_info, play_info;
-	pjmedia_aud_stream *strm;
-	pjmedia_aud_param param;
-
-	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
-	pjmedia_aud_stream_get_param(strm, &param);
-
-	if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
-	    cap_info.name[0] = '\0';
-	if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
-	    play_info.name[0] = '\0';
-
-	PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
-			     "%s sound capture device",
-			     play_info.name, cap_info.name));
-
-	pjmedia_snd_port_disconnect(pjsua_var.snd_port);
-	pjmedia_snd_port_destroy(pjsua_var.snd_port);
-	pjsua_var.snd_port = NULL;
-    }
-
-    /* Close null sound device */
-    if (pjsua_var.null_snd) {
-	PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
-	pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
-	pjsua_var.null_snd = NULL;
-    }
-
-    if (pjsua_var.snd_pool) 
-	pj_pool_release(pjsua_var.snd_pool);
-    pjsua_var.snd_pool = NULL;
-    pjsua_var.snd_is_on = PJ_FALSE;
-
-    pj_log_pop_indent();
-}
-
-
-/*
- * Select or change sound device. Application may call this function at
- * any time to replace current sound device.
- */
-PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
-				       int playback_dev)
-{
-    unsigned alt_cr_cnt = 1;
-    unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
-    unsigned i;
-    pj_status_t status = -1;
-
-    PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
-	      capture_dev, playback_dev));
-    pj_log_push_indent();
-
-    /* Null-sound */
-    if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
-	status = pjsua_set_null_snd_dev();
-	pj_log_pop_indent();
-	return status;
-    }
-
-    /* Set default clock rate */
-    alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
-    if (alt_cr[0] == 0)
-	alt_cr[0] = pjsua_var.media_cfg.clock_rate;
-
-    /* Allow retrying of different clock rate if we're using conference 
-     * bridge (meaning audio format is always PCM), otherwise lock on
-     * to one clock rate.
-     */
-    if (pjsua_var.is_mswitch) {
-	alt_cr_cnt = 1;
-    } else {
-	alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
-    }
-
-    /* Attempts to open the sound device with different clock rates */
-    for (i=0; i<alt_cr_cnt; ++i) {
-	pjmedia_snd_port_param param;
-	unsigned samples_per_frame;
-
-	/* Create the default audio param */
-	samples_per_frame = alt_cr[i] *
-			    pjsua_var.media_cfg.audio_frame_ptime *
-			    pjsua_var.media_cfg.channel_count / 1000;
-	status = create_aud_param(&param.base, capture_dev, playback_dev, 
-				  alt_cr[i], pjsua_var.media_cfg.channel_count,
-				  samples_per_frame, 16);
-	if (status != PJ_SUCCESS)
-	    goto on_error;
-
-	/* Open! */
-	param.options = 0;
-	status = open_snd_dev(&param);
-	if (status == PJ_SUCCESS)
-	    break;
-    }
-
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to open sound device", status);
-	goto on_error;
-    }
-
-    pjsua_var.no_snd = PJ_FALSE;
-    pjsua_var.snd_is_on = PJ_TRUE;
-
-    pj_log_pop_indent();
-    return PJ_SUCCESS;
-
-on_error:
-    pj_log_pop_indent();
-    return status;
-}
-
-
-/*
- * Get currently active sound devices. If sound devices has not been created
- * (for example when pjsua_start() is not called), it is possible that
- * the function returns PJ_SUCCESS with -1 as device IDs.
- */
-PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
-				      int *playback_dev)
-{
-    if (capture_dev) {
-	*capture_dev = pjsua_var.cap_dev;
-    }
-    if (playback_dev) {
-	*playback_dev = pjsua_var.play_dev;
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Use null sound device.
- */
-PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
-{
-    pjmedia_port *conf_port;
-    pj_status_t status;
-
-    PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
-    pj_log_push_indent();
-
-
-    /* Close existing sound device */
-    close_snd_dev();
-
-    /* Notify app */
-    if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
-	(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
-    }
-
-    /* Create memory pool for sound device. */
-    pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
-    PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
-
-    PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
-
-    /* Get the port0 of the conference bridge. */
-    conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
-    pj_assert(conf_port != NULL);
-
-    /* Create master port, connecting port0 of the conference bridge to
-     * a null port.
-     */
-    status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
-					conf_port, 0, &pjsua_var.null_snd);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create null sound device",
-		     status);
-	pj_log_pop_indent();
-	return status;
-    }
-
-    /* Start the master port */
-    status = pjmedia_master_port_start(pjsua_var.null_snd);
-    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
-
-    pjsua_var.cap_dev = NULL_SND_DEV_ID;
-    pjsua_var.play_dev = NULL_SND_DEV_ID;
-
-    pjsua_var.no_snd = PJ_FALSE;
-    pjsua_var.snd_is_on = PJ_TRUE;
-
-    pj_log_pop_indent();
-    return PJ_SUCCESS;
-}
-
-
-
-/*
- * Use no device!
- */
-PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
-{
-    /* Close existing sound device */
-    close_snd_dev();
-
-    pjsua_var.no_snd = PJ_TRUE;
-    return pjmedia_conf_get_master_port(pjsua_var.mconf);
-}
-
-
-/*
- * Configure the AEC settings of the sound port.
- */
-PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
-{
-    pjsua_var.media_cfg.ec_tail_len = tail_ms;
-
-    if (pjsua_var.snd_port)
-	return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
-					tail_ms, options);
-    
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Get current AEC tail length.
- */
-PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
-{
-    *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Check whether the sound device is currently active.
- */
-PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
-{
-    return pjsua_var.snd_port != NULL;
-}
-
-
-/*
- * Configure sound device setting to the sound device being used. 
- */
-PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
-					   const void *pval,
-					   pj_bool_t keep)
-{
-    pj_status_t status;
-
-    /* Check if we are allowed to set the cap */
-    if ((cap & pjsua_var.aud_svmask) == 0) {
-	return PJMEDIA_EAUD_INVCAP;
-    }
-
-    /* If sound is active, set it immediately */
-    if (pjsua_snd_is_active()) {
-	pjmedia_aud_stream *strm;
-	
-	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
-	status = pjmedia_aud_stream_set_cap(strm, cap, pval);
-    } else {
-	status = PJ_SUCCESS;
-    }
-
-    if (status != PJ_SUCCESS)
-	return status;
-
-    /* Save in internal param for later device open */
-    if (keep) {
-	status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
-					   cap, pval);
-    }
-
-    return status;
-}
-
-/*
- * Retrieve a sound device setting.
- */
-PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
-					   void *pval)
-{
-    /* If sound device has never been opened before, open it to 
-     * retrieve the initial setting from the device (e.g. audio
-     * volume)
-     */
-    if (pjsua_var.aud_open_cnt==0) {
-	PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
-	pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
-	close_snd_dev();
-    }
-
-    if (pjsua_snd_is_active()) {
-	/* Sound is active, retrieve from device directly */
-	pjmedia_aud_stream *strm;
-	
-	strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
-	return pjmedia_aud_stream_get_cap(strm, cap, pval);
-    } else {
-	/* Otherwise retrieve from internal param */
-	return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
-					 cap, pval);
-    }
-}
-
-
 /*****************************************************************************
  * Codecs.
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c	(revision 3981)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c	(revision 3982)
@@ -20,4 +20,6 @@
 #include <pjsua-lib/pjsua_internal.h>
 
+#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0
+
 #define THIS_FILE	"pjsua_vid.c"
 
@@ -668,16 +670,35 @@
 }
 
+/* Initialize video call media */
+pj_status_t pjsua_vid_channel_init(pjsua_call_media *call_med)
+{
+    pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
+
+    call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
+    call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
+    if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
+	pjmedia_vid_dev_info info;
+	pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
+	call_med->strm.v.rdr_dev = info.id;
+    }
+    if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
+	pjmedia_vid_dev_info info;
+	pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
+	call_med->strm.v.cap_dev = info.id;
+    }
+
+    return PJ_SUCCESS;
+}
 
 /* Internal function: update video channel after SDP negotiation */
-pj_status_t video_channel_update(pjsua_call_media *call_med,
-                                 pj_pool_t *tmp_pool,
-			         const pjmedia_sdp_session *local_sdp,
-			         const pjmedia_sdp_session *remote_sdp)
+pj_status_t pjsua_vid_channel_update(pjsua_call_media *call_med,
+				     pj_pool_t *tmp_pool,
+				     pjmedia_vid_stream_info *si,
+				     const pjmedia_sdp_session *local_sdp,
+				     const pjmedia_sdp_session *remote_sdp)
 {
     pjsua_call *call = call_med->call;
     pjsua_acc  *acc  = &pjsua_var.acc[call->acc_id];
-    pjmedia_vid_stream_info the_si, *si = &the_si;
     pjmedia_port *media_port;
-    unsigned strm_idx = call_med->idx;
     pj_status_t status;
     
@@ -685,46 +706,6 @@
     pj_log_push_indent();
 
-    status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
-					      local_sdp, remote_sdp, strm_idx);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
     /* Check if no media is active */
-    if (si->dir == PJMEDIA_DIR_NONE) {
-	/* Call media state */
-	call_med->state = PJSUA_CALL_MEDIA_NONE;
-
-	/* Call media direction */
-	call_med->dir = PJMEDIA_DIR_NONE;
-
-    } else {
-	pjmedia_transport_info tp_info;
-
-	/* Start/restart media transport */
-	status = pjmedia_transport_media_start(call_med->tp,
-					       tmp_pool, local_sdp,
-					       remote_sdp, strm_idx);
-	if (status != PJ_SUCCESS)
-	    goto on_error;
-
-	set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
-
-	/* Get remote SRTP usage policy */
-	pjmedia_transport_info_init(&tp_info);
-	pjmedia_transport_get_info(call_med->tp, &tp_info);
-	if (tp_info.specific_info_cnt > 0) {
-	    unsigned i;
-	    for (i = 0; i < tp_info.specific_info_cnt; ++i) {
-		if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP) 
-		{
-		    pjmedia_srtp_info *srtp_info = 
-				(pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
-
-		    call_med->rem_srtp_use = srtp_info->peer_use;
-		    break;
-		}
-	    }
-	}
-
+    if (si->dir != PJMEDIA_DIR_NONE) {
 	/* Optionally, application may modify other stream settings here
 	 * (such as jitter buffer parameters, codec ptime, etc.)
@@ -926,48 +907,4 @@
 	}
 
-	/* Call media direction */
-	call_med->dir = si->dir;
-
-	/* Call media state */
-	if (call->local_hold)
-	    call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
-	else if (call_med->dir == PJMEDIA_DIR_DECODING)
-	    call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
-	else
-	    call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
-    }
-
-    /* Print info. */
-    {
-	char info[80];
-	int info_len = 0;
-	int len;
-	const char *dir;
-
-	switch (si->dir) {
-	case PJMEDIA_DIR_NONE:
-	    dir = "inactive";
-	    break;
-	case PJMEDIA_DIR_ENCODING:
-	    dir = "sendonly";
-	    break;
-	case PJMEDIA_DIR_DECODING:
-	    dir = "recvonly";
-	    break;
-	case PJMEDIA_DIR_ENCODING_DECODING:
-	    dir = "sendrecv";
-	    break;
-	default:
-	    dir = "unknown";
-	    break;
-	}
-	len = pj_ansi_sprintf( info+info_len,
-			       ", stream #%d: %.*s (%s)", strm_idx,
-			       (int)si->codec_info.encoding_name.slen,
-			       si->codec_info.encoding_name.ptr,
-			       dir);
-	if (len > 0)
-	    info_len += len;
-	PJ_LOG(4,(THIS_FILE,"Video updated%s", info));
     }
 
@@ -989,5 +926,5 @@
 
 /* Internal function to stop video stream */
-void stop_video_stream(pjsua_call_media *call_med)
+void pjsua_vid_stop_stream(pjsua_call_media *call_med)
 {
     pjmedia_vid_stream *strm = call_med->strm.v.stream;
@@ -1590,5 +1527,5 @@
 	goto on_error;
 
-    set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
+    pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
 
     /* Get transport address info */
@@ -1785,5 +1722,5 @@
 	// Don't close this here, as SDP negotiation has not been
 	// done and stream may be still active.
-	set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
+	pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
 
 	/* Deactivate the stream */
@@ -2186,2 +2123,3 @@
 #endif /* PJSUA_HAS_VIDEO */
 
+#endif /* PJSUA_MEDIA_HAS_PJMEDIA */
