/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 #include #include #include #include #include #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; ibuf[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(&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; }