Index: /pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- /pjproject/trunk/pjmedia/build/Makefile	(revision 630)
+++ /pjproject/trunk/pjmedia/build/Makefile	(revision 631)
@@ -69,5 +69,5 @@
 			g711.o jbuf.o master_port.o mem_capture.o mem_player.o \
 			null_port.o plc_common.o plc_g711.o \
-			port.o resample.o \
+			port.o splitcomb.o resample.o \
 			resample_port.o rtcp.o rtp.o sdp.o sdp_cmp.o sdp_neg.o \
 			session.o silencedet.o sound_port.o stream.o \
Index: /pjproject/trunk/pjmedia/build/pjmedia.dsp
===================================================================
--- /pjproject/trunk/pjmedia/build/pjmedia.dsp	(revision 630)
+++ /pjproject/trunk/pjmedia/build/pjmedia.dsp	(revision 631)
@@ -200,4 +200,8 @@
 # Begin Source File
 
+SOURCE=..\src\pjmedia\splitcomb.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\pjmedia\stream.c
 # End Source File
Index: /pjproject/trunk/pjmedia/include/pjmedia/port.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/port.h	(revision 630)
+++ /pjproject/trunk/pjmedia/include/pjmedia/port.h	(revision 631)
@@ -357,5 +357,32 @@
 
 /**
+ * This is an auxiliary function to initialize port info for
+ * ports which deal with PCM audio.
+ *
+ * @param info		    The port info to be initialized.
+ * @param name		    Port name.
+ * @param signature	    Port signature.
+ * @param channel_count	    Number of channels.
+ * @param bits_per_sample   Bits per sample.
+ * @param samples_per_frame Number of samples per frame.
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_port_info_init( pjmedia_port_info *info,
+					     const pj_str_t *name,
+					     unsigned signature,
+					     unsigned clock_rate,
+					     unsigned channel_count,
+					     unsigned bits_per_sample,
+					     unsigned samples_per_frame);
+
+
+/**
  * Get a frame from the port (and subsequent downstream ports).
+ *
+ * @param port	    The media port.
+ * @param frame	    Frame to store samples.
+ *
+ * @return	    PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjmedia_port_get_frame( pjmedia_port *port,
@@ -364,4 +391,9 @@
 /**
  * Put a frame to the port (and subsequent downstream ports).
+ *
+ * @param port	    The media port.
+ * @param frame	    Frame to the put to the port.
+ *
+ * @return	    PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjmedia_port_put_frame( pjmedia_port *port,
@@ -371,4 +403,8 @@
 /**
  * Destroy port (and subsequent downstream ports)
+ *
+ * @param port	    The media port.
+ *
+ * @return	    PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjmedia_port_destroy( pjmedia_port *port );
Index: /pjproject/trunk/pjmedia/include/pjmedia/splitcomb.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/splitcomb.h	(revision 630)
+++ /pjproject/trunk/pjmedia/include/pjmedia/splitcomb.h	(revision 631)
@@ -25,5 +25,5 @@
  * @brief Media channel splitter/combiner port.
  */
-#include <pjmedia/types.h>
+#include <pjmedia/port.h>
 
 
@@ -35,4 +35,10 @@
  * This section describes media port to split and combine media
  * channels in the stream.
+ *
+ * A splitter/combiner splits a single stereo/multichannels audio frame into
+ * multiple audio frames to each channel when put_frame() is called, 
+ * and combines mono frames from each channel into a stereo/multichannel 
+ * frame when get_frame() is called. A common application for the splitter/
+ * combiner is to split frames from stereo to mono and vise versa.
  */
 
