| /* $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/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), |
| (unsigned)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; |
| } |
| |