/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #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 # define TRACE_(x) PJ_LOG(5,x) #else # define TRACE_(x) #endif #define THIS_FILE "conf_switch.c" #define SIGNATURE PJMEDIA_CONF_SWITCH_SIGNATURE #define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('S', 'W', 'T', 'P') #define NORMAL_LEVEL 128 #define SLOT_TYPE unsigned #define INVALID_SLOT ((SLOT_TYPE)-1) #define BUFFER_SIZE PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE #define MAX_LEVEL (32767) #define MIN_LEVEL (-32768) /* * 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 { SLOT_TYPE slot; /**< Array of listeners. */ 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;/**info.fmt.id == PJMEDIA_FORMAT_PCM && PJMEDIA_PIA_SPF(&port->info)*2 > BUFFER_SIZE - sizeof(pjmedia_frame)) { pj_assert(!"Too small buffer size for audio switchboard. " "Try increase PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE"); return PJ_ETOOSMALL; } /* Create port. */ conf_port = PJ_POOL_ZALLOC_T(pool, struct conf_port); /* 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. */ conf_port->port = port; conf_port->info = &port->info; conf_port->samples_per_frame = PJMEDIA_PIA_SPF(&port->info); /* Init pjmedia_frame structure in the TX buffer. */ f = (pjmedia_frame*)conf_port->tx_buf; f->buf = conf_port->tx_buf + sizeof(pjmedia_frame); /* Done */ *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_conf_port(pool, conf, conf->master_port, &name, &conf_port); if (status != PJ_SUCCESS) return status; /* Add the port to the bridge */ conf_port->slot = 0; 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); 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; /* 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) return status; /* Create mutex. */ status = pj_mutex_create_recursive(pool, "conf", &conf->mutex); if (status != PJ_SUCCESS) 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 ) { PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL); /* Destroy 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); } /* * Get port zero interface. */ PJ_DEF(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf) { /* Sanity check. */ PJ_ASSERT_RETURN(conf != NULL, NULL); /* Can only return port interface when PJMEDIA_CONF_NO_DEVICE was * present in the option. */ PJ_ASSERT_RETURN((conf->options & PJMEDIA_CONF_NO_DEVICE) != 0, NULL); return conf->master_port; } /* * Set master port name. */ PJ_DEF(pj_status_t) pjmedia_conf_set_port0_name(pjmedia_conf *conf, const pj_str_t *name) { unsigned len; /* Sanity check. */ PJ_ASSERT_RETURN(conf != NULL && name != NULL, PJ_EINVAL); len = name->slen; if (len > sizeof(conf->master_name_buf)) len = sizeof(conf->master_name_buf); if (len > 0) pj_memcpy(conf->master_name_buf, name->ptr, len); conf->ports[0]->name.ptr = conf->master_name_buf; conf->ports[0]->name.slen = len; 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); /* PJ_ASSERT_RETURN(conf->clock_rate == strm_port->info.clock_rate, PJMEDIA_ENCCLOCKRATE); PJ_ASSERT_RETURN(conf->channel_count == strm_port->info.channel_count, PJMEDIA_ENCCHANNEL); PJ_ASSERT_RETURN(conf->bits_per_sample == strm_port->info.bits_per_sample, PJMEDIA_ENCBITS); */ /* Port's samples per frame should be equal to or multiplication of * conference's samples per frame. */ /* Not sure if this is needed! PJ_ASSERT_RETURN((conf->samples_per_frame % strm_port->info.samples_per_frame==0) || (strm_port->info.samples_per_frame % conf->samples_per_frame==0), PJMEDIA_ENCSAMPLESPFRAME); */ /* If port_name is not specified, use the port's name */ if (!port_name) port_name = &strm_port->info.name; 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_port->slot = index; 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 ) { PJ_UNUSED_ARG(conf); PJ_UNUSED_ARG(pool); PJ_UNUSED_ARG(name); PJ_UNUSED_ARG(clock_rate); PJ_UNUSED_ARG(channel_count); PJ_UNUSED_ARG(samples_per_frame); PJ_UNUSED_ARG(bits_per_sample); PJ_UNUSED_ARG(options); PJ_UNUSED_ARG(p_slot); PJ_UNUSED_ARG(p_port); return PJ_ENOTSUP; } /* * 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 && slotmax_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; } 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; pjmedia_audio_format_detail *src_afd, *dst_afd; unsigned i; /* Check arguments */ PJ_ASSERT_RETURN(conf && src_slotmax_ports && sink_slotmax_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; } src_afd = pjmedia_format_get_audio_format_detail(&src_port->info->fmt, 1); dst_afd = pjmedia_format_get_audio_format_detail(&dst_port->info->fmt, 1); /* Format must match. */ if (src_port->info->fmt.id != dst_port->info->fmt.id || src_afd->avg_bps != dst_afd->avg_bps) { pj_mutex_unlock(conf->mutex); return PJMEDIA_ENOTCOMPATIBLE; } /* Clock rate must match. */ if (src_afd->clock_rate != dst_afd->clock_rate) { pj_mutex_unlock(conf->mutex); return PJMEDIA_ENCCLOCKRATE; } /* Channel count must match. */ if (src_afd->channel_count != dst_afd->channel_count) { pj_mutex_unlock(conf->mutex); return PJMEDIA_ENCCHANNEL; } /* Source and sink ptime must be equal or a multiplication factor. */ if ((src_afd->frame_time_usec % dst_afd->frame_time_usec != 0) && (dst_afd->frame_time_usec % src_afd->frame_time_usec != 0)) { pj_mutex_unlock(conf->mutex); return PJMEDIA_ENCSAMPLESPFRAME; } /* If sink is currently listening to other ports, it needs to be released * first before the new connection made. */ if (dst_port->transmitter_cnt > 0) { unsigned j; pj_bool_t transmitter_found = PJ_FALSE; pj_assert(dst_port->transmitter_cnt == 1); for (j=0; jmax_ports && !transmitter_found; ++j) { if (conf->ports[j]) { unsigned k; for (k=0; k < conf->ports[j]->listener_cnt; ++k) { if (conf->ports[j]->listener_slots[k] == sink_slot) { PJ_LOG(2,(THIS_FILE, "Connection [%d->%d] is " "disconnected for new connection [%d->%d]", j, sink_slot, src_slot, sink_slot)); pjmedia_conf_disconnect_port(conf, j, sink_slot); transmitter_found = PJ_TRUE; break; } } } } pj_assert(dst_port->transmitter_cnt == 0); } /* Check if connection has been made */ for (i=0; ilistener_cnt; ++i) { if (src_port->listener_slots[i] == sink_slot) break; } /* Update master port info shortcut, note that application may update * the master port info when the audio device needs to be reopened with * a new format to match to ports connection format. */ conf->ports[0]->samples_per_frame = PJMEDIA_PIA_SPF(conf->ports[0]->info); 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_slotmax_ports && sink_slotmax_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; ilistener_cnt; ++i) { if (src_port->listener_slots[i] == sink_slot) break; } if (i != src_port->listener_cnt) { pjmedia_frame_ext *f; 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; /* Cleanup listener TX buffer. */ f = (pjmedia_frame_ext*)dst_port->tx_buf; f->base.type = PJMEDIA_FRAME_TYPE_NONE; f->base.size = 0; f->samples_cnt = 0; f->subframe_cnt = 0; 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)); } 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; imax_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; jlistener_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; pjmedia_frame_ext *f; 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; /* Cleanup & reinit listener TX buffer. */ f = (pjmedia_frame_ext*)dst_port->tx_buf; f->base.type = PJMEDIA_FRAME_TYPE_NONE; f->base.size = 0; f->samples_cnt = 0; f->subframe_cnt = 0; } /* 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; imax_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; const pjmedia_audio_format_detail *afd; /* Check arguments */ PJ_ASSERT_RETURN(conf && slotmax_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; } afd = pjmedia_format_get_audio_format_detail(&conf_port->info->fmt, 1); pj_bzero(info, sizeof(pjmedia_conf_port_info)); 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 = afd->clock_rate; info->channel_count = afd->channel_count; info->samples_per_frame = conf_port->samples_per_frame; info->bits_per_sample = afd->bits_per_sample; info->format = conf_port->port->info.fmt; 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; imax_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 && slotmax_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 && slotmax_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; } /* Level adjustment is applicable only for ports that work with raw PCM. */ PJ_ASSERT_RETURN(conf_port->info->fmt.id == PJMEDIA_FORMAT_L16, PJ_EIGNORED); /* 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 && slotmax_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; } /* Level adjustment is applicable only for ports that work with raw PCM. */ PJ_ASSERT_RETURN(conf_port->info->fmt.id == PJMEDIA_FORMAT_L16, PJ_EIGNORED); /* Set normalized adjustment level. */ conf_port->tx_adj_level = adj_level + NORMAL_LEVEL; /* Unlock mutex */ pj_mutex_unlock(conf->mutex); return PJ_SUCCESS; } /* Deliver frm_src to a listener port, eventually call port's put_frame() * when samples count in the frm_dst are equal to port's samples_per_frame. */ static pj_status_t write_frame(struct conf_port *cport_dst, const pjmedia_frame *frm_src) { pjmedia_frame *frm_dst = (pjmedia_frame*)cport_dst->tx_buf; PJ_TODO(MAKE_SURE_DEST_FRAME_HAS_ENOUGH_SPACE); frm_dst->type = frm_src->type; frm_dst->timestamp = cport_dst->ts_tx; if (frm_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) { pjmedia_frame_ext *f_src = (pjmedia_frame_ext*)frm_src; pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst; unsigned i; for (i = 0; i < f_src->subframe_cnt; ++i) { pjmedia_frame_ext_subframe *sf; /* Copy frame to listener's TX buffer. */ sf = pjmedia_frame_ext_get_subframe(f_src, i); pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen, f_src->samples_cnt / f_src->subframe_cnt); /* Check if it's time to deliver the TX buffer to listener, * i.e: samples count in TX buffer equal to listener's * samples per frame. */ if (f_dst->samples_cnt >= cport_dst->samples_per_frame) { if (cport_dst->slot) { pjmedia_port_put_frame(cport_dst->port, (pjmedia_frame*)f_dst); /* Reset TX buffer. */ f_dst->subframe_cnt = 0; f_dst->samples_cnt = 0; } /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->samples_per_frame); } } } else if (frm_src->type == PJMEDIA_FRAME_TYPE_AUDIO) { pj_int16_t *f_start, *f_end; f_start = (pj_int16_t*)frm_src->buf; f_end = f_start + (frm_src->size >> 1); while (f_start < f_end) { unsigned nsamples_to_copy, nsamples_req; /* Copy frame to listener's TX buffer. * Note that if the destination is port 0, just copy the whole * available samples. */ nsamples_to_copy = f_end - f_start; nsamples_req = cport_dst->samples_per_frame - (frm_dst->size>>1); if (cport_dst->slot && nsamples_to_copy > nsamples_req) nsamples_to_copy = nsamples_req; /* Adjust TX level. */ if (cport_dst->tx_adj_level != NORMAL_LEVEL) { pj_int16_t *p, *p_end; p = f_start; p_end = p + nsamples_to_copy; while (p < p_end) { pj_int32_t itemp = *p; /* Adjust the level */ itemp = (itemp * cport_dst->tx_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. */ *p = (pj_int16_t)itemp; ++p; } } pjmedia_copy_samples((pj_int16_t*)frm_dst->buf + (frm_dst->size>>1), f_start, nsamples_to_copy); frm_dst->size += nsamples_to_copy << 1; f_start += nsamples_to_copy; /* Check if it's time to deliver the TX buffer to listener, * i.e: samples count in TX buffer equal to listener's * samples per frame. Note that for destination port 0 this * function will just populate all samples in the TX buffer. */ if (cport_dst->slot == 0) { /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, nsamples_to_copy); } else if ((frm_dst->size >> 1) == cport_dst->samples_per_frame) { pjmedia_port_put_frame(cport_dst->port, frm_dst); /* Reset TX buffer. */ frm_dst->size = 0; /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->samples_per_frame); } } } else if (frm_src->type == PJMEDIA_FRAME_TYPE_NONE) { /* Check port format. */ if (cport_dst->port && cport_dst->port->info.fmt.id == PJMEDIA_FORMAT_L16) { /* When there is already some samples in listener's TX buffer, * pad the buffer with "zero samples". */ if (frm_dst->size != 0) { pjmedia_zero_samples((pj_int16_t*)frm_dst->buf, cport_dst->samples_per_frame - (frm_dst->size>>1)); frm_dst->type = PJMEDIA_FRAME_TYPE_AUDIO; frm_dst->size = cport_dst->samples_per_frame << 1; if (cport_dst->slot) { pjmedia_port_put_frame(cport_dst->port, frm_dst); /* Reset TX buffer. */ frm_dst->size = 0; } /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->samples_per_frame); } } else { pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst; if (f_dst->samples_cnt != 0) { frm_dst->type = PJMEDIA_FRAME_TYPE_EXTENDED; pjmedia_frame_ext_append_subframe(f_dst, NULL, 0, (pj_uint16_t) (cport_dst->samples_per_frame - f_dst->samples_cnt)); if (cport_dst->slot) { pjmedia_port_put_frame(cport_dst->port, frm_dst); /* Reset TX buffer. */ f_dst->subframe_cnt = 0; f_dst->samples_cnt = 0; } /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->samples_per_frame); } } /* Synchronize clock. */ while (pj_cmp_timestamp(&cport_dst->ts_clock, &cport_dst->ts_tx) > 0) { frm_dst->type = PJMEDIA_FRAME_TYPE_NONE; frm_dst->timestamp = cport_dst->ts_tx; if (cport_dst->slot) pjmedia_port_put_frame(cport_dst->port, frm_dst); /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->samples_per_frame); } } return PJ_SUCCESS; } /* * 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; unsigned ci, i; /* Lock mutex */ pj_mutex_lock(conf->mutex); /* Call get_frame() from all ports (except port 0) that has * receiver and distribute the frame (put the frame to the destination * port's buffer to accommodate different ptime, and ultimately call * put_frame() of that port) to ports that are receiving from this port. */ for (i=1, ci=1; imax_ports && ciport_cnt; ++i) { struct conf_port *cport = conf->ports[i]; unsigned master_samples_per_frame; /* Skip empty port. */ if (!cport) continue; /* Var "ci" is to count how many ports have been visited so far. */ ++ci; master_samples_per_frame = PJMEDIA_PIA_SPF(&conf->master_port->info); /* Update clock of the port. */ pj_add_timestamp32(&cport->ts_clock, master_samples_per_frame); /* Skip if we're not allowed to receive from this port or * the port doesn't have listeners. */ if (cport->rx_setting == PJMEDIA_PORT_DISABLE || cport->listener_cnt == 0) { cport->rx_level = 0; pj_add_timestamp32(&cport->ts_rx, master_samples_per_frame); continue; } /* Get frame from each port, put it to the listener TX buffer, * and eventually call put_frame() of the listener. This loop * will also make sure the ptime between conf & port synchronized. */ while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_rx) > 0) { pjmedia_frame *f = (pjmedia_frame*)conf->buf; pj_status_t status; unsigned j; pj_int32_t level = 0; pj_add_timestamp32(&cport->ts_rx, cport->samples_per_frame); f->buf = &conf->buf[sizeof(pjmedia_frame)]; f->size = cport->samples_per_frame<<1; /* Get frame from port. */ status = pjmedia_port_get_frame(cport->port, f); if (status != PJ_SUCCESS) continue; /* Calculate & adjust RX level. */ if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) { if (cport->rx_adj_level != NORMAL_LEVEL) { pj_int16_t *p = (pj_int16_t*)f->buf; pj_int16_t *end; end = p + (f->size >> 1); while (p < end) { pj_int32_t itemp = *p; /* Adjust the level */ itemp = (itemp * cport->rx_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; level += PJ_ABS(itemp); /* Put back in the buffer. */ *p = (pj_int16_t)itemp; ++p; } level /= (f->size >> 1); } else { level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf, f->size >> 1); } } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) { /* For extended frame, level is unknown, so we just set * it to NORMAL_LEVEL. */ level = NORMAL_LEVEL; } cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff; /* Put the frame to all listeners. */ for (j=0; j < cport->listener_cnt; ++j) { struct conf_port *listener; listener = conf->ports[cport->listener_slots[j]]; /* Skip if this listener doesn't want to receive audio */ if (listener->tx_setting == PJMEDIA_PORT_DISABLE) { pj_add_timestamp32(&listener->ts_tx, listener->samples_per_frame); listener->tx_level = 0; continue; } status = write_frame(listener, f); if (status != PJ_SUCCESS) { listener->tx_level = 0; continue; } /* Set listener TX level based on transmitter RX level & * listener TX level. */ listener->tx_level = (cport->rx_level * listener->tx_adj_level) >> 8; } } } /* Keep alive. Update TX timestamp and send frame type NONE to all * underflow ports at their own clock. */ for (i=1, ci=1; imax_ports && ciport_cnt; ++i) { struct conf_port *cport = conf->ports[i]; /* Skip empty port. */ if (!cport) continue; /* Var "ci" is to count how many ports have been visited so far. */ ++ci; if (cport->tx_setting==PJMEDIA_PORT_MUTE || cport->transmitter_cnt==0) { pjmedia_frame_ext *f; /* Clear left-over samples in tx_buffer, if any, so that it won't * be transmitted next time we have audio signal. */ f = (pjmedia_frame_ext*)cport->tx_buf; f->base.type = PJMEDIA_FRAME_TYPE_NONE; f->base.size = 0; f->samples_cnt = 0; f->subframe_cnt = 0; cport->tx_level = 0; while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_tx) > 0) { if (cport->tx_setting == PJMEDIA_PORT_ENABLE) { pjmedia_frame tmp_f; tmp_f.timestamp = cport->ts_tx; tmp_f.type = PJMEDIA_FRAME_TYPE_NONE; tmp_f.buf = NULL; tmp_f.size = 0; pjmedia_port_put_frame(cport->port, &tmp_f); pj_add_timestamp32(&cport->ts_tx, cport->samples_per_frame); } } } } /* Return sound playback frame. */ do { struct conf_port *this_cport = conf->ports[this_port->port_data.ldata]; pjmedia_frame *f_src = (pjmedia_frame*) this_cport->tx_buf; frame->type = f_src->type; if (f_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) { pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src; pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frame; pjmedia_frame_ext_subframe *sf; unsigned samples_per_subframe; if (f_src_->samples_cnt < this_cport->samples_per_frame) { f_dst->base.type = PJMEDIA_FRAME_TYPE_NONE; f_dst->samples_cnt = 0; f_dst->subframe_cnt = 0; break; } f_dst->samples_cnt = 0; f_dst->subframe_cnt = 0; i = 0; samples_per_subframe = f_src_->samples_cnt / f_src_->subframe_cnt; while (f_dst->samples_cnt < this_cport->samples_per_frame) { sf = pjmedia_frame_ext_get_subframe(f_src_, i++); pj_assert(sf); pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen, samples_per_subframe); } /* Shift left TX buffer. */ pjmedia_frame_ext_pop_subframes(f_src_, i); } else if (f_src->type == PJMEDIA_FRAME_TYPE_AUDIO) { if ((f_src->size>>1) < this_cport->samples_per_frame) { frame->type = PJMEDIA_FRAME_TYPE_NONE; frame->size = 0; break; } pjmedia_copy_samples((pj_int16_t*)frame->buf, (pj_int16_t*)f_src->buf, this_cport->samples_per_frame); frame->size = this_cport->samples_per_frame << 1; /* Shift left TX buffer. */ f_src->size -= frame->size; if (f_src->size) pjmedia_move_samples((pj_int16_t*)f_src->buf, (pj_int16_t*)f_src->buf + this_cport->samples_per_frame, f_src->size >> 1); } else { /* PJMEDIA_FRAME_TYPE_NONE */ pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src; /* Reset source/TX buffer */ f_src_->base.size = 0; f_src_->samples_cnt = 0; f_src_->subframe_cnt = 0; } } while (0); /* Unlock mutex */ pj_mutex_unlock(conf->mutex); return PJ_SUCCESS; } /* * Recorder callback. */ static pj_status_t put_frame(pjmedia_port *this_port, pjmedia_frame *f) { pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; struct conf_port *cport; unsigned j; pj_int32_t level = 0; /* Lock mutex */ pj_mutex_lock(conf->mutex); /* Get conf port of this port */ cport = conf->ports[this_port->port_data.ldata]; if (cport == NULL) { /* Unlock mutex */ pj_mutex_unlock(conf->mutex); return PJ_SUCCESS; } pj_add_timestamp32(&cport->ts_rx, cport->samples_per_frame); /* Skip if this port is muted/disabled. */ if (cport->rx_setting == PJMEDIA_PORT_DISABLE) { cport->rx_level = 0; /* Unlock mutex */ pj_mutex_unlock(conf->mutex); return PJ_SUCCESS; } /* Skip if no port is listening to the microphone */ if (cport->listener_cnt == 0) { cport->rx_level = 0; /* Unlock mutex */ pj_mutex_unlock(conf->mutex); return PJ_SUCCESS; } /* Calculate & adjust RX level. */ if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) { if (cport->rx_adj_level != NORMAL_LEVEL) { pj_int16_t *p = (pj_int16_t*)f->buf; pj_int16_t *end; end = p + (f->size >> 1); while (p < end) { pj_int32_t itemp = *p; /* Adjust the level */ itemp = (itemp * cport->rx_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; level += PJ_ABS(itemp); /* Put back in the buffer. */ *p = (pj_int16_t)itemp; ++p; } level /= (f->size >> 1); } else { level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf, f->size >> 1); } } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) { /* For extended frame, level is unknown, so we just set * it to NORMAL_LEVEL. */ level = NORMAL_LEVEL; } cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff; /* Put the frame to all listeners. */ for (j=0; j < cport->listener_cnt; ++j) { struct conf_port *listener; pj_status_t status; listener = conf->ports[cport->listener_slots[j]]; /* Skip if this listener doesn't want to receive audio */ if (listener->tx_setting == PJMEDIA_PORT_DISABLE) { pj_add_timestamp32(&listener->ts_tx, listener->samples_per_frame); listener->tx_level = 0; continue; } /* Skip loopback for now. */ if (listener == cport) { pj_add_timestamp32(&listener->ts_tx, listener->samples_per_frame); listener->tx_level = 0; continue; } status = write_frame(listener, f); if (status != PJ_SUCCESS) { listener->tx_level = 0; continue; } /* Set listener TX level based on transmitter RX level & listener * TX level. */ listener->tx_level = (cport->rx_level * listener->tx_adj_level) >> 8; } /* Unlock mutex */ pj_mutex_unlock(conf->mutex); return PJ_SUCCESS; } #endif