summaryrefslogtreecommitdiff
path: root/pjmedia
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-07-27 22:03:51 +0000
committerBenny Prijono <bennylp@teluu.com>2006-07-27 22:03:51 +0000
commit9716653fda5019f1c944b0e1dafe304ab14e5aa3 (patch)
tree70c64b3db887daf41b04d4bcae4961a02e8ce93b /pjmedia
parentf3610aa459cf39dcb6707f2062be88d8880a123e (diff)
- Added splitter/combiner port (splitcomb.c)
- Added function to initialize pjmedia_port_info (still unused though) git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@631 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia')
-rw-r--r--pjmedia/build/Makefile2
-rw-r--r--pjmedia/build/pjmedia.dsp4
-rw-r--r--pjmedia/include/pjmedia/port.h36
-rw-r--r--pjmedia/include/pjmedia/splitcomb.h28
-rw-r--r--pjmedia/src/pjmedia/port.c31
-rw-r--r--pjmedia/src/pjmedia/splitcomb.c656
6 files changed, 742 insertions, 15 deletions
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index b9861951..7e183502 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -68,7 +68,7 @@ export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
clock_thread.o codec.o conference.o endpoint.o errno.o \
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 \
transport_udp.o wav_player.o wav_writer.o wave.o \
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp
index 0149b16a..e024f655 100644
--- a/pjmedia/build/pjmedia.dsp
+++ b/pjmedia/build/pjmedia.dsp
@@ -199,6 +199,10 @@ SOURCE=..\src\pjmedia\sound_port.c
# End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\splitcomb.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\stream.c
# End Source File
# Begin Source File
diff --git a/pjmedia/include/pjmedia/port.h b/pjmedia/include/pjmedia/port.h
index 794b3139..fb8bd839 100644
--- a/pjmedia/include/pjmedia/port.h
+++ b/pjmedia/include/pjmedia/port.h
@@ -356,13 +356,45 @@ struct pjmedia_port
/**
+ * 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,
pjmedia_frame *frame );
/**
* 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,
const pjmedia_frame *frame );
@@ -370,6 +402,10 @@ PJ_DECL(pj_status_t) pjmedia_port_put_frame( pjmedia_port *port,
/**
* 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 );
diff --git a/pjmedia/include/pjmedia/splitcomb.h b/pjmedia/include/pjmedia/splitcomb.h
index d1104488..4911499c 100644
--- a/pjmedia/include/pjmedia/splitcomb.h
+++ b/pjmedia/include/pjmedia/splitcomb.h
@@ -24,7 +24,7 @@
* @file splitcomb.h
* @brief Media channel splitter/combiner port.
*/
-#include <pjmedia/types.h>
+#include <pjmedia/port.h>
/**
@@ -34,6 +34,12 @@
* @{
* 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.
*/
PJ_BEGIN_DECL
@@ -41,11 +47,6 @@ PJ_BEGIN_DECL
/**
* 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
* of the splitter/combiner. Application needs to supply the splitter/
@@ -79,10 +80,8 @@ PJ_DECL(pj_status_t) pjmedia_splitcomb_create(pj_pool_t *pool,
* for all ports that have the same phase. And similarly for put_frame().
*
* @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.
*
* @return PJ_SUCCESS on success, or the appropriate error
@@ -107,10 +106,11 @@ PJ_DECL(pj_status_t) pjmedia_splitcomb_set_channel(pjmedia_port *splitcomb,
* @param pool The pool to allocate memory for the port and
* 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.
*
diff --git a/pjmedia/src/pjmedia/port.c b/pjmedia/src/pjmedia/port.c
index 96c07c08..f1baa98a 100644
--- a/pjmedia/src/pjmedia/port.c
+++ b/pjmedia/src/pjmedia/port.c
@@ -25,6 +25,37 @@
/**
+ * 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;
+}
+
+
+/**
* Get a frame from the port (and subsequent downstream ports).
*/
PJ_DEF(pj_status_t) pjmedia_port_get_frame( pjmedia_port *port,
diff --git a/pjmedia/src/pjmedia/splitcomb.c b/pjmedia/src/pjmedia/splitcomb.c
new file mode 100644
index 00000000..5130c73a
--- /dev/null
+++ b/pjmedia/src/pjmedia/splitcomb.c
@@ -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;
+}
+