@@ -42,9 +48,4 @@
 /**
  * Create a media splitter/combiner with the specified parameters.
- * A splitter/combiner splits a single stereo/multichannel audio frame into
- * multiple mono audio frames to each channel when put_frame() is called, 
- * and combines mono frames from each channel into a stereo/multichannel 
- * frame when get_frame() is called.
- *
  * When the splitter/combiner is created, it creates an instance of
  * pjmedia_port. This media port represents the stereo/multichannel side
@@ -80,8 +81,6 @@
  *
  * @param splitcomb	    The splitter/combiner.
- * @param ch_num	    Audio channel number.
- * @param options	    Valid options are:
- *			    - PJMEDIA_SPLITCOMB_AUTO_DESTROY to destroy the
- *			      media port when the splitter is destroyed.
+ * @param ch_num	    Audio channel starting number (zero based).
+ * @param options	    Must be zero at the moment.
  * @param port		    The media port.
  *
@@ -108,8 +107,9 @@
  *			    buffers.
  * @param splitcomb	    The splitter/combiner.
- * @param ch_num	    Audio channel number.
- * @param options	    Valid options are:
- *			    - PJMEDIA_SPLITCOMB_AUTO_DESTROY to destroy the
- *			      media port when the splitter is destroyed.
+ * @param ch_num	    Audio channel starting number (zero based).
+ * @param options	    Normally is zero, but the lower 8-bit of the 
+ *			    options can be used to specify the number of 
+ *			    buffers in the circular buffer. If zero, then
+ *			    default number will be used (default: 8).
  * @param p_chport	    The media port created with reverse phase for
  *			    the specified audio channel.
Index: /pjproject/trunk/pjmedia/src/pjmedia/port.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/port.c	(revision 630)
+++ /pjproject/trunk/pjmedia/src/pjmedia/port.c	(revision 631)
@@ -23,4 +23,35 @@
 
 #define THIS_FILE	"port.c"
+
+
+/**
+ * This is an auxiliary function to initialize port info for
+ * ports which deal with PCM audio.
+ */
+PJ_DEF(pj_status_t) pjmedia_port_info_init( pjmedia_port_info *info,
+					    const pj_str_t *name,
+					    unsigned signature,
+					    unsigned clock_rate,
+					    unsigned channel_count,
+					    unsigned bits_per_sample,
+					    unsigned samples_per_frame)
+{
+    pj_bzero(info, sizeof(*info));
+
+    info->name = *name;
+    info->signature = signature;
+    info->type = PJMEDIA_TYPE_AUDIO;
+    info->has_info = PJ_TRUE;
+    info->need_info = PJ_FALSE;
+    info->pt = 0xFF;
+    info->encoding_name = pj_str("pcm");
+    info->clock_rate = clock_rate;
+    info->channel_count = channel_count;
+    info->bits_per_sample = bits_per_sample;
+    info->samples_per_frame = samples_per_frame;
+    info->bytes_per_frame = samples_per_frame * bits_per_sample / 8;
+
+    return PJ_SUCCESS;
+}
 
 
Index: /pjproject/trunk/pjmedia/src/pjmedia/splitcomb.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/splitcomb.c	(revision 631)
+++ /pjproject/trunk/pjmedia/src/pjmedia/splitcomb.c	(revision 631)
@@ -0,0 +1,656 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 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/splitcomb.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+
+#define SIGNATURE	    PJMEDIA_PORT_SIGNATURE('S', 'p', 'C', 'b')
+#define THIS_FILE	    "splitcomb.c"
+#define TMP_SAMP_TYPE	    pj_int16_t
+#define MAX_BUF_CNT	    8
+
+#if 0
+#   define TRACE_UP_(x)	PJ_LOG(5,x)
+#   define TRACE_DN_(x)	PJ_LOG(5,x)
+#else
+#   define TRACE_UP_(x)
+#   define TRACE_DN_(x)
+#endif
+
+#if 1
+#   define LOG_UP_(x)	PJ_LOG(5,x)
+#   define LOG_DN_(x)	PJ_LOG(5,x)
+#else
+#   define LOG_UP_(x)
+#   define LOG_DN_(x)
+#endif
+
+/*
+ * This structure describes the splitter/combiner.
+ */
+struct splitcomb
+{
+    pjmedia_port      base;
+
+    unsigned	      options;
+
+    /* Array of ports, one for each channel */
+    struct {
+	pjmedia_port *port;
+	pj_bool_t     reversed;
+    } *port_desc;
+
+    /* Temporary buffers needed to extract mono frame from
+     * multichannel frame. We could use stack for this, but this
+     * way it should be safer for devices with small stack size.
+     */
+    TMP_SAMP_TYPE    *get_buf;
+    TMP_SAMP_TYPE    *put_buf;
+};
+
+
+/*
+ * This structure describes reverse port.
+ */
+struct reverse_port
+{
+    pjmedia_port     base;
+    struct splitcomb*parent;
+    unsigned	     ch_num;
+
+    /* A reverse port need a temporary buffer to store frame
+     * (because of the different phase, see splitcomb.h for details). 
+     * Since we can not expect get_frame() and put_frame() to be
+     * called evenly one after another, we use circular buffers to
+     * accomodate the "jitter".
+     */
+    unsigned	     buf_cnt;
+
+    /* Downstream is the direction when get_frame() is called to the
+     * splitter/combiner port.
+     */
+    unsigned	     dn_read_pos, dn_write_pos, 
+		     dn_overflow_pos, dn_underflow_pos;
+    pj_int16_t	    *dnstream_buf[MAX_BUF_CNT];
+
+    /* Upstream is the direction when put_frame() is called to the
+     * splitter/combiner port.
+     */
+    unsigned	     up_read_pos, up_write_pos, 
+		     up_overflow_pos, up_underflow_pos;
+    pj_int16_t	    *upstream_buf[MAX_BUF_CNT];
+};
+
+
+/*
+ * Prototypes.
+ */
+static pj_status_t put_frame(pjmedia_port *this_port, 
+			     const pjmedia_frame *frame);
+static pj_status_t get_frame(pjmedia_port *this_port, 
+			     pjmedia_frame *frame);
+static pj_status_t on_destroy(pjmedia_port *this_port);
+
+static pj_status_t rport_put_frame(pjmedia_port *this_port, 
+				   const pjmedia_frame *frame);
+static pj_status_t rport_get_frame(pjmedia_port *this_port, 
+				   pjmedia_frame *frame);
+static pj_status_t rport_on_destroy(pjmedia_port *this_port);
+
+
+/*
+ * Create the splitter/combiner.
+ */
+PJ_DEF(pj_status_t) pjmedia_splitcomb_create( pj_pool_t *pool,
+					      unsigned clock_rate,
+					      unsigned channel_count,
+					      unsigned samples_per_frame,
+					      unsigned bits_per_sample,
+					      unsigned options,
+					      pjmedia_port **p_splitcomb)
+{
+    struct splitcomb *sc;
+
+    /* Sanity check */
+    PJ_ASSERT_RETURN(pool && clock_rate && channel_count &&
+		     samples_per_frame && bits_per_sample &&
+		     p_splitcomb, PJ_EINVAL);
+
+    /* Only supports 16 bits per sample */
+    PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+    *p_splitcomb = NULL;
+
+    /* Create the splitter/combiner structure */
+    sc = pj_pool_zalloc(pool, sizeof(struct splitcomb));
+    PJ_ASSERT_RETURN(sc != NULL, PJ_ENOMEM);
+
+    /* Create temporary buffers */
+    sc->get_buf = pj_pool_alloc(pool, samples_per_frame * 
+				      sizeof(TMP_SAMP_TYPE) /
+				      channel_count);
+    PJ_ASSERT_RETURN(sc->get_buf, PJ_ENOMEM);
+
+    sc->put_buf = pj_pool_alloc(pool, samples_per_frame * 
+				      sizeof(TMP_SAMP_TYPE) /
+				      channel_count);
+    PJ_ASSERT_RETURN(sc->put_buf, PJ_ENOMEM);
+
+
+    /* Save options */
+    sc->options = options;
+
+    /* Initialize port */
+    sc->base.info.name = pj_str("splitcomb");
+    sc->base.info.signature = SIGNATURE;
+    sc->base.info.type = PJMEDIA_TYPE_AUDIO;
+    sc->base.info.has_info = PJ_TRUE;
+    sc->base.info.need_info = PJ_FALSE;
+    sc->base.info.pt = 0xFF;
+    sc->base.info.encoding_name = pj_str("pcm");
+    sc->base.info.clock_rate = clock_rate;
+    sc->base.info.channel_count = channel_count;
+    sc->base.info.bits_per_sample = bits_per_sample;
+    sc->base.info.samples_per_frame = samples_per_frame;
+    sc->base.info.bytes_per_frame = samples_per_frame * bits_per_sample / 8;
+
+    sc->base.put_frame = &put_frame;
+    sc->base.get_frame = &get_frame;
+    sc->base.on_destroy = &on_destroy;
+
+    /* Init ports array */
+    sc->port_desc = pj_pool_zalloc(pool, channel_count*sizeof(*sc->port_desc));
+
+    /* Done for now */
+    *p_splitcomb = &sc->base;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Attach media port with the same phase as the splitter/combiner.
+ */
+PJ_DEF(pj_status_t) pjmedia_splitcomb_set_channel( pjmedia_port *splitcomb,
+						   unsigned ch_num,
+						   unsigned options,
+						   pjmedia_port *port)
+{
+    struct splitcomb *sc = (struct splitcomb*) splitcomb;
+
+    /* Sanity check */
+    PJ_ASSERT_RETURN(splitcomb && port, PJ_EINVAL);
+
+    /* Make sure this is really a splitcomb port */
+    PJ_ASSERT_RETURN(sc->base.info.signature == SIGNATURE, PJ_EINVAL);
+
+    /* Check the channel number */
+    PJ_ASSERT_RETURN(ch_num < sc->base.info.channel_count, PJ_EINVAL);
+
+    /* options is unused for now */
+    PJ_UNUSED_ARG(options);
+
+    sc->port_desc[ch_num].port = port;
+    sc->port_desc[ch_num].reversed = PJ_FALSE;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Create reverse phase port for the specified channel.
+ */
+PJ_DEF(pj_status_t) 
+pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool,
+				      pjmedia_port *splitcomb,
+				      unsigned ch_num,
+				      unsigned options,
+				      pjmedia_port **p_chport)
+{
+    struct splitcomb *sc = (struct splitcomb*) splitcomb;
+    struct reverse_port *rport;
+    unsigned i;
+    pjmedia_port *port;
+
+    /* Sanity check */
+    PJ_ASSERT_RETURN(pool && splitcomb, PJ_EINVAL);
+
+    /* Make sure this is really a splitcomb port */
+    PJ_ASSERT_RETURN(sc->base.info.signature == SIGNATURE, PJ_EINVAL);
+
+    /* Check the channel number */
+    PJ_ASSERT_RETURN(ch_num < sc->base.info.channel_count, PJ_EINVAL);
+
+    /* options is unused for now */
+    PJ_UNUSED_ARG(options);
+
+    /* Create the port */
+    rport = pj_pool_zalloc(pool, sizeof(struct reverse_port));
+    rport->parent = sc;
+    rport->ch_num = ch_num;
+
+    /* Initialize port info... */
+    port = &rport->base;
+    port->info.name = pj_str("splitcomb-ch");
+    port->info.signature = 0;
+    port->info.type = PJMEDIA_TYPE_AUDIO;
+    port->info.has_info = PJ_TRUE;
+    port->info.need_info = PJ_FALSE;
+    port->info.pt = 0xFF;
+    port->info.encoding_name = pj_str("pcm");
+    port->info.clock_rate = splitcomb->info.clock_rate;
+    port->info.channel_count = 1;
+    port->info.bits_per_sample = splitcomb->info.bits_per_sample;
+    port->info.samples_per_frame = splitcomb->info.samples_per_frame /
+				   splitcomb->info.channel_count;
+    port->info.bytes_per_frame = port->info.samples_per_frame * 
+				 port->info.bits_per_sample / 8;
+
+    /* ... and the callbacks */
+    port->put_frame = &rport_put_frame;
+    port->get_frame = &rport_get_frame;
+    port->on_destroy = &rport_on_destroy;
+
+
+    rport->buf_cnt = options & 0xFF;
+    if (rport->buf_cnt == 0)
+	rport->buf_cnt = MAX_BUF_CNT;
+
+    /* Create put buffers */
+    for (i=0; i<rport->buf_cnt; ++i) {
+	rport->dnstream_buf[i] = pj_pool_zalloc(pool, port->info.bytes_per_frame);
+	PJ_ASSERT_RETURN(rport->dnstream_buf[i], PJ_ENOMEM);
+    }
+    rport->dn_write_pos = rport->buf_cnt/2;
+
+    /* Create get buffers */
+    for (i=0; i<rport->buf_cnt; ++i) {
+	rport->upstream_buf[i] = pj_pool_zalloc(pool, 
+						port->info.bytes_per_frame);
+	PJ_ASSERT_RETURN(rport->upstream_buf[i], PJ_ENOMEM);
+    }
+    rport->up_write_pos = rport->buf_cnt/2;
+
+
+    /* Save port in the splitcomb */
+    sc->port_desc[ch_num].port = &rport->base;
+    sc->port_desc[ch_num].reversed = PJ_TRUE;
+
+
+    /* Done */
+    *p_chport = port;
+    return PJ_SUCCESS;
+}
+
+
+/* 
+ * Extract one mono frame from a multichannel frame. 
+ */
+static void extract_mono_frame( const pj_int16_t *in,
+			        pj_int16_t *out,
+				unsigned ch,
+				unsigned ch_cnt,
+				unsigned samples_count)
+{
+    unsigned i;
+
+    in += ch;
+    for (i=0; i<samples_count; ++i) {
+	*out++ = *in;
+	in += ch_cnt;
+    }
+}
+
+
+/* 
+ * Put one mono frame into a multichannel frame 
+ */
+static void store_mono_frame( const pj_int16_t *in,
+			      pj_int16_t *out,
+			      unsigned ch,
+			      unsigned ch_cnt,
+			      unsigned samples_count)
+{
+    unsigned i;
+
+    out += ch;
+    for (i=0; i<samples_count; ++i) {
+	*out = *in++;
+	out += ch_cnt;
+    }
+}
+
+
+/*
+ * "Write" a multichannel frame. This would split the multichannel frame
+ * into individual mono channel, and write it to the appropriate port.
+ */
+static pj_status_t put_frame(pjmedia_port *this_port, 
+			     const pjmedia_frame *frame)
+{
+    struct splitcomb *sc = (struct splitcomb*) this_port;
+    unsigned ch;
+
+    /* Handle null frame */
+    if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+	for (ch=0; ch < this_port->info.channel_count; ++ch) {
+	    pjmedia_port *port = sc->port_desc[ch].port;
+
+	    if (!port) continue;
+
+	    pjmedia_port_put_frame(port, frame);
+	}
+	return PJ_SUCCESS;
+    }
+
+    /* Not sure how we would handle partial frame, so better reject
+     * it for now.
+     */
+    PJ_ASSERT_RETURN(frame->size == this_port->info.bytes_per_frame,
+		     PJ_EINVAL);
+
+    /* 
+     * Write mono frame into each channels 
+     */
+    for (ch=0; ch < this_port->info.channel_count; ++ch) {
+	pjmedia_port *port = sc->port_desc[ch].port;
+
+	if (!port)
+	    continue;
+
+	if (!sc->port_desc[ch].reversed) {
+	    /* Write to normal port */
+	    pjmedia_frame mono_frame;
+
+	    /* Extract the mono frame */
+	    extract_mono_frame(frame->buf, sc->put_buf, ch, 
+			       this_port->info.channel_count, 
+			       frame->size * 8 / 
+				 this_port->info.bits_per_sample /
+				 this_port->info.channel_count);
+
+	    mono_frame.buf = sc->put_buf;
+	    mono_frame.size = frame->size / this_port->info.channel_count;
+	    mono_frame.type = frame->type;
+	    mono_frame.timestamp.u64 = frame->timestamp.u64;
+
+	    /* Write */
+	    pjmedia_port_put_frame(port, &mono_frame);
+
+	} else {
+	    /* Write to reversed phase port */
+	    struct reverse_port *rport = (struct reverse_port*)port;
+	    
+	    if (rport->dn_write_pos == rport->dn_read_pos) {
+
+		/* Only report overflow if the frame is constantly read
+		 * by the 'consumer' of the reverse port.
+		 * It is possible that nobody reads the buffer, so causing
+		 * overflow to happen rapidly, and writing log message this
+		 * way does not seem to be wise.
+		 */
+		if (rport->dn_read_pos != rport->dn_overflow_pos) {
+		    rport->dn_overflow_pos = rport->dn_read_pos;
+		    LOG_DN_((THIS_FILE, "Overflow in downstream direction"));
+		}
+
+		/* Adjust write position */
+		rport->dn_write_pos = 
+		    (rport->dn_write_pos + rport->buf_cnt/2) % 
+		    rport->buf_cnt;
+	    }
+
+	    /* Extract mono-frame and put it in downstream buffer */
+	    extract_mono_frame(frame->buf, 
+			       rport->dnstream_buf[rport->dn_write_pos],
+			       ch, this_port->info.channel_count, 
+			       frame->size * 8 / 
+				 this_port->info.bits_per_sample /
+				 this_port->info.channel_count);
+
+	    rport->dn_write_pos = (rport->dn_write_pos + 1) %
+			           rport->buf_cnt;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get a multichannel frame.
+ * This will get mono channel frame from each port and put the
+ * mono frame into the multichannel frame.
+ */
+static pj_status_t get_frame(pjmedia_port *this_port, 
+			     pjmedia_frame *frame)
+{
+    struct splitcomb *sc = (struct splitcomb*) this_port;
+    unsigned ch;
+    pj_bool_t has_frame = PJ_FALSE;
+
+    /* Clear output frame */
+    pjmedia_zero_samples(frame->buf, this_port->info.samples_per_frame);
+
+    /* Read frame from each port */
+    for (ch=0; ch < this_port->info.channel_count; ++ch) {
+	pjmedia_port *port = sc->port_desc[ch].port;
+	pjmedia_frame mono_frame;
+	pj_status_t status;
+
+	if (!port)
+	    continue;
+
+	/* Read from the port */
+	if (sc->port_desc[ch].reversed == PJ_FALSE) {
+	    /* Read from normal port */
+	    mono_frame.buf = sc->get_buf;
+	    mono_frame.size = port->info.bytes_per_frame;
+	    mono_frame.timestamp.u64 = frame->timestamp.u64;
+
+	    status = pjmedia_port_get_frame(port, &mono_frame);
+	    if (status != PJ_SUCCESS || 
+		mono_frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+	    {
+		continue;
+	    }
+
+	    /* Combine the mono frame into multichannel frame */
+	    store_mono_frame(mono_frame.buf, frame->buf, ch,
+			     this_port->info.channel_count,
+			     mono_frame.size * 8 /
+				this_port->info.bits_per_sample);
+
+	    frame->timestamp.u64 = mono_frame.timestamp.u64;
+
+	} else {
+	    /* Read from temporary buffer for reverse port */
+	    struct reverse_port *rport = (struct reverse_port*)port;
+
+	    /* Check for underflows */
+	    if (rport->up_read_pos == rport->up_write_pos) {
+
+		/* Only report underflow if the buffer is constantly filled
+		 * up at the other side.
+		 * It is possible that nobody writes the buffer, so causing
+		 * underflow to happen rapidly, and writing log message this
+		 * way does not seem to be wise.
+		 */
+		if (rport->up_write_pos != rport->up_underflow_pos) {
+		    rport->up_underflow_pos = rport->up_write_pos;
+		    LOG_UP_((THIS_FILE, "Underflow in upstream direction"));
+		}
+
+		/* Adjust read position */
+		rport->up_read_pos = 
+		    (rport->up_write_pos - rport->buf_cnt/2) %
+		    rport->buf_cnt;
+	    }
+
+	    TRACE_UP_((THIS_FILE, "Upstream read at buffer pos %d", 
+		       rport->up_read_pos));
+
+	    /* Combine the mono frame into multichannel frame */
+	    store_mono_frame(rport->upstream_buf[rport->up_read_pos], 
+			     frame->buf, ch,
+			     this_port->info.channel_count,
+			     port->info.samples_per_frame);
+
+	    rport->up_read_pos = (rport->up_read_pos + 1) %
+				   rport->buf_cnt;
+	}
+
+
+	has_frame = PJ_TRUE;
+    }
+
+    /* Return NO_FRAME is we don't get any frames from downstream ports */
+    if (has_frame) {
+	frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+	frame->size = this_port->info.bytes_per_frame;
+    } else
+	frame->type = PJMEDIA_FRAME_TYPE_NONE;
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_status_t on_destroy(pjmedia_port *this_port)
+{
+    /* Nothing to do */
+    PJ_UNUSED_ARG(this_port);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get a mono frame from a reversed phase channel.
+ */
+static pj_status_t rport_put_frame(pjmedia_port *this_port, 
+				   const pjmedia_frame *frame)
+{
+    struct reverse_port *rport = (struct reverse_port*) this_port;
+    unsigned count;
+
+    pj_assert(frame->size <= rport->base.info.bytes_per_frame);
+
+    /* Check for overflows */
+    if (rport->up_write_pos == rport->up_read_pos) {
+
+	/* Only report overflow if the frame is constantly read
+	 * at the other end of the buffer (the multichannel side).
+	 * It is possible that nobody reads the buffer, so causing
+	 * overflow to happen rapidly, and writing log message this
+	 * way does not seem to be wise.
+	 */
+	if (rport->up_read_pos != rport->up_overflow_pos) {
+	    rport->up_overflow_pos = rport->up_read_pos;
+	    LOG_UP_((THIS_FILE, "Overflow in upstream direction"));
+	}
+
+	/* Adjust the write position */
+	rport->up_write_pos = (rport->up_read_pos + rport->buf_cnt/2) %
+			       rport->buf_cnt;
+    }
+
+    /* Handle NULL frame */
+    if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) {
+	TRACE_UP_((THIS_FILE, "Upstream write %d null samples at buf pos %d",
+		   this_port->info.samples_per_frame, rport->up_write_pos));
+	pjmedia_zero_samples(rport->upstream_buf[rport->up_write_pos],
+			     this_port->info.samples_per_frame);
+	rport->up_write_pos = (rport->up_write_pos+1) % rport->buf_cnt;
+	return PJ_SUCCESS;
+    }
+
+    /* Not sure how to handle partial frame, so better reject for now */
+    PJ_ASSERT_RETURN(frame->size == this_port->info.bytes_per_frame,
+		     PJ_EINVAL);
+
+    /* Copy normal frame to curcular buffer */
+    count = frame->size * 8 / this_port->info.bits_per_sample;
+
+    TRACE_UP_((THIS_FILE, "Upstream write %d samples at buf pos %d",
+	       count, rport->up_write_pos));
+
+
+    pjmedia_copy_samples(rport->upstream_buf[rport->up_write_pos],
+			 frame->buf, count);
+    rport->up_write_pos = (rport->up_write_pos+1) % rport->buf_cnt;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get a mono frame from a reversed phase channel.
+ */
+static pj_status_t rport_get_frame(pjmedia_port *this_port, 
+				   pjmedia_frame *frame)
+{
+    struct reverse_port *rport = (struct reverse_port*) this_port;
+    unsigned count;
+
+    count = rport->base.info.samples_per_frame;
+
+    frame->size = this_port->info.bytes_per_frame;
+    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+    /* Check for underflows */
+    if (rport->dn_read_pos == rport->dn_write_pos) {
+
+	/* Only report underflow if the buffer is constantly filled
+	 * up at the other side.
+	 * It is possible that nobody writes the buffer, so causing
+	 * underflow to happen rapidly, and writing log message this
+	 * way does not seem to be wise.
+	 */
+	if (rport->dn_write_pos != rport->dn_underflow_pos) {
+	    rport->dn_underflow_pos = rport->dn_write_pos;
+	    LOG_DN_((THIS_FILE, "Underflow in downstream direction"));
+	}
+
+	/* Adjust read position */
+	rport->dn_read_pos = 
+	    (rport->dn_write_pos - rport->buf_cnt/2) % rport->buf_cnt;
+	
+    }
+
+    /* Get the samples from the circular buffer */
+    pjmedia_copy_samples(frame->buf, 
+			 rport->dnstream_buf[rport->dn_read_pos],
+			 count);
+    rport->dn_read_pos = (rport->dn_read_pos+1) % rport->buf_cnt;
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_status_t rport_on_destroy(pjmedia_port *this_port)
+{
+    /* Nothing to do */
+    PJ_UNUSED_ARG(this_port);
+
+    return PJ_SUCCESS;
+}
+
