diff options
author | David M. Lee <dlee@digium.com> | 2013-01-07 14:24:28 -0600 |
---|---|---|
committer | David M. Lee <dlee@digium.com> | 2013-01-07 14:24:28 -0600 |
commit | f3ab456a17af1c89a6e3be4d20c5944853df1cb0 (patch) | |
tree | d00e1a332cd038a6d906a1ea0ac91e1a4458e617 /pjmedia/src/pjmedia/conference.c |
Import pjproject-2.0.1
Diffstat (limited to 'pjmedia/src/pjmedia/conference.c')
-rw-r--r-- | pjmedia/src/pjmedia/conference.c | 2098 |
1 files changed, 2098 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c new file mode 100644 index 0000000..a08e7b0 --- /dev/null +++ b/pjmedia/src/pjmedia/conference.c @@ -0,0 +1,2098 @@ +/* $Id: conference.c 4162 2012-06-11 04:17:54Z 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/conference.h> +#include <pjmedia/alaw_ulaw.h> +#include <pjmedia/delaybuf.h> +#include <pjmedia/errno.h> +#include <pjmedia/port.h> +#include <pjmedia/resample.h> +#include <pjmedia/silencedet.h> +#include <pjmedia/sound_port.h> +#include <pjmedia/stereo.h> +#include <pj/array.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0 + +/* CONF_DEBUG enables detailed operation of the conference bridge. + * Beware that it prints large amounts of logs (several lines per frame). + */ +//#define CONF_DEBUG +#ifdef CONF_DEBUG +# include <stdio.h> +# define TRACE_(x) PJ_LOG(5,x) +#else +# define TRACE_(x) +#endif + + +/* REC_FILE macro enables recording of the samples written to the sound + * device. The file contains RAW PCM data with no header, and has the + * same settings (clock rate etc) as the conference bridge. + * This should only be enabled when debugging audio quality *only*. + */ +//#define REC_FILE "confrec.pcm" +#ifdef REC_FILE +static FILE *fhnd_rec; +#endif + + +#define THIS_FILE "conference.c" + +#define RX_BUF_COUNT PJMEDIA_SOUND_BUFFER_COUNT + +#define BYTES_PER_SAMPLE 2 + +#define SIGNATURE PJMEDIA_CONF_BRIDGE_SIGNATURE +#define SIGNATURE_PORT PJMEDIA_SIG_PORT_CONF_PASV +/* Normal level is hardcodec to 128 in all over places */ +#define NORMAL_LEVEL 128 +#define SLOT_TYPE unsigned +#define INVALID_SLOT ((SLOT_TYPE)-1) + + +/* These are settings to control the adaptivity of changes in the + * signal level of the ports, so that sudden change in signal level + * in the port does not cause misaligned signal (which causes noise). + */ +#define ATTACK_A (conf->clock_rate / conf->samples_per_frame) +#define ATTACK_B 1 +#define DECAY_A 0 +#define DECAY_B 1 + +#define SIMPLE_AGC(last, target) \ + if (target >= last) \ + target = (ATTACK_A*(last+1)+ATTACK_B*target)/(ATTACK_A+ATTACK_B); \ + else \ + target = (DECAY_A*last+DECAY_B*target)/(DECAY_A+DECAY_B) + +#define MAX_LEVEL (32767) +#define MIN_LEVEL (-32768) + +#define IS_OVERFLOW(s) ((s > MAX_LEVEL) || (s < MIN_LEVEL)) + + +/* + * DON'T GET CONFUSED WITH TX/RX!! + * + * TX and RX directions are always viewed from the conference bridge's point + * of view, and NOT from the port's point of view. So TX means the bridge + * is transmitting to the port, RX means the bridge is receiving from the + * port. + */ + + +/** + * This is a port connected to conference bridge. + */ +struct conf_port +{ + pj_str_t name; /**< Port name. */ + pjmedia_port *port; /**< get_frame() and put_frame() */ + pjmedia_port_op rx_setting; /**< Can we receive from this port */ + pjmedia_port_op tx_setting; /**< Can we transmit to this port */ + unsigned listener_cnt; /**< Number of listeners. */ + SLOT_TYPE *listener_slots;/**< Array of listeners. */ + unsigned transmitter_cnt;/**<Number of transmitters. */ + + /* Shortcut for port info. */ + unsigned clock_rate; /**< Port's clock rate. */ + unsigned samples_per_frame; /**< Port's samples per frame. */ + unsigned channel_count; /**< Port's channel count. */ + + /* Calculated signal levels: */ + unsigned tx_level; /**< Last tx level to this port. */ + unsigned rx_level; /**< Last rx level from this port. */ + + /* The normalized signal level adjustment. + * A value of 128 (NORMAL_LEVEL) means there's no adjustment. + */ + unsigned tx_adj_level; /**< Adjustment for TX. */ + unsigned rx_adj_level; /**< Adjustment for RX. */ + + /* Resample, for converting clock rate, if they're different. */ + pjmedia_resample *rx_resample; + pjmedia_resample *tx_resample; + + /* RX buffer is temporary buffer to be used when there is mismatch + * between port's sample rate or ptime with conference's sample rate + * or ptime. The buffer is used for sampling rate conversion AND/OR to + * buffer the samples until there are enough samples to fulfill a + * complete frame to be processed by the bridge. + * + * When both sample rate AND ptime of the port match the conference + * settings, this buffer will not be created. + * + * This buffer contains samples at port's clock rate. + * The size of this buffer is the sum between port's samples per frame + * and bridge's samples per frame. + */ + pj_int16_t *rx_buf; /**< The RX buffer. */ + unsigned rx_buf_cap; /**< Max size, in samples */ + unsigned rx_buf_count; /**< # of samples in the buf. */ + + /* Mix buf is a temporary buffer used to mix all signal received + * by this port from all other ports. The mixed signal will be + * automatically adjusted to the appropriate level whenever + * there is possibility of clipping. + * + * This buffer contains samples at bridge's clock rate. + * The size of this buffer is equal to samples per frame of the bridge. + */ + + int mix_adj; /**< Adjustment level for mix_buf. */ + int last_mix_adj; /**< Last adjustment level. */ + pj_int32_t *mix_buf; /**< Total sum of signal. */ + + /* Tx buffer is a temporary buffer to be used when there's mismatch + * between port's clock rate or ptime with conference's sample rate + * or ptime. This buffer is used as the source of the sampling rate + * conversion AND/OR to buffer the samples until there are enough + * samples to fulfill a complete frame to be transmitted to the port. + * + * When both sample rate and ptime of the port match the bridge's + * settings, this buffer will not be created. + * + * This buffer contains samples at port's clock rate. + * The size of this buffer is the sum between port's samples per frame + * and bridge's samples per frame. + */ + pj_int16_t *tx_buf; /**< Tx buffer. */ + unsigned tx_buf_cap; /**< Max size, in samples. */ + unsigned tx_buf_count; /**< # of samples in the buffer. */ + + /* When the port is not receiving signal from any other ports (e.g. when + * no other ports is transmitting to this port), the bridge periodically + * transmit NULL frame to the port to keep the port "alive" (for example, + * a stream port needs this heart-beat to periodically transmit silence + * frame to keep NAT binding alive). + * + * This NULL frame should be sent to the port at the port's ptime rate. + * So if the port's ptime is greater than the bridge's ptime, the bridge + * needs to delay the NULL frame until it's the right time to do so. + * + * This variable keeps track of how many pending NULL samples are being + * "held" for this port. Once this value reaches samples_per_frame + * value of the port, a NULL frame is sent. The samples value on this + * variable is clocked at the port's clock rate. + */ + unsigned tx_heart_beat; + + /* Delay buffer is a special buffer for sound device port (port 0, master + * port) and other passive ports (sound device port is also passive port). + * + * We need the delay buffer because we can not expect the mic and speaker + * thread to run equally after one another. In most systems, each thread + * will run multiple times before the other thread gains execution time. + * For example, in my system, mic thread is called three times, then + * speaker thread is called three times, and so on. This we call burst. + * + * There is also possibility of drift, unbalanced rate between put_frame + * and get_frame operation, in passive ports. If drift happens, snd_buf + * needs to be expanded or shrinked. + * + * Burst and drift are handled by delay buffer. + */ + pjmedia_delay_buf *delay_buf; +}; + + +/* + * Conference bridge. + */ +struct pjmedia_conf +{ + unsigned options; /**< Bitmask options. */ + unsigned max_ports; /**< Maximum ports. */ + unsigned port_cnt; /**< Current number of ports. */ + unsigned connect_cnt; /**< Total number of connections */ + pjmedia_snd_port *snd_dev_port; /**< Sound device port. */ + pjmedia_port *master_port; /**< Port zero's port. */ + char master_name_buf[80]; /**< Port0 name buffer. */ + pj_mutex_t *mutex; /**< Conference mutex. */ + struct conf_port **ports; /**< Array of ports. */ + unsigned clock_rate; /**< Sampling rate. */ + unsigned channel_count;/**< Number of channels (1=mono). */ + unsigned samples_per_frame; /**< Samples per frame. */ + unsigned bits_per_sample; /**< Bits per sample. */ +}; + + +/* 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 get_frame_pasv(pjmedia_port *this_port, + pjmedia_frame *frame); +static pj_status_t destroy_port(pjmedia_port *this_port); +static pj_status_t destroy_port_pasv(pjmedia_port *this_port); + + +/* + * Create port. + */ +static pj_status_t create_conf_port( pj_pool_t *pool, + pjmedia_conf *conf, + pjmedia_port *port, + const pj_str_t *name, + struct conf_port **p_conf_port) +{ + struct conf_port *conf_port; + pj_status_t status; + + /* Create port. */ + conf_port = PJ_POOL_ZALLOC_T(pool, struct conf_port); + PJ_ASSERT_RETURN(conf_port, PJ_ENOMEM); + + /* Set name */ + pj_strdup_with_null(pool, &conf_port->name, name); + + /* Default has tx and rx enabled. */ + conf_port->rx_setting = PJMEDIA_PORT_ENABLE; + conf_port->tx_setting = PJMEDIA_PORT_ENABLE; + + /* Default level adjustment is 128 (which means no adjustment) */ + conf_port->tx_adj_level = NORMAL_LEVEL; + conf_port->rx_adj_level = NORMAL_LEVEL; + + /* Create transmit flag array */ + conf_port->listener_slots = (SLOT_TYPE*) + pj_pool_zalloc(pool, + conf->max_ports * sizeof(SLOT_TYPE)); + PJ_ASSERT_RETURN(conf_port->listener_slots, PJ_ENOMEM); + + /* Save some port's infos, for convenience. */ + if (port) { + pjmedia_audio_format_detail *afd; + + afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, 1); + conf_port->port = port; + conf_port->clock_rate = afd->clock_rate; + conf_port->samples_per_frame = PJMEDIA_AFD_SPF(afd); + conf_port->channel_count = afd->channel_count; + } else { + conf_port->port = NULL; + conf_port->clock_rate = conf->clock_rate; + conf_port->samples_per_frame = conf->samples_per_frame; + conf_port->channel_count = conf->channel_count; + } + + /* If port's clock rate is different than conference's clock rate, + * create a resample sessions. + */ + if (conf_port->clock_rate != conf->clock_rate) { + + pj_bool_t high_quality; + pj_bool_t large_filter; + + high_quality = ((conf->options & PJMEDIA_CONF_USE_LINEAR)==0); + large_filter = ((conf->options & PJMEDIA_CONF_SMALL_FILTER)==0); + + /* Create resample for rx buffer. */ + status = pjmedia_resample_create( pool, + high_quality, + large_filter, + conf->channel_count, + conf_port->clock_rate,/* Rate in */ + conf->clock_rate, /* Rate out */ + conf->samples_per_frame * + conf_port->clock_rate / + conf->clock_rate, + &conf_port->rx_resample); + if (status != PJ_SUCCESS) + return status; + + + /* Create resample for tx buffer. */ + status = pjmedia_resample_create(pool, + high_quality, + large_filter, + conf->channel_count, + conf->clock_rate, /* Rate in */ + conf_port->clock_rate, /* Rate out */ + conf->samples_per_frame, + &conf_port->tx_resample); + if (status != PJ_SUCCESS) + return status; + } + + /* + * Initialize rx and tx buffer, only when port's samples per frame or + * port's clock rate or channel number is different then the conference + * bridge settings. + */ + if (conf_port->clock_rate != conf->clock_rate || + conf_port->channel_count != conf->channel_count || + conf_port->samples_per_frame != conf->samples_per_frame) + { + unsigned port_ptime, conf_ptime, buff_ptime; + + port_ptime = conf_port->samples_per_frame / conf_port->channel_count * + 1000 / conf_port->clock_rate; + conf_ptime = conf->samples_per_frame / conf->channel_count * + 1000 / conf->clock_rate; + + /* Calculate the size (in ptime) for the port buffer according to + * this formula: + * - if either ptime is an exact multiple of the other, then use + * the larger ptime (e.g. 20ms and 40ms, use 40ms). + * - if not, then the ptime is sum of both ptimes (e.g. 20ms + * and 30ms, use 50ms) + */ + if (port_ptime > conf_ptime) { + buff_ptime = port_ptime; + if (port_ptime % conf_ptime) + buff_ptime += conf_ptime; + } else { + buff_ptime = conf_ptime; + if (conf_ptime % port_ptime) + buff_ptime += port_ptime; + } + + /* Create RX buffer. */ + //conf_port->rx_buf_cap = (unsigned)(conf_port->samples_per_frame + + // conf->samples_per_frame * + // conf_port->clock_rate * 1.0 / + // conf->clock_rate + 0.5); + conf_port->rx_buf_cap = conf_port->clock_rate * buff_ptime / 1000; + if (conf_port->channel_count > conf->channel_count) + conf_port->rx_buf_cap *= conf_port->channel_count; + else + conf_port->rx_buf_cap *= conf->channel_count; + + conf_port->rx_buf_count = 0; + conf_port->rx_buf = (pj_int16_t*) + pj_pool_alloc(pool, conf_port->rx_buf_cap * + sizeof(conf_port->rx_buf[0])); + PJ_ASSERT_RETURN(conf_port->rx_buf, PJ_ENOMEM); + + /* Create TX buffer. */ + conf_port->tx_buf_cap = conf_port->rx_buf_cap; + conf_port->tx_buf_count = 0; + conf_port->tx_buf = (pj_int16_t*) + pj_pool_alloc(pool, conf_port->tx_buf_cap * + sizeof(conf_port->tx_buf[0])); + PJ_ASSERT_RETURN(conf_port->tx_buf, PJ_ENOMEM); + } + + + /* Create mix buffer. */ + conf_port->mix_buf = (pj_int32_t*) + pj_pool_zalloc(pool, conf->samples_per_frame * + sizeof(conf_port->mix_buf[0])); + PJ_ASSERT_RETURN(conf_port->mix_buf, PJ_ENOMEM); + conf_port->last_mix_adj = NORMAL_LEVEL; + + + /* Done */ + *p_conf_port = conf_port; + return PJ_SUCCESS; +} + + +/* + * Add passive port. + */ +static pj_status_t create_pasv_port( pjmedia_conf *conf, + pj_pool_t *pool, + const pj_str_t *name, + pjmedia_port *port, + struct conf_port **p_conf_port) +{ + struct conf_port *conf_port; + pj_status_t status; + unsigned ptime; + + /* Create port */ + status = create_conf_port(pool, conf, port, name, &conf_port); + if (status != PJ_SUCCESS) + return status; + + /* Passive port has delay buf. */ + ptime = conf->samples_per_frame * 1000 / conf->clock_rate / + conf->channel_count; + status = pjmedia_delay_buf_create(pool, name->ptr, + conf->clock_rate, + conf->samples_per_frame, + conf->channel_count, + RX_BUF_COUNT * ptime, /* max delay */ + 0, /* options */ + &conf_port->delay_buf); + if (status != PJ_SUCCESS) + return status; + + *p_conf_port = conf_port; + + return PJ_SUCCESS; +} + + +/* + * Create port zero for the sound device. + */ +static pj_status_t create_sound_port( pj_pool_t *pool, + pjmedia_conf *conf ) +{ + struct conf_port *conf_port; + pj_str_t name = { "Master/sound", 12 }; + pj_status_t status; + + + status = create_pasv_port(conf, pool, &name, NULL, &conf_port); + if (status != PJ_SUCCESS) + return status; + + + /* Create sound device port: */ + + if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) { + pjmedia_aud_stream *strm; + pjmedia_aud_param param; + + /* + * If capture is disabled then create player only port. + * Otherwise create bidirectional sound device port. + */ + if (conf->options & PJMEDIA_CONF_NO_MIC) { + status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate, + conf->channel_count, + conf->samples_per_frame, + conf->bits_per_sample, + 0, /* options */ + &conf->snd_dev_port); + + } else { + status = pjmedia_snd_port_create( pool, -1, -1, conf->clock_rate, + conf->channel_count, + conf->samples_per_frame, + conf->bits_per_sample, + 0, /* Options */ + &conf->snd_dev_port); + + } + + if (status != PJ_SUCCESS) + return status; + + strm = pjmedia_snd_port_get_snd_stream(conf->snd_dev_port); + status = pjmedia_aud_stream_get_param(strm, ¶m); + if (status == PJ_SUCCESS) { + pjmedia_aud_dev_info snd_dev_info; + if (conf->options & PJMEDIA_CONF_NO_MIC) + pjmedia_aud_dev_get_info(param.play_id, &snd_dev_info); + else + pjmedia_aud_dev_get_info(param.rec_id, &snd_dev_info); + pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info.name); + } + + PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0")); + } + + + /* Add the port to the bridge */ + conf->ports[0] = conf_port; + conf->port_cnt++; + + return PJ_SUCCESS; +} + +/* + * Create conference bridge. + */ +PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, + unsigned max_ports, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned options, + pjmedia_conf **p_conf ) +{ + pjmedia_conf *conf; + const pj_str_t name = { "Conf", 4 }; + pj_status_t status; + + /* Can only accept 16bits per sample, for now.. */ + PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); + + PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports", + max_ports)); + + /* Create and init conf structure. */ + conf = PJ_POOL_ZALLOC_T(pool, pjmedia_conf); + PJ_ASSERT_RETURN(conf, PJ_ENOMEM); + + conf->ports = (struct conf_port**) + pj_pool_zalloc(pool, max_ports*sizeof(void*)); + PJ_ASSERT_RETURN(conf->ports, PJ_ENOMEM); + + conf->options = options; + conf->max_ports = max_ports; + conf->clock_rate = clock_rate; + conf->channel_count = channel_count; + conf->samples_per_frame = samples_per_frame; + conf->bits_per_sample = bits_per_sample; + + + /* Create and initialize the master port interface. */ + conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port); + PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM); + + pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE, + clock_rate, channel_count, bits_per_sample, + samples_per_frame); + + conf->master_port->port_data.pdata = conf; + conf->master_port->port_data.ldata = 0; + + conf->master_port->get_frame = &get_frame; + conf->master_port->put_frame = &put_frame; + conf->master_port->on_destroy = &destroy_port; + + + /* Create port zero for sound device. */ + status = create_sound_port(pool, conf); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy(conf); + return status; + } + + /* Create mutex. */ + status = pj_mutex_create_recursive(pool, "conf", &conf->mutex); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy(conf); + return status; + } + + /* If sound device was created, connect sound device to the + * master port. + */ + if (conf->snd_dev_port) { + status = pjmedia_snd_port_connect( conf->snd_dev_port, + conf->master_port ); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy(conf); + return status; + } + } + + + /* Done */ + + *p_conf = conf; + + return PJ_SUCCESS; +} + + +/* + * Pause sound device. + */ +static pj_status_t pause_sound( pjmedia_conf *conf ) +{ + /* Do nothing. */ + PJ_UNUSED_ARG(conf); + return PJ_SUCCESS; +} + +/* + * Resume sound device. + */ +static pj_status_t resume_sound( pjmedia_conf *conf ) +{ + /* Do nothing. */ + PJ_UNUSED_ARG(conf); + return PJ_SUCCESS; +} + + +/** + * Destroy conference bridge. + */ +PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) +{ + unsigned i, ci; + + PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL); + + /* Destroy sound device port. */ + if (conf->snd_dev_port) { + pjmedia_snd_port_destroy(conf->snd_dev_port); + conf->snd_dev_port = NULL; + } + + /* Destroy delay buf of all (passive) ports. */ + for (i=0, ci=0; i<conf->max_ports && ci<conf->port_cnt; ++i) { + struct conf_port *cport; + + cport = conf->ports[i]; + if (!cport) + continue; + + ++ci; + if (cport->delay_buf) { + pjmedia_delay_buf_destroy(cport->delay_buf); + cport->delay_buf = NULL; + } + } + + /* Destroy mutex */ + if (conf->mutex) + pj_mutex_destroy(conf->mutex); + + return PJ_SUCCESS; +} + + +/* + * Destroy the master port (will destroy the conference) + */ +static pj_status_t destroy_port(pjmedia_port *this_port) +{ + pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + return pjmedia_conf_destroy(conf); +} + +static pj_status_t destroy_port_pasv(pjmedia_port *this_port) { + pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + struct conf_port *port = conf->ports[this_port->port_data.ldata]; + pj_status_t status; + + status = pjmedia_delay_buf_destroy(port->delay_buf); + if (status == PJ_SUCCESS) + port->delay_buf = NULL; + + return status; +} + +/* + * Get port zero interface. + */ +PJ_DEF(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf) +{ + /* Sanity check. */ + PJ_ASSERT_RETURN(conf != NULL, NULL); + + /* Can only return port interface when PJMEDIA_CONF_NO_DEVICE was + * present in the option. + */ + PJ_ASSERT_RETURN((conf->options & PJMEDIA_CONF_NO_DEVICE) != 0, NULL); + + return conf->master_port; +} + + +/* + * Set master port name. + */ +PJ_DEF(pj_status_t) pjmedia_conf_set_port0_name(pjmedia_conf *conf, + const pj_str_t *name) +{ + unsigned len; + + /* Sanity check. */ + PJ_ASSERT_RETURN(conf != NULL && name != NULL, PJ_EINVAL); + + len = name->slen; + if (len > sizeof(conf->master_name_buf)) + len = sizeof(conf->master_name_buf); + + if (len > 0) pj_memcpy(conf->master_name_buf, name->ptr, len); + + conf->ports[0]->name.ptr = conf->master_name_buf; + conf->ports[0]->name.slen = len; + + if (conf->master_port) + conf->master_port->info.name = conf->ports[0]->name; + + return PJ_SUCCESS; +} + +/* + * Add stream port to the conference bridge. + */ +PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, + pj_pool_t *pool, + pjmedia_port *strm_port, + const pj_str_t *port_name, + unsigned *p_port ) +{ + struct conf_port *conf_port; + unsigned index; + pj_status_t status; + + PJ_ASSERT_RETURN(conf && pool && strm_port, PJ_EINVAL); + + /* If port_name is not specified, use the port's name */ + if (!port_name) + port_name = &strm_port->info.name; + + /* For this version of PJMEDIA, channel(s) number MUST be: + * - same between port & conference bridge. + * - monochannel on port or conference bridge. + */ + if (PJMEDIA_PIA_CCNT(&strm_port->info) != conf->channel_count && + (PJMEDIA_PIA_CCNT(&strm_port->info) != 1 && + conf->channel_count != 1)) + { + pj_assert(!"Number of channels mismatch"); + return PJMEDIA_ENCCHANNEL; + } + + pj_mutex_lock(conf->mutex); + + if (conf->port_cnt >= conf->max_ports) { + pj_assert(!"Too many ports"); + pj_mutex_unlock(conf->mutex); + return PJ_ETOOMANY; + } + + /* Find empty port in the conference bridge. */ + for (index=0; index < conf->max_ports; ++index) { + if (conf->ports[index] == NULL) + break; + } + + pj_assert(index != conf->max_ports); + + /* Create conf port structure. */ + status = create_conf_port(pool, conf, strm_port, port_name, &conf_port); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(conf->mutex); + return status; + } + + /* Put the port. */ + conf->ports[index] = conf_port; + conf->port_cnt++; + + /* Done. */ + if (p_port) { + *p_port = index; + } + + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + + +/* + * Add passive port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf, + pj_pool_t *pool, + const pj_str_t *name, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned options, + unsigned *p_slot, + pjmedia_port **p_port ) +{ + struct conf_port *conf_port; + pjmedia_port *port; + unsigned index; + pj_str_t tmp; + pj_status_t status; + + PJ_LOG(1, (THIS_FILE, "This API has been deprecated since 1.3 and will " + "be removed in the future release!")); + + PJ_ASSERT_RETURN(conf && pool, PJ_EINVAL); + + /* For this version of PJMEDIA, channel(s) number MUST be: + * - same between port & conference bridge. + * - monochannel on port or conference bridge. + */ + if (channel_count != conf->channel_count && + (channel_count != 1 && conf->channel_count != 1)) + { + pj_assert(!"Number of channels mismatch"); + return PJMEDIA_ENCCHANNEL; + } + + /* For this version, options must be zero */ + PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); + PJ_UNUSED_ARG(options); + + pj_mutex_lock(conf->mutex); + + if (conf->port_cnt >= conf->max_ports) { + pj_assert(!"Too many ports"); + pj_mutex_unlock(conf->mutex); + return PJ_ETOOMANY; + } + + /* Find empty port in the conference bridge. */ + for (index=0; index < conf->max_ports; ++index) { + if (conf->ports[index] == NULL) + break; + } + + pj_assert(index != conf->max_ports); + + if (name == NULL) { + name = &tmp; + + tmp.ptr = (char*) pj_pool_alloc(pool, 32); + tmp.slen = pj_ansi_snprintf(tmp.ptr, 32, "ConfPort#%d", index); + } + + /* Create and initialize the media port structure. */ + port = PJ_POOL_ZALLOC_T(pool, pjmedia_port); + PJ_ASSERT_RETURN(port, PJ_ENOMEM); + + pjmedia_port_info_init(&port->info, name, SIGNATURE_PORT, + clock_rate, channel_count, bits_per_sample, + samples_per_frame); + + port->port_data.pdata = conf; + port->port_data.ldata = index; + + port->get_frame = &get_frame_pasv; + port->put_frame = &put_frame; + port->on_destroy = &destroy_port_pasv; + + + /* Create conf port structure. */ + status = create_pasv_port(conf, pool, name, port, &conf_port); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(conf->mutex); + return status; + } + + + /* Put the port. */ + conf->ports[index] = conf_port; + conf->port_cnt++; + + /* Done. */ + if (p_slot) + *p_slot = index; + if (p_port) + *p_port = port; + + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + + + +/* + * Change TX and RX settings for the port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf, + unsigned slot, + pjmedia_port_op tx, + pjmedia_port_op rx) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + pj_mutex_lock(conf->mutex); + + /* Port must be valid. */ + conf_port = conf->ports[slot]; + if (conf_port == NULL) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + conf_port = conf->ports[slot]; + + if (tx != PJMEDIA_PORT_NO_CHANGE) + conf_port->tx_setting = tx; + + if (rx != PJMEDIA_PORT_NO_CHANGE) + conf_port->rx_setting = rx; + + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + + +/* + * Connect port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf, + unsigned src_slot, + unsigned sink_slot, + int level ) +{ + struct conf_port *src_port, *dst_port; + pj_bool_t start_sound = PJ_FALSE; + unsigned i; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports && + sink_slot<conf->max_ports, PJ_EINVAL); + + /* For now, level MUST be zero. */ + PJ_ASSERT_RETURN(level == 0, PJ_EINVAL); + + pj_mutex_lock(conf->mutex); + + /* Ports must be valid. */ + src_port = conf->ports[src_slot]; + dst_port = conf->ports[sink_slot]; + if (!src_port || !dst_port) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + /* Check if connection has been made */ + for (i=0; i<src_port->listener_cnt; ++i) { + if (src_port->listener_slots[i] == sink_slot) + break; + } + + if (i == src_port->listener_cnt) { + src_port->listener_slots[src_port->listener_cnt] = sink_slot; + ++conf->connect_cnt; + ++src_port->listener_cnt; + ++dst_port->transmitter_cnt; + + if (conf->connect_cnt == 1) + start_sound = 1; + + PJ_LOG(4,(THIS_FILE,"Port %d (%.*s) transmitting to port %d (%.*s)", + src_slot, + (int)src_port->name.slen, + src_port->name.ptr, + sink_slot, + (int)dst_port->name.slen, + dst_port->name.ptr)); + } + + pj_mutex_unlock(conf->mutex); + + /* Sound device must be started without mutex, otherwise the + * sound thread will deadlock (?) + */ + if (start_sound) + resume_sound(conf); + + return PJ_SUCCESS; +} + + +/* + * Disconnect port + */ +PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf, + unsigned src_slot, + unsigned sink_slot ) +{ + struct conf_port *src_port, *dst_port; + unsigned i; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports && + sink_slot<conf->max_ports, PJ_EINVAL); + + pj_mutex_lock(conf->mutex); + + /* Ports must be valid. */ + src_port = conf->ports[src_slot]; + dst_port = conf->ports[sink_slot]; + if (!src_port || !dst_port) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + /* Check if connection has been made */ + for (i=0; i<src_port->listener_cnt; ++i) { + if (src_port->listener_slots[i] == sink_slot) + break; + } + + if (i != src_port->listener_cnt) { + pj_assert(src_port->listener_cnt > 0 && + src_port->listener_cnt < conf->max_ports); + pj_assert(dst_port->transmitter_cnt > 0 && + dst_port->transmitter_cnt < conf->max_ports); + pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE), + src_port->listener_cnt, i); + --conf->connect_cnt; + --src_port->listener_cnt; + --dst_port->transmitter_cnt; + + PJ_LOG(4,(THIS_FILE, + "Port %d (%.*s) stop transmitting to port %d (%.*s)", + src_slot, + (int)src_port->name.slen, + src_port->name.ptr, + sink_slot, + (int)dst_port->name.slen, + dst_port->name.ptr)); + + /* if source port is passive port and has no listener, reset delaybuf */ + if (src_port->delay_buf && src_port->listener_cnt == 0) + pjmedia_delay_buf_reset(src_port->delay_buf); + } + + pj_mutex_unlock(conf->mutex); + + if (conf->connect_cnt == 0) { + pause_sound(conf); + } + + return PJ_SUCCESS; +} + +/* + * Get number of ports currently registered to the conference bridge. + */ +PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf) +{ + return conf->port_cnt; +} + +/* + * Get total number of ports connections currently set up in the bridge. + */ +PJ_DEF(unsigned) pjmedia_conf_get_connect_count(pjmedia_conf *conf) +{ + return conf->connect_cnt; +} + + +/* + * Remove the specified port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf, + unsigned port ) +{ + struct conf_port *conf_port; + unsigned i; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL); + + /* Suspend the sound devices. + * Don't want to remove port while port is being accessed by sound + * device's threads! + */ + + pj_mutex_lock(conf->mutex); + + /* Port must be valid. */ + conf_port = conf->ports[port]; + if (conf_port == NULL) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + conf_port->tx_setting = PJMEDIA_PORT_DISABLE; + conf_port->rx_setting = PJMEDIA_PORT_DISABLE; + + /* Remove this port from transmit array of other ports. */ + for (i=0; i<conf->max_ports; ++i) { + unsigned j; + struct conf_port *src_port; + + src_port = conf->ports[i]; + + if (!src_port) + continue; + + if (src_port->listener_cnt == 0) + continue; + + for (j=0; j<src_port->listener_cnt; ++j) { + if (src_port->listener_slots[j] == port) { + pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE), + src_port->listener_cnt, j); + pj_assert(conf->connect_cnt > 0); + --conf->connect_cnt; + --src_port->listener_cnt; + break; + } + } + } + + /* Update transmitter_cnt of ports we're transmitting to */ + while (conf_port->listener_cnt) { + unsigned dst_slot; + struct conf_port *dst_port; + + dst_slot = conf_port->listener_slots[conf_port->listener_cnt-1]; + dst_port = conf->ports[dst_slot]; + --dst_port->transmitter_cnt; + --conf_port->listener_cnt; + pj_assert(conf->connect_cnt > 0); + --conf->connect_cnt; + } + + /* Destroy pjmedia port if this conf port is passive port, + * i.e: has delay buf. + */ + if (conf_port->delay_buf) { + pjmedia_port_destroy(conf_port->port); + conf_port->port = NULL; + } + + /* Remove the port. */ + conf->ports[port] = NULL; + --conf->port_cnt; + + pj_mutex_unlock(conf->mutex); + + + /* Stop sound if there's no connection. */ + if (conf->connect_cnt == 0) { + pause_sound(conf); + } + + return PJ_SUCCESS; +} + + +/* + * Enum ports. + */ +PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf, + unsigned ports[], + unsigned *p_count ) +{ + unsigned i, count=0; + + PJ_ASSERT_RETURN(conf && p_count && ports, PJ_EINVAL); + + /* Lock mutex */ + pj_mutex_lock(conf->mutex); + + for (i=0; i<conf->max_ports && count<*p_count; ++i) { + if (!conf->ports[i]) + continue; + + ports[count++] = i; + } + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + *p_count = count; + return PJ_SUCCESS; +} + +/* + * Get port info + */ +PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf, + unsigned slot, + pjmedia_conf_port_info *info) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Lock mutex */ + pj_mutex_lock(conf->mutex); + + /* Port must be valid. */ + conf_port = conf->ports[slot]; + if (conf_port == NULL) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + info->slot = slot; + info->name = conf_port->name; + info->tx_setting = conf_port->tx_setting; + info->rx_setting = conf_port->rx_setting; + info->listener_cnt = conf_port->listener_cnt; + info->listener_slots = conf_port->listener_slots; + info->transmitter_cnt = conf_port->transmitter_cnt; + info->clock_rate = conf_port->clock_rate; + info->channel_count = conf_port->channel_count; + info->samples_per_frame = conf_port->samples_per_frame; + info->bits_per_sample = conf->bits_per_sample; + info->tx_adj_level = conf_port->tx_adj_level - NORMAL_LEVEL; + info->rx_adj_level = conf_port->rx_adj_level - NORMAL_LEVEL; + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf, + unsigned *size, + pjmedia_conf_port_info info[]) +{ + unsigned i, count=0; + + PJ_ASSERT_RETURN(conf && size && info, PJ_EINVAL); + + /* Lock mutex */ + pj_mutex_lock(conf->mutex); + + for (i=0; i<conf->max_ports && count<*size; ++i) { + if (!conf->ports[i]) + continue; + + pjmedia_conf_get_port_info(conf, i, &info[count]); + ++count; + } + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + *size = count; + return PJ_SUCCESS; +} + + +/* + * Get signal level. + */ +PJ_DEF(pj_status_t) pjmedia_conf_get_signal_level( pjmedia_conf *conf, + unsigned slot, + unsigned *tx_level, + unsigned *rx_level) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Lock mutex */ + pj_mutex_lock(conf->mutex); + + /* Port must be valid. */ + conf_port = conf->ports[slot]; + if (conf_port == NULL) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + if (tx_level != NULL) { + *tx_level = conf_port->tx_level; + } + + if (rx_level != NULL) + *rx_level = conf_port->rx_level; + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + + +/* + * Adjust RX level of individual port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_adjust_rx_level( pjmedia_conf *conf, + unsigned slot, + int adj_level ) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Value must be from -128 to +127 */ + /* Disabled, you can put more than +127, at your own risk: + PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL); + */ + PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL); + + /* Lock mutex */ + pj_mutex_lock(conf->mutex); + + /* Port must be valid. */ + conf_port = conf->ports[slot]; + if (conf_port == NULL) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + /* Set normalized adjustment level. */ + conf_port->rx_adj_level = adj_level + NORMAL_LEVEL; + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + + +/* + * Adjust TX level of individual port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_adjust_tx_level( pjmedia_conf *conf, + unsigned slot, + int adj_level ) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Value must be from -128 to +127 */ + /* Disabled, you can put more than +127,, at your own risk: + PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL); + */ + PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL); + + /* Lock mutex */ + pj_mutex_lock(conf->mutex); + + /* Port must be valid. */ + conf_port = conf->ports[slot]; + if (conf_port == NULL) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + /* Set normalized adjustment level. */ + conf_port->tx_adj_level = adj_level + NORMAL_LEVEL; + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + + +/* + * Read from port. + */ +static pj_status_t read_port( pjmedia_conf *conf, + struct conf_port *cport, pj_int16_t *frame, + pj_size_t count, pjmedia_frame_type *type ) +{ + + pj_assert(count == conf->samples_per_frame); + + TRACE_((THIS_FILE, "read_port %.*s: count=%d", + (int)cport->name.slen, cport->name.ptr, + count)); + + /* + * If port's samples per frame and sampling rate and channel count + * matche conference bridge's settings, get the frame directly from + * the port. + */ + if (cport->rx_buf_cap == 0) { + pjmedia_frame f; + pj_status_t status; + + f.buf = frame; + f.size = count * BYTES_PER_SAMPLE; + + TRACE_((THIS_FILE, " get_frame %.*s: count=%d", + (int)cport->name.slen, cport->name.ptr, + count)); + + status = pjmedia_port_get_frame(cport->port, &f); + + *type = f.type; + + return status; + + } else { + unsigned samples_req; + + /* Initialize frame type */ + if (cport->rx_buf_count == 0) { + *type = PJMEDIA_FRAME_TYPE_NONE; + } else { + /* we got some samples in the buffer */ + *type = PJMEDIA_FRAME_TYPE_AUDIO; + } + + /* + * If we don't have enough samples in rx_buf, read from the port + * first. Remember that rx_buf may be in different clock rate and + * channel count! + */ + + samples_req = (unsigned) (count * 1.0 * + cport->clock_rate / conf->clock_rate + 0.5); + + while (cport->rx_buf_count < samples_req) { + + pjmedia_frame f; + pj_status_t status; + + f.buf = cport->rx_buf + cport->rx_buf_count; + f.size = cport->samples_per_frame * BYTES_PER_SAMPLE; + + TRACE_((THIS_FILE, " get_frame, count=%d", + cport->samples_per_frame)); + + status = pjmedia_port_get_frame(cport->port, &f); + + if (status != PJ_SUCCESS) { + /* Fatal error! */ + return status; + } + + if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) { + TRACE_((THIS_FILE, " get_frame returned non-audio")); + pjmedia_zero_samples( cport->rx_buf + cport->rx_buf_count, + cport->samples_per_frame); + } else { + /* We've got at least one frame */ + *type = PJMEDIA_FRAME_TYPE_AUDIO; + } + + /* Adjust channels */ + if (cport->channel_count != conf->channel_count) { + if (cport->channel_count == 1) { + pjmedia_convert_channel_1ton((pj_int16_t*)f.buf, + (const pj_int16_t*)f.buf, + conf->channel_count, + cport->samples_per_frame, + 0); + cport->rx_buf_count += (cport->samples_per_frame * + conf->channel_count); + } else { /* conf->channel_count == 1 */ + pjmedia_convert_channel_nto1((pj_int16_t*)f.buf, + (const pj_int16_t*)f.buf, + cport->channel_count, + cport->samples_per_frame, + PJMEDIA_STEREO_MIX, 0); + cport->rx_buf_count += (cport->samples_per_frame / + cport->channel_count); + } + } else { + cport->rx_buf_count += cport->samples_per_frame; + } + + TRACE_((THIS_FILE, " rx buffer size is now %d", + cport->rx_buf_count)); + + pj_assert(cport->rx_buf_count <= cport->rx_buf_cap); + } + + /* + * If port's clock_rate is different, resample. + * Otherwise just copy. + */ + if (cport->clock_rate != conf->clock_rate) { + + unsigned src_count; + + TRACE_((THIS_FILE, " resample, input count=%d", + pjmedia_resample_get_input_size(cport->rx_resample))); + + pjmedia_resample_run( cport->rx_resample,cport->rx_buf, frame); + + src_count = (unsigned)(count * 1.0 * cport->clock_rate / + conf->clock_rate + 0.5); + cport->rx_buf_count -= src_count; + if (cport->rx_buf_count) { + pjmedia_move_samples(cport->rx_buf, cport->rx_buf+src_count, + cport->rx_buf_count); + } + + TRACE_((THIS_FILE, " rx buffer size is now %d", + cport->rx_buf_count)); + + } else { + + pjmedia_copy_samples(frame, cport->rx_buf, count); + cport->rx_buf_count -= count; + if (cport->rx_buf_count) { + pjmedia_move_samples(cport->rx_buf, cport->rx_buf+count, + cport->rx_buf_count); + } + } + } + + return PJ_SUCCESS; +} + + +/* + * Write the mixed signal to the port. + */ +static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, + const pj_timestamp *timestamp, + pjmedia_frame_type *frm_type) +{ + pj_int16_t *buf; + unsigned j, ts; + pj_status_t status; + pj_int32_t adj_level; + pj_int32_t tx_level; + unsigned dst_count; + + *frm_type = PJMEDIA_FRAME_TYPE_AUDIO; + + /* If port is muted or nobody is transmitting to this port, + * transmit NULL frame. + */ + if (cport->tx_setting == PJMEDIA_PORT_MUTE || cport->transmitter_cnt==0) { + + pjmedia_frame frame; + + /* Clear left-over samples in tx_buffer, if any, so that it won't + * be transmitted next time we have audio signal. + */ + cport->tx_buf_count = 0; + + /* Add sample counts to heart-beat samples */ + cport->tx_heart_beat += conf->samples_per_frame * cport->clock_rate / + conf->clock_rate * + cport->channel_count / conf->channel_count; + + /* Set frame timestamp */ + frame.timestamp.u64 = timestamp->u64 * cport->clock_rate / + conf->clock_rate; + frame.type = PJMEDIA_FRAME_TYPE_NONE; + frame.buf = NULL; + frame.size = 0; + + /* Transmit heart-beat frames (may transmit more than one NULL frame + * if port's ptime is less than bridge's ptime. + */ + if (cport->port && cport->port->put_frame) { + while (cport->tx_heart_beat >= cport->samples_per_frame) { + + pjmedia_port_put_frame(cport->port, &frame); + + cport->tx_heart_beat -= cport->samples_per_frame; + frame.timestamp.u64 += cport->samples_per_frame; + } + } + + cport->tx_level = 0; + *frm_type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; + + } else if (cport->tx_setting != PJMEDIA_PORT_ENABLE) { + cport->tx_level = 0; + *frm_type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; + } + + /* Reset heart-beat sample count */ + cport->tx_heart_beat = 0; + + buf = (pj_int16_t*) cport->mix_buf; + + /* If there are sources in the mix buffer, convert the mixed samples + * from 32bit to 16bit in the mixed samples itself. This is possible + * because mixed sample is 32bit. + * + * In addition to this process, if we need to change the level of + * TX signal, we adjust is here too. + */ + + /* Calculate signal level and adjust the signal when needed. + * Two adjustments performed at once: + * 1. user setting adjustment (tx_adj_level). + * 2. automatic adjustment of overflowed mixed buffer (mix_adj). + */ + + /* Apply simple AGC to the mix_adj, the automatic adjust, to avoid + * dramatic change in the level thus causing noise because the signal + * is now not aligned with the signal from the previous frame. + */ + SIMPLE_AGC(cport->last_mix_adj, cport->mix_adj); + cport->last_mix_adj = cport->mix_adj; + + /* adj_level = cport->tx_adj_level * cport->mix_adj / NORMAL_LEVEL;*/ + adj_level = cport->tx_adj_level * cport->mix_adj; + adj_level >>= 7; + + tx_level = 0; + + if (adj_level != NORMAL_LEVEL) { + for (j=0; j<conf->samples_per_frame; ++j) { + pj_int32_t itemp = cport->mix_buf[j]; + + /* Adjust the level */ + /*itemp = itemp * adj_level / NORMAL_LEVEL;*/ + itemp = (itemp * adj_level) >> 7; + + /* Clip the signal if it's too loud */ + if (itemp > MAX_LEVEL) itemp = MAX_LEVEL; + else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL; + + /* Put back in the buffer. */ + buf[j] = (pj_int16_t) itemp; + + tx_level += (buf[j]>=0? buf[j] : -buf[j]); + } + } else { + for (j=0; j<conf->samples_per_frame; ++j) { + buf[j] = (pj_int16_t) cport->mix_buf[j]; + tx_level += (buf[j]>=0? buf[j] : -buf[j]); + } + } + + tx_level /= conf->samples_per_frame; + + /* Convert level to 8bit complement ulaw */ + tx_level = pjmedia_linear2ulaw(tx_level) ^ 0xff; + + cport->tx_level = tx_level; + + /* If port has the same clock_rate and samples_per_frame and + * number of channels as the conference bridge, transmit the + * frame as is. + */ + if (cport->clock_rate == conf->clock_rate && + cport->samples_per_frame == conf->samples_per_frame && + cport->channel_count == conf->channel_count) + { + if (cport->port != NULL) { + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = buf; + frame.size = conf->samples_per_frame * BYTES_PER_SAMPLE; + /* No need to adjust timestamp, port has the same + * clock rate as conference bridge + */ + frame.timestamp = *timestamp; + + TRACE_((THIS_FILE, "put_frame %.*s, count=%d", + (int)cport->name.slen, cport->name.ptr, + frame.size / BYTES_PER_SAMPLE)); + + return pjmedia_port_put_frame(cport->port, &frame); + } else + return PJ_SUCCESS; + } + + /* If it has different clock_rate, must resample. */ + if (cport->clock_rate != conf->clock_rate) { + pjmedia_resample_run( cport->tx_resample, buf, + cport->tx_buf + cport->tx_buf_count ); + dst_count = (unsigned)(conf->samples_per_frame * 1.0 * + cport->clock_rate / conf->clock_rate + 0.5); + } else { + /* Same clock rate. + * Just copy the samples to tx_buffer. + */ + pjmedia_copy_samples( cport->tx_buf + cport->tx_buf_count, + buf, conf->samples_per_frame ); + dst_count = conf->samples_per_frame; + } + + /* Adjust channels */ + if (cport->channel_count != conf->channel_count) { + pj_int16_t *tx_buf = cport->tx_buf + cport->tx_buf_count; + if (conf->channel_count == 1) { + pjmedia_convert_channel_1ton(tx_buf, tx_buf, + cport->channel_count, + dst_count, 0); + dst_count *= cport->channel_count; + } else { /* cport->channel_count == 1 */ + pjmedia_convert_channel_nto1(tx_buf, tx_buf, + conf->channel_count, + dst_count, PJMEDIA_STEREO_MIX, 0); + dst_count /= conf->channel_count; + } + } + + cport->tx_buf_count += dst_count; + + pj_assert(cport->tx_buf_count <= cport->tx_buf_cap); + + /* Transmit while we have enough frame in the tx_buf. */ + status = PJ_SUCCESS; + ts = 0; + while (cport->tx_buf_count >= cport->samples_per_frame && + status == PJ_SUCCESS) + { + + TRACE_((THIS_FILE, "write_port %.*s: count=%d", + (int)cport->name.slen, cport->name.ptr, + cport->samples_per_frame)); + + if (cport->port) { + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = cport->tx_buf; + frame.size = cport->samples_per_frame * BYTES_PER_SAMPLE; + /* Adjust timestamp as port may have different clock rate + * than the bridge. + */ + frame.timestamp.u64 = timestamp->u64 * cport->clock_rate / + conf->clock_rate; + + /* Add timestamp for individual frame */ + frame.timestamp.u64 += ts; + ts += cport->samples_per_frame; + + TRACE_((THIS_FILE, "put_frame %.*s, count=%d", + (int)cport->name.slen, cport->name.ptr, + frame.size / BYTES_PER_SAMPLE)); + + status = pjmedia_port_put_frame(cport->port, &frame); + + } else + status = PJ_SUCCESS; + + cport->tx_buf_count -= cport->samples_per_frame; + if (cport->tx_buf_count) { + pjmedia_move_samples(cport->tx_buf, + cport->tx_buf + cport->samples_per_frame, + cport->tx_buf_count); + } + + TRACE_((THIS_FILE, " tx_buf count now is %d", + cport->tx_buf_count)); + } + + return status; +} + + +/* + * Player callback. + */ +static pj_status_t get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + pjmedia_frame_type speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE; + unsigned ci, cj, i, j; + pj_int16_t *p_in; + + TRACE_((THIS_FILE, "- clock -")); + + /* Check that correct size is specified. */ + pj_assert(frame->size == conf->samples_per_frame * + conf->bits_per_sample / 8); + + /* Must lock mutex */ + pj_mutex_lock(conf->mutex); + + /* Reset port source count. We will only reset port's mix + * buffer when we have someone transmitting to it. + */ + for (i=0, ci=0; i<conf->max_ports && ci < conf->port_cnt; ++i) { + struct conf_port *conf_port = conf->ports[i]; + + /* Skip empty port. */ + if (!conf_port) + continue; + + /* Var "ci" is to count how many ports have been visited so far. */ + ++ci; + + /* Reset buffer (only necessary if more than one transmitter) and + * reset auto adjustment level for mixed signal. + */ + conf_port->mix_adj = NORMAL_LEVEL; + if (conf_port->transmitter_cnt > 1) { + pj_bzero(conf_port->mix_buf, + conf->samples_per_frame*sizeof(conf_port->mix_buf[0])); + } + } + + /* Get frames from all ports, and "mix" the signal + * to mix_buf of all listeners of the port. + */ + for (i=0, ci=0; i < conf->max_ports && ci < conf->port_cnt; ++i) { + struct conf_port *conf_port = conf->ports[i]; + pj_int32_t level = 0; + + /* Skip empty port. */ + if (!conf_port) + continue; + + /* Var "ci" is to count how many ports have been visited so far. */ + ++ci; + + /* Skip if we're not allowed to receive from this port. */ + if (conf_port->rx_setting == PJMEDIA_PORT_DISABLE) { + conf_port->rx_level = 0; + continue; + } + + /* Also skip if this port doesn't have listeners. */ + if (conf_port->listener_cnt == 0) { + conf_port->rx_level = 0; + continue; + } + + /* Get frame from this port. + * For passive ports, get the frame from the delay_buf. + * For other ports, get the frame from the port. + */ + if (conf_port->delay_buf != NULL) { + pj_status_t status; + + status = pjmedia_delay_buf_get(conf_port->delay_buf, + (pj_int16_t*)frame->buf); + if (status != PJ_SUCCESS) + continue; + + } else { + + pj_status_t status; + pjmedia_frame_type frame_type; + + status = read_port(conf, conf_port, (pj_int16_t*)frame->buf, + conf->samples_per_frame, &frame_type); + + if (status != PJ_SUCCESS) { + /* bennylp: why do we need this???? + * Also see comments on similar issue with write_port(). + PJ_LOG(4,(THIS_FILE, "Port %.*s get_frame() returned %d. " + "Port is now disabled", + (int)conf_port->name.slen, + conf_port->name.ptr, + status)); + conf_port->rx_setting = PJMEDIA_PORT_DISABLE; + */ + continue; + } + + /* Check that the port is not removed when we call get_frame() */ + if (conf->ports[i] == NULL) + continue; + + /* Ignore if we didn't get any frame */ + if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) + continue; + } + + p_in = (pj_int16_t*) frame->buf; + + /* Adjust the RX level from this port + * and calculate the average level at the same time. + */ + if (conf_port->rx_adj_level != NORMAL_LEVEL) { + for (j=0; j<conf->samples_per_frame; ++j) { + /* For the level adjustment, we need to store the sample to + * a temporary 32bit integer value to avoid overflowing the + * 16bit sample storage. + */ + pj_int32_t itemp; + + itemp = p_in[j]; + /*itemp = itemp * adj / NORMAL_LEVEL;*/ + /* bad code (signed/unsigned badness): + * itemp = (itemp * conf_port->rx_adj_level) >> 7; + */ + itemp *= conf_port->rx_adj_level; + itemp >>= 7; + + /* Clip the signal if it's too loud */ + if (itemp > MAX_LEVEL) itemp = MAX_LEVEL; + else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL; + + p_in[j] = (pj_int16_t) itemp; + level += (p_in[j]>=0? p_in[j] : -p_in[j]); + } + } else { + for (j=0; j<conf->samples_per_frame; ++j) { + level += (p_in[j]>=0? p_in[j] : -p_in[j]); + } + } + + level /= conf->samples_per_frame; + + /* Convert level to 8bit complement ulaw */ + level = pjmedia_linear2ulaw(level) ^ 0xff; + + /* Put this level to port's last RX level. */ + conf_port->rx_level = level; + + // Ticket #671: Skipping very low audio signal may cause noise + // to be generated in the remote end by some hardphones. + /* Skip processing frame if level is zero */ + //if (level == 0) + // continue; + + /* Add the signal to all listeners. */ + for (cj=0; cj < conf_port->listener_cnt; ++cj) + { + struct conf_port *listener; + pj_int32_t *mix_buf; + unsigned k; + + listener = conf->ports[conf_port->listener_slots[cj]]; + + /* Skip if this listener doesn't want to receive audio */ + if (listener->tx_setting != PJMEDIA_PORT_ENABLE) + continue; + + mix_buf = listener->mix_buf; + + if (listener->transmitter_cnt > 1) { + /* Mixing signals, + * and calculate appropriate level adjustment if there is + * any overflowed level in the mixed signal. + */ + for (k=0; k < conf->samples_per_frame; ++k) { + mix_buf[k] += p_in[k]; + /* Check if normalization adjustment needed. */ + if (IS_OVERFLOW(mix_buf[k])) { + /* NORMAL_LEVEL * MAX_LEVEL / mix_buf[k]; */ + int tmp_adj = (MAX_LEVEL<<7) / mix_buf[k]; + if (tmp_adj<0) tmp_adj = -tmp_adj; + + if (tmp_adj<listener->mix_adj) + listener->mix_adj = tmp_adj; + + } /* if any overflow in the mixed signals */ + } /* loop mixing signals */ + } else { + /* Only 1 transmitter: + * just copy the samples to the mix buffer + * no mixing and level adjustment needed + */ + for (k=0; k<conf->samples_per_frame; ++k) { + mix_buf[k] = p_in[k]; + } + } + } /* loop the listeners of conf port */ + } /* loop of all conf ports */ + + /* Time for all ports to transmit whetever they have in their + * buffer. + */ + for (i=0, ci=0; i<conf->max_ports && ci<conf->port_cnt; ++i) { + struct conf_port *conf_port = conf->ports[i]; + pjmedia_frame_type frm_type; + pj_status_t status; + + if (!conf_port) + continue; + + /* Var "ci" is to count how many ports have been visited. */ + ++ci; + + status = write_port( conf, conf_port, &frame->timestamp, + &frm_type); + if (status != PJ_SUCCESS) { + /* bennylp: why do we need this???? + One thing for sure, put_frame()/write_port() may return + non-successfull status on Win32 if there's temporary glitch + on network interface, so disabling the port here does not + sound like a good idea. + + PJ_LOG(4,(THIS_FILE, "Port %.*s put_frame() returned %d. " + "Port is now disabled", + (int)conf_port->name.slen, + conf_port->name.ptr, + status)); + conf_port->tx_setting = PJMEDIA_PORT_DISABLE; + */ + continue; + } + + /* Set the type of frame to be returned to sound playback + * device. + */ + if (i == 0) + speaker_frame_type = frm_type; + } + + /* Return sound playback frame. */ + if (conf->ports[0]->tx_level) { + TRACE_((THIS_FILE, "write to audio, count=%d", + conf->samples_per_frame)); + pjmedia_copy_samples( (pj_int16_t*)frame->buf, + (const pj_int16_t*)conf->ports[0]->mix_buf, + conf->samples_per_frame); + } else { + /* Force frame type NONE */ + speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE; + } + + /* MUST set frame type */ + frame->type = speaker_frame_type; + + pj_mutex_unlock(conf->mutex); + +#ifdef REC_FILE + if (fhnd_rec == NULL) + fhnd_rec = fopen(REC_FILE, "wb"); + if (fhnd_rec) + fwrite(frame->buf, frame->size, 1, fhnd_rec); +#endif + + return PJ_SUCCESS; +} + + +/* + * get_frame() for passive port + */ +static pj_status_t get_frame_pasv(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + pj_assert(0); + PJ_UNUSED_ARG(this_port); + PJ_UNUSED_ARG(frame); + return -1; +} + + +/* + * Recorder (or passive port) callback. + */ +static pj_status_t put_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + struct conf_port *port = conf->ports[this_port->port_data.ldata]; + pj_status_t status; + + /* Check for correct size. */ + PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame * + conf->bits_per_sample / 8, + PJMEDIA_ENCSAMPLESPFRAME); + + /* Check existance of delay_buf instance */ + PJ_ASSERT_RETURN( port->delay_buf, PJ_EBUG ); + + /* Skip if this port is muted/disabled. */ + if (port->rx_setting != PJMEDIA_PORT_ENABLE) { + return PJ_SUCCESS; + } + + /* Skip if no port is listening to the microphone */ + if (port->listener_cnt == 0) { + return PJ_SUCCESS; + } + + status = pjmedia_delay_buf_put(port->delay_buf, (pj_int16_t*)frame->buf); + + return status; +} + +#endif |