/* $Id: conference.c 4198 2012-07-05 10:25:46Z 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, &param);
	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)
{
    pj_size_t 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, (unsigned)count);
	    cport->rx_buf_count -= (unsigned)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 the port has transmitter) and
	 * reset auto adjustment level for mixed signal.
	 */
	conf_port->mix_adj = NORMAL_LEVEL;
	if (conf_port->transmitter_cnt) {
	    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
