From b100d69072efc161585782e076758afdd4a8bda1 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Fri, 17 Mar 2006 00:16:01 +0000 Subject: [PATCH] Added feature in conference bridge to get and set the signal level of individual port and individual stream direction git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@323 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/include/pjmedia/conference.h | 126 ++++++- pjmedia/src/pjmedia/conference.c | 517 ++++++++++++++++++++------- pjmedia/src/pjmedia/resample.c | 38 +- pjsip-apps/src/pjsua/main.c | 3 +- pjsip/src/pjsua-lib/pjsua_call.c | 16 +- 5 files changed, 542 insertions(+), 158 deletions(-) diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h index f822260fc..04ead090e 100644 --- a/pjmedia/include/pjmedia/conference.h +++ b/pjmedia/include/pjmedia/conference.h @@ -58,12 +58,36 @@ enum pjmedia_conf_option { PJMEDIA_CONF_NO_MIC = 1, /**< Disable audio streams from the microphone device. */ + PJMEDIA_CONF_NO_DEVICE = 2, /**< Do not create sound device. */ }; /** - * Create conference bridge. This normally will also create instances of - * sound device to be attached to the port zero of the bridge. + * Create conference bridge with the specified parameters. The sampling rate, + * samples per frame, and bits per sample will be used for the internal + * operation of the bridge (e.g. when mixing audio frames). However, ports + * with different configuration may be connected to the bridge. In this case, + * the bridge is able to perform sampling rate conversion, and buffering in + * case the samples per frame is different. + * + * For this version of PJMEDIA, only 16bits per sample is supported. + * + * For this version of PJMEDIA, the channel count of the ports MUST match + * the channel count of the bridge. + * + * Under normal operation (i.e. when PJMEDIA_CONF_NO_DEVICE option is NOT + * specified), the bridge internally create an instance of sound device + * and connect the sound device to port zero of the bridge. + * + * If PJMEDIA_CONF_NO_DEVICE options is specified, no sound device will + * be created in the conference bridge. Application MUST acquire the port + * interface of the bridge by calling #pjmedia_conf_get_master_port(), and + * connect this port interface to a sound device port by calling + * #pjmedia_snd_port_connect(). + * + * The sound device is crucial for the bridge's operation, because it provides + * the bridge with necessary clock to process the audio frames periodically. + * Internally, the bridge runs when get_frame() to port zero is called. * * @param pool Pool to use to allocate the bridge and * additional buffers for the sound device. @@ -112,6 +136,27 @@ PJ_DECL(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, PJ_DECL(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ); +/** + * Get the master port interface of the conference bridge. The master port + * corresponds to the port zero of the bridge. This is only usefull when + * application wants to manage the sound device by itself, instead of + * allowing the bridge to automatically create a sound device implicitly. + * + * This function will only return a port interface if PJMEDIA_CONF_NO_DEVICE + * option was specified when the bridge was created. + * + * Application can connect the port returned by this function to a + * sound device by calling #pjmedia_snd_port_connect(). + * + * @param conf The conference bridge. + * + * @return The port interface of port zero of the bridge, + * only when PJMEDIA_CONF_NO_DEVICE options was + * specified when the bridge was created. + */ +PJ_DECL(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf); + + /** * Add stream port to the conference bridge. By default, the new conference * port will have both TX and RX enabled, but it is not connected to any @@ -160,12 +205,17 @@ PJ_DECL(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf, * @param conf The conference bridge. * @param src_slot Source slot. * @param sink_slot Sink slot. + * @param level This argument is reserved for future improvements + * where it is possible to adjust the level of signal + * transmitted in a specific connection. For now, + * this argument MUST be zero. * * @return PJ_SUCCES on success. */ PJ_DECL(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf, unsigned src_slot, - unsigned sink_slot ); + unsigned sink_slot, + int level ); /** @@ -223,7 +273,75 @@ PJ_DECL(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf, */ PJ_DECL(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf, unsigned *size, - pjmedia_conf_port_info info[]); + pjmedia_conf_port_info info[] + ); + + +/** + * Get last signal level transmitted to or received from the specified port. + * The signal level is an integer value in zero to 255, with zero indicates + * no signal, and 255 indicates the loudest signal level. + * + * @param conf The conference bridge. + * @param slot Slot number. + * @param tx_level Optional argument to receive the level of signal + * transmitted to the specified port (i.e. the direction + * is from the bridge to the port). + * @param rx_level Optional argument to receive the level of signal + * received from the port (i.e. the direction is from the + * port to the bridge). + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_conf_get_signal_level(pjmedia_conf *conf, + unsigned slot, + unsigned *tx_level, + unsigned *rx_level); + + +/** + * Adjust the level of signal received from the specified port. + * Application may adjust the level to make the signal received from the port + * either louder or more quiet, by giving the value from +127 to -128. The + * value zero indicates no adjustment, the value -128 will mute the signal, + * and the value of +127 will make the signal twice as loud. + * + * @param conf The conference bridge. + * @param slot Slot number. + * @param adj_level Adjustment level, with valid values are from -128 + * to +127. A value of zero means there is no level + * adjustment to be made, the value -128 will mute the + * signal, and the value of +127 will make the signal + * twice as loud. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_conf_adjust_rx_level( pjmedia_conf *conf, + unsigned slot, + int adj_level ); + + +/** + * Adjust the level of signal to be transmitted to the specified port. + * Application may adjust the level to make the signal transmitted to the port + * either louder or more quiet, by giving the value from +127 to -128. The + * value zero indicates no adjustment, the value -128 will mute the signal, + * and the value of +127 will make the signal twice as loud. + * + * @param conf The conference bridge. + * @param slot Slot number. + * @param adj_level Adjustment level, with valid values are from -128 + * to +127. A value of zero means there is no level + * adjustment to be made, the value -128 will mute the + * signal, and the value of +127 will make the signal + * twice as loud. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_conf_adjust_tx_level( pjmedia_conf *conf, + unsigned slot, + int adj_level ); + PJ_END_DECL diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index ddeb837cb..b10ce536b 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include @@ -46,7 +46,9 @@ * DON'T GET CONFUSED!! * * TX and RX directions are always viewed from the conference bridge's point - * of view, and NOT from the port's point of view. + * 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. */ @@ -67,14 +69,29 @@ struct conf_port unsigned clock_rate; /**< Port's clock rate. */ unsigned samples_per_frame; /**< Port's samples per frame. */ + /* Calculated signal levels: */ + pj_bool_t need_tx_level; /**< Need to calculate tx level? */ + 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 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. When both sample rate and ptime of the port match the - * conference settings, this buffer will not be used. + * 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 @@ -85,7 +102,10 @@ struct conf_port 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. + * 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 sources count 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. @@ -97,8 +117,12 @@ struct conf_port /* 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. When both sample rate and ptime of the port match the - * conference's settings, this buffer will not be used. + * 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 @@ -108,8 +132,8 @@ struct conf_port 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). - * It's not used by other ports. + /* 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 @@ -131,8 +155,9 @@ struct pjmedia_conf unsigned max_ports; /**< Maximum ports. */ unsigned port_cnt; /**< Current number of ports. */ unsigned connect_cnt; /**< Total number of connections */ - pjmedia_snd_stream *snd_rec; /**< Sound recorder stream. */ - pjmedia_snd_stream *snd_player; /**< Sound player stream. */ + pjmedia_snd_port *snd_rec; /**< Sound recorder stream. */ + pjmedia_snd_port *snd_player; /**< Sound player stream. */ + pjmedia_port *master_port; /**< Port zero's port. */ pj_mutex_t *mutex; /**< Conference mutex. */ struct conf_port **ports; /**< Array of ports. */ pj_uint16_t *uns_buf; /**< Buf for unsigned conversion */ @@ -147,14 +172,11 @@ struct pjmedia_conf unsigned char linear2ulaw(int pcm_val); /* Prototypes */ -static pj_status_t play_cb( /* in */ void *user_data, - /* in */ pj_uint32_t timestamp, - /* out */ void *output, - /* out */ unsigned size); -static pj_status_t rec_cb( /* in */ void *user_data, - /* in */ pj_uint32_t timestamp, - /* in */ const void *input, - /* in*/ unsigned size); +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); + /* * Create port. @@ -179,6 +201,10 @@ static pj_status_t create_conf_port( pj_pool_t *pool, 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 = 128; + conf_port->rx_adj_level = 128; + /* Create transmit flag array */ conf_port->listeners = pj_pool_zalloc(pool, conf->max_ports*sizeof(pj_bool_t)); @@ -309,6 +335,44 @@ static pj_status_t create_sound_port( pj_pool_t *pool, conf->ports[0] = conf_port; conf->port_cnt++; + + /* Create sound devices: */ + + /* Create recorder only if mic is not disabled. */ + if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0 && + (conf->options & PJMEDIA_CONF_NO_MIC) == 0) + { + status = pjmedia_snd_port_create_rec( pool, -1, conf->clock_rate, + conf->channel_count, + conf->samples_per_frame, + conf->bits_per_sample, + 0, /* Options */ + &conf->snd_rec); + if (status != PJ_SUCCESS) { + conf->snd_rec = NULL; + return status; + } + } + + /* Create player device */ + if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) { + 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_player); + if (status != PJ_SUCCESS) { + if (conf->snd_rec) { + pjmedia_snd_port_destroy(conf->snd_rec); + conf->snd_rec = NULL; + } + conf->snd_player = NULL; + return status; + } + } + + PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0")); return PJ_SUCCESS; @@ -348,9 +412,34 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, 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(pool, sizeof(pjmedia_port)); + PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM); + + conf->master_port->info.bits_per_sample = bits_per_sample; + conf->master_port->info.bytes_per_frame = samples_per_frame * + bits_per_sample / 8; + conf->master_port->info.channel_count = channel_count; + conf->master_port->info.encoding_name = pj_str("pcm"); + conf->master_port->info.has_info = 1; + conf->master_port->info.name = pj_str("master port"); + conf->master_port->info.need_info = 0; + conf->master_port->info.pt = 0xFF; + conf->master_port->info.sample_rate = clock_rate; + conf->master_port->info.samples_per_frame = samples_per_frame; + conf->master_port->info.signature = 0; + conf->master_port->info.type = PJMEDIA_TYPE_AUDIO; + + conf->master_port->get_frame = &get_frame; + conf->master_port->put_frame = &put_frame; + + conf->master_port->user_data = conf; + /* Create port zero for sound device. */ status = create_sound_port(pool, conf); @@ -366,6 +455,27 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, if (status != PJ_SUCCESS) return status; + /* If sound device was created, connect sound device to the + * master port. + */ + if (conf->snd_player) { + status = pjmedia_snd_port_connect( conf->snd_player, + conf->master_port ); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy(conf); + return status; + } + } + + if (conf->snd_rec) { + status = pjmedia_snd_port_connect( conf->snd_rec, + conf->master_port); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy(conf); + return status; + } + } + /* Done */ @@ -376,92 +486,23 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, /* - * Create sound device + * Pause sound device. */ -static pj_status_t create_sound( pjmedia_conf *conf ) +static pj_status_t pause_sound( pjmedia_conf *conf ) { - pj_status_t status; - - /* Open recorder only if mic is not disabled. */ - if ((conf->options & PJMEDIA_CONF_NO_MIC) == 0) { - status = pjmedia_snd_open_recorder(-1, conf->clock_rate, 1, - conf->samples_per_frame, - conf->bits_per_sample, - &rec_cb, conf, &conf->snd_rec); - if (status != PJ_SUCCESS) { - conf->snd_rec = NULL; - return status; - } - } - - /* Open player */ - status = pjmedia_snd_open_player(-1, conf->clock_rate, 1, - conf->samples_per_frame, - conf->bits_per_sample, - &play_cb, conf, &conf->snd_player); - if (status != PJ_SUCCESS) { - if (conf->snd_rec) { - pjmedia_snd_stream_close(conf->snd_rec); - conf->snd_rec = NULL; - } - conf->snd_player = NULL; - return status; - } - + /* Do nothing. */ + PJ_UNUSED_ARG(conf); return PJ_SUCCESS; } /* - * Destroy sound device - */ -static pj_status_t destroy_sound( pjmedia_conf *conf ) -{ - if (conf->snd_rec) { - pjmedia_snd_stream_close(conf->snd_rec); - conf->snd_rec = NULL; - } - if (conf->snd_player) { - pjmedia_snd_stream_close(conf->snd_player); - conf->snd_player = NULL; - } - return PJ_SUCCESS; -} - -/* - * Activate sound device. + * Resume sound device. */ static pj_status_t resume_sound( pjmedia_conf *conf ) { - char errmsg[PJ_ERR_MSG_SIZE]; - pj_status_t status; - - if (conf->snd_player == NULL) { - status = create_sound(conf); - if (status != PJ_SUCCESS) - return status; - } - - /* Start recorder. */ - if (conf->snd_rec) { - status = pjmedia_snd_stream_start(conf->snd_rec); - if (status != PJ_SUCCESS) - goto on_error; - } - - /* Start player. */ - if (conf->snd_player) { - status = pjmedia_snd_stream_start(conf->snd_player); - if (status != PJ_SUCCESS) - goto on_error; - } - + /* Do nothing. */ + PJ_UNUSED_ARG(conf); return PJ_SUCCESS; - -on_error: - pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(4,(THIS_FILE, "Error starting sound player/recorder: %s", - errmsg)); - return status; } @@ -472,14 +513,40 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) { PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL); - //suspend_sound(conf); - destroy_sound(conf); + /* Destroy sound devices. */ + if (conf->snd_rec) { + pjmedia_snd_port_destroy(conf->snd_rec); + conf->snd_rec = NULL; + } + if (conf->snd_player) { + pjmedia_snd_port_destroy(conf->snd_player); + conf->snd_player = NULL; + } + + /* Destroy mutex */ pj_mutex_destroy(conf->mutex); return PJ_SUCCESS; } +/* + * 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; +} + + /* * Add stream port to the conference bridge. */ @@ -519,7 +586,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, pj_assert(index != conf->max_ports); - /* Create port structure. */ + /* 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); @@ -574,7 +641,8 @@ PJ_DECL(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf, */ PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf, unsigned src_slot, - unsigned sink_slot ) + unsigned sink_slot, + int level ) { struct conf_port *src_port, *dst_port; pj_bool_t start_sound = PJ_FALSE; @@ -587,6 +655,9 @@ PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf, PJ_ASSERT_RETURN(conf->ports[src_slot] != NULL, PJ_EINVAL); PJ_ASSERT_RETURN(conf->ports[sink_slot] != NULL, PJ_EINVAL); + /* For now, level MUST be zero. */ + PJ_ASSERT_RETURN(level == 0, PJ_EINVAL); + pj_mutex_lock(conf->mutex); src_port = conf->ports[src_slot]; @@ -658,7 +729,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf, pj_mutex_unlock(conf->mutex); if (conf->connect_cnt == 0) { - destroy_sound(conf); + pause_sound(conf); } return PJ_SUCCESS; @@ -723,7 +794,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf, /* Stop sound if there's no connection. */ if (conf->connect_cnt == 0) { - destroy_sound(conf); + pause_sound(conf); } return PJ_SUCCESS; @@ -779,6 +850,90 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf, } +/* + * 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 && slotmax_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL); + + conf_port = conf->ports[slot]; + + if (tx_level != NULL) { + conf_port->need_tx_level = 1; + *tx_level = conf_port->tx_level; + } + + if (rx_level != NULL) + *rx_level = conf_port->rx_level; + + 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 && slotmax_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL); + + /* Value must be from -128 to +127 */ + PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL); + + conf_port = conf->ports[slot]; + + /* Set normalized adjustment level. */ + conf_port->rx_adj_level = adj_level + 128; + + 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 && slotmax_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL); + + /* Value must be from -128 to +127 */ + PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL); + + conf_port = conf->ports[slot]; + + /* Set normalized adjustment level. */ + conf_port->tx_adj_level = adj_level + 128; + + return PJ_SUCCESS; +} + + /* Convert signed 16bit pcm sample to unsigned 16bit sample */ static pj_uint16_t pcm2unsigned(pj_int32_t pcm) { @@ -924,19 +1079,74 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, if (cport->port) pjmedia_port_put_frame(cport->port, &frame); + cport->tx_level = 0; return PJ_SUCCESS; } else if (cport->tx_setting != PJMEDIA_PORT_ENABLE) { + cport->tx_level = 0; return PJ_SUCCESS; } /* If there are sources in the mix buffer, convert the mixed samples * to 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. */ buf = (pj_int16_t*)cport->mix_buf; - for (j=0; jsamples_per_frame; ++j) { - buf[j] = unsigned2pcm(cport->mix_buf[j] / cport->sources); + + if (cport->tx_adj_level != 128) { + + unsigned adj_level = cport->tx_adj_level; + + /* We need to adjust signal level. */ + for (j=0; jsamples_per_frame; ++j) { + pj_int32_t itemp; + + /* Calculate average level, and convert the sample to + * 16bit signed integer. + */ + itemp = unsigned2pcm(cport->mix_buf[j] / cport->sources); + + /* Adjust the level */ + itemp = itemp * adj_level / 128; + + /* Clip the signal if it's too loud */ + if (itemp > 32767) itemp = 32767; + else if (itemp < -32768) itemp = -32768; + + /* Put back in the buffer. */ + buf[j] = (pj_int16_t) itemp; + } + + } else { + /* No need to adjust signal level. */ + for (j=0; jsamples_per_frame; ++j) { + buf[j] = unsigned2pcm(cport->mix_buf[j] / cport->sources); + } + } + + /* Calculate TX level if we need to do so. + * This actually is not the most correct place to calculate TX signal + * level of the port; it should calculate the level of the actual + * frame just before put_frame() is called. + * But doing so would make the code more complicated than it is + * necessary, since the purpose of level calculation mostly is just + * for VU meter display. By doing it here, it should give the acceptable + * indication of the signal level of the port. + */ + if (cport->need_tx_level) { + pj_uint32_t level; + + /* Get the signal level. */ + level = pjmedia_calc_avg_signal(buf, conf->samples_per_frame); + + /* Convert level to 8bit complement ulaw */ + cport->tx_level = linear2ulaw(level) ^ 0xff; + + } else { + cport->tx_level = 0; } /* If port has the same clock_date and samples_per_frame settings as @@ -1012,17 +1222,17 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, /* * Player callback. */ -static pj_status_t play_cb( /* in */ void *user_data, - /* in */ pj_uint32_t timestamp, - /* out */ void *output, - /* out */ unsigned size) +static pj_status_t get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) { - pjmedia_conf *conf = user_data; + pjmedia_conf *conf = this_port->user_data; unsigned ci, cj, i, j; - PJ_UNUSED_ARG(timestamp); - PJ_UNUSED_ARG(size); + /* Check that correct size is specified. */ + pj_assert(frame->size == conf->samples_per_frame * + conf->bits_per_sample / 8); + /* Must lock mutex (must we??) */ pj_mutex_lock(conf->mutex); TRACE_(("p")); @@ -1056,16 +1266,20 @@ static pj_status_t play_cb( /* in */ void *user_data, 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) + if (conf_port->listener_cnt == 0) { + conf_port->rx_level = 0; continue; + } /* Get frame from this port. * For port zero (sound port), get the frame from the rx_buffer @@ -1082,18 +1296,13 @@ static pj_status_t play_cb( /* in */ void *user_data, /* Skip if this port is muted/disabled. */ if (conf_port->rx_setting != PJMEDIA_PORT_ENABLE) { - continue; - } - - - /* Skip if no port is listening to the microphone */ - if (conf_port->listener_cnt == 0) { + conf_port->rx_level = 0; continue; } snd_buf = conf_port->snd_buf[conf_port->snd_read_pos]; for (j=0; jsamples_per_frame; ++j) { - ((pj_int16_t*)output)[j] = snd_buf[j]; + ((pj_int16_t*)frame->buf)[j] = snd_buf[j]; } conf_port->snd_read_pos = (conf_port->snd_read_pos+1) % RX_BUF_COUNT; @@ -1102,7 +1311,7 @@ static pj_status_t play_cb( /* in */ void *user_data, pj_status_t status; pjmedia_frame_type frame_type; - status = read_port(conf, conf_port, output, + status = read_port(conf, conf_port, frame->buf, conf->samples_per_frame, &frame_type); if (status != PJ_SUCCESS) { @@ -1116,15 +1325,49 @@ static pj_status_t play_cb( /* in */ void *user_data, } } - /* Get the signal level. */ - level = pjmedia_calc_avg_signal(output, conf->samples_per_frame); + /* If we need to adjust the RX level from this port, adjust the level + * and calculate the average level at the same time. + * Otherwise just calculate the averate level. + */ + if (conf_port->rx_adj_level != 128) { + pj_int16_t *input = frame->buf; + pj_int32_t adj = conf_port->rx_adj_level; + + level = 0; + for (j=0; jsamples_per_frame; ++j) { + pj_int32_t itemp; + + /* For the level adjustment, we need to store the sample to + * a temporary 32bit integer value to avoid overflowing the + * 16bit sample storage. + */ + itemp = input[j]; + itemp = itemp * adj / 128; + + /* Clip the signal if it's too loud */ + if (itemp > 32767) itemp = 32767; + else if (itemp < -32768) itemp = -32768; + + input[j] = (pj_int16_t) itemp; + level += itemp; + } + + level /= conf->samples_per_frame; + + } else { + level = pjmedia_calc_avg_signal(frame->buf, + conf->samples_per_frame); + } /* Convert level to 8bit complement ulaw */ level = linear2ulaw(level) ^ 0xff; + /* Put this level to port's last RX level. */ + conf_port->rx_level = level; + /* Convert the buffer to unsigned 16bit value */ for (j=0; jsamples_per_frame; ++j) - conf->uns_buf[j] = pcm2unsigned(((pj_int16_t*)output)[j]); + conf->uns_buf[j] = pcm2unsigned(((pj_int16_t*)frame->buf)[j]); /* Add the signal to all listeners. */ for (j=0, cj=0; @@ -1142,6 +1385,7 @@ static pj_status_t play_cb( /* in */ void *user_data, if (!conf_port->listeners[j]) continue; + /* Var "cj" is the number of listeners we have visited so far */ ++cj; /* Skip if this listener doesn't want to receive audio */ @@ -1157,7 +1401,9 @@ static pj_status_t play_cb( /* in */ void *user_data, } } - /* For all ports, calculate avg signal. */ + /* Time for all ports to transmit whetever they have in their + * buffer. + */ for (i=0, ci=0; imax_ports && ciport_cnt; ++i) { struct conf_port *conf_port = conf->ports[i]; pj_status_t status; @@ -1165,9 +1411,10 @@ static pj_status_t play_cb( /* in */ void *user_data, if (!conf_port) continue; + /* Var "ci" is to count how many ports have been visited. */ ++ci; - status = write_port( conf, conf_port, timestamp); + status = write_port( conf, conf_port, frame->timestamp.u32.lo); if (status != PJ_SUCCESS) { PJ_LOG(4,(THIS_FILE, "Port %.*s put_frame() returned %d. " "Port is now disabled", @@ -1181,10 +1428,10 @@ static pj_status_t play_cb( /* in */ void *user_data, /* Return sound playback frame. */ if (conf->ports[0]->sources) { - copy_samples( output, (pj_int16_t*)conf->ports[0]->mix_buf, + copy_samples( frame->buf, (pj_int16_t*)conf->ports[0]->mix_buf, conf->samples_per_frame); } else { - zero_samples( output, conf->samples_per_frame ); + zero_samples( frame->buf, conf->samples_per_frame ); } pj_mutex_unlock(conf->mutex); @@ -1196,23 +1443,21 @@ static pj_status_t play_cb( /* in */ void *user_data, /* * Recorder callback. */ -static pj_status_t rec_cb( /* in */ void *user_data, - /* in */ pj_uint32_t timestamp, - /* in */ const void *input, - /* in */ unsigned size) +static pj_status_t put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame) { - pjmedia_conf *conf = user_data; + pjmedia_conf *conf = this_port->user_data; struct conf_port *snd_port = conf->ports[0]; + const pj_int16_t *input = frame->buf; pj_int16_t *target_snd_buf; unsigned i; - PJ_UNUSED_ARG(timestamp); - TRACE_(("r")); - if (size != conf->samples_per_frame*2) { - TRACE_(("rxerr ")); - } + /* Check for correct size. */ + PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame * + conf->bits_per_sample / 8, + PJMEDIA_ENCSAMPLESPFRAME); /* Skip if this port is muted/disabled. */ if (snd_port->rx_setting != PJMEDIA_PORT_ENABLE) { diff --git a/pjmedia/src/pjmedia/resample.c b/pjmedia/src/pjmedia/resample.c index 37102d6c6..246f4cc6e 100644 --- a/pjmedia/src/pjmedia/resample.c +++ b/pjmedia/src/pjmedia/resample.c @@ -19,18 +19,38 @@ /* * Based on: - * resample-1.2.tar.Z (from ftp://ccrma-ftp.stanford.edu/pub/NeXT) + * resample-1.8.tar.gz from the + * Digital Audio Resampling Home Page located at + * http://www-ccrma.stanford.edu/~jos/resample/. + * * SOFTWARE FOR SAMPLING-RATE CONVERSION AND FIR DIGITAL FILTER DESIGN * - * COPYING + * Snippet from the resample.1 man page: * - * This software package is Copyright 1994 by Julius O. Smith - * (jos@ccrma.stanford.edu), all rights reserved. Permission to use and copy - * is granted subject to the terms of the "GNU Software General Public - * License" (see ftp://prep.ai.mit.edu/pub/gnu/COPYING). In addition, we - * request that a copy of any modified files be sent by email to - * jos@ccrma.stanford.edu so that we may incorporate them in the CCRMA - * version. + * HISTORY + * + * The first version of this software was written by Julius O. Smith III + * at CCRMA in + * 1981. It was called SRCONV and was written in SAIL for PDP-10 + * compatible machines. The algorithm was first published in + * + * Smith, Julius O. and Phil Gossett. ``A Flexible Sampling-Rate + * Conversion Method,'' Proceedings (2): 19.4.1-19.4.4, IEEE Conference + * on Acoustics, Speech, and Signal Processing, San Diego, March 1984. + * + * An expanded tutorial based on this paper is available at the Digital + * Audio Resampling Home Page given above. + * + * Circa 1988, the SRCONV program was translated from SAIL to C by + * Christopher Lee Fraley working with Roger Dannenberg at CMU. + * + * Since then, the C version has been maintained by jos. + * + * Sndlib support was added 6/99 by John Gibson . + * + * The resample program is free software distributed in accordance + * with the Lesser GNU Public License (LGPL). There is NO warranty; not + * even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ /* PJMEDIA modification: diff --git a/pjsip-apps/src/pjsua/main.c b/pjsip-apps/src/pjsua/main.c index 5f5ee3afc..ca1f37b37 100644 --- a/pjsip-apps/src/pjsua/main.c +++ b/pjsip-apps/src/pjsua/main.c @@ -812,7 +812,8 @@ static void ui_console_main(void) if (menuin[1]=='c') { status = pjmedia_conf_connect_port(pjsua.mconf, atoi(src_port), - atoi(dst_port)); + atoi(dst_port), + 0); } else { status = pjmedia_conf_disconnect_port(pjsua.mconf, atoi(src_port), diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 16823e4d9..25f6f8aeb 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -961,19 +961,19 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, { pjmedia_conf_connect_port( pjsua.mconf, pjsua.wav_slot, - call->conf_slot); + call->conf_slot, 0); } else if (pjsua.auto_loop && call->inv->role == PJSIP_ROLE_UAS) { pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, - call->conf_slot); + call->conf_slot, 0); } else if (pjsua.auto_conf) { int i; - pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot); - pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0); + pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot, 0); + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0, 0); for (i=0; i < pjsua.max_calls; ++i) { @@ -981,9 +981,9 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, continue; pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, - pjsua.calls[i].conf_slot); + pjsua.calls[i].conf_slot, 0); pjmedia_conf_connect_port( pjsua.mconf, pjsua.calls[i].conf_slot, - call->conf_slot); + call->conf_slot, 0); } } else { @@ -991,8 +991,8 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, /* Connect new call to the sound device port (port zero) in the * main conference bridge. */ - pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot); - pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0); + pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot, 0); + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0, 0); }