📄 conference.c
字号:
/* $Id: conference.c 1301 2007-05-25 06:13:55Z bennylp $ */
/*
* Copyright (C) 2003-2007 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/errno.h>
#include <pjmedia/port.h>
#include <pjmedia/resample.h>
#include <pjmedia/silencedet.h>
#include <pjmedia/sound_port.h>
#include <pjmedia/stream.h>
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
/* 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_PORT_SIGNATURE('C', 'O', 'N', 'F')
#define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'P')
/* 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).
*/
#if 1
# define ATTACK_A 10
# define ATTACK_B 2
# define DECAY_A 10
# define DECAY_B 2
#else
/* To simulate old behavior */
# define ATTACK_A 0
# define ATTACK_B 1
# define DECAY_A 0
# define DECAY_B 1
#endif
/*
* 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. */
pjmedia_silence_det *vad; /**< VAD for this port. */
/* Shortcut for port info. */
unsigned clock_rate; /**< Port's clock rate. */
unsigned samples_per_frame; /**< Port's samples per frame. */
/* Last level calculated from this port */
pj_int32_t last_level;
/* 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 calculate the average signal
* received by this port from all other ports. Samples from all ports
* that are transmitting to this port will be accumulated here, then
* they will be divided by the source level before the samples are put
* to the TX buffer of this port.
*
* This buffer contains samples at bridge's clock rate.
* The size of this buffer is equal to samples per frame of the bridge.
*
* Note that the samples here are unsigned 32bit.
*/
unsigned src_level; /**< Sum of input levels */
unsigned src_cnt; /**< Number of sources. */
pj_uint32_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. */
/* Snd buffers is a special buffer for sound device port (port 0, master
* port). It's not used by other ports.
*
* There are multiple numbers of this 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.
*/
int snd_write_pos, snd_read_pos;
pj_int16_t *snd_buf[RX_BUF_COUNT]; /**< Buffer */
};
/*
* 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. */
pj_uint16_t *uns_buf; /**< Buf for unsigned conversion */
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,
const 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);
/*
* 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) {
conf_port->port = port;
conf_port->clock_rate = port->info.clock_rate;
conf_port->samples_per_frame = port->info.samples_per_frame;
} else {
conf_port->port = NULL;
conf_port->clock_rate = conf->clock_rate;
conf_port->samples_per_frame = conf->samples_per_frame;
}
/* Create and init vad. */
status = pjmedia_silence_det_create( pool,
conf_port->clock_rate,
conf_port->samples_per_frame,
&conf_port->vad);
if (status != PJ_SUCCESS)
return status;
/* Set fixed */
pjmedia_silence_det_set_fixed(conf_port->vad, 2);
/* Set VAD name */
pjmedia_silence_det_set_name(conf_port->vad, conf_port->name.ptr);
/* 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 is different then the conference bridge settings.
*/
if (conf_port->clock_rate != conf->clock_rate ||
conf_port->samples_per_frame != conf->samples_per_frame)
{
/* 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);
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_uint32_t*)
pj_pool_zalloc(pool, conf->samples_per_frame *
sizeof(conf_port->mix_buf[0]));
PJ_ASSERT_RETURN(conf_port->mix_buf, PJ_ENOMEM);
/* 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;
unsigned i;
pj_status_t status;
/* Create port */
status = create_conf_port(pool, conf, port, name, &conf_port);
if (status != PJ_SUCCESS)
return status;
/* Passive port has rx buffers. */
for (i=0; i<RX_BUF_COUNT; ++i) {
conf_port->snd_buf[i] = (pj_int16_t*)
pj_pool_zalloc(pool, conf->samples_per_frame *
sizeof(conf_port->snd_buf[0][0]));
if (conf_port->snd_buf[i] == NULL) {
return PJ_ENOMEM;
}
}
conf_port->snd_write_pos = 0;
conf_port->snd_read_pos = 0;
*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_snd_stream *strm;
pjmedia_snd_stream_info si;
/*
* 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_snd_stream_get_info(strm, &si);
if (status == PJ_SUCCESS) {
const pjmedia_snd_dev_info *snd_dev_info;
if (conf->options & PJMEDIA_CONF_NO_MIC)
snd_dev_info = pjmedia_snd_get_dev_info(si.play_id);
else
snd_dev_info = pjmedia_snd_get_dev_info(si.rec_id);
pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info->name);
}
}
/* Add the port to the bridge */
conf->ports[0] = conf_port;
conf->port_cnt++;
PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0"));
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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -