summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia/splitcomb.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia/splitcomb.c')
-rw-r--r--pjmedia/src/pjmedia/splitcomb.c807
1 files changed, 807 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/splitcomb.c b/pjmedia/src/pjmedia/splitcomb.c
new file mode 100644
index 0000000..dd2a944
--- /dev/null
+++ b/pjmedia/src/pjmedia/splitcomb.c
@@ -0,0 +1,807 @@
+/* $Id: splitcomb.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * 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/splitcomb.h>
+#include <pjmedia/delaybuf.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+
+#define SIGNATURE PJMEDIA_SIG_PORT_SPLIT_COMB
+#define SIGNATURE_PORT PJMEDIA_SIG_PORT_SPLIT_COMB_P
+#define THIS_FILE "splitcomb.c"
+#define TMP_SAMP_TYPE pj_int16_t
+
+/* Maximum number of channels. */
+#define MAX_CHANNELS 16
+
+/* Maximum number of buffers to be accommodated by delaybuf */
+#define MAX_BUF_CNT PJMEDIA_SOUND_BUFFER_COUNT
+
+/* Maximum number of burst before we pause the media flow */
+#define MAX_BURST (buf_cnt + 6)
+
+/* Maximum number of NULL frames received before we pause the
+ * media flow.
+ */
+#define MAX_NULL_FRAMES (rport->max_burst)
+
+
+/* Operations */
+#define OP_PUT (1)
+#define OP_GET (-1)
+
+
+/*
+ * Media flow directions:
+ *
+ * put_frame() +-----+
+ * UPSTREAM ------------>|split|<--> DOWNSTREAM
+ * <------------|comb |
+ * get_frame() +-----+
+ *
+ */
+enum sc_dir
+{
+ /* This is the media direction from the splitcomb to the
+ * downstream port(s), which happens when:
+ * - put_frame() is called to the splitcomb
+ * - get_frame() is called to the reverse channel port.
+ */
+ DIR_DOWNSTREAM,
+
+ /* This is the media direction from the downstream port to
+ * the splitcomb, which happens when:
+ * - get_frame() is called to the splitcomb
+ * - put_frame() is called to the reverse channel port.
+ */
+ DIR_UPSTREAM
+};
+
+
+
+/*
+ * 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[MAX_CHANNELS];
+
+ /* 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;
+
+ /* Maximum burst before media flow is suspended.
+ * With reverse port, it's possible that either end of the
+ * port doesn't actually process the media flow (meaning, it
+ * stops calling get_frame()/put_frame()). When this happens,
+ * the other end will encounter excessive underflow or overflow,
+ * depending on which direction is not actively processed by
+ * the stopping end.
+ *
+ * To avoid excessive underflow/overflow, the media flow will
+ * be suspended once underflow/overflow goes over this max_burst
+ * limit.
+ */
+ int max_burst;
+
+ /* When the media interface port of the splitcomb or the reverse
+ * channel port is registered to conference bridge, the bridge
+ * will transmit NULL frames to the media port when the media
+ * port is not receiving any audio from other slots (for example,
+ * when no other slots are connected to the media port).
+ *
+ * When this happens, we will generate zero frame to our buffer,
+ * to avoid underflow/overflow. But after too many NULL frames
+ * are received, we will pause the media flow instead, to save
+ * some processing.
+ *
+ * This value controls how many NULL frames can be received
+ * before we suspend media flow for a particular direction.
+ */
+ unsigned max_null_frames;
+
+ /* A reverse port need a temporary buffer to store frames
+ * (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 delay buffers to
+ * accomodate the burst.
+ *
+ * We maintain state for each direction, hence the array. The
+ * array is indexed by direction (sc_dir).
+ */
+ struct {
+
+ /* The delay buffer where frames will be stored */
+ pjmedia_delay_buf *dbuf;
+
+ /* Flag to indicate that audio flow on this direction
+ * is currently being suspended (perhaps because nothing
+ * is processing the frame on the other end).
+ */
+ pj_bool_t paused;
+
+ /* Operation level. When the level exceeds a maximum value,
+ * the media flow on this direction will be paused.
+ */
+ int level;
+
+ /* Timestamp. */
+ pj_timestamp ts;
+
+ /* Number of NULL frames transmitted to this port so far.
+ * NULL frame indicate that nothing is transmitted, and
+ * once we get too many of this, we should pause the media
+ * flow to reduce processing.
+ */
+ unsigned null_cnt;
+
+ } buf[2];
+
+ /* Must have temporary put buffer for the delay buf,
+ * unfortunately.
+ */
+ pj_int16_t *tmp_up_buf;
+};
+
+
+/*
+ * Prototypes.
+ */
+static pj_status_t put_frame(pjmedia_port *this_port,
+ 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,
+ 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)
+{
+ const pj_str_t name = pj_str("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_T(pool, struct splitcomb);
+ PJ_ASSERT_RETURN(sc != NULL, PJ_ENOMEM);
+
+ /* Create temporary buffers */
+ sc->get_buf = (TMP_SAMP_TYPE*)
+ pj_pool_alloc(pool, samples_per_frame *
+ sizeof(TMP_SAMP_TYPE) /
+ channel_count);
+ PJ_ASSERT_RETURN(sc->get_buf, PJ_ENOMEM);
+
+ sc->put_buf = (TMP_SAMP_TYPE*)
+ 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 */
+ pjmedia_port_info_init(&sc->base.info, &name, SIGNATURE, clock_rate,
+ channel_count, bits_per_sample, samples_per_frame);
+
+ 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));
+ */
+ pj_bzero(sc->port_desc, 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 < PJMEDIA_PIA_CCNT(&sc->base.info), 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)
+{
+ const pj_str_t name = pj_str("scomb-rev");
+ struct splitcomb *sc = (struct splitcomb*) splitcomb;
+ struct reverse_port *rport;
+ unsigned buf_cnt;
+ const pjmedia_audio_format_detail *sc_afd, *p_afd;
+ pjmedia_port *port;
+ pj_status_t status;
+
+ /* 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 < PJMEDIA_PIA_CCNT(&sc->base.info), PJ_EINVAL);
+
+ /* options is unused for now */
+ PJ_UNUSED_ARG(options);
+
+ sc_afd = pjmedia_format_get_audio_format_detail(&splitcomb->info.fmt, 1);
+
+ /* Create the port */
+ rport = PJ_POOL_ZALLOC_T(pool, struct reverse_port);
+ rport->parent = sc;
+ rport->ch_num = ch_num;
+
+ /* Initialize port info... */
+ port = &rport->base;
+ pjmedia_port_info_init(&port->info, &name, SIGNATURE_PORT,
+ sc_afd->clock_rate, 1,
+ sc_afd->bits_per_sample,
+ PJMEDIA_PIA_SPF(&splitcomb->info) /
+ sc_afd->channel_count);
+
+ p_afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, 1);
+
+ /* ... and the callbacks */
+ port->put_frame = &rport_put_frame;
+ port->get_frame = &rport_get_frame;
+ port->on_destroy = &rport_on_destroy;
+
+ /* Buffer settings */
+ buf_cnt = options & 0xFF;
+ if (buf_cnt == 0)
+ buf_cnt = MAX_BUF_CNT;
+
+ rport->max_burst = MAX_BURST;
+ rport->max_null_frames = MAX_NULL_FRAMES;
+
+ /* Create downstream/put buffers */
+ status = pjmedia_delay_buf_create(pool, "scombdb-dn",
+ p_afd->clock_rate,
+ PJMEDIA_PIA_SPF(&port->info),
+ p_afd->channel_count,
+ buf_cnt * p_afd->frame_time_usec / 1000,
+ 0, &rport->buf[DIR_DOWNSTREAM].dbuf);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Create upstream/get buffers */
+ status = pjmedia_delay_buf_create(pool, "scombdb-up",
+ p_afd->clock_rate,
+ PJMEDIA_PIA_SPF(&port->info),
+ p_afd->channel_count,
+ buf_cnt * p_afd->frame_time_usec / 1000,
+ 0, &rport->buf[DIR_UPSTREAM].dbuf);
+ if (status != PJ_SUCCESS) {
+ pjmedia_delay_buf_destroy(rport->buf[DIR_DOWNSTREAM].dbuf);
+ return status;
+ }
+
+ /* And temporary upstream/get buffer */
+ rport->tmp_up_buf = (pj_int16_t*)
+ pj_pool_alloc(pool,
+ PJMEDIA_PIA_AVG_FSZ(&port->info));
+
+ /* 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 status;
+}
+
+
+/*
+ * 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;
+ }
+}
+
+/* Update operation on the specified direction */
+static void op_update(struct reverse_port *rport, int dir, int op)
+{
+ char *dir_name[2] = {"downstream", "upstream"};
+
+ rport->buf[dir].level += op;
+
+ if (op == OP_PUT) {
+ rport->buf[dir].ts.u64 += PJMEDIA_PIA_SPF(&rport->base.info);
+ }
+
+ if (rport->buf[dir].paused) {
+ if (rport->buf[dir].level < -rport->max_burst) {
+ /* Prevent the level from overflowing and resets back to zero */
+ rport->buf[dir].level = -rport->max_burst;
+ } else if (rport->buf[dir].level > rport->max_burst) {
+ /* Prevent the level from overflowing and resets back to zero */
+ rport->buf[dir].level = rport->max_burst;
+ } else {
+ /* Level has fallen below max level, we can resume
+ * media flow.
+ */
+ PJ_LOG(5,(rport->base.info.name.ptr,
+ "Resuming media flow on %s direction (level=%d)",
+ dir_name[dir], rport->buf[dir].level));
+ rport->buf[dir].level = 0;
+ rport->buf[dir].paused = PJ_FALSE;
+
+ //This will cause disruption in audio, and it seems to be
+ //working fine without this anyway, so we disable it for now.
+ //pjmedia_delay_buf_learn(rport->buf[dir].dbuf);
+
+ }
+ } else {
+ if (rport->buf[dir].level >= rport->max_burst ||
+ rport->buf[dir].level <= -rport->max_burst)
+ {
+ /* Level has reached maximum level, the other side of
+ * rport is not sending/retrieving frames. Pause the
+ * rport on this direction.
+ */
+ PJ_LOG(5,(rport->base.info.name.ptr,
+ "Pausing media flow on %s direction (level=%d)",
+ dir_name[dir], rport->buf[dir].level));
+ rport->buf[dir].paused = PJ_TRUE;
+ }
+ }
+}
+
+
+/*
+ * "Write" a multichannel frame downstream. 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,
+ 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 < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) {
+ pjmedia_port *port = sc->port_desc[ch].port;
+
+ if (!port) continue;
+
+ if (!sc->port_desc[ch].reversed) {
+ pjmedia_port_put_frame(port, frame);
+ } else {
+ struct reverse_port *rport = (struct reverse_port*)port;
+
+ /* Update the number of NULL frames received. Once we have too
+ * many of this, we'll stop calling op_update() to let the
+ * media be suspended.
+ */
+
+ if (++rport->buf[DIR_DOWNSTREAM].null_cnt >
+ rport->max_null_frames)
+ {
+ /* Prevent the counter from overflowing and resetting
+ * back to zero
+ */
+ rport->buf[DIR_DOWNSTREAM].null_cnt =
+ rport->max_null_frames + 1;
+ continue;
+ }
+
+ /* Write zero port to delaybuf so that it doesn't underflow.
+ * If we don't do this, get_frame() on this direction will
+ * cause delaybuf to generate missing frame and the last
+ * frame transmitted to delaybuf will be replayed multiple
+ * times, which doesn't sound good.
+ */
+
+ /* Update rport state. */
+ op_update(rport, DIR_DOWNSTREAM, OP_PUT);
+
+ /* Discard frame if rport is paused on this direction */
+ if (rport->buf[DIR_DOWNSTREAM].paused)
+ continue;
+
+ /* Generate zero frame. */
+ pjmedia_zero_samples(sc->put_buf,
+ PJMEDIA_PIA_SPF(&this_port->info));
+
+ /* Put frame to delay buffer */
+ pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf,
+ sc->put_buf);
+
+ }
+ }
+ return PJ_SUCCESS;
+ }
+
+ /* Not sure how we would handle partial frame, so better reject
+ * it for now.
+ */
+ PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info),
+ PJ_EINVAL);
+
+ /*
+ * Write mono frame into each channels
+ */
+ for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) {
+ pjmedia_port *port = sc->port_desc[ch].port;
+
+ if (!port)
+ continue;
+
+ /* Extract the mono frame to temporary buffer */
+ extract_mono_frame((const pj_int16_t*)frame->buf, sc->put_buf, ch,
+ PJMEDIA_PIA_CCNT(&this_port->info),
+ frame->size * 8 /
+ PJMEDIA_PIA_BITS(&this_port->info) /
+ PJMEDIA_PIA_CCNT(&this_port->info));
+
+ if (!sc->port_desc[ch].reversed) {
+ /* Write to normal port */
+ pjmedia_frame mono_frame;
+
+ mono_frame.buf = sc->put_buf;
+ mono_frame.size = frame->size / PJMEDIA_PIA_CCNT(&this_port->info);
+ 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;
+
+ /* Reset NULL frame counter */
+ rport->buf[DIR_DOWNSTREAM].null_cnt = 0;
+
+ /* Update rport state. */
+ op_update(rport, DIR_DOWNSTREAM, OP_PUT);
+
+ if (!rport->buf[DIR_DOWNSTREAM].paused) {
+ pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf,
+ sc->put_buf);
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get a multichannel frame upstream.
+ * 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;
+
+ /* Read frame from each port */
+ for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) {
+ pjmedia_port *port = sc->port_desc[ch].port;
+ pjmedia_frame mono_frame;
+ pj_status_t status;
+
+ if (!port) {
+ pjmedia_zero_samples(sc->get_buf,
+ PJMEDIA_PIA_SPF(&this_port->info) /
+ PJMEDIA_PIA_CCNT(&this_port->info));
+
+ } else if (sc->port_desc[ch].reversed == PJ_FALSE) {
+ /* Read from normal port */
+ mono_frame.buf = sc->get_buf;
+ mono_frame.size = PJMEDIA_PIA_AVG_FSZ(&port->info);
+ 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)
+ {
+ pjmedia_zero_samples(sc->get_buf,
+ PJMEDIA_PIA_SPF(&port->info));
+ }
+
+ frame->timestamp.u64 = mono_frame.timestamp.u64;
+
+ } else {
+ /* Read from temporary buffer for reverse port */
+ struct reverse_port *rport = (struct reverse_port*)port;
+
+ /* Update rport state. */
+ op_update(rport, DIR_UPSTREAM, OP_GET);
+
+ if (!rport->buf[DIR_UPSTREAM].paused) {
+ pjmedia_delay_buf_get(rport->buf[DIR_UPSTREAM].dbuf,
+ sc->get_buf);
+
+ } else {
+ pjmedia_zero_samples(sc->get_buf,
+ PJMEDIA_PIA_SPF(&port->info));
+ }
+
+ frame->timestamp.u64 = rport->buf[DIR_UPSTREAM].ts.u64;
+ }
+
+ /* Combine the mono frame into multichannel frame */
+ store_mono_frame(sc->get_buf,
+ (pj_int16_t*)frame->buf, ch,
+ PJMEDIA_PIA_CCNT(&this_port->info),
+ PJMEDIA_PIA_SPF(&this_port->info) /
+ PJMEDIA_PIA_CCNT(&this_port->info));
+
+ 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 = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
+ } else
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t on_destroy(pjmedia_port *this_port)
+{
+ /* Nothing to do for the splitcomb
+ * Reverse ports must be destroyed separately.
+ */
+ PJ_UNUSED_ARG(this_port);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Put a frame in the reverse port (upstream direction). This frame
+ * will be picked up by get_frame() above.
+ */
+static pj_status_t rport_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct reverse_port *rport = (struct reverse_port*) this_port;
+
+ pj_assert(frame->size <= PJMEDIA_PIA_AVG_FSZ(&rport->base.info));
+
+ /* Handle NULL frame */
+ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ /* Update the number of NULL frames received. Once we have too
+ * many of this, we'll stop calling op_update() to let the
+ * media be suspended.
+ */
+ if (++rport->buf[DIR_UPSTREAM].null_cnt > rport->max_null_frames) {
+ /* Prevent the counter from overflowing and resetting back
+ * to zero
+ */
+ rport->buf[DIR_UPSTREAM].null_cnt = rport->max_null_frames + 1;
+ return PJ_SUCCESS;
+ }
+
+ /* Write zero port to delaybuf so that it doesn't underflow.
+ * If we don't do this, get_frame() on this direction will
+ * cause delaybuf to generate missing frame and the last
+ * frame transmitted to delaybuf will be replayed multiple
+ * times, which doesn't sound good.
+ */
+
+ /* Update rport state. */
+ op_update(rport, DIR_UPSTREAM, OP_PUT);
+
+ /* Discard frame if rport is paused on this direction */
+ if (rport->buf[DIR_UPSTREAM].paused)
+ return PJ_SUCCESS;
+
+ /* Generate zero frame. */
+ pjmedia_zero_samples(rport->tmp_up_buf,
+ PJMEDIA_PIA_SPF(&this_port->info));
+
+ /* Put frame to delay buffer */
+ return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf,
+ rport->tmp_up_buf);
+ }
+
+ /* Not sure how to handle partial frame, so better reject for now */
+ PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info),
+ PJ_EINVAL);
+
+ /* Reset NULL frame counter */
+ rport->buf[DIR_UPSTREAM].null_cnt = 0;
+
+ /* Update rport state. */
+ op_update(rport, DIR_UPSTREAM, OP_PUT);
+
+ /* Discard frame if rport is paused on this direction */
+ if (rport->buf[DIR_UPSTREAM].paused)
+ return PJ_SUCCESS;
+
+ /* Unfortunately must copy to temporary buffer since delay buf
+ * modifies the frame content.
+ */
+ pjmedia_copy_samples(rport->tmp_up_buf, (const pj_int16_t*)frame->buf,
+ PJMEDIA_PIA_SPF(&this_port->info));
+
+ /* Put frame to delay buffer */
+ return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf,
+ rport->tmp_up_buf);
+}
+
+
+/* Get a mono frame from a reversed phase channel (downstream direction).
+ * The frame is put by put_frame() call to the splitcomb.
+ */
+static pj_status_t rport_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct reverse_port *rport = (struct reverse_port*) this_port;
+
+ /* Update state */
+ op_update(rport, DIR_DOWNSTREAM, OP_GET);
+
+ /* Return no frame if media flow on this direction is being
+ * paused.
+ */
+ if (rport->buf[DIR_DOWNSTREAM].paused) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+ }
+
+ /* Get frame from delay buffer */
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->timestamp.u64 = rport->buf[DIR_DOWNSTREAM].ts.u64;
+
+ return pjmedia_delay_buf_get(rport->buf[DIR_DOWNSTREAM].dbuf,
+ (short*)frame->buf);
+}
+
+
+static pj_status_t rport_on_destroy(pjmedia_port *this_port)
+{
+ struct reverse_port *rport = (struct reverse_port*) this_port;
+
+ pjmedia_delay_buf_destroy(rport->buf[DIR_DOWNSTREAM].dbuf);
+ pjmedia_delay_buf_destroy(rport->buf[DIR_UPSTREAM].dbuf);
+
+ return PJ_SUCCESS;
+}
+