From 20ef6fa4b8b2f488e0420ba91491ba657ea527b1 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sun, 5 Jan 2025 12:01:09 +0100 Subject: [PATCH 01/16] parallel conference bridge --- pjmedia/src/pjmedia/conference.c | 1164 ++++++++++++++++++++++++------ 1 file changed, 952 insertions(+), 212 deletions(-) diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 80bfa66c84..69cd6d14f7 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -31,8 +31,38 @@ #include #include +#if defined(PJ_STACK_IMPLEMENTATION) +#include +#endif + #if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0 +#ifdef _OPENMP +# include + +/* Open MP related modification by Leonid Goltsblat 2021-2025 + * + * Enabling OpenMP support makes it possible to parallelize the processing of audio frames. + * To activate OpenMP support you must perform the appropriate development environment setup. + * For example, in Visual Studio, you must enable OpenMP support in the project settings. + * see: https://learn.microsoft.com/en-us/cpp/build/reference/openmp-enable-openmp-2-0-support?view=msvc-170 + * In GCC, you must use the appropriate compiler options. + * + * Current implementation uses only basic subset of OpenMP features corresponding to OpenMP 2.0 + * and should be compatible with any OpenMP 2.0 compliant compiler. + */ + +# ifndef PJ_OPENMP_FOR_CLAUSES +# define PJ_OPENMP_FOR_CLAUSES +/* for example + * #define PJSIP_OMP_CHUNK_SIZE 16 + * #define PJ_OPENMP_FOR_CLAUSES schedule(dynamic, PJSIP_OMP_CHUNK_SIZE) if (max_thread > 1) num_threads(max_thread) + * will produce + * #pragma omp parallel for schedule(dynamic, PJSIP_OMP_CHUNK_SIZE) if (max_thread > 1) num_threads(max_thread) + */ +# endif +#endif //_OPENMP + /* CONF_DEBUG enables detailed operation of the conference bridge. * Beware that it prints large amounts of logs (several lines per frame). */ @@ -45,6 +75,15 @@ #endif +//#define CONF_DEBUG_EX +#ifdef CONF_DEBUG_EX +//# include +# define TRACE_EX(x) PJ_LOG(5,x) +#else +# define TRACE_EX(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. @@ -110,7 +149,9 @@ static FILE *fhnd_rec; */ struct conf_port { - pj_pool_t *pool; /**< Pool. */ + pj_pool_t *pool; /**< for autonomous lifetime control + * we need separate memory pool + * this port created from */ 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 */ @@ -223,8 +264,34 @@ struct conf_port */ pjmedia_delay_buf *delay_buf; +#if 0 + /* We always know lower_bound and upper_bound and never use port_cnt + * There is no need for asynchronous port creation at all. + * This flag has become redundant. + */ pj_bool_t is_new; /**< Newly added port, avoid read/write data from/to. */ +#endif + +#ifdef _OPENMP + pj_int16_t *rx_frame_buf; /**< The RX buffer. */ + unsigned rx_frame_buf_cap;/**< Max size, in bytes */ + + omp_lock_t tx_Lock; /**< Lock to protect transmit + buffer */ +#endif //_OPENMP + + pj_timestamp last_timestamp;/**< last transmited packet + * timestamp. We set this when + * first time put something into + * the mix_buf. + * If this time stamp is equals to + * current frame timestamp, + * we have data to transmite */ + +#ifdef CONF_DEBUG_EX + SLOT_TYPE slot; /**< SLOT for debug purpose */ +#endif //CONF_DEBUG_EX }; @@ -232,6 +299,31 @@ struct conf_port typedef struct op_entry op_entry; +/* + * port_slot is an array item, which index is index in ports[] (i.e. ports[] slot) + * port_slot used to quick find empty slot. Only array index is used for this purpose. + */ +#if defined(PJ_STACK_IMPLEMENTATION) + +typedef struct PJ_STACK_ALIGN_PREFIX port_slot { + PJ_DECL_STACK_MEMBER(struct port_slot); +} PJ_STACK_ALIGN_SUFFIX port_slot; + +typedef pj_stack_type unused_slots_cache; /**< Container to store unused + * port's slots. */ + +#else + +typedef struct port_slot { + PJ_DECL_LIST_MEMBER(struct port_slot); +} port_slot; + +typedef port_slot unused_slots_cache; /**< Container to store unused + * port's slots. */ + +#endif + + /* * Conference bridge. */ @@ -240,7 +332,14 @@ struct pjmedia_conf pj_pool_t *pool; /**< Pool */ unsigned options; /**< Bitmask options. */ unsigned max_ports; /**< Maximum ports. */ +#if 0 unsigned port_cnt; /**< Current number of ports. */ +#endif //0 + /**< lower and upper boundaries to scan ports[] */ + pj_uint32_t lower_bound; /**< The least connected port's slot*/ + pj_uint32_t upper_bound; /**< The next after greatest + * connected port's slot */ + unsigned connect_cnt; /**< Total number of connections */ pjmedia_snd_port *snd_dev_port; /**< Sound device port. */ pjmedia_port *master_port; /**< Port zero's port. */ @@ -254,6 +353,33 @@ struct pjmedia_conf op_entry *op_queue; /**< Queue of operations. */ op_entry *op_queue_free;/**< Queue of free entries. */ + + + unused_slots_cache *unused_slots; /**< Unused port's slots. */ + port_slot *free_port_slots; /**< Persistent array of max_ports + * size used for quick and locking + * free finding unused ports[] + * slot (i.e. index in ports[]) */ + pj_int32_t listener_counter;/**< current quantity of + * active_listener */ + struct conf_port **active_listener; /**< listener with data to transmit */ + + pj_int32_t *active_ports; /**< array of port slots from 0 + * to upper_bound with no gaps. + * Compacted port's array should + * help OpenMP to distribute task + * throught team's threads */ + + +#ifdef _OPENMP + pj_thread_desc *omp_threads; /**< Thread description's + * to register omp threads + * with pjsip */ + int omp_max_threads; /**< omp_threads[] dimension */ + int omp_threads_idx; /**< omp_threads[] current idx */ + +#endif //_OPENMP + }; @@ -271,6 +397,14 @@ static pj_status_t destroy_port_pasv(pjmedia_port *this_port); #endif +#ifdef _OPENMP +/* register omp thread with pjsip */ +static void register_omp_thread(pjmedia_conf* conf); +#endif //_OPENMP + + +static void destroy_conf_port(struct conf_port *conf_port); + /* As we don't hold mutex in the clock/get_frame(), some conference operations * that change conference states need to be synchronized with the clock. * So some steps of the operations needs to be executed within the clock tick @@ -281,7 +415,9 @@ static pj_status_t destroy_port_pasv(pjmedia_port *this_port); typedef enum op_type { OP_UNKNOWN, +#if 0 OP_ADD_PORT, +#endif OP_REMOVE_PORT, OP_CONNECT_PORTS, OP_DISCONNECT_PORTS, @@ -290,9 +426,11 @@ typedef enum op_type /* Synchronized operation parameter. */ typedef union op_param { +#if 0 struct { unsigned port; } add_port; +#endif struct { unsigned port; @@ -319,7 +457,9 @@ typedef struct op_entry { } op_entry; /* Prototypes of synchronized operation */ +#if 0 static void op_add_port(pjmedia_conf *conf, const op_param *prm); +#endif static void op_remove_port(pjmedia_conf *conf, const op_param *prm); static void op_connect_ports(pjmedia_conf *conf, const op_param *prm); static void op_disconnect_ports(pjmedia_conf *conf, const op_param *prm); @@ -340,17 +480,37 @@ static op_entry* get_free_op_entry(pjmedia_conf *conf) static void handle_op_queue(pjmedia_conf *conf) { - op_entry *op, *next_op; + op_entry *op = NULL; + + while (1) { + + /* Only op_queue_free and op_queue are accessed concurrently + * and require mutex protection. + * The function itself cannot be invoked simultaneously from + * different threads, so no other data requires mutex protection. + */ + pj_mutex_lock(conf->mutex); + + if (op) + pj_list_push_back(conf->op_queue_free, op); + + op = conf->op_queue->next; + if (op != conf->op_queue) + pj_list_erase(op); - op = conf->op_queue->next; - while (op != conf->op_queue) { - next_op = op->next; - pj_list_erase(op); + pj_mutex_unlock(conf->mutex); + + if (op == conf->op_queue) + break; + + pj_assert(op); switch(op->type) { +#if 0 case OP_ADD_PORT: op_add_port(conf, &op->param); break; +#endif case OP_REMOVE_PORT: op_remove_port(conf, &op->param); break; @@ -366,8 +526,6 @@ static void handle_op_queue(pjmedia_conf *conf) } op->type = OP_UNKNOWN; - pj_list_push_back(conf->op_queue_free, op); - op = next_op; } } @@ -376,10 +534,64 @@ static void handle_op_queue(pjmedia_conf *conf) static void conf_port_on_destroy(void *arg) { struct conf_port *conf_port = (struct conf_port*)arg; - if (conf_port->pool) - pj_pool_safe_release(&conf_port->pool); + destroy_conf_port(conf_port); +} + +/* +* port is active (has listers or transmitters), i.e. conference bridge should not skip this port, +* as voice should be send to or receive from this port. +*/ +PJ_INLINE(pj_bool_t) is_port_active(struct conf_port* p_conf_port) +{ + return p_conf_port && (p_conf_port->listener_cnt || p_conf_port->transmitter_cnt); +} + +PJ_INLINE(void) correct_port_boundary(pjmedia_conf *conf, SLOT_TYPE src_slot) +{ + pj_assert(conf && src_slot < conf->max_ports); + + if (is_port_active(conf->ports[src_slot])) { + + if (src_slot >= conf->upper_bound) + conf->upper_bound = src_slot + 1; + if (src_slot < conf->lower_bound) + conf->lower_bound = src_slot; + + pj_assert(conf->lower_bound < conf->upper_bound); + + } else { + if (src_slot + 1 >= conf->upper_bound) { + while (conf->lower_bound < conf->upper_bound && is_port_active(conf->ports[conf->upper_bound - 1])) { + pj_assert(conf->upper_bound); + --conf->upper_bound; + } + } + if (src_slot <= conf->lower_bound) { + while (conf->lower_bound < conf->upper_bound && !is_port_active(conf->ports[conf->lower_bound])) { + pj_assert(conf->lower_bound < conf->max_ports); + ++conf->lower_bound; + } + } + if (conf->lower_bound >= conf->upper_bound) { + conf->lower_bound = conf->max_ports; + conf->upper_bound = 0; + } + } + } +/* + * Find empty port slot in the conference bridge and reserve this slot. + * O(1) thread-safe operation + */ +static SLOT_TYPE conf_reserve_port(pjmedia_conf *conf); + +/* + * Return conf_port slot to unused slots cache. + * O(1) thread-safe operation + */ +static pj_status_t conf_release_port(pjmedia_conf *conf, SLOT_TYPE slot); + /* * Create port. @@ -390,7 +602,7 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, const pj_str_t *name, struct conf_port **p_conf_port) { - struct conf_port *conf_port; + struct conf_port *conf_port = NULL; pj_pool_t *pool = NULL; char pname[PJ_MAX_OBJ_NAME]; pj_status_t status = PJ_SUCCESS; @@ -584,6 +796,17 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, conf_port->last_mix_adj = NORMAL_LEVEL; +#ifdef _OPENMP + /* Get the bytes_per_frame value, to determine the size of the + * buffer. + */ + conf_port->rx_frame_buf_cap = PJMEDIA_AFD_AVG_FSZ(pjmedia_format_get_audio_format_detail(&conf->master_port->info.fmt, PJ_TRUE)); + conf_port->rx_frame_buf = (pj_int16_t *)pj_pool_zalloc(pool, conf_port->rx_frame_buf_cap); + + omp_init_lock(&conf_port->tx_Lock); +#endif //_OPENMP + + /* Done */ *p_conf_port = conf_port; @@ -695,10 +918,15 @@ static pj_status_t create_sound_port( pj_pool_t *pool, PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0")); } +#ifdef CONF_DEBUG_EX + conf_port->slot = 0; +#endif //CONF_DEBUG_EX - /* Add the port to the bridge */ + /* Add the port to the bridge */ conf->ports[0] = conf_port; - conf->port_cnt++; +#if 0 + conf->port_cnt++; // the port will become active only when connected +#endif return PJ_SUCCESS; } @@ -739,9 +967,14 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, PJ_ASSERT_RETURN(conf, PJ_ENOMEM); conf->pool = pool; - conf->ports = (struct conf_port**) - pj_pool_zalloc(pool, max_ports*sizeof(void*)); - PJ_ASSERT_RETURN(conf->ports, PJ_ENOMEM); + conf->ports = pj_pool_calloc(pool, max_ports, sizeof(void*)); + PJ_ASSERT_ON_FAIL( conf->ports, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + + conf->active_ports = pj_pool_calloc(pool, max_ports, sizeof(pj_int32_t) ); + PJ_ASSERT_ON_FAIL( conf->active_ports, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + + conf->active_listener = pj_pool_calloc(pool, max_ports, sizeof(struct conf_port*)); + PJ_ASSERT_ON_FAIL( conf->active_listener, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); conf->options = options; conf->max_ports = max_ports; @@ -750,6 +983,9 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, conf->samples_per_frame = samples_per_frame; conf->bits_per_sample = bits_per_sample; + conf->lower_bound = max_ports; // no connected ports yet + conf->upper_bound = 0; // no connected ports yet + /* Create and initialize the master port interface. */ conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port); @@ -805,6 +1041,36 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, pj_list_init(conf->op_queue); pj_list_init(conf->op_queue_free); + +#if defined(PJ_STACK_IMPLEMENTATION) + status = pj_stack_create( pool, &conf->unused_slots ); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy( conf ); + return status; + } +#else + conf->unused_slots = PJ_POOL_ZALLOC_T(pool, unused_slots_cache); + pj_list_init(conf->unused_slots); +#endif + conf->free_port_slots = pj_pool_calloc(pool, max_ports, sizeof(port_slot)); + PJ_ASSERT_ON_FAIL( conf->free_port_slots, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + unsigned i = conf->max_ports; + while (i--) { /* prepare unused slots to later reservation, reverse order due to FILO */ + if (!conf->ports[i]) { /* If sound device was created, skip it's slot */ + status = conf_release_port( conf, i ); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy( conf ); + return status; + } + } + } + +#ifdef _OPENMP + conf->omp_max_threads = omp_get_max_threads(); + /* Thread description's to register omp threads with pjsip */ + conf->omp_threads = (pj_thread_desc *)pj_pool_calloc( pool, 2 * conf->omp_max_threads, sizeof(pj_thread_desc) ); +#endif //_OPENMP + /* Done */ *p_conf = conf; @@ -852,7 +1118,8 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) } /* Flush any pending operation (connect, disconnect, etc) */ - handle_op_queue(conf); + if (conf->op_queue) + handle_op_queue(conf); /* Remove all ports (may destroy them too). */ for (i=0; imax_ports; ++i) { @@ -863,6 +1130,12 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) } } +#if defined(PJ_STACK_IMPLEMENTATION) + /* Destroy stack */ + if (conf->unused_slots) + pj_stack_destroy(conf->unused_slots); +#endif + /* Destroy mutex */ if (conf->mutex) pj_mutex_destroy(conf->mutex); @@ -953,8 +1226,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, unsigned *p_port ) { struct conf_port *conf_port; - unsigned index; - op_entry *ope; + SLOT_TYPE index = INVALID_SLOT; pj_status_t status = PJ_SUCCESS; PJ_ASSERT_RETURN(conf && pool && strm_port, PJ_EINVAL); @@ -978,26 +1250,29 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, goto on_return; } - pj_mutex_lock(conf->mutex); - /* Find empty port in the conference bridge. */ - for (index=0; index < conf->max_ports; ++index) { - if (conf->ports[index] == NULL) - break; - } - - if (index == conf->max_ports) { + index = conf_reserve_port(conf); + if (index == INVALID_SLOT) { PJ_PERROR(3,(THIS_FILE, PJ_ETOOMANY, "Add port %s failed", port_name->ptr)); + //pj_assert( !"Too many ports" ); status = PJ_ETOOMANY; goto on_return; } + pj_assert(index < conf->max_ports && conf->ports[index] == NULL); /* Create conf port structure. */ status = create_conf_port(pool, conf, strm_port, port_name, &conf_port); if (status != PJ_SUCCESS) goto on_return; + pj_assert(conf_port != NULL && !is_port_active(conf_port)); + +#ifdef CONF_DEBUG_EX + conf_port->slot = index; +#endif //CONF_DEBUG_EX + +#if 0 /* Audio data flow is not protected, avoid processing this newly * added port. */ @@ -1006,8 +1281,10 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, /* Put the port, but don't add port counter yet */ conf->ports[index] = conf_port; //conf->port_cnt++; + pj_mutex_lock( conf->mutex ); /* Queue the operation */ + op_entry *ope; ope = get_free_op_entry(conf); if (ope) { ope->type = OP_ADD_PORT; @@ -1019,6 +1296,14 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, status = PJ_ENOMEM; goto on_return; } +#endif //0 + + /* Put the port to the reserved slot. */ + conf->ports[index] = conf_port; /* - the port will become active only when connected + * - pointer assignment is processor level atomic + */ + PJ_LOG(4,(THIS_FILE, "Added port %d (%.*s)", + index, (int)port_name->slen, port_name->ptr)); /* Done. */ if (p_port) { @@ -1026,13 +1311,64 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, } on_return: - pj_mutex_unlock(conf->mutex); + if (status != PJ_SUCCESS) { + if (index != INVALID_SLOT) { + conf->ports[index] = NULL; + conf_release_port( conf, index ); + } + } + pj_log_pop_indent(); return status; } +/* + * Find empty port in the conference bridge. + */ +static SLOT_TYPE conf_reserve_port(pjmedia_conf *conf) +{ + port_slot *pslot; +#if defined(PJ_STACK_IMPLEMENTATION) + pslot = pj_stack_pop(conf->unused_slots); +#else + pj_mutex_lock(conf->mutex); + if (!pj_list_empty(conf->unused_slots)) { + pslot = conf->unused_slots->next; + pj_list_erase(pslot); + } else { + pslot = NULL; + } + pj_mutex_unlock(conf->mutex); +#endif + if (!pslot) + return INVALID_SLOT; + SLOT_TYPE slot = pslot - conf->free_port_slots; + pj_assert( slot < conf->max_ports && conf->ports[slot] == NULL ); + return slot; +} + +/* + * Return conf_port slot to unused slots cache. + */ +static pj_status_t conf_release_port(pjmedia_conf *conf, SLOT_TYPE slot) +{ + /* Check arguments */ + PJ_ASSERT_RETURN( conf && slot < conf->max_ports, PJ_EINVAL ); + PJ_ASSERT_RETURN( conf->ports[slot] == NULL, PJ_EINVALIDOP ); +#if defined(PJ_STACK_IMPLEMENTATION) + return pj_stack_push( conf->unused_slots, conf->free_port_slots + slot ); +#else + pj_mutex_lock(conf->mutex); + pj_list_push_front(conf->unused_slots, conf->free_port_slots + slot); + pj_mutex_unlock(conf->mutex); + return PJ_SUCCESS; +#endif +} + + +#if 0 static void op_add_port(pjmedia_conf *conf, const op_param *prm) { unsigned port = prm->add_port.port; @@ -1044,11 +1380,14 @@ static void op_add_port(pjmedia_conf *conf, const op_param *prm) /* Activate newly added port */ cport->is_new = PJ_FALSE; +#if 0 ++conf->port_cnt; PJ_LOG(4,(THIS_FILE, "Added port %d (%.*s), port count=%d", port, (int)cport->name.slen, cport->name.ptr, conf->port_cnt)); +#endif } +#endif #if !DEPRECATED_FOR_TICKET_2234 @@ -1068,7 +1407,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf, { struct conf_port *conf_port; pjmedia_port *port; - unsigned index; + SLOT_TYPE index; pj_str_t tmp; pj_status_t status; @@ -1092,21 +1431,13 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf, PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); PJ_UNUSED_ARG(options); - pj_mutex_lock(conf->mutex); - - if (conf->port_cnt >= conf->max_ports) { + /* Find empty port in the conference bridge. */ + slot = conf_reserve_port(conf); + if (slot == INVALID_SLOT) { 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); + pj_assert(slot < conf->max_ports && conf->ports[slot] == NULL); if (name == NULL) { name = &tmp; @@ -1134,14 +1465,14 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf, /* 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. */ + /* Put the port to the reserved slot. */ conf->ports[index] = conf_port; +#if 0 conf->port_cnt++; +#endif /* Done. */ if (p_slot) @@ -1149,8 +1480,6 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf, if (p_port) *p_port = port; - pj_mutex_unlock(conf->mutex); - return PJ_SUCCESS; } #endif @@ -1308,13 +1637,17 @@ static void op_connect_ports(pjmedia_conf *conf, const op_param *prm) ++src_port->listener_cnt; ++dst_port->transmitter_cnt; - 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)); + correct_port_boundary( conf, src_slot ); + correct_port_boundary( conf, sink_slot ); + pj_assert( conf->lower_bound < conf->upper_bound ); + + 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)); } /* @@ -1378,9 +1711,9 @@ PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf, static void op_disconnect_ports(pjmedia_conf *conf, const op_param *prm) { - unsigned src_slot, sink_slot; + SLOT_TYPE src_slot, sink_slot; struct conf_port *src_port = NULL, *dst_port = NULL; - int i; + SLOT_TYPE i; /* Ports must be valid. */ src_slot = prm->disconnect_ports.src; @@ -1416,6 +1749,10 @@ static void op_disconnect_ports(pjmedia_conf *conf, --src_port->listener_cnt; --dst_port->transmitter_cnt; + correct_port_boundary( conf, src_slot ); + if (src_port != dst_port) + correct_port_boundary( conf, sink_slot ); + PJ_LOG(4,(THIS_FILE, "Port %d (%.*s) stop transmitting to port %d (%.*s)", src_slot, @@ -1433,49 +1770,57 @@ static void op_disconnect_ports(pjmedia_conf *conf, /* Disconnect multiple conn: any -> sink */ } else if (dst_port) { - PJ_LOG(4,(THIS_FILE, - "Stop any transmission to port %d (%.*s)", - sink_slot, - (int)dst_port->name.slen, - dst_port->name.ptr)); - - for (i=0; imax_ports; ++i) { - int j; - - src_port = conf->ports[i]; - if (!src_port || src_port->listener_cnt == 0) - continue; - - /* We need to iterate backwards since the listener count - * can potentially decrease. - */ - for (j=src_port->listener_cnt-1; j>=0; --j) { - if (src_port->listener_slots[j] == sink_slot) { - op_param op_prm = {0}; - op_prm.disconnect_ports.src = i; - op_prm.disconnect_ports.sink = sink_slot; - op_disconnect_ports(conf, &op_prm); - break; + /* Remove this port from transmit array of other ports. */ + if (dst_port->transmitter_cnt) { + PJ_LOG(4,(THIS_FILE, + "Stop any transmission to port %d (%.*s)", + sink_slot, + (int)dst_port->name.slen, + dst_port->name.ptr)); + + for (i = conf->lower_bound; i < conf->upper_bound; ++i) { + int j; + + src_port = conf->ports[i]; + if (!src_port || src_port->listener_cnt == 0) + continue; + + /* We need to iterate backwards since the listener count + * can potentially decrease. + */ + for (j = src_port->listener_cnt - 1; j >= 0; --j) { + if (src_port->listener_slots[j] == sink_slot) { + op_param op_prm = { 0 }; + op_prm.disconnect_ports.src = i; + op_prm.disconnect_ports.sink = sink_slot; + op_disconnect_ports( conf, &op_prm ); + break; + } } } + pj_assert( !dst_port->transmitter_cnt ); } /* Disconnect multiple conn: source -> any */ } else if (src_port) { - PJ_LOG(4,(THIS_FILE, - "Stop any transmission from port %d (%.*s)", - src_slot, - (int)src_port->name.slen, - src_port->name.ptr)); + if (src_port->listener_cnt) { + int j; /* should be signed! */ + PJ_LOG(4,(THIS_FILE, + "Stop any transmission from port %d (%.*s)", + src_slot, + (int)src_port->name.slen, + src_port->name.ptr)); - /* We need to iterate backwards since the listener count - * will keep decreasing. - */ - for (i=src_port->listener_cnt-1; i>=0; --i) { - op_param op_prm = {0}; - op_prm.disconnect_ports.src = src_slot; - op_prm.disconnect_ports.sink = src_port->listener_slots[i]; - op_disconnect_ports(conf, &op_prm); + /* We need to iterate backwards since the listener count + * will keep decreasing. + */ + for (j = src_port->listener_cnt - 1; j >= 0; --j) { + op_param op_prm = {0}; + op_prm.disconnect_ports.src = src_slot; + op_prm.disconnect_ports.sink = src_port->listener_slots[j]; + op_disconnect_ports( conf, &op_prm ); + } + pj_assert( !src_port->listener_cnt ); } /* Invalid ports */ @@ -1608,7 +1953,15 @@ pjmedia_conf_disconnect_port_from_sinks( pjmedia_conf *conf, */ PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf) { +#if defined(PJ_STACK_IMPLEMENTATION) + return conf->max_ports - pj_stack_size(conf->unused_slots); //O(1) +#else + return conf->max_ports - pj_list_size(conf->unused_slots); //O(n) +#endif + +#if 0 return conf->port_cnt; +#endif } /* @@ -1699,6 +2052,43 @@ static void op_remove_port(pjmedia_conf *conf, const op_param *prm) op_prm.disconnect_ports.sink = INVALID_SLOT; op_disconnect_ports(conf, &op_prm); + pj_assert( !is_port_active( conf_port ) ); + /* Remove the port. */ + conf->ports[port] = NULL; +#if 0 + if (!conf_port->is_new) + --conf->port_cnt; + + PJ_LOG(4,(THIS_FILE,"Removed port %d (%.*s), port count=%d", + port, (int)conf_port->name.slen, conf_port->name.ptr, + conf->port_cnt)); +#endif + PJ_LOG(4,(THIS_FILE,"Removed port %d (%.*s)", + port, (int)conf_port->name.slen, conf_port->name.ptr)); + + /* Return conf_port slot to unused slots cache. */ + conf_release_port( conf, port ); + + /* Decrease conf port ref count */ + if (conf_port->port && conf_port->port->grp_lock) + pj_grp_lock_dec_ref(conf_port->port->grp_lock); + else + destroy_conf_port(conf_port); +} + +static void destroy_conf_port( struct conf_port *conf_port ) +{ + pj_assert( conf_port ); + + TRACE_EX( (THIS_FILE, "%s: destroy_conf_port %p (%.*s, %d) transmitter_cnt=%d, listener_cnt=%d", + pj_thread_get_name( pj_thread_this() ), + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + conf_port->slot, + conf_port->transmitter_cnt, + conf_port->listener_cnt) ); + /* Destroy resample if this conf port has it. */ if (conf_port->rx_resample) { pjmedia_resample_destroy(conf_port->rx_resample); @@ -1716,25 +2106,18 @@ static void op_remove_port(pjmedia_conf *conf, const op_param *prm) pjmedia_delay_buf_destroy(conf_port->delay_buf); conf_port->delay_buf = NULL; - if (conf_port->port) + if (conf_port->port) { pjmedia_port_destroy(conf_port->port); - conf_port->port = NULL; + conf_port->port = NULL; + } } - /* Remove the port. */ - conf->ports[port] = NULL; - if (!conf_port->is_new) - --conf->port_cnt; - - PJ_LOG(4,(THIS_FILE,"Removed port %d (%.*s), port count=%d", - port, (int)conf_port->name.slen, conf_port->name.ptr, - conf->port_cnt)); +#ifdef _OPENMP + if (conf_port->tx_Lock != NULL) + omp_destroy_lock( &conf_port->tx_Lock ); +#endif // _OPENMP - /* Decrease conf port ref count */ - if (conf_port->port && conf_port->port->grp_lock) - pj_grp_lock_dec_ref(conf_port->port->grp_lock); - else - conf_port_on_destroy(conf_port); + pj_pool_safe_release(&conf_port->pool); } @@ -2169,6 +2552,8 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, pj_int32_t tx_level; unsigned dst_count; + pj_assert( conf && cport && timestamp && frm_type ); + *frm_type = PJMEDIA_FRAME_TYPE_AUDIO; /* Skip port if it is disabled */ @@ -2181,7 +2566,18 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, * transmit NULL frame. */ else if ((cport->tx_setting == PJMEDIA_PORT_MUTE) || - (cport->transmitter_cnt == 0)) { + cport->last_timestamp.u64 != timestamp->u64 // no data in mix_buf + /*(cport->transmitter_cnt == 0)*/) { + + TRACE_EX( (THIS_FILE, "%s: Transmit heart-beat frames to port %p (%.*s, %d, transmitter_cnt=%d) last_timestamp=%llu, timestamp=%llu", + pj_thread_get_name( pj_thread_this() ), + cport, + (int)cport->name.slen, + cport->name.ptr, + cport->slot, + cport->transmitter_cnt, + cport->last_timestamp.u64, timestamp->u64) ); + pjmedia_frame frame; /* Clear left-over samples in tx_buffer, if any, so that it won't @@ -2301,9 +2697,10 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, */ frame.timestamp = *timestamp; - TRACE_((THIS_FILE, "put_frame %.*s, count=%d", + TRACE_((THIS_FILE, "put_frame %.*s, count=%d, last_timestamp=%llu, timestamp=%llu", (int)cport->name.slen, cport->name.ptr, - frame.size / BYTES_PER_SAMPLE)); + frame.size / BYTES_PER_SAMPLE, + cport->last_timestamp.u64, timestamp->u64)); return pjmedia_port_put_frame(cport->port, &frame); } else @@ -2404,15 +2801,19 @@ static pj_status_t get_frame(pjmedia_port *this_port, { 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; - + + //parallelization requires signed int + pj_int32_t i, + begin, end, /* this is lower_bound and upper_bound for conf->ports[] array */ + upper_bound; /* this is upper_bound for conf->active_ports[] array */ + TRACE_((THIS_FILE, "- clock -")); /* Check that correct size is specified. */ pj_assert(frame->size == conf->samples_per_frame * conf->bits_per_sample / 8); +#if 0 /* Perform any queued operations that need to be synchronized with * the clock such as connect, disonnect, remove. */ @@ -2423,6 +2824,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_mutex_unlock(conf->mutex); pj_log_pop_indent(); } +#endif /* No mutex from this point! Otherwise it may cause deadlock as * put_frame()/get_frame() may invoke callback. @@ -2431,58 +2833,129 @@ static pj_status_t get_frame(pjmedia_port *this_port, * synchronized. */ - /* Reset port source count. We will only reset port's mix - * buffer when we have someone transmitting to it. + begin = conf->lower_bound; + end = conf->upper_bound; + + conf->listener_counter = 0; + pj_int32_t listener_counter = 0; + +#if defined(_OPENMP) && defined(CONF_DEBUG) + int threads[64] = {0}; +#endif + + /* Step 1 initialization + * Single threaded loop to get the active_ports[] (transmitters) + * and active_listener[] (receivers) arrays. */ - for (i=0, ci=0; imax_ports && ci < conf->port_cnt; ++i) { + for (i = begin, upper_bound = 0; i < end; ++i) { + pj_assert( (unsigned)i < conf->max_ports ); struct conf_port *conf_port = conf->ports[i]; - /* Skip empty or new port. */ - if (!conf_port || conf_port->is_new) - continue; + /* Skip empty port. + * Newly added ports are not connected yet + * and so we skip them as not active + */ + if (is_port_active( conf_port )) + { + /* Reset auto adjustment level for mixed signal. */ + conf_port->mix_adj = NORMAL_LEVEL; +#if 0 + /* We need not reset buffer, we just want to copy the first (and probably only) frame there. */ + if (conf_port->transmitter_cnt > 1) { + pj_bzero( conf_port->mix_buf, + conf->samples_per_frame * sizeof(conf_port->mix_buf[0]) ); + } +#endif + if (conf_port->transmitter_cnt && conf_port->tx_setting != PJMEDIA_PORT_DISABLE) { + + /* In the final loop of the get_frame() function, + * we might call write_port() either concurrently with + * or even after the OP_PORT_REMOVE operation. + * Therefore, ports that are being written to + * (i.e., those with conf_port->transmitter_cnt != 0) + * require protection from deletion. + * For such ports, we must increment the ref_count + * (and decrement it after transmission, + * which may result in deferred deletion of the port). + * + * At least "Master/sound" has no media_port. + * We don't manage the lifetime of such ports, + * their lifetime is the conference bridge lifetime. + */ + if (conf_port->port && conf_port->port->grp_lock) + pj_grp_lock_add_ref(conf_port->port->grp_lock); - /* Var "ci" is to count how many ports have been visited so far. */ - ++ci; + //sound device (from 0 SLOT) to 0 idx, other to next idx + conf->active_listener[i == 0 ? 0 : ++conf->listener_counter] = conf_port; - /* Skip if we're not allowed to transmit to this port. */ - if (conf_port->tx_setting != PJMEDIA_PORT_ENABLE) - continue; + /* We need not reset mix_buf, we just want to copy the first + * (and probably only) frame there. + * The criteria for "this frame is from the first transmitter" + * condition is: + * (conf_port->last_timestamp.u64 != frame->timestamp.u64) + */ + if (conf_port->last_timestamp.u64 == frame->timestamp.u64) + { //this port have not yet received data on this timer tick + // enforce "this frame is from the first transmitter" condition + //we usually shouldn't come here + conf_port->last_timestamp.u64 = (frame->timestamp.u64 ? PJ_UINT64(0) : (pj_uint64_t)-1); + } + pj_assert( conf_port->last_timestamp.u64 != frame->timestamp.u64 ); + } + + /* 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; + } + + /* compacted transmitter's array should help OpenMP to distribute task throught team's threads */ + conf->active_ports[upper_bound++] = i; + pj_assert( upper_bound <= (end - begin) ); - /* 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 + struct conf_port *sound_port = NULL; + +#ifdef _OPENMP +# pragma omp parallel for PJ_OPENMP_FOR_CLAUSES +#endif //_OPENMP + /* Step 2 + * Get frames from all ports, and "mix" the signal * to mix_buf of all listeners of the port. + * + * Here we use the current switching states, + * which must be stable during this cycle, + * i.e. must not change in parallel. + * + * To receive frames from all ports in parallel, + * we receive data from each port into separate + * buffers conf_port->rx_frame_buf */ - 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 or new port. */ - if (!conf_port || conf_port->is_new) - continue; + for (i = 0; i < upper_bound; ++i) { + pj_int32_t port_idx = conf->active_ports[i]; + pj_assert( (unsigned)port_idx < conf->max_ports ); + struct conf_port *conf_port = conf->ports[port_idx]; - /* Var "ci" is to count how many ports have been visited so far. */ - ++ci; +#ifdef _OPENMP + /* register omp thread with pjsip */ + register_omp_thread( conf ); - /* 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; +# ifdef CONF_DEBUG + { + int num = omp_get_thread_num(); + if (num < PJ_ARRAY_SIZE( threads )) + threads[num]++; } +# endif +#endif //_OPENMP /* Get frame from this port. * For passive ports, get the frame from the delay_buf. @@ -2490,11 +2963,23 @@ static pj_status_t get_frame(pjmedia_port *this_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); + +#ifdef _OPENMP + /* Check that correct size is specified. */ + pj_assert( frame->size == conf_port->rx_frame_buf_cap ); + /* read data to different buffers to different conf_port's parallel processing */ + status = pjmedia_delay_buf_get( conf_port->delay_buf, conf_port->rx_frame_buf ); +#else //_OPENMP + status = pjmedia_delay_buf_get( conf_port->delay_buf, + (pj_int16_t *)frame->buf ); +#endif //_OPENMP if (status != PJ_SUCCESS) { conf_port->rx_level = 0; + TRACE_EX( (THIS_FILE, "%s: No frame from the passive port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); continue; } @@ -2503,10 +2988,52 @@ static pj_status_t get_frame(pjmedia_port *this_port, 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); - +#ifdef _OPENMP + + /* Check that correct size is specified. */ + pj_assert( frame->size == conf_port->rx_frame_buf_cap ); + /* read data to different buffers to different conf_port's parallel processing */ + status = read_port( conf, conf_port, conf_port->rx_frame_buf, + conf->samples_per_frame, &frame_type ); + +#else //_OPENMP + status = read_port( conf, conf_port, (pj_int16_t *)frame->buf, + conf->samples_per_frame, &frame_type ); +#endif //_OPENMP + + /* Check that the port is not removed when we call get_frame() */ + /* + * if port is removed old conf_port may point to not authorized memory + * We can not call conf_port->rx_level = 0; here! + * "Port is not removed" check should take priority over the return code check + * + * However this check is not necessary for async conference bridge, + * because application can not remove port while we are in get_frame() callback. + * The only thing that can happen is that port removing will be sheduled + * there but still will processed later (see Step 3). + */ + if (conf->ports[port_idx] != conf_port) { + //conf_port->rx_level = 0; + PJ_LOG( 4, (THIS_FILE, "Port %d is removed when we call get_frame()", port_idx) ); + continue; + } + if (status != PJ_SUCCESS) { + +//#ifdef _OPENMP + /* check status and disable port here. + * Prevent multiply eof callback invoke, + * if fileplayer has reached EOF (i.e. status == PJ_EEOF) + */ + if (status == PJ_EEOF) { + TRACE_( (THIS_FILE, "Port %.*s reached EOF and is now disabled", + (int)conf_port->name.slen, + conf_port->name.ptr) ); + conf_port->rx_setting = PJMEDIA_PORT_DISABLE; + } +//#endif //_OPENMP + + /* 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. " @@ -2517,24 +3044,47 @@ static pj_status_t get_frame(pjmedia_port *this_port, conf_port->rx_setting = PJMEDIA_PORT_DISABLE; */ conf_port->rx_level = 0; + + TRACE_EX( (THIS_FILE, "%s: No frame from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); + continue; } +#if 0 /* Check that the port is not removed when we call get_frame() */ if (conf->ports[i] == NULL) { + /* if port is removed old conf_port may point to not authorized memory */ conf_port->rx_level = 0; continue; } - +#endif /* Ignore if we didn't get any frame */ if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) { conf_port->rx_level = 0; + TRACE_EX( (THIS_FILE, "%s: frame_type %d != PJMEDIA_FRAME_TYPE_AUDIO from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + frame_type, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); continue; } } - p_in = (pj_int16_t*) frame->buf; + pj_int32_t level = 0; + pj_int16_t *p_in; + unsigned j; + +#ifdef _OPENMP + p_in = conf_port->rx_frame_buf; +#else //_OPENMP + p_in = (pj_int16_t *)frame->buf; +#endif //_OPENMP /* Adjust the RX level from this port * and calculate the average level at the same time. @@ -2582,20 +3132,30 @@ static pj_status_t get_frame(pjmedia_port *this_port, //if (level == 0) // continue; + pj_int32_t cj, listener_cnt; //parallelization requires signed int + /* Add the signal to all listeners. */ - for (cj=0; cj < conf_port->listener_cnt; ++cj) + for (cj = 0, listener_cnt = conf_port->listener_cnt; cj < listener_cnt; ++cj) { struct conf_port *listener; - pj_int32_t *mix_buf; pj_int16_t *p_in_conn_leveled; 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) + { + TRACE_EX( (THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) doesn't want to receive audio from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + (int)listener->name.slen, + listener->name.ptr, + conf_port->listener_slots[cj], + listener->transmitter_cnt, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); continue; - - mix_buf = listener->mix_buf; + } /* apply connection level, if not normal */ if (conf_port->listener_adj_level[cj] != NORMAL_LEVEL) { @@ -2629,6 +3189,9 @@ static pj_status_t get_frame(pjmedia_port *this_port, p_in_conn_leveled = p_in; } + pj_int32_t *mix_buf; + mix_buf = listener->mix_buf; + if (listener->transmitter_cnt > 1) { /* Mixing signals, * and calculate appropriate level adjustment if there is @@ -2638,12 +3201,52 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_int32_t mix_buf_min = 0; pj_int32_t mix_buf_max = 0; - for (k = 0; k < samples_per_frame; ++k) { - mix_buf[k] += p_in_conn_leveled[k]; - if (mix_buf[k] < mix_buf_min) - mix_buf_min = mix_buf[k]; - if (mix_buf[k] > mix_buf_max) - mix_buf_max = mix_buf[k]; +#ifdef _OPENMP + //protect listener->mix_buf, listener->mix_adj, listener->last_timestamp + omp_set_lock( &listener->tx_Lock ); +#endif + if (listener->last_timestamp.u64 == frame->timestamp.u64) { + //this frame is NOT from the first transmitter + for (k = 0; k < samples_per_frame; ++k) { + mix_buf[k] += p_in_conn_leveled[k]; // not the first - sum + if (mix_buf[k] < mix_buf_min) + mix_buf_min = mix_buf[k]; + if (mix_buf[k] > mix_buf_max) + mix_buf_max = mix_buf[k]; + } + TRACE_EX( (THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) get (sum) audio from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + (int)listener->name.slen, + listener->name.ptr, + conf_port->listener_slots[cj], + listener->transmitter_cnt, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); + + } else { + //this frame is from the first transmitter + listener->last_timestamp = frame->timestamp; + + /* We do not want to reset buffer, we just copy the first frame there. */ + for (k = 0; k < samples_per_frame; ++k) { + mix_buf[k] = p_in_conn_leveled[k]; // the first - copy + if (mix_buf[k] < mix_buf_min) + mix_buf_min = mix_buf[k]; + if (mix_buf[k] > mix_buf_max) + mix_buf_max = mix_buf[k]; + } + TRACE_EX( (THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d) get (copy) audio from the port %p (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + listener, + (int)listener->name.slen, + listener->name.ptr, + conf_port->listener_slots[cj], + listener->transmitter_cnt, + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); } /* Check if normalization adjustment needed. */ @@ -2658,7 +3261,14 @@ static pj_status_t get_frame(pjmedia_port *this_port, if (tmp_adj < listener->mix_adj) listener->mix_adj = tmp_adj; } +#ifdef _OPENMP + omp_unset_lock( &listener->tx_Lock ); +#endif } else { + //this frame is from the only transmitter + pj_assert( listener->transmitter_cnt == 1 && listener->last_timestamp.u64 != frame->timestamp.u64 ); + listener->last_timestamp = frame->timestamp; + /* Only 1 transmitter: * just copy the samples to the mix buffer * no mixing and level adjustment needed @@ -2666,67 +3276,176 @@ static pj_status_t get_frame(pjmedia_port *this_port, unsigned k, samples_per_frame = conf->samples_per_frame; for (k = 0; k < samples_per_frame; ++k) { - mix_buf[k] = p_in_conn_leveled[k]; + mix_buf[k] = p_in_conn_leveled[k]; // here copying 16 bit value to 32 bit dst } + TRACE_EX( (THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d)" + " get audio from the (only) port %p (%.*s, %d, listener_cnt=%d) last_timestamp=%llu, timestamp=%llu", + pj_thread_get_name( pj_thread_this() ), + listener, + (int)listener->name.slen, + listener->name.ptr, + listener->slot, + listener->transmitter_cnt, + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + conf_port->slot, conf_port->listener_cnt, + listener->last_timestamp.u64, frame->timestamp.u64) ); + } + } /* 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; imax_ports && ciport_cnt; ++i) { - struct conf_port *conf_port = conf->ports[i]; - pjmedia_frame_type frm_type; - pj_status_t status; +//#if 0 + /* all ports have data in their buffers + * and may do all work independently. + * We must garantee the lifetime of + * conf_port see listener->ref_counter + * and may release mutex now. */ +// pj_mutex_unlock( conf->mutex ); +//#endif - if (!conf_port || conf_port->is_new) - continue; + listener_counter = conf->listener_counter + 1; - /* 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; - } +#ifdef _OPENMP +# pragma omp parallel + { +#endif //_OPENMP + /* Perform any queued operations that need to be synchronized with + * the clock such as connect, disonnect, remove. + * All those operations performed on the single thread + * but perhaps not on the master thread + */ +#ifdef _OPENMP +# pragma omp single nowait + { +#endif //_OPENMP + if (!pj_list_empty(conf->op_queue)) { + +#ifdef _OPENMP + /* register omp thread with pjsip */ + register_omp_thread(conf); +# ifdef CONF_DEBUG + { + int num = omp_get_thread_num(); + if (num < 16) + threads[num]++; + } +# endif +#endif //_OPENMP + + pj_log_push_indent(); + /* Calling any callback while a mutex is locked can result in a deadlock, + * since operations can lock other mutexes in an arbitrary order. + * At least OP_REMOVE_PORT invokes grp_lock handlers callbacks. + * We should move lock inside each operations + */ + //pj_mutex_lock(conf->mutex); + handle_op_queue(conf); + //pj_mutex_unlock(conf->mutex); + pj_log_pop_indent(); + } +#ifdef _OPENMP + } //pragma omp single +#endif //_OPENMP - /* Set the type of frame to be returned to sound playback - * device. + /* Step 3 + * Time for all ports to transmit whatever they have in their + * buffer. */ - if (i == 0) - speaker_frame_type = frm_type; - } +#ifdef _OPENMP +# pragma omp for nowait +#endif //_OPENMP + for (i = 0; i < listener_counter; ++i) { + + pjmedia_frame_type frm_type; + pj_status_t status; + + //sound device (from port[0]) has 0 idx here too + struct conf_port* conf_port = conf->active_listener[i]; + conf->active_listener[i] = NULL; + if (!conf_port) + continue; + +#ifdef _OPENMP + /* register omp thread with pjsip */ + register_omp_thread(conf); +# ifdef CONF_DEBUG + { + int num = omp_get_thread_num(); + if (num < 16) + threads[num]++; + } +# endif +#endif //_OPENMP + + + status = write_port(conf, conf_port, &frame->timestamp, + &frm_type); + +#if 0 + 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; + } +#endif + /* Set the type of frame to be returned to sound playback + * device. + */ + if (status == PJ_SUCCESS && i == 0) { + speaker_frame_type = frm_type; + sound_port = conf_port; + } + + //At least "Master/sound" may have no media_port + if (conf_port->port && conf_port->port->grp_lock) + pj_grp_lock_dec_ref(conf_port->port->grp_lock);// conf_port may be destroyed here + + } + +#ifdef _OPENMP + } //pragma omp parallel +#endif //_OPENMP /* 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; + if (sound_port != NULL) { + if (sound_port->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 *)sound_port->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; +#ifdef _OPENMP + + TRACE_( (THIS_FILE, "Ports processed by omp team's threads 0:%d, 1:%d, 2:%d, 3:%d, 4:%d, 5:%d, 6:%d, 7:%d, 8:%d, 9:%d, 10:%d, 11:%d, 12:%d, 13:%d, 14:%d, 15:%d.", + threads[0], threads[1], threads[2], threads[3], threads[4], threads[5], threads[6], threads[7], + threads[8], threads[9], threads[10], threads[11], threads[12], threads[13], threads[14], threads[15]) ); + +#endif + #ifdef REC_FILE if (fhnd_rec == NULL) fhnd_rec = fopen(REC_FILE, "wb"); @@ -2737,6 +3456,27 @@ static pj_status_t get_frame(pjmedia_port *this_port, return PJ_SUCCESS; } +#ifdef _OPENMP + +/* register omp thread with pjsip */ +static void register_omp_thread( pjmedia_conf * conf ) +{ + if (!pj_thread_is_registered()) + { + pj_thread_t *p; + int num; +#pragma omp critical (register_conf_bridge_omp_thread) + { + num = conf->omp_threads_idx++; + } + pj_assert( num < 2 * conf->omp_max_threads ); + char obj_name[PJ_MAX_OBJ_NAME]; + pj_ansi_snprintf( obj_name, sizeof(obj_name), "omp_conf_%d", num ); + pj_thread_register( obj_name, conf->omp_threads[num], &p ); + } +} +#endif //_OPENMP + #if !DEPRECATED_FOR_TICKET_2234 /* From e4ff03fc9b19f0c223aac596d47ce78ba1eb65ed Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sun, 5 Jan 2025 12:57:38 +0100 Subject: [PATCH 02/16] create_pasv_port() fix --- pjmedia/src/pjmedia/conference.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 69cd6d14f7..d6c1fbef6d 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -612,6 +612,7 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, pj_ansi_strxcpy2(pname, name, sizeof(pname)); /* Create own pool */ + /* replace pool to control it's lifetime */ pool = pj_pool_create(parent_pool->factory, pname, 500, 500, NULL); if (!pool) { status = PJ_ENOMEM; @@ -838,6 +839,8 @@ static pj_status_t create_pasv_port( pjmedia_conf *conf, if (status != PJ_SUCCESS) return status; + pool = conf_port->pool; + /* Passive port has delay buf. */ ptime = conf->samples_per_frame * 1000 / conf->clock_rate / conf->channel_count; @@ -848,8 +851,10 @@ static pj_status_t create_pasv_port( pjmedia_conf *conf, RX_BUF_COUNT * ptime, /* max delay */ 0, /* options */ &conf_port->delay_buf); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { + destroy_conf_port(conf_port); return status; + } *p_conf_port = conf_port; From 58d911a2f4810f4770a92c3765cdcc4536faa0e1 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Mon, 20 Jan 2025 18:42:23 +0100 Subject: [PATCH 03/16] minimized conditional compilation universal omp/non omp code --- pjmedia/src/pjmedia/conference.c | 254 +++++++++++++++---------------- 1 file changed, 125 insertions(+), 129 deletions(-) diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index d6c1fbef6d..6b78f45332 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -37,8 +37,6 @@ #if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0 -#ifdef _OPENMP -# include /* Open MP related modification by Leonid Goltsblat 2021-2025 * @@ -52,6 +50,11 @@ * and should be compatible with any OpenMP 2.0 compliant compiler. */ +#ifdef _OPENMP +# include +# define PRAGMA(clause) _Pragma(#clause) +# define PRAGMA_OMP(clause) PRAGMA(omp clause) + # ifndef PJ_OPENMP_FOR_CLAUSES # define PJ_OPENMP_FOR_CLAUSES /* for example @@ -61,6 +64,14 @@ * #pragma omp parallel for schedule(dynamic, PJSIP_OMP_CHUNK_SIZE) if (max_thread > 1) num_threads(max_thread) */ # endif + +# define IS_PARALLEL PJ_TRUE // OpenMP is enabled + +#else //_OPENMP + +# define PRAGMA_OMP(clause) +# define IS_PARALLEL PJ_FALSE // OpenMP is disabled + #endif //_OPENMP /* CONF_DEBUG enables detailed operation of the conference bridge. @@ -273,13 +284,13 @@ struct conf_port data from/to. */ #endif -#ifdef _OPENMP - pj_int16_t *rx_frame_buf; /**< The RX buffer. */ - unsigned rx_frame_buf_cap;/**< Max size, in bytes */ - - omp_lock_t tx_Lock; /**< Lock to protect transmit - buffer */ -#endif //_OPENMP + /* parallel conference bridge support */ + pj_int16_t *rx_frame_buf; /**< The RX buffer of size (in bytes) + * conf->rx_frame_buf_cap used in + * parallel bridge implementation. + */ + pj_lock_t *tx_Lock; /**< Lock to protect mix_buf,mix_adj, + * last_timestamp */ pj_timestamp last_timestamp;/**< last transmited packet * timestamp. We set this when @@ -370,16 +381,15 @@ struct pjmedia_conf * help OpenMP to distribute task * throught team's threads */ - -#ifdef _OPENMP + /* parallel conference bridge support */ + unsigned rx_frame_buf_cap;/**< size in bytes to allocate + * conf_port->rx_frame_buf */ pj_thread_desc *omp_threads; /**< Thread description's * to register omp threads * with pjsip */ int omp_max_threads; /**< omp_threads[] dimension */ - int omp_threads_idx; /**< omp_threads[] current idx */ - -#endif //_OPENMP - + pj_atomic_t *omp_threads_idx; /**< omp_threads[] current idx */ + pj_bool_t is_parallel; /**< parallel bridge flag */ }; @@ -397,10 +407,12 @@ static pj_status_t destroy_port_pasv(pjmedia_port *this_port); #endif -#ifdef _OPENMP /* register omp thread with pjsip */ -static void register_omp_thread(pjmedia_conf* conf); -#endif //_OPENMP +static inline void register_omp_thread(pjmedia_conf *conf); +#ifdef CONF_DEBUG +static inline void inc_omp_thread_usage(int *threads, pj_ssize_t threads_sz); +#endif //CONF_DEBUG +static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_frame *frame); static void destroy_conf_port(struct conf_port *conf_port); @@ -796,17 +808,15 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, {status = PJ_ENOMEM; goto on_return;}); conf_port->last_mix_adj = NORMAL_LEVEL; - -#ifdef _OPENMP - /* Get the bytes_per_frame value, to determine the size of the - * buffer. - */ - conf_port->rx_frame_buf_cap = PJMEDIA_AFD_AVG_FSZ(pjmedia_format_get_audio_format_detail(&conf->master_port->info.fmt, PJ_TRUE)); - conf_port->rx_frame_buf = (pj_int16_t *)pj_pool_zalloc(pool, conf_port->rx_frame_buf_cap); - - omp_init_lock(&conf_port->tx_Lock); -#endif //_OPENMP - + if (IS_PARALLEL) { + /* the compiler should potentially optimize this "if" away + * for the serial conference bridge + */ + conf_port->rx_frame_buf = (pj_int16_t *)pj_pool_zalloc(pool, conf/*_port*/->rx_frame_buf_cap); + status = pj_lock_create_simple_mutex(pool, "tx_Lock", &conf_port->tx_Lock); + if (status != PJ_SUCCESS) + goto on_return; + } /* Done */ *p_conf_port = conf_port; @@ -1007,6 +1017,10 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, conf->master_port->put_frame = &put_frame; conf->master_port->on_destroy = &destroy_port; + /* Get the bytes_per_frame value, to determine the size of the + * buffer. + */ + conf->rx_frame_buf_cap = PJMEDIA_AFD_AVG_FSZ(pjmedia_format_get_audio_format_detail(&conf->master_port->info.fmt, PJ_TRUE)); /* Create port zero for sound device. */ status = create_sound_port(pool, conf); @@ -1074,8 +1088,16 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, conf->omp_max_threads = omp_get_max_threads(); /* Thread description's to register omp threads with pjsip */ conf->omp_threads = (pj_thread_desc *)pj_pool_calloc( pool, 2 * conf->omp_max_threads, sizeof(pj_thread_desc) ); + status = pj_atomic_create(pool, 0, &conf->omp_threads_idx); + if (status != PJ_SUCCESS) + { + pjmedia_conf_destroy(conf); + return status; + } #endif //_OPENMP + conf->is_parallel = IS_PARALLEL; + /* Done */ *p_conf = conf; @@ -1145,6 +1167,10 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) if (conf->mutex) pj_mutex_destroy(conf->mutex); + /* Destroy atomic */ + if (conf->omp_threads_idx) + pj_atomic_destroy(conf->omp_threads_idx); + /* Destroy pool */ if (conf->pool) pj_pool_safe_release(&conf->pool); @@ -2117,10 +2143,8 @@ static void destroy_conf_port( struct conf_port *conf_port ) } } -#ifdef _OPENMP if (conf_port->tx_Lock != NULL) - omp_destroy_lock( &conf_port->tx_Lock ); -#endif // _OPENMP + pj_lock_destroy(conf_port->tx_Lock); pj_pool_safe_release(&conf_port->pool); } @@ -2797,6 +2821,16 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, return status; } +static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_frame *frame) { + pj_assert(IS_PARALLEL == (conf_port->rx_frame_buf != NULL)); + /* the compiler should potentially optimize this away + * for the serial conference bridge + */ + if (IS_PARALLEL) + return conf_port->rx_frame_buf; // parallel conference bridge + else + return (pj_int16_t *)frame->buf; // sequential conference bridge +} /* * Player callback. @@ -2844,9 +2878,9 @@ static pj_status_t get_frame(pjmedia_port *this_port, conf->listener_counter = 0; pj_int32_t listener_counter = 0; -#if defined(_OPENMP) && defined(CONF_DEBUG) - int threads[64] = {0}; -#endif +#ifdef CONF_DEBUG + int threads[IS_PARALLEL ? 64 : 1] = {0}; +#endif //CONF_DEBUG /* Step 1 initialization * Single threaded loop to get the active_ports[] (transmitters) @@ -2929,9 +2963,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, struct conf_port *sound_port = NULL; -#ifdef _OPENMP -# pragma omp parallel for PJ_OPENMP_FOR_CLAUSES -#endif //_OPENMP + PRAGMA_OMP(parallel for PJ_OPENMP_FOR_CLAUSES) /* Step 2 * Get frames from all ports, and "mix" the signal * to mix_buf of all listeners of the port. @@ -2949,18 +2981,12 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_assert( (unsigned)port_idx < conf->max_ports ); struct conf_port *conf_port = conf->ports[port_idx]; -#ifdef _OPENMP /* register omp thread with pjsip */ register_omp_thread( conf ); -# ifdef CONF_DEBUG - { - int num = omp_get_thread_num(); - if (num < PJ_ARRAY_SIZE( threads )) - threads[num]++; - } -# endif -#endif //_OPENMP +#ifdef CONF_DEBUG + inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); +#endif //CONF_DEBUG /* Get frame from this port. * For passive ports, get the frame from the delay_buf. @@ -2969,15 +2995,10 @@ static pj_status_t get_frame(pjmedia_port *this_port, if (conf_port->delay_buf != NULL) { pj_status_t status; -#ifdef _OPENMP /* Check that correct size is specified. */ - pj_assert( frame->size == conf_port->rx_frame_buf_cap ); + pj_assert( frame->size == conf/*_port*/->rx_frame_buf_cap); /* read data to different buffers to different conf_port's parallel processing */ - status = pjmedia_delay_buf_get( conf_port->delay_buf, conf_port->rx_frame_buf ); -#else //_OPENMP - status = pjmedia_delay_buf_get( conf_port->delay_buf, - (pj_int16_t *)frame->buf ); -#endif //_OPENMP + status = pjmedia_delay_buf_get( conf_port->delay_buf, get_read_buffer(conf_port, frame) ); if (status != PJ_SUCCESS) { conf_port->rx_level = 0; TRACE_EX( (THIS_FILE, "%s: No frame from the passive port (%.*s, %d, listener_cnt=%d)", @@ -2993,19 +3014,12 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_status_t status; pjmedia_frame_type frame_type; -#ifdef _OPENMP - /* Check that correct size is specified. */ - pj_assert( frame->size == conf_port->rx_frame_buf_cap ); + pj_assert( frame->size == conf/*_port */->rx_frame_buf_cap); /* read data to different buffers to different conf_port's parallel processing */ - status = read_port( conf, conf_port, conf_port->rx_frame_buf, + status = read_port(conf, conf_port, get_read_buffer(conf_port, frame), conf->samples_per_frame, &frame_type ); -#else //_OPENMP - status = read_port( conf, conf_port, (pj_int16_t *)frame->buf, - conf->samples_per_frame, &frame_type ); -#endif //_OPENMP - /* Check that the port is not removed when we call get_frame() */ /* * if port is removed old conf_port may point to not authorized memory @@ -3025,7 +3039,6 @@ static pj_status_t get_frame(pjmedia_port *this_port, if (status != PJ_SUCCESS) { -//#ifdef _OPENMP /* check status and disable port here. * Prevent multiply eof callback invoke, * if fileplayer has reached EOF (i.e. status == PJ_EEOF) @@ -3036,7 +3049,6 @@ static pj_status_t get_frame(pjmedia_port *this_port, conf_port->name.ptr) ); conf_port->rx_setting = PJMEDIA_PORT_DISABLE; } -//#endif //_OPENMP /* bennylp: why do we need this???? @@ -3085,11 +3097,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_int16_t *p_in; unsigned j; -#ifdef _OPENMP - p_in = conf_port->rx_frame_buf; -#else //_OPENMP - p_in = (pj_int16_t *)frame->buf; -#endif //_OPENMP + p_in = get_read_buffer(conf_port, frame); /* Adjust the RX level from this port * and calculate the average level at the same time. @@ -3206,10 +3214,11 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_int32_t mix_buf_min = 0; pj_int32_t mix_buf_max = 0; -#ifdef _OPENMP + pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); //protect listener->mix_buf, listener->mix_adj, listener->last_timestamp - omp_set_lock( &listener->tx_Lock ); -#endif + if (IS_PARALLEL) + pj_lock_acquire(listener->tx_Lock); + if (listener->last_timestamp.u64 == frame->timestamp.u64) { //this frame is NOT from the first transmitter for (k = 0; k < samples_per_frame; ++k) { @@ -3266,9 +3275,11 @@ static pj_status_t get_frame(pjmedia_port *this_port, if (tmp_adj < listener->mix_adj) listener->mix_adj = tmp_adj; } -#ifdef _OPENMP - omp_unset_lock( &listener->tx_Lock ); -#endif + + pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); + if (IS_PARALLEL) + pj_lock_release(listener->tx_Lock); + } else { //this frame is from the only transmitter pj_assert( listener->transmitter_cnt == 1 && listener->last_timestamp.u64 != frame->timestamp.u64 ); @@ -3302,44 +3313,33 @@ static pj_status_t get_frame(pjmedia_port *this_port, } /* loop the listeners of conf port */ } /* loop of all conf ports */ -//#if 0 /* all ports have data in their buffers * and may do all work independently. - * We must garantee the lifetime of - * conf_port see listener->ref_counter - * and may release mutex now. */ -// pj_mutex_unlock( conf->mutex ); -//#endif + * Here we use ports from conf->active_listener[] + * whose lifetime is garanteed at the moment of adding + * port to active_listener[] by incrementing + * grp_lock->ref_counter. + */ listener_counter = conf->listener_counter + 1; -#ifdef _OPENMP -# pragma omp parallel + PRAGMA_OMP(parallel) { -#endif //_OPENMP /* Perform any queued operations that need to be synchronized with * the clock such as connect, disonnect, remove. * All those operations performed on the single thread * but perhaps not on the master thread */ -#ifdef _OPENMP -# pragma omp single nowait + PRAGMA_OMP(single nowait) { -#endif //_OPENMP if (!pj_list_empty(conf->op_queue)) { -#ifdef _OPENMP /* register omp thread with pjsip */ register_omp_thread(conf); -# ifdef CONF_DEBUG - { - int num = omp_get_thread_num(); - if (num < 16) - threads[num]++; - } -# endif -#endif //_OPENMP +#ifdef CONF_DEBUG + inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); +#endif //CONF_DEBUG pj_log_push_indent(); /* Calling any callback while a mutex is locked can result in a deadlock, @@ -3352,17 +3352,13 @@ static pj_status_t get_frame(pjmedia_port *this_port, //pj_mutex_unlock(conf->mutex); pj_log_pop_indent(); } -#ifdef _OPENMP } //pragma omp single -#endif //_OPENMP /* Step 3 * Time for all ports to transmit whatever they have in their * buffer. */ -#ifdef _OPENMP -# pragma omp for nowait -#endif //_OPENMP + PRAGMA_OMP(for nowait) for (i = 0; i < listener_counter; ++i) { pjmedia_frame_type frm_type; @@ -3374,17 +3370,11 @@ static pj_status_t get_frame(pjmedia_port *this_port, if (!conf_port) continue; -#ifdef _OPENMP /* register omp thread with pjsip */ register_omp_thread(conf); -# ifdef CONF_DEBUG - { - int num = omp_get_thread_num(); - if (num < 16) - threads[num]++; - } -# endif -#endif //_OPENMP +#ifdef CONF_DEBUG + inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); +#endif //CONF_DEBUG status = write_port(conf, conf_port, &frame->timestamp, @@ -3422,9 +3412,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, } -#ifdef _OPENMP } //pragma omp parallel -#endif //_OPENMP /* Return sound playback frame. */ if (sound_port != NULL) { @@ -3443,14 +3431,10 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* MUST set frame type */ frame->type = speaker_frame_type; -#ifdef _OPENMP - TRACE_( (THIS_FILE, "Ports processed by omp team's threads 0:%d, 1:%d, 2:%d, 3:%d, 4:%d, 5:%d, 6:%d, 7:%d, 8:%d, 9:%d, 10:%d, 11:%d, 12:%d, 13:%d, 14:%d, 15:%d.", threads[0], threads[1], threads[2], threads[3], threads[4], threads[5], threads[6], threads[7], threads[8], threads[9], threads[10], threads[11], threads[12], threads[13], threads[14], threads[15]) ); -#endif - #ifdef REC_FILE if (fhnd_rec == NULL) fhnd_rec = fopen(REC_FILE, "wb"); @@ -3461,26 +3445,38 @@ static pj_status_t get_frame(pjmedia_port *this_port, return PJ_SUCCESS; } -#ifdef _OPENMP /* register omp thread with pjsip */ -static void register_omp_thread( pjmedia_conf * conf ) +static inline void register_omp_thread(pjmedia_conf *conf) { - if (!pj_thread_is_registered()) + /* the compiler should potentially optimize this away + * for the serial conference bridge + */ + if (IS_PARALLEL && !pj_thread_is_registered()) { + pj_assert(conf->omp_threads_idx && conf->omp_threads); pj_thread_t *p; - int num; -#pragma omp critical (register_conf_bridge_omp_thread) - { - num = conf->omp_threads_idx++; - } - pj_assert( num < 2 * conf->omp_max_threads ); + int num = pj_atomic_inc_and_get(conf->omp_threads_idx); + pj_assert(num < 2 * conf->omp_max_threads); char obj_name[PJ_MAX_OBJ_NAME]; - pj_ansi_snprintf( obj_name, sizeof(obj_name), "omp_conf_%d", num ); - pj_thread_register( obj_name, conf->omp_threads[num], &p ); + pj_ansi_snprintf(obj_name, sizeof(obj_name), "omp_conf_%d", num); + pj_thread_register(obj_name, conf->omp_threads[num], &p); } + pj_assert(pj_thread_is_registered()); +} + +#ifdef CONF_DEBUG +static inline void inc_omp_thread_usage(int *threads, pj_ssize_t threads_sz) { +# ifdef _OPENMP + int num = omp_get_thread_num(); + if (num < threads_sz) + threads[num]++; +# else + PJ_UNUSED_ARG(threads); + PJ_UNUSED_ARG(threads_sz); +# endif //_OPENMP } -#endif //_OPENMP +#endif //CONF_DEBUG #if !DEPRECATED_FOR_TICKET_2234 From cc83b8cd32ba816cbf3c95cac5c8019e3c2a9d73 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Tue, 21 Jan 2025 00:22:27 +0100 Subject: [PATCH 04/16] signed/unsigned fix --- pjmedia/src/pjmedia/conference.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 9f1353d995..ed740faa9b 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -1666,13 +1666,13 @@ static void op_connect_ports(pjmedia_conf *conf, const op_param *prm) correct_port_boundary( conf, sink_slot ); pj_assert( conf->lower_bound < conf->upper_bound ); - 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_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)); } /* @@ -1752,11 +1752,11 @@ static void op_disconnect_ports(pjmedia_conf *conf, /* Disconnect source -> sink */ if (src_port && dst_port) { /* Check if connection has been made */ - for (i=0; i<(int)src_port->listener_cnt; ++i) { + for (i=0; ilistener_cnt; ++i) { if (src_port->listener_slots[i] == sink_slot) break; } - if (i == (int)src_port->listener_cnt) { + if (i == src_port->listener_cnt) { PJ_LOG(3,(THIS_FILE, "Ports connection %d->%d does not exist", src_slot, sink_slot)); return; @@ -1815,7 +1815,7 @@ static void op_disconnect_ports(pjmedia_conf *conf, */ for (j = src_port->listener_cnt - 1; j >= 0; --j) { if (src_port->listener_slots[j] == sink_slot) { - op_param op_prm = { 0 }; + op_param op_prm = {0}; op_prm.disconnect_ports.src = i; op_prm.disconnect_ports.sink = sink_slot; op_disconnect_ports( conf, &op_prm ); @@ -2138,7 +2138,9 @@ static void op_remove_port(pjmedia_conf *conf, const op_param *prm) pj_assert( !is_port_active( conf_port ) ); /* Remove the port. */ + //pj_mutex_lock(conf->mutex); conf->ports[port] = NULL; + //pj_mutex_unlock(conf->mutex); #if 0 if (!conf_port->is_new) --conf->port_cnt; From 5d8d81af53246fe53343d9075df461dd9f846922 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sun, 26 Jan 2025 20:04:48 +0100 Subject: [PATCH 05/16] conference bridge with native pjsip multithreading + pj_barrier_r --- pjlib/include/pj/os.h | 83 + pjlib/include/pj/types.h | 3 + pjlib/src/pj/os_core_unix.c | 124 + pjlib/src/pj/os_core_win32.c | 137 + pjmedia/build/pjmedia.vcxproj | 1 + pjmedia/build/pjmedia.vcxproj.filters | 3 + pjmedia/src/pjmedia/conf_openmp.c | 4311 +++++++++++++++++++++++++ pjmedia/src/pjmedia/conference.c | 815 ++--- 8 files changed, 5102 insertions(+), 375 deletions(-) create mode 100644 pjmedia/src/pjmedia/conf_openmp.c diff --git a/pjlib/include/pj/os.h b/pjlib/include/pj/os.h index b913781383..3e8039e35d 100644 --- a/pjlib/include/pj/os.h +++ b/pjlib/include/pj/os.h @@ -1093,6 +1093,89 @@ PJ_DECL(pj_status_t) pj_event_destroy(pj_event_t *event); */ #endif /* PJ_HAS_EVENT_OBJ */ + + /* **************************************************************************/ + /** + * @defgroup PJ_BARRIER_SEC Barrier sections. + * @ingroup PJ_OS + * @{ + * This module provides abstraction to pj_barrier_t - synchronization barrier. + * It allows threads to block until all participating threads have reached + * the barrier,ensuring synchronization at specific points in execution. + * pj_barrier_t provides a barrier mechanism for synchronizing threads in + * a multithreaded application, similar to + * the POSIX pthread_barrier_wait or Windows EnterSynchronizationBarrier. + */ + +/** + * Flags that control the behavior of the barrier + * Supported on Windows platform starting from Windows 8 + * Otherwize, the flags are ignored. + */ +enum pj_barrier_flags { + /* Specifies that the thread entering the barrier should block + * immediately until the last thread enters the barrier. */ + PJ_BARRIER_FLAGS_BLOCK_ONLY = 1, + + /* Specifies that the thread entering the barrier should spin until + * the last thread enters the barrier, + * even if the spinning thread exceeds the barrier's maximum spin count.*/ + PJ_BARRIER_FLAGS_SPIN_ONLY = 2, + + /* Specifies that the function can skip the work required to ensure + * that it is safe to delete the barrier, which can improve performance. + * All threads that enter this barrier must specify the flag; + * otherwise, the flag is ignored. + * This flag should be used only if the barrier will never be deleted. + * "Never" means "when some thread is waiting on this barrier". + */ + PJ_BARRIER_FLAGS_NO_DELETE = 4 +}; + +/** + * Create a barrier object. + * pj_barrier_create() creates a barrier object that can be used to synchronize threads. + * The barrier object is initialized with a trip count that specifies the number of threads + * that must call pj_barrier_wait() before any are allowed to proceed. + * + * @param pool The pool to allocate the barrier object. + * @param trip_count The number of threads that must call pj_barrier_wait() before any are allowed to proceed. + * @param p_barrier Pointer to hold the barrier object upon return. + * + * @return PJ_SUCCESS on success, or the error code. + */ +pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier); + +/** + * Destroy a barrier object. + * pj_barrier_destroy() destroys a barrier object and releases any resources associated with the barrier. + * + * @param barrier The barrier to destroy. + * + * @return PJ_SUCCESS on success, or the error code. + */ +pj_status_t pj_barrier_destroy(pj_barrier_t *barrier); + +/** + * Wait for all threads to reach the barrier + * pj_barrier_wait() allows threads to block until all participating threads have reached the barrier, + * ensuring synchronization at specific points in execution. + * It provides a barrier mechanism for synchronizing threads in a multithreaded application, + * similar to the POSIX pthread_barrier_wait or Windows EnterSynchronizationBarrier. + * + * @param barrier The barrier to wait on + * @param flags Flags that control the behavior of the barrier (combination of pj_barrier_flags) + * + * @return Returns PJ_TRUE for a single (arbitrary) thread synchronized + * at the barrier and PJ_FALSE for each of the other threads. + * Otherwise, an error number shall be returned to indicate the error. + */ +pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags); + + /** + * @} + */ + /* **************************************************************************/ /** * @addtogroup PJ_TIME Time Data Type and Manipulation. diff --git a/pjlib/include/pj/types.h b/pjlib/include/pj/types.h index ccaaf7f4d1..ce9d13315c 100644 --- a/pjlib/include/pj/types.h +++ b/pjlib/include/pj/types.h @@ -268,6 +268,9 @@ typedef struct pj_sem_t pj_sem_t; /** Event object. */ typedef struct pj_event_t pj_event_t; +/** Barrier object. */ +typedef struct pj_barrier_t pj_barrier_t; + /** Unidirectional stream pipe object. */ typedef struct pj_pipe_t pj_pipe_t; diff --git a/pjlib/src/pj/os_core_unix.c b/pjlib/src/pj/os_core_unix.c index c90c5ef690..7d5eb2b1f5 100644 --- a/pjlib/src/pj/os_core_unix.c +++ b/pjlib/src/pj/os_core_unix.c @@ -146,6 +146,22 @@ struct pj_event_t }; #endif /* PJ_HAS_EVENT_OBJ */ +#if defined(_POSIX_BARRIERS) +/* pthread_barrier is supported. */ +struct pj_barrier_t { + pthread_barrier_t barrier; +}; + +#else +/* pthread_barrier is not supported. */ +struct pj_barrier_t { + pthread_mutex_t mutex; + pthread_cond_t cond; + unsigned count; + unsigned trip_count; +}; + +#endif /* * Flag and reference counter for PJLIB instance. @@ -2080,6 +2096,114 @@ PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) #endif /* PJ_HAS_EVENT_OBJ */ +/////////////////////////////////////////////////////////////////////////////// +#if defined(_POSIX_BARRIERS) + /* pthread_barrier is supported. */ + +/** + * Barrier object. + */ +pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { + pj_barrier_t *barrier; + int rc; + PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); + barrier = (pj_barrier_t *)pj_pool_zalloc(pool, sizeof(pj_barrier_t)); + if (barrier == NULL) + return PJ_ENOMEM; + rc = pthread_barrier_init(&barrier->barrier, NULL, trip_count); + if (rc != 0) + return PJ_RETURN_OS_ERROR(rc); + *p_barrier = barrier; + return PJ_SUCCESS; +} + +/** + * Wait on the barrier. + */ +pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { + PJ_UNUSED_ARG(flags); + int rc = pthread_barrier_wait(&barrier->barrier); + switch (rc) + { + case 0: + return PJ_FALSE; + case PTHREAD_BARRIER_SERIAL_THREAD: + return PJ_TRUE; + default: + return PJ_RETURN_OS_ERROR(rc); + } +} + +/** + * Destroy the barrier. + */ +pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { + int status = pthread_barrier_destroy(&barrier->barrier); + if (status == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(status); +} + +#else // _POSIX_BARRIERS + /* pthread_barrier is not supported. */ + +/** + * Barrier object. + */ +pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { + pj_barrier_t *barrier; + int rc; + + PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); + barrier = (pj_barrier_t *)pj_pool_zalloc(pool, sizeof(pj_barrier_t)); + if (barrier == NULL) + return PJ_ENOMEM; + rc = pthread_mutex_init(&barrier->mutex, &attr); + if (rc != 0) + return PJ_RETURN_OS_ERROR(rc); + pthread_cond_init(&barrier->cond, NULL); + barrier->count = 0; + barrier->trip_count = trip_count; + *p_barrier = barrier; + return PJ_SUCCESS; +} + +/** + * Wait on the barrier. + */ +pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { + PJ_UNUSED_ARG(flags); + + pthread_mutex_lock(&barrier->mutex); + barrier->count++; + if (barrier->count >= barrier->trip_count) + { + barrier->count = 0; + pthread_cond_broadcast(&barrier->cond); + } + else + { + pthread_cond_wait(&barrier->cond, &barrier->mutex); + } + pthread_mutex_unlock(&barrier->mutex); +} + +/** + * Destroy the barrier. + */ +pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { + pthread_cond_destroy(&barrier->cond); + int status = pthread_mutex_destroy(&barrier->mutex); + if (status == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(status); +} + +#endif // _POSIX_BARRIERS + + /////////////////////////////////////////////////////////////////////////////// #if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 /* diff --git a/pjlib/src/pj/os_core_win32.c b/pjlib/src/pj/os_core_win32.c index 68a538dcba..a4f6bc1f57 100644 --- a/pjlib/src/pj/os_core_win32.c +++ b/pjlib/src/pj/os_core_win32.c @@ -122,6 +122,28 @@ struct pj_atomic_t long value; }; +/* + * Implementation of pj_barrier_t. + */ +#if PJ_WIN32_WINNT >= _WIN32_WINNT_WIN8 +struct pj_barrier_t { + SYNCHRONIZATION_BARRIER sync_barrier; +}; +#elif PJ_WIN32_WINNT >= _WIN32_WINNT_VISTA +struct pj_barrier_t { + CRITICAL_SECTION mutex; + CONDITION_VARIABLE cond; + unsigned count; + unsigned waiting; +}; +#else +struct pj_barrier_t { + HANDLE cond; /* Semaphore */ + LONG count; /* Number of threads required to pass the barrier */ + LONG waiting; /* Number of threads waiting at the barrier */ +}; +#endif + /* * Flag and reference counter for PJLIB instance. */ @@ -1546,6 +1568,121 @@ PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) #endif /* PJ_HAS_EVENT_OBJ */ +/////////////////////////////////////////////////////////////////////////////// + +/* + * pj_barrier_create() + */ +pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { + pj_barrier_t *barrier; + PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); + barrier = (pj_barrier_t *)pj_pool_zalloc(pool, sizeof(pj_barrier_t)); + if (barrier == NULL) + return PJ_ENOMEM; +#if PJ_WIN32_WINNT >= _WIN32_WINNT_WIN8 + if (InitializeSynchronizationBarrier(&barrier->sync_barrier, trip_count, -1)) + { + *p_barrier = barrier; + return PJ_SUCCESS; + } + else + return pj_get_os_error(); +#elif PJ_WIN32_WINNT >= _WIN32_WINNT_VISTA + InitializeCriticalSection(&barrier->mutex); + InitializeConditionVariable(&barrier->cond); + barrier->count = trip_count; + barrier->waiting = 0; + *p_barrier = barrier; + return PJ_SUCCESS; +#else + barrier->cond = CreateSemaphore(NULL, + 0, /* initial count */ + count, /* max count */ + NULL); + if (!barrier->cond) + return pj_get_os_error(); + barrier->count = trip_count; + barrier->waiting = 0; + *p_barrier = barrier; + return PJ_SUCCESS; +#endif +} + +/* + * pj_barrier_destroy() + */ +pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { + PJ_ASSERT_RETURN(barrier, PJ_EINVAL); +#if PJ_WIN32_WINNT >= _WIN32_WINNT_WIN8 + DeleteSynchronizationBarrier(&barrier->sync_barrier); + return PJ_SUCCESS; +#elif PJ_WIN32_WINNT >= _WIN32_WINNT_VISTA + DeleteCriticalSection(&barrier->mutex); + return PJ_SUCCESS; +#else + if (CloseHandle(barrier->cond)) + return PJ_SUCCESS; + else + return pj_get_os_error(); +#endif +} + +/* + * pj_barrier_wait() + */ +pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { + PJ_ASSERT_RETURN(barrier, PJ_EINVAL); +#if PJ_WIN32_WINNT >= _WIN32_WINNT_WIN8 + DWORD dwFlags = ((flags & PJ_BARRIER_FLAGS_BLOCK_ONLY) ? SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY : 0) | + ((flags & PJ_BARRIER_FLAGS_SPIN_ONLY) ? SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY : 0) | + ((flags & PJ_BARRIER_FLAGS_NO_DELETE) ? SYNCHRONIZATION_BARRIER_FLAGS_NO_DELETE : 0); + return EnterSynchronizationBarrier(&barrier->sync_barrier, dwFlags); +#elif PJ_WIN32_WINNT >= _WIN32_WINNT_VISTA + PJ_UNUSED_ARG(flags); + EnterCriticalSection(&barrier->mutex); + barrier->waiting++; + if (barrier->waiting == barrier->count) + { + barrier->waiting = 0; + WakeAllConditionVariable(&barrier->cond); + LeaveCriticalSection(&barrier->mutex); + return PJ_TRUE; + } + else + { + BOOL rc = SleepConditionVariableCS(&barrier->cond, &barrier->mutex, INFINITE); + LeaveCriticalSection(&barrier->mutex); + if (rc) + return PJ_FALSE; + else + return pj_get_os_error(); + } +#else + PJ_UNUSED_ARG(flags); + if (InterlockedIncrement(&barrier->waiting) == barrier->count) + { + LONG previousCount; + barrier->waiting = 0; + /* Release all threads waiting on the semaphore */ + if (ReleaseSemaphore(barrier->cond, barrier->count, &previousCount)) + { + PJ_ASSERT_RETURN(previousCount == 0, PJ_EBUG); + return PJ_TRUE; + } + else + return pj_get_os_error(); + } + else + { + DWORD rc = WaitForSingleObject(barrier->cond, INFINITE); + if (rc == WAIT_OBJECT_0) + return PJ_FALSE; + else + return pj_get_os_error(); + } +#endif +} + /////////////////////////////////////////////////////////////////////////////// #if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 /* diff --git a/pjmedia/build/pjmedia.vcxproj b/pjmedia/build/pjmedia.vcxproj index 146bfa8ded..e2da5d40eb 100644 --- a/pjmedia/build/pjmedia.vcxproj +++ b/pjmedia/build/pjmedia.vcxproj @@ -614,6 +614,7 @@ + diff --git a/pjmedia/build/pjmedia.vcxproj.filters b/pjmedia/build/pjmedia.vcxproj.filters index 222e58cb14..580476c7f5 100644 --- a/pjmedia/build/pjmedia.vcxproj.filters +++ b/pjmedia/build/pjmedia.vcxproj.filters @@ -230,6 +230,9 @@ Source Files + + Source Files + diff --git a/pjmedia/src/pjmedia/conf_openmp.c b/pjmedia/src/pjmedia/conf_openmp.c new file mode 100644 index 0000000000..a6bf75cde0 --- /dev/null +++ b/pjmedia/src/pjmedia/conf_openmp.c @@ -0,0 +1,4311 @@ +/* + * 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 +#include +#include + +#if defined(PJ_STACK_IMPLEMENTATION) +#include +#endif + +#if (!defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0) && \ + (defined(PJMEDIA_CONF_USE_OPENMP) && PJMEDIA_CONF_USE_OPENMP == 1) + +#ifndef PJ_CONF_BRIDGE_MAX_THREADS +# define PJ_CONF_BRIDGE_MAX_THREADS 1 +#endif + + +/* Open MP related modification by Leonid Goltsblat 2021-2025 + * + * Enabling OpenMP support makes it possible to parallelize the processing of audio frames. + * To activate OpenMP support you must perform the appropriate development environment setup. + * For example, in Visual Studio, you must enable OpenMP support in the project settings. + * see: https://learn.microsoft.com/en-us/cpp/build/reference/openmp-enable-openmp-2-0-support?view=msvc-170 + * In GCC, you must use the appropriate compiler options. + * + * Current implementation uses only basic subset of OpenMP features corresponding to OpenMP 2.0 + * and should be compatible with any OpenMP 2.0 compliant compiler. + */ + +#ifdef _OPENMP +# include +# define PRAGMA(clause) _Pragma(#clause) +# define PRAGMA_OMP(clause) PRAGMA(omp clause) + +# ifndef PJ_OPENMP_FOR_CLAUSES +# define PJ_OPENMP_FOR_CLAUSES +/* for example + * #define PJSIP_OMP_CHUNK_SIZE 16 + * #define PJ_OPENMP_FOR_CLAUSES schedule(dynamic, PJSIP_OMP_CHUNK_SIZE) if (max_thread > 1) num_threads(max_thread) + * will produce + * #pragma omp parallel for schedule(dynamic, PJSIP_OMP_CHUNK_SIZE) if (max_thread > 1) num_threads(max_thread) + */ +# endif + +# define IS_PARALLEL PJ_TRUE // OpenMP is enabled + +#elif PJ_CONF_BRIDGE_MAX_THREADS > 1 + +# define PRAGMA_OMP(clause) +# define IS_PARALLEL PJ_TRUE // pj_thread MP is enabled + +#else //_OPENMP + +# define PRAGMA_OMP(clause) +# define IS_PARALLEL PJ_FALSE // OpenMP is disabled + +#endif //_OPENMP + +/* 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 CONF_DEBUG_EX +#ifdef CONF_DEBUG_EX +//# include +# define TRACE_EX(x) PJ_LOG(5,x) +#else +# define TRACE_EX(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). + */ +#if defined(PJMEDIA_CONF_USE_AGC) && PJMEDIA_CONF_USE_AGC != 0 +# define ATTACK_A ((conf->clock_rate / conf->samples_per_frame) >> 4) +# 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) +#else +# define SIMPLE_AGC(last, target) +#endif + +#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_pool_t *pool; /**< for autonomous lifetime control + * we need separate memory pool + * this port created from */ + 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 *listener_adj_level; + /**< Array of listeners' level + adjustment. */ + unsigned transmitter_cnt;/**rx_frame_buf_cap used in + * parallel bridge implementation. + */ + pj_lock_t *tx_Lock; /**< Lock to protect mix_buf,mix_adj, + * last_timestamp, mixed_cnt */ + + pj_timestamp last_timestamp;/**< last transmited packet + * timestamp. We set this when + * first time put something into + * the mix_buf. + * If this time stamp is equals to + * current frame timestamp, + * we have data to transmite */ + unsigned mixed_cnt; /**rx_frame_buf */ +#ifdef _OPENMP + /* Open MP support */ + struct conf_port **active_listener; /**< listener with data to transmit */ + pj_thread_desc *omp_threads; /**< Thread description's + * to register omp threads + * with pjsip */ + int omp_max_threads; /**< omp_threads[] dimension */ + pj_atomic_t *omp_threads_idx; /**< omp_threads[] current idx */ +#endif // _OPENMP + + + /* native pjsip multithreading */ + pj_thread_t **pool_threads; /**< Thread pool's threads */ + pj_barrier_t *active_thread; /**< entry barrier */ + pj_barrier_t *barrier; /**< exit barrier */ + pj_bool_t quit_flag; /**< quit flag for threads */ + pj_bool_t running; /**< thread pool is running */ + pj_atomic_t *active_ports_idx;/**< index of the element of the + * active_ports[] array processed + * by the current thread */ + pjmedia_frame *frame; /**< Frame buffer for conference + * bridge at the current tick. */ + struct conf_port *sound_port; + +}; + + +/* 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 destroy_port(pjmedia_port *this_port); + +#if !DEPRECATED_FOR_TICKET_2234 +static pj_status_t get_frame_pasv(pjmedia_port *this_port, + pjmedia_frame *frame); +static pj_status_t destroy_port_pasv(pjmedia_port *this_port); +#endif + + +/* register omp thread with pjsip */ +static inline void register_omp_thread(pjmedia_conf *conf); +#ifdef CONF_DEBUG +static inline void inc_omp_thread_usage(int *threads, pj_ssize_t threads_sz); +#endif //CONF_DEBUG +static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_frame *frame); +static pj_status_t thread_pool_start(pjmedia_conf *conf); +static void perform_get_frame(pjmedia_conf *conf); +/* Conf thread pool's thread function.*/ +static int conf_thread(void *arg); + + +static void destroy_conf_port(struct conf_port *conf_port); + +/* As we don't hold mutex in the clock/get_frame(), some conference operations + * that change conference states need to be synchronized with the clock. + * So some steps of the operations needs to be executed within the clock tick + * context, especially the steps related to changing ports connection. + */ + +/* Synchronized operation type enumeration. */ +typedef enum op_type +{ + OP_UNKNOWN, + OP_ADD_PORT, + OP_REMOVE_PORT, + OP_CONNECT_PORTS, + OP_DISCONNECT_PORTS, +} op_type; + +/* Synchronized operation parameter. */ +typedef union op_param +{ + struct { + unsigned port; + } add_port; + + struct { + unsigned port; + } remove_port; + + struct { + unsigned src; + unsigned sink; + int adj_level; + } connect_ports; + + struct { + unsigned src; + unsigned sink; + } disconnect_ports; + +} op_param; + +/* Synchronized operation list entry. */ +typedef struct op_entry { + PJ_DECL_LIST_MEMBER(struct op_entry); + op_type type; + op_param param; +} op_entry; + +/* Prototypes of synchronized operation */ +static void op_add_port(pjmedia_conf *conf, const op_param *prm); +static void op_remove_port(pjmedia_conf *conf, const op_param *prm); +static void op_connect_ports(pjmedia_conf *conf, const op_param *prm); +static void op_disconnect_ports(pjmedia_conf *conf, const op_param *prm); + +static op_entry* get_free_op_entry(pjmedia_conf *conf) +{ + op_entry *ope = NULL; + + /* Get from empty list if any, otherwise, allocate a new one */ + if (!pj_list_empty(conf->op_queue_free)) { + ope = conf->op_queue_free->next; + pj_list_erase(ope); + } else { + ope = PJ_POOL_ZALLOC_T(conf->pool, op_entry); + } + return ope; +} + +static void handle_op_queue(pjmedia_conf *conf) +{ + /* The queue may grow while mutex is released, better put a limit? */ + enum { MAX_PROCESSED_OP = 100 }; + int i = 0; + + while (i++ < MAX_PROCESSED_OP) { + op_entry *op; + op_type type; + op_param param; + + pj_mutex_lock(conf->mutex); + + /* Stop when queue empty */ + if (pj_list_empty(conf->op_queue)) { + pj_mutex_unlock(conf->mutex); + break; + } + + /* Copy op */ + op = conf->op_queue->next; + type = op->type; + param = op->param; + + /* Free op */ + pj_list_erase(op); + op->type = OP_UNKNOWN; + pj_list_push_back(conf->op_queue_free, op); + + pj_mutex_unlock(conf->mutex); + + /* Process op */ + switch(type) { + case OP_ADD_PORT: + op_add_port(conf, ¶m); + break; + case OP_REMOVE_PORT: + op_remove_port(conf, ¶m); + break; + case OP_CONNECT_PORTS: + op_connect_ports(conf, ¶m); + break; + case OP_DISCONNECT_PORTS: + op_disconnect_ports(conf, ¶m); + break; + default: + pj_assert(!"Invalid sync-op in conference"); + break; + } + } +} + + +/* Group lock handler */ +static void conf_port_on_destroy(void *arg) +{ + struct conf_port *conf_port = (struct conf_port*)arg; + destroy_conf_port(conf_port); +} + +/* +* port is active (has listers or transmitters), i.e. conference bridge should not skip this port, +* as voice should be send to or receive from this port. +*/ +PJ_INLINE(pj_bool_t) is_port_active(struct conf_port* p_conf_port) +{ + return p_conf_port && (p_conf_port->listener_cnt || p_conf_port->transmitter_cnt); +} + +PJ_INLINE(void) correct_port_boundary(pjmedia_conf *conf, SLOT_TYPE src_slot) +{ + pj_assert(conf && src_slot < conf->max_ports); + + if (is_port_active(conf->ports[src_slot])) { + + if (src_slot >= conf->upper_bound) + conf->upper_bound = src_slot + 1; + if (src_slot < conf->lower_bound) + conf->lower_bound = src_slot; + + pj_assert(conf->lower_bound < conf->upper_bound); + + } else { + if (src_slot + 1 >= conf->upper_bound) { + while (conf->lower_bound < conf->upper_bound && is_port_active(conf->ports[conf->upper_bound - 1])) { + pj_assert(conf->upper_bound); + --conf->upper_bound; + } + } + if (src_slot <= conf->lower_bound) { + while (conf->lower_bound < conf->upper_bound && !is_port_active(conf->ports[conf->lower_bound])) { + pj_assert(conf->lower_bound < conf->max_ports); + ++conf->lower_bound; + } + } + if (conf->lower_bound >= conf->upper_bound) { + conf->lower_bound = conf->max_ports; + conf->upper_bound = 0; + } + } + +} + +/* + * Find empty port slot in the conference bridge and reserve this slot. + * O(1) thread-safe operation + */ +static SLOT_TYPE conf_reserve_port(pjmedia_conf *conf); + +/* + * Return conf_port slot to unused slots cache. + * O(1) thread-safe operation + */ +static pj_status_t conf_release_port(pjmedia_conf *conf, SLOT_TYPE slot); + + +/* + * Create port. + */ +static pj_status_t create_conf_port( pj_pool_t *parent_pool, + pjmedia_conf *conf, + pjmedia_port *port, + const pj_str_t *name, + struct conf_port **p_conf_port) +{ + struct conf_port *conf_port = NULL; + pj_pool_t *pool = NULL; + char pname[PJ_MAX_OBJ_NAME]; + pj_status_t status = PJ_SUCCESS; + + /* Make sure pool name is NULL terminated */ + pj_assert(name); + pj_ansi_strxcpy2(pname, name, sizeof(pname)); + + /* Create own pool */ + /* replace pool to control it's lifetime */ + pool = pj_pool_create(parent_pool->factory, pname, 500, 500, NULL); + if (!pool) { + status = PJ_ENOMEM; + goto on_return; + } + + /* Create port. */ + conf_port = PJ_POOL_ZALLOC_T(pool, struct conf_port); + PJ_ASSERT_ON_FAIL(conf_port, {status = PJ_ENOMEM; goto on_return;}); + conf_port->pool = pool; + + /* Increment port ref count to avoid premature destroy due to + * asynchronous port removal. + */ + if (port) { + if (!port->grp_lock) { + /* Create group lock if it does not have one */ + pjmedia_port_init_grp_lock(port, pool, NULL); + } + + pj_grp_lock_add_ref(port->grp_lock); + + /* Pool may be used for creating port's group lock and the group lock + * may be used by app, so pool destroy must be done via handler. + */ + status = pj_grp_lock_add_handler(port->grp_lock, NULL, conf_port, + &conf_port_on_destroy); + } + + /* 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_ON_FAIL(conf_port->listener_slots, + {status = PJ_ENOMEM; goto on_return;}); + + /* Create adjustment level array */ + conf_port->listener_adj_level = (unsigned *) pj_pool_zalloc(pool, + conf->max_ports * sizeof(unsigned)); + PJ_ASSERT_ON_FAIL(conf_port->listener_adj_level, + {status = PJ_ENOMEM; goto on_return;}); + + /* 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; + } + + /* Create adjustment level buffer. */ + conf_port->adj_level_buf = (pj_int16_t*) pj_pool_zalloc(pool, + conf->samples_per_frame * sizeof(pj_int16_t)); + PJ_ASSERT_ON_FAIL(conf_port->adj_level_buf, + {status = PJ_ENOMEM; goto on_return;}); + + /* 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) + goto on_return; + + + /* 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) + goto on_return; + } + + /* + * 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_ON_FAIL(conf_port->rx_buf, + {status = PJ_ENOMEM; goto on_return;}); + + /* 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_ON_FAIL(conf_port->tx_buf, + {status = PJ_ENOMEM; goto on_return;}); + } + + + /* 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_ON_FAIL(conf_port->mix_buf, + {status = PJ_ENOMEM; goto on_return;}); + conf_port->last_mix_adj = NORMAL_LEVEL; + + if (IS_PARALLEL) { + /* the compiler should potentially optimize this "if" away + * for the serial conference bridge + */ + conf_port->rx_frame_buf = (pj_int16_t *)pj_pool_zalloc(pool, conf/*_port*/->rx_frame_buf_cap); + status = pj_lock_create_simple_mutex(pool, "tx_Lock", &conf_port->tx_Lock); + if (status != PJ_SUCCESS) + goto on_return; + } + + /* Done */ + *p_conf_port = conf_port; + +on_return: + if (status != PJ_SUCCESS) { + if (pool) + pj_pool_release(pool); + } + + return status; +} + + +/* + * 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; + + pool = conf_port->pool; + + /* 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) { + destroy_conf_port(conf_port); + return status; + } + + *p_conf_port = conf_port; + + return PJ_SUCCESS; +} + + +/* + * Create port zero for the sound device. + */ +static pj_status_t create_sound_port( pj_pool_t *pool, + pjmedia_conf *conf ) +{ + struct conf_port *conf_port; + pj_str_t name = { "Master/sound", 12 }; + pj_status_t status; + + + status = create_pasv_port(conf, pool, &name, NULL, &conf_port); + if (status != PJ_SUCCESS) + return status; + + + /* Create sound device port: */ + + if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) { + pjmedia_aud_stream *strm; + pjmedia_aud_param param; + + /* + * If capture is disabled then create player only port. + * Otherwise create bidirectional sound device port. + */ + if (conf->options & PJMEDIA_CONF_NO_MIC) { + status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate, + conf->channel_count, + conf->samples_per_frame, + conf->bits_per_sample, + 0, /* options */ + &conf->snd_dev_port); + + } else { + status = pjmedia_snd_port_create( pool, -1, -1, conf->clock_rate, + conf->channel_count, + conf->samples_per_frame, + conf->bits_per_sample, + 0, /* Options */ + &conf->snd_dev_port); + + } + + if (status != PJ_SUCCESS) + return status; + + strm = pjmedia_snd_port_get_snd_stream(conf->snd_dev_port); + status = pjmedia_aud_stream_get_param(strm, ¶m); + if (status == PJ_SUCCESS) { + pjmedia_aud_dev_info snd_dev_info; + if (conf->options & PJMEDIA_CONF_NO_MIC) + pjmedia_aud_dev_get_info(param.play_id, &snd_dev_info); + else + pjmedia_aud_dev_get_info(param.rec_id, &snd_dev_info); + pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info.name); + } + + PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0")); + } + +#ifdef CONF_DEBUG_EX + conf_port->slot = 0; +#endif //CONF_DEBUG_EX + + /* Add the port to the bridge */ + conf->ports[0] = conf_port; +#if 0 + conf->port_cnt++; // the port will become active only when connected +#endif + + 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 ) +{ + pj_pool_t *pool; + pjmedia_conf *conf; + const pj_str_t name = { "Conf", 4 }; + pj_status_t status; + + PJ_ASSERT_RETURN(samples_per_frame > 0, PJ_EINVAL); + /* Can only accept 16bits per sample, for now.. */ + PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); + +#if defined(CONF_DEBUG_EX) || defined(CONF_DEBUG) + pj_log_set_level(5); +#endif + + PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports", + max_ports)); + + /* Create own pool */ + pool = pj_pool_create(pool_->factory, name.ptr, 512, 512, NULL); + if (!pool) { + PJ_PERROR(1, (THIS_FILE, PJ_ENOMEM, "Create failed in alloc")); + return PJ_ENOMEM; + } + + /* Create and init conf structure. */ + conf = PJ_POOL_ZALLOC_T(pool, pjmedia_conf); + PJ_ASSERT_RETURN(conf, PJ_ENOMEM); + conf->pool = pool; + + conf->ports = pj_pool_calloc(pool, max_ports, sizeof(void*)); + PJ_ASSERT_ON_FAIL( conf->ports, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + + conf->active_ports = pj_pool_calloc(pool, max_ports, sizeof(pj_int32_t) ); + PJ_ASSERT_ON_FAIL( conf->active_ports, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + +#ifdef _OPENMP + /* Open MP support */ + conf->active_listener = pj_pool_calloc(pool, max_ports, sizeof(struct conf_port*)); + PJ_ASSERT_ON_FAIL( conf->active_listener, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); +#endif // _OPENMP + + 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; + + conf->lower_bound = max_ports; // no connected ports yet + conf->upper_bound = 0; // no connected ports yet + + + /* 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; + + /* Get the bytes_per_frame value, to determine the size of the + * buffer. + */ + conf->rx_frame_buf_cap = PJMEDIA_AFD_AVG_FSZ(pjmedia_format_get_audio_format_detail(&conf->master_port->info.fmt, PJ_TRUE)); + + /* 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; + } + } + + + /* Allocate synchronized operation queues */ + conf->op_queue = PJ_POOL_ZALLOC_T(pool, op_entry); + conf->op_queue_free = PJ_POOL_ZALLOC_T(pool, op_entry); + if (!conf->op_queue || !conf->op_queue_free) { + PJ_PERROR(1, (THIS_FILE, PJ_ENOMEM, "Create failed in create queues")); + pjmedia_conf_destroy(conf); + return PJ_ENOMEM; + } + pj_list_init(conf->op_queue); + pj_list_init(conf->op_queue_free); + + +#if defined(PJ_STACK_IMPLEMENTATION) + status = pj_stack_create( pool, &conf->unused_slots ); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy( conf ); + return status; + } +#else + conf->unused_slots = PJ_POOL_ZALLOC_T(pool, unused_slots_cache); + pj_list_init(conf->unused_slots); +#endif + conf->free_port_slots = pj_pool_calloc(pool, max_ports, sizeof(port_slot)); + PJ_ASSERT_ON_FAIL( conf->free_port_slots, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + unsigned i = conf->max_ports; + while (i--) { /* prepare unused slots to later reservation, reverse order due to FILO */ + if (!conf->ports[i]) { /* If sound device was created, skip it's slot */ + status = conf_release_port( conf, i ); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy( conf ); + return status; + } + } + } + + conf->is_parallel = IS_PARALLEL; + +#ifdef _OPENMP + + conf->omp_max_threads = omp_get_max_threads(); + /* Thread description's to register omp threads with pjsip */ + conf->omp_threads = (pj_thread_desc *)pj_pool_calloc( pool, 2 * conf->omp_max_threads, sizeof(pj_thread_desc) ); + status = pj_atomic_create(pool, 0, &conf->omp_threads_idx); + if (status != PJ_SUCCESS) + goto on_return; + +#else + + status = pj_atomic_create(conf->pool, 0, &conf->active_ports_idx); + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, goto on_return); + + if (conf->is_parallel) + { + status = thread_pool_start(conf); + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, goto on_return); + } + +#endif //_OPENMP + + /* Done */ + *p_conf = conf; + + return PJ_SUCCESS; + +on_return: + pjmedia_conf_destroy(conf); + + return status; + +} + + +/* + * 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; + + PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL); + + pj_log_push_indent(); + + /* Signal threads to quit */ + conf->quit_flag = PJ_TRUE; + + /* all threads have reached the barrier and the conference bridge thread no longer exists. + * Should be a very short waiting. + * + * If we couldn't create all the threads from the pool, we shouldn't get close to the barrier. + */ + if (conf->running && conf->active_thread) + pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_SPIN_ONLY); + + /* Destroy thread pool */ + if (conf->pool_threads) + { + pj_thread_t **threads = conf->pool_threads; + pj_thread_t **end = threads + (PJ_CONF_BRIDGE_MAX_THREADS - 1); + while (threads < end) + { + if (*threads) + { + pj_thread_join(*threads); + pj_thread_destroy(*threads); + *threads = NULL; + } + ++threads; + } + } + if (conf->active_thread) + pj_barrier_destroy(conf->active_thread); + if (conf->barrier) + pj_barrier_destroy(conf->barrier); + + + /* Destroy sound device port. */ + if (conf->snd_dev_port) { + pjmedia_snd_port_destroy(conf->snd_dev_port); + conf->snd_dev_port = NULL; + } + + /* Flush any pending operation (connect, disconnect, etc) */ + if (conf->op_queue) + handle_op_queue(conf); + + /* Remove all ports (may destroy them too). */ + for (i=0; imax_ports; ++i) { + if (conf->ports[i]) { + op_param oprm = {0}; + oprm.remove_port.port = i; + op_remove_port(conf, &oprm); + } + } + +#if defined(PJ_STACK_IMPLEMENTATION) + /* Destroy stack */ + if (conf->unused_slots) + pj_stack_destroy(conf->unused_slots); +#endif + + /* Destroy mutex */ + if (conf->mutex) + pj_mutex_destroy(conf->mutex); + +#ifdef _OPENMP + /* Open MP support */ + /* Destroy atomic */ + if (conf->omp_threads_idx) + pj_atomic_destroy(conf->omp_threads_idx); +#endif + + /* Destroy atomic */ + if (conf->active_ports_idx) + pj_atomic_destroy(conf->active_ports_idx); + + /* Destroy pool */ + if (conf->pool) + pj_pool_safe_release(&conf->pool); + + pj_log_pop_indent(); + + 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); +} + +#if !DEPRECATED_FOR_TICKET_2234 +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; +} +#endif + +/* + * 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; + SLOT_TYPE index = INVALID_SLOT; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(conf && pool && strm_port, PJ_EINVAL); + + pj_log_push_indent(); + + /* 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"); + status = PJMEDIA_ENCCHANNEL; + goto on_return; + } + + /* Find empty port in the conference bridge. */ + index = conf_reserve_port(conf); + if (index == INVALID_SLOT) { + PJ_PERROR(3,(THIS_FILE, PJ_ETOOMANY, "Add port %s failed", + port_name->ptr)); + //pj_assert( !"Too many ports" ); + status = PJ_ETOOMANY; + goto on_return; + } + pj_assert(index < conf->max_ports && conf->ports[index] == NULL); + + /* Create conf port structure. */ + status = create_conf_port(pool, conf, strm_port, port_name, &conf_port); + if (status != PJ_SUCCESS) + goto on_return; + + pj_assert(conf_port != NULL && !is_port_active(conf_port)); + +#ifdef CONF_DEBUG_EX + conf_port->slot = index; +#endif //CONF_DEBUG_EX + + /* redundant flag and code + * however it is still used for compatibility with: + * "Synchronous port removal in audio conference bridge if port is newly added #4253" + */ + conf_port->is_new = PJ_TRUE; + + /* Put the port to the reserved slot. */ + conf->ports[index] = conf_port; /* - the port will become active only when connected + * - pointer assignment is processor level atomic + */ + +#if 0 + /* Put the port, but don't add port counter yet */ + conf->ports[index] = conf_port; + //conf->port_cnt++; +#endif //0 + + pj_mutex_lock( conf->mutex ); + /* Queue the operation */ + op_entry *ope; + ope = get_free_op_entry(conf); + if (ope) { + ope->type = OP_ADD_PORT; + ope->param.add_port.port = index; + pj_list_push_back(conf->op_queue, ope); + pj_mutex_unlock(conf->mutex); + PJ_LOG(4,(THIS_FILE, "Add port %d (%.*s) queued", + index, (int)port_name->slen, port_name->ptr)); + } else { + pj_mutex_unlock(conf->mutex); + status = PJ_ENOMEM; + goto on_return; + } + + /* Done. */ + if (p_port) { + *p_port = index; + } + +on_return: + if (status != PJ_SUCCESS) { + if (index != INVALID_SLOT) { + conf->ports[index] = NULL; + conf_release_port( conf, index ); + } + } + + pj_log_pop_indent(); + + return status; +} + +/* + * Find empty port in the conference bridge. + */ +static SLOT_TYPE conf_reserve_port(pjmedia_conf *conf) +{ + port_slot *pslot; +#if defined(PJ_STACK_IMPLEMENTATION) + pslot = pj_stack_pop(conf->unused_slots); +#else + pj_mutex_lock(conf->mutex); + if (!pj_list_empty(conf->unused_slots)) { + pslot = conf->unused_slots->next; + pj_list_erase(pslot); + } else { + pslot = NULL; + } + pj_mutex_unlock(conf->mutex); +#endif + if (!pslot) + return INVALID_SLOT; + + SLOT_TYPE slot = pslot - conf->free_port_slots; + pj_assert( slot < conf->max_ports && conf->ports[slot] == NULL ); + return slot; +} + +/* + * Return conf_port slot to unused slots cache. + */ +static pj_status_t conf_release_port(pjmedia_conf *conf, SLOT_TYPE slot) +{ + /* Check arguments */ + PJ_ASSERT_RETURN( conf && slot < conf->max_ports, PJ_EINVAL ); + PJ_ASSERT_RETURN( conf->ports[slot] == NULL, PJ_EINVALIDOP ); +#if defined(PJ_STACK_IMPLEMENTATION) + return pj_stack_push( conf->unused_slots, conf->free_port_slots + slot ); +#else + pj_mutex_lock(conf->mutex); + pj_list_push_front(conf->unused_slots, conf->free_port_slots + slot); + pj_mutex_unlock(conf->mutex); + return PJ_SUCCESS; +#endif +} + + +static void op_add_port(pjmedia_conf *conf, const op_param *prm) +{ + unsigned port = prm->add_port.port; + struct conf_port *cport = conf->ports[port]; + + /* Port must be valid and flagged as new. */ + if (!cport || !cport->is_new) + return; + + /* Activate newly added port */ + cport->is_new = PJ_FALSE; +#if 0 + ++conf->port_cnt; + + PJ_LOG(4,(THIS_FILE, "Added port %d (%.*s), port count=%d", + port, (int)cport->name.slen, cport->name.ptr, conf->port_cnt)); +#endif +} + + +#if !DEPRECATED_FOR_TICKET_2234 +/* + * 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; + SLOT_TYPE 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); + + /* Find empty port in the conference bridge. */ + slot = conf_reserve_port(conf); + if (slot == INVALID_SLOT) { + pj_assert(!"Too many ports"); + return PJ_ETOOMANY; + } + pj_assert(slot < conf->max_ports && conf->ports[slot] == NULL); + + 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) { + return status; + } + + /* Put the port to the reserved slot. */ + conf->ports[index] = conf_port; +#if 0 + conf->port_cnt++; +#endif + + /* Done. */ + if (p_slot) + *p_slot = index; + if (p_port) + *p_port = port; + + return PJ_SUCCESS; +} +#endif + + + +/* + * 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; + } + + 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 adj_level ) +{ + struct conf_port *src_port, *dst_port; + pj_bool_t start_sound = PJ_FALSE; + op_entry *ope; + pj_status_t status = PJ_SUCCESS; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && src_slotmax_ports && + sink_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); + + pj_log_push_indent(); + + PJ_LOG(5,(THIS_FILE, "Connect ports %d->%d requested", + src_slot, sink_slot)); + + 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) { + status = PJ_EINVAL; + goto on_return; + } + + /* Queue the operation */ + ope = get_free_op_entry(conf); + if (ope) { + ope->type = OP_CONNECT_PORTS; + ope->param.connect_ports.src = src_slot; + ope->param.connect_ports.sink = sink_slot; + ope->param.connect_ports.adj_level = adj_level; + pj_list_push_back(conf->op_queue, ope); + + PJ_LOG(4,(THIS_FILE, "Connect ports %d->%d queued", + src_slot, sink_slot)); + } else { + status = PJ_ENOMEM; + goto on_return; + } + + /* This is first connection, start clock */ + if (conf->connect_cnt == 0) + start_sound = 1; + +on_return: + pj_mutex_unlock(conf->mutex); + + /* Sound device must be started without mutex, otherwise the + * sound thread will deadlock (?) + */ + if (start_sound) + resume_sound(conf); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3,(THIS_FILE, status, "Connect ports %d->%d failed", + src_slot, sink_slot)); + } + + pj_log_pop_indent(); + + return status; +} + +static void op_connect_ports(pjmedia_conf *conf, const op_param *prm) +{ + unsigned src_slot, sink_slot; + struct conf_port *src_port, *dst_port; + unsigned i; + + /* Ports must be valid. */ + src_slot = prm->connect_ports.src; + sink_slot = prm->connect_ports.sink; + src_port = conf->ports[src_slot]; + dst_port = conf->ports[sink_slot]; + + if (!src_port || !dst_port) { + PJ_PERROR(3,(THIS_FILE, PJ_EINVAL, + "Failed connecting %d->%d, make sure ports are valid", + src_slot, sink_slot)); + return; + } + + /* Check if connection has been made */ + for (i=0; ilistener_cnt; ++i) { + if (src_port->listener_slots[i] == sink_slot) { + PJ_LOG(3,(THIS_FILE, "Ports connection %d->%d already exists", + src_slot, sink_slot)); + return; + } + } + + src_port->listener_slots[src_port->listener_cnt] = sink_slot; + + /* Set normalized adjustment level. */ + src_port->listener_adj_level[src_port->listener_cnt] = + prm->connect_ports.adj_level + NORMAL_LEVEL; + + ++conf->connect_cnt; + ++src_port->listener_cnt; + ++dst_port->transmitter_cnt; + + correct_port_boundary( conf, src_slot ); + correct_port_boundary( conf, sink_slot ); + pj_assert( conf->lower_bound < conf->upper_bound ); + + 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)); +} + +/* + * 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; + op_entry *ope; + pj_status_t status = PJ_SUCCESS; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && src_slotmax_ports && + sink_slotmax_ports, PJ_EINVAL); + + pj_log_push_indent(); + + PJ_LOG(5,(THIS_FILE, "Disconnect ports %d->%d requested", + src_slot, sink_slot)); + + 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) { + status = PJ_EINVAL; + goto on_return; + } + + /* Queue the operation */ + ope = get_free_op_entry(conf); + if (ope) { + ope->type = OP_DISCONNECT_PORTS; + ope->param.disconnect_ports.src = src_slot; + ope->param.disconnect_ports.sink = sink_slot; + pj_list_push_back(conf->op_queue, ope); + + PJ_LOG(4,(THIS_FILE, "Disconnect ports %d->%d queued", + src_slot, sink_slot)); + } else { + status = PJ_ENOMEM; + goto on_return; + } + +on_return: + pj_mutex_unlock(conf->mutex); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3,(THIS_FILE, status, "Disconnect ports %d->%d failed", + src_slot, sink_slot)); + } + + pj_log_pop_indent(); + + return status; +} + +static void op_disconnect_ports(pjmedia_conf *conf, + const op_param *prm) +{ + SLOT_TYPE src_slot, sink_slot; + struct conf_port *src_port = NULL, *dst_port = NULL; + SLOT_TYPE i; + + /* Ports must be valid. */ + src_slot = prm->disconnect_ports.src; + sink_slot = prm->disconnect_ports.sink; + + if (src_slot != INVALID_SLOT) + src_port = conf->ports[src_slot]; + if (sink_slot != INVALID_SLOT) + dst_port = conf->ports[sink_slot]; + + /* Disconnect source -> sink */ + if (src_port && dst_port) { + /* 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) { + PJ_LOG(3,(THIS_FILE, "Ports connection %d->%d does not exist", + src_slot, sink_slot)); + return; + } + + 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); + pj_array_erase(src_port->listener_adj_level, sizeof(unsigned), + src_port->listener_cnt, i); + --conf->connect_cnt; + --src_port->listener_cnt; + --dst_port->transmitter_cnt; + + correct_port_boundary( conf, src_slot ); + if (src_port != dst_port) + correct_port_boundary( conf, sink_slot ); + + 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); + + /* Disconnect multiple conn: any -> sink */ + } else if (dst_port) { + /* Remove this port from transmit array of other ports. */ + if (dst_port->transmitter_cnt) { + PJ_LOG(4,(THIS_FILE, + "Stop any transmission to port %d (%.*s)", + sink_slot, + (int)dst_port->name.slen, + dst_port->name.ptr)); + + for (i = conf->lower_bound; i < conf->upper_bound; ++i) { + int j; + + src_port = conf->ports[i]; + if (!src_port || src_port->listener_cnt == 0) + continue; + + /* We need to iterate backwards since the listener count + * can potentially decrease. + */ + for (j = src_port->listener_cnt - 1; j >= 0; --j) { + if (src_port->listener_slots[j] == sink_slot) { + op_param op_prm = {0}; + op_prm.disconnect_ports.src = i; + op_prm.disconnect_ports.sink = sink_slot; + op_disconnect_ports( conf, &op_prm ); + break; + } + } + } + pj_assert( !dst_port->transmitter_cnt ); + } + + /* Disconnect multiple conn: source -> any */ + } else if (src_port) { + if (src_port->listener_cnt) { + int j; /* should be signed! */ + PJ_LOG(4,(THIS_FILE, + "Stop any transmission from port %d (%.*s)", + src_slot, + (int)src_port->name.slen, + src_port->name.ptr)); + + /* We need to iterate backwards since the listener count + * will keep decreasing. + */ + for (j = src_port->listener_cnt - 1; j >= 0; --j) { + op_param op_prm = {0}; + op_prm.disconnect_ports.src = src_slot; + op_prm.disconnect_ports.sink = src_port->listener_slots[j]; + op_disconnect_ports( conf, &op_prm ); + } + pj_assert( !src_port->listener_cnt ); + } + + /* Invalid ports */ + } else { + pj_assert(!"Invalid ports specified in conf disconnect"); + } + + /* Pause sound dev when there is no connection, the pause should be done + * outside mutex to avoid possible deadlock. + * Note that currently this is done with mutex, it is safe because + * pause_sound() is a no-op (just maintaining old code). + */ + if (conf->connect_cnt == 0) { + pause_sound(conf); + } +} + +/* + * Disconnect port from all sources + */ +PJ_DEF(pj_status_t) +pjmedia_conf_disconnect_port_from_sources( pjmedia_conf *conf, + unsigned sink_slot) +{ + struct conf_port *dst_port; + op_entry *ope; + pj_status_t status = PJ_SUCCESS; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && sink_slotmax_ports, PJ_EINVAL); + + pj_log_push_indent(); + PJ_LOG(5,(THIS_FILE, "Disconnect ports any->%d requested", + sink_slot)); + + pj_mutex_lock(conf->mutex); + + /* Ports must be valid. */ + dst_port = conf->ports[sink_slot]; + if (!dst_port) { + status = PJ_EINVAL; + goto on_return; + } + + /* Queue the operation */ + ope = get_free_op_entry(conf); + if (ope) { + ope->type = OP_DISCONNECT_PORTS; + ope->param.disconnect_ports.src = INVALID_SLOT; + ope->param.disconnect_ports.sink = sink_slot; + pj_list_push_back(conf->op_queue, ope); + + PJ_LOG(4,(THIS_FILE, "Disconnect ports any->%d queued", sink_slot)); + } else { + status = PJ_ENOMEM; + goto on_return; + } + +on_return: + pj_mutex_unlock(conf->mutex); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3,(THIS_FILE, status, "Disconnect ports any->%d failed", + sink_slot)); + } + + pj_log_pop_indent(); + + return status; +} + + +/* + * Disconnect port from all sinks + */ +PJ_DEF(pj_status_t) +pjmedia_conf_disconnect_port_from_sinks( pjmedia_conf *conf, + unsigned src_slot) +{ + struct conf_port *src_port; + op_entry *ope; + pj_status_t status = PJ_SUCCESS; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && src_slotmax_ports, PJ_EINVAL); + + pj_log_push_indent(); + + PJ_LOG(5,(THIS_FILE, "Disconnect ports %d->any requested", + src_slot)); + + pj_mutex_lock(conf->mutex); + + /* Port must be valid. */ + src_port = conf->ports[src_slot]; + if (!src_port) { + status = PJ_EINVAL; + goto on_return; + } + + ope = get_free_op_entry(conf); + if (ope) { + ope->type = OP_DISCONNECT_PORTS; + ope->param.disconnect_ports.src = src_slot; + ope->param.disconnect_ports.sink = INVALID_SLOT; + pj_list_push_back(conf->op_queue, ope); + + PJ_LOG(4,(THIS_FILE, "Disconnect ports %d->any queued", src_slot)); + } else { + status = PJ_ENOMEM; + goto on_return; + } + +on_return: + pj_mutex_unlock(conf->mutex); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3,(THIS_FILE, status, "Disconnect ports %d->any failed", + src_slot)); + } + + pj_log_pop_indent(); + + return status; +} + + +/* + * Get number of ports currently registered to the conference bridge. + */ +PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf) +{ +#if defined(PJ_STACK_IMPLEMENTATION) + return conf->max_ports - pj_stack_size(conf->unused_slots); //O(1) +#else + return conf->max_ports - pj_list_size(conf->unused_slots); //O(n) +#endif + +#if 0 + return conf->port_cnt; +#endif +} + +/* + * 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; + op_entry *ope; + pj_status_t status = PJ_SUCCESS; + + pj_log_push_indent(); + + PJ_LOG(5,(THIS_FILE, "Remove port %d requested", port)); + + PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL); + + pj_mutex_lock(conf->mutex); + + /* Port must be valid. */ + conf_port = conf->ports[port]; + if (conf_port == NULL) { + status = PJ_EINVAL; + goto on_return; + } + + /* If port is new, remove it synchronously */ + if (conf_port->is_new) { + pj_bool_t found = PJ_FALSE; + + /* Find & cancel the add-op. + * Also cancel all following ops involving the slot. + * Note that after removed, the slot may be reused by another port + * so if not cancelled, those following ops may be applied to the + * wrong port. + */ + ope = conf->op_queue->next; + while (ope != conf->op_queue) { + op_entry* cancel_op; + + cancel_op = NULL; + if (ope->type == OP_ADD_PORT && ope->param.add_port.port == port) + { + found = PJ_TRUE; + cancel_op = ope; + } else if (found && ope->type == OP_CONNECT_PORTS && + (ope->param.connect_ports.src == port || + ope->param.connect_ports.sink == port)) + { + cancel_op = ope; + } else if (found && ope->type == OP_DISCONNECT_PORTS && + (ope->param.disconnect_ports.src == port || + ope->param.disconnect_ports.sink == port)) + { + cancel_op = ope; + } + + ope = ope->next; + + /* Cancel op */ + if (cancel_op) { + pj_list_erase(cancel_op); + cancel_op->type = OP_UNKNOWN; + pj_list_push_back(conf->op_queue_free, cancel_op); + } + } + + /* If the add-op is not found, it may be being executed, + * do not remove it synchronously to avoid race condition. + */ + if (found) { + op_param prm; + + /* Release mutex to avoid deadlock */ + pj_mutex_unlock(conf->mutex); + + /* Remove it */ + prm.remove_port.port = port; + op_remove_port(conf, &prm); + + pj_log_pop_indent(); + return PJ_SUCCESS; + } + } + + /* Queue the operation */ + ope = get_free_op_entry(conf); + if (ope) { + ope->type = OP_REMOVE_PORT; + ope->param.remove_port.port = port; + pj_list_push_back(conf->op_queue, ope); + + PJ_LOG(4,(THIS_FILE, "Remove port %d queued", port)); + } else { + status = PJ_ENOMEM; + goto on_return; + } + +on_return: + pj_mutex_unlock(conf->mutex); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3,(THIS_FILE, status, "Remove port %d failed", port)); + } + + pj_log_pop_indent(); + + return status; +} + + +static void op_remove_port(pjmedia_conf *conf, const op_param *prm) +{ + unsigned port = prm->remove_port.port; + struct conf_port *conf_port; + op_param op_prm; + + /* Port must be valid. */ + conf_port = conf->ports[port]; + if (conf_port == NULL) { + PJ_PERROR(3, (THIS_FILE, PJ_ENOTFOUND, "Remove port failed")); + return; + } + + conf_port->tx_setting = PJMEDIA_PORT_DISABLE; + conf_port->rx_setting = PJMEDIA_PORT_DISABLE; + + /* disconnect port from all sources which are transmitting to it */ + pj_bzero(&op_prm, sizeof(op_prm)); + op_prm.disconnect_ports.src = INVALID_SLOT; + op_prm.disconnect_ports.sink = port; + op_disconnect_ports(conf, &op_prm); + + /* disconnect port from all sinks to which it is transmitting to */ + pj_bzero(&op_prm, sizeof(op_prm)); + op_prm.disconnect_ports.src = port; + op_prm.disconnect_ports.sink = INVALID_SLOT; + op_disconnect_ports(conf, &op_prm); + + pj_assert( !is_port_active( conf_port ) ); + /* Remove the port. */ + //pj_mutex_lock(conf->mutex); + conf->ports[port] = NULL; + //pj_mutex_unlock(conf->mutex); +#if 0 + if (!conf_port->is_new) + --conf->port_cnt; + + PJ_LOG(4,(THIS_FILE,"Removed port %d (%.*s), port count=%d", + port, (int)conf_port->name.slen, conf_port->name.ptr, + conf->port_cnt)); +#endif + PJ_LOG(4,(THIS_FILE,"Removed port %d (%.*s)", + port, (int)conf_port->name.slen, conf_port->name.ptr)); + + /* Return conf_port slot to unused slots cache. */ + conf_release_port( conf, port ); + + /* Decrease conf port ref count */ + if (conf_port->port && conf_port->port->grp_lock) + pj_grp_lock_dec_ref(conf_port->port->grp_lock); + else + destroy_conf_port(conf_port); +} + +static void destroy_conf_port( struct conf_port *conf_port ) +{ + pj_assert( conf_port ); + + TRACE_EX( (THIS_FILE, "%s: destroy_conf_port %p (%.*s, %d) transmitter_cnt=%d, listener_cnt=%d", + pj_thread_get_name( pj_thread_this() ), + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + conf_port->slot, + conf_port->transmitter_cnt, + conf_port->listener_cnt) ); + + /* Destroy resample if this conf port has it. */ + if (conf_port->rx_resample) { + pjmedia_resample_destroy(conf_port->rx_resample); + conf_port->rx_resample = NULL; + } + if (conf_port->tx_resample) { + pjmedia_resample_destroy(conf_port->tx_resample); + conf_port->tx_resample = NULL; + } + + /* Destroy pjmedia port if this conf port is passive port, + * i.e: has delay buf. + */ + if (conf_port->delay_buf) { + pjmedia_delay_buf_destroy(conf_port->delay_buf); + conf_port->delay_buf = NULL; + + if (conf_port->port) { + pjmedia_port_destroy(conf_port->port); + conf_port->port = NULL; + } + } + + if (conf_port->tx_Lock != NULL) + pj_lock_destroy(conf_port->tx_Lock); + + pj_pool_safe_release(&conf_port->pool); +} + + +/* + * 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; + + /* 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; + } + + info->slot = slot; + info->name = conf_port->name; + if (conf_port->port) { + pjmedia_format_copy(&info->format, &conf_port->port->info.fmt); + } else { + pj_bzero(&info->format, sizeof(info->format)); + info->format.id = (pj_uint32_t)PJMEDIA_FORMAT_INVALID; + } + 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; 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; + } + + /* 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; + } + + /* Set normalized adjustment level. */ + conf_port->tx_adj_level = adj_level + NORMAL_LEVEL; + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + +/* + * Adjust level of individual connection. + */ +PJ_DEF(pj_status_t) pjmedia_conf_adjust_conn_level( pjmedia_conf *conf, + unsigned src_slot, + unsigned sink_slot, + int adj_level ) +{ + struct conf_port *src_port, *dst_port; + unsigned i; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && src_slotmax_ports && + sink_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); + + 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) { + /* connection hasn't been made */ + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + /* Set normalized adjustment level. */ + src_port->listener_adj_level[i] = adj_level + NORMAL_LEVEL; + + 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; + + pj_assert( conf && cport && timestamp && frm_type ); + + *frm_type = PJMEDIA_FRAME_TYPE_AUDIO; + + /* Skip port if it is disabled */ + if (cport->tx_setting == PJMEDIA_PORT_DISABLE) { + cport->tx_level = 0; + *frm_type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; + } + /* If port is muted or nobody is transmitting to this port, + * transmit NULL frame. + */ + else if ((cport->tx_setting == PJMEDIA_PORT_MUTE) || + cport->last_timestamp.u64 != timestamp->u64 // no data in mix_buf + /*(cport->transmitter_cnt == 0)*/) { + + TRACE_EX( (THIS_FILE, "%s: Transmit heart-beat frames to port %p (%.*s, %d, transmitter_cnt=%d) last_timestamp=%llu, timestamp=%llu", + pj_thread_get_name( pj_thread_this() ), + cport, + (int)cport->name.slen, + cport->name.ptr, + cport->slot, + cport->transmitter_cnt, + cport->last_timestamp.u64, timestamp->u64) ); + + 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; + } + + /* 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; jsamples_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; jsamples_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, last_timestamp=%llu, timestamp=%llu", + (int)cport->name.slen, cport->name.ptr, + frame.size / BYTES_PER_SAMPLE, + cport->last_timestamp.u64, timestamp->u64)); + + 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; +} + +static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_frame *frame) { + pj_assert(IS_PARALLEL == (conf_port->rx_frame_buf != NULL)); + /* the compiler should potentially optimize this away + * for the serial conference bridge + */ + if (IS_PARALLEL) + return conf_port->rx_frame_buf; // parallel conference bridge + else + return (pj_int16_t *)frame->buf; // sequential conference bridge +} + +/* + * 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; + +#ifdef _OPENMP + pjmedia_frame_type speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE; + struct conf_port *sound_port = NULL; + + //conf->listener_counter = 0; + pj_int32_t listener_counter = 0; +#endif + + //parallelization requires signed int + pj_int32_t i, + begin, end, /* this is lower_bound and upper_bound for conf->ports[] array */ + upper_bound; /* this is upper_bound for conf->active_ports[] array */ + + TRACE_((THIS_FILE, "- clock -")); + + /* Check that correct size is specified. */ + pj_assert(frame->size == conf->samples_per_frame * + conf->bits_per_sample / 8); + +#if 0 + /* Perform any queued operations that need to be synchronized with + * the clock such as connect, disonnect, remove. + */ + if (!pj_list_empty(conf->op_queue)) { + pj_log_push_indent(); + handle_op_queue(conf); + pj_log_pop_indent(); + } +#endif + + /* No mutex from this point! Otherwise it may cause deadlock as + * put_frame()/get_frame() may invoke callback. + * + * Note that any changes on the conference connections must be + * synchronized. + */ + + begin = conf->lower_bound; + end = conf->upper_bound; + +#ifdef CONF_DEBUG + int threads[IS_PARALLEL ? 64 : 1] = {0}; +#endif //CONF_DEBUG + + /* Step 1 initialization + * Single threaded loop to get the active_ports[] (transmitters) + * and active_listener[] (receivers) arrays. + */ + for (i = begin, upper_bound = 0; i < end; ++i) { + pj_assert( (unsigned)i < conf->max_ports ); + struct conf_port *conf_port = conf->ports[i]; + + /* Skip empty port. + * Newly added ports are not connected yet + * and so we skip them as not active + */ + if (is_port_active( conf_port )) + { + /* Reset auto adjustment level for mixed signal. */ + conf_port->mix_adj = NORMAL_LEVEL; +#if 0 + /* We need not reset buffer, we just want to copy the first (and probably only) frame there. */ + if (conf_port->transmitter_cnt > 1) { + pj_bzero( conf_port->mix_buf, + conf->samples_per_frame * sizeof(conf_port->mix_buf[0]) ); + } +#endif + if (conf_port->transmitter_cnt && conf_port->tx_setting != PJMEDIA_PORT_DISABLE) { + +#ifdef _OPENMP + /* In the final loop of the get_frame() function, + * we might call write_port() either concurrently with + * or even after the OP_PORT_REMOVE operation. + * Therefore, ports that are being written to + * (i.e., those with conf_port->transmitter_cnt != 0) + * require protection from deletion. + * For such ports, we must increment the ref_count + * (and decrement it after transmission, + * which may result in deferred deletion of the port). + * + * At least "Master/sound" has no media_port. + * We don't manage the lifetime of such ports, + * their lifetime is the conference bridge lifetime. + */ + if (conf_port->port && conf_port->port->grp_lock) + pj_grp_lock_add_ref(conf_port->port->grp_lock); + + //sound device (from 0 SLOT) to 0 idx, other to next idx + conf->active_listener[i == 0 ? 0 : ++/*conf->*/listener_counter] = conf_port; +#endif + + /* We need not reset mix_buf, we just want to copy the first + * (and probably only) frame there. + * The criteria for "this frame is from the first transmitter" + * condition is: + * (conf_port->last_timestamp.u64 != frame->timestamp.u64) + */ + if (conf_port->last_timestamp.u64 == frame->timestamp.u64) + { //this port have not yet received data on this timer tick + // enforce "this frame is from the first transmitter" condition + //we usually shouldn't come here + conf_port->last_timestamp.u64 = (frame->timestamp.u64 ? PJ_UINT64(0) : (pj_uint64_t)-1); + } + pj_assert( conf_port->last_timestamp.u64 != frame->timestamp.u64 ); + } + + /* 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; + } + + /* compacted transmitter's array should help OpenMP to distribute task throught team's threads */ + conf->active_ports[upper_bound++] = i; + pj_assert( upper_bound <= (end - begin) ); + + } + } + +#ifdef _OPENMP + + PRAGMA_OMP(parallel for PJ_OPENMP_FOR_CLAUSES) + /* Step 2 + * Get frames from all ports, and "mix" the signal + * to mix_buf of all listeners of the port. + * + * Here we use the current switching states, + * which must be stable during this cycle, + * i.e. must not change in parallel. + * + * To receive frames from all ports in parallel, + * we receive data from each port into separate + * buffers conf_port->rx_frame_buf + */ + for (i = 0; i < upper_bound; ++i) { + pj_int32_t port_idx = conf->active_ports[i]; + pj_assert( (unsigned)port_idx < conf->max_ports ); + struct conf_port *conf_port = conf->ports[port_idx]; + + /* register omp thread with pjsip */ + register_omp_thread( conf ); + +#ifdef CONF_DEBUG + inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); +#endif //CONF_DEBUG + + /* 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; + + /* Check that correct size is specified. */ + pj_assert( frame->size == conf/*_port*/->rx_frame_buf_cap); + /* read data to different buffers to different conf_port's parallel processing */ + status = pjmedia_delay_buf_get( conf_port->delay_buf, get_read_buffer(conf_port, frame) ); + if (status != PJ_SUCCESS) { + conf_port->rx_level = 0; + TRACE_EX( (THIS_FILE, "%s: No frame from the passive port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); + continue; + } + + } else { + + pj_status_t status; + pjmedia_frame_type frame_type; + + /* Check that correct size is specified. */ + pj_assert( frame->size == conf/*_port */->rx_frame_buf_cap); + /* read data to different buffers to different conf_port's parallel processing */ + status = read_port(conf, conf_port, get_read_buffer(conf_port, frame), + conf->samples_per_frame, &frame_type ); + + /* Check that the port is not removed when we call get_frame() */ + /* + * if port is removed old conf_port may point to not authorized memory + * We can not call conf_port->rx_level = 0; here! + * "Port is not removed" check should take priority over the return code check + * + * However this check is not necessary for async conference bridge, + * because application can not remove port while we are in get_frame() callback. + * The only thing that can happen is that port removing will be sheduled + * there but still will processed later (see Step 3). + */ + if (conf->ports[port_idx] != conf_port) { + //conf_port->rx_level = 0; + PJ_LOG( 4, (THIS_FILE, "Port %d is removed when we call get_frame()", port_idx) ); + continue; + } + + if (status != PJ_SUCCESS) { + + /* check status and disable port here. + * Prevent multiply eof callback invoke, + * if fileplayer has reached EOF (i.e. status == PJ_EEOF) + */ + if (status == PJ_EEOF) { + TRACE_( (THIS_FILE, "Port %.*s reached EOF and is now disabled", + (int)conf_port->name.slen, + conf_port->name.ptr) ); + conf_port->rx_setting = PJMEDIA_PORT_DISABLE; + } + + + /* 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; + */ + conf_port->rx_level = 0; + + TRACE_EX( (THIS_FILE, "%s: No frame from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); + + continue; + } + +#if 0 + /* Check that the port is not removed when we call get_frame() */ + if (conf->ports[i] == NULL) { + /* if port is removed old conf_port may point to not authorized memory */ + conf_port->rx_level = 0; + continue; + } +#endif + + /* Ignore if we didn't get any frame */ + if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) { + conf_port->rx_level = 0; + TRACE_EX( (THIS_FILE, "%s: frame_type %d != PJMEDIA_FRAME_TYPE_AUDIO from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + frame_type, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); + continue; + } + } + + pj_int32_t level = 0; + pj_int16_t *p_in; + unsigned j; + + p_in = get_read_buffer(conf_port, frame); + + /* 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; jsamples_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; jsamples_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; + + pj_int32_t cj, listener_cnt; //parallelization requires signed int + + /* Add the signal to all listeners. */ + for (cj = 0, listener_cnt = conf_port->listener_cnt; cj < listener_cnt; ++cj) + { + struct conf_port *listener; + pj_int16_t *p_in_conn_leveled; + + 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) + { + TRACE_EX( (THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) doesn't want to receive audio from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + (int)listener->name.slen, + listener->name.ptr, + conf_port->listener_slots[cj], + listener->transmitter_cnt, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); + continue; + } + + /* apply connection level, if not normal */ + if (conf_port->listener_adj_level[cj] != NORMAL_LEVEL) { + unsigned k = 0; + for (; k < conf->samples_per_frame; ++k) { + /* 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[k]; + /*itemp = itemp * adj / NORMAL_LEVEL;*/ + /* bad code (signed/unsigned badness): + * itemp = (itemp * conf_port->listsener_adj_level) >> 7; + */ + itemp *= conf_port->listener_adj_level[cj]; + 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; + + conf_port->adj_level_buf[k] = (pj_int16_t)itemp; + } + + /* take the leveled frame */ + p_in_conn_leveled = conf_port->adj_level_buf; + } else { + /* take the frame as-is */ + p_in_conn_leveled = p_in; + } + + pj_int32_t *mix_buf; + 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. + */ + unsigned k, samples_per_frame = conf->samples_per_frame; + pj_int32_t mix_buf_min = 0; + pj_int32_t mix_buf_max = 0; + + pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); + //protect listener->mix_buf, listener->mix_adj, listener->last_timestamp + if (IS_PARALLEL) + pj_lock_acquire(listener->tx_Lock); + + if (listener->last_timestamp.u64 == frame->timestamp.u64) { + //this frame is NOT from the first transmitter + for (k = 0; k < samples_per_frame; ++k) { + mix_buf[k] += p_in_conn_leveled[k]; // not the first - sum + if (mix_buf[k] < mix_buf_min) + mix_buf_min = mix_buf[k]; + if (mix_buf[k] > mix_buf_max) + mix_buf_max = mix_buf[k]; + } + TRACE_EX( (THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) get (sum) audio from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + (int)listener->name.slen, + listener->name.ptr, + conf_port->listener_slots[cj], + listener->transmitter_cnt, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); + + } else { + //this frame is from the first transmitter + listener->last_timestamp = frame->timestamp; + + /* We do not want to reset buffer, we just copy the first frame there. */ + for (k = 0; k < samples_per_frame; ++k) { + mix_buf[k] = p_in_conn_leveled[k]; // the first - copy + if (mix_buf[k] < mix_buf_min) + mix_buf_min = mix_buf[k]; + if (mix_buf[k] > mix_buf_max) + mix_buf_max = mix_buf[k]; + } + TRACE_EX( (THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d) get (copy) audio from the port %p (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name( pj_thread_this() ), + listener, + (int)listener->name.slen, + listener->name.ptr, + conf_port->listener_slots[cj], + listener->transmitter_cnt, + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt) ); + } + + /* Check if normalization adjustment needed. */ + if (mix_buf_min < MIN_LEVEL || mix_buf_max > MAX_LEVEL) { + int tmp_adj; + + if (-mix_buf_min > mix_buf_max) + mix_buf_max = -mix_buf_min; + + /* NORMAL_LEVEL * MAX_LEVEL / mix_buf_max; */ + tmp_adj = (MAX_LEVEL<<7) / mix_buf_max; + if (tmp_adj < listener->mix_adj) + listener->mix_adj = tmp_adj; + } + + pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); + if (IS_PARALLEL) + pj_lock_release(listener->tx_Lock); + + } else { + //this frame is from the only transmitter + pj_assert( listener->transmitter_cnt == 1 && listener->last_timestamp.u64 != frame->timestamp.u64 ); + listener->last_timestamp = frame->timestamp; + + /* Only 1 transmitter: + * just copy the samples to the mix buffer + * no mixing and level adjustment needed + */ + unsigned k, samples_per_frame = conf->samples_per_frame; + + for (k = 0; k < samples_per_frame; ++k) { + mix_buf[k] = p_in_conn_leveled[k]; // here copying 16 bit value to 32 bit dst + } + TRACE_EX( (THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d)" + " get audio from the (only) port %p (%.*s, %d, listener_cnt=%d) last_timestamp=%llu, timestamp=%llu", + pj_thread_get_name( pj_thread_this() ), + listener, + (int)listener->name.slen, + listener->name.ptr, + listener->slot, + listener->transmitter_cnt, + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + conf_port->slot, conf_port->listener_cnt, + listener->last_timestamp.u64, frame->timestamp.u64) ); + + } + + } /* loop the listeners of conf port */ + } /* loop of all conf ports */ + + /* all ports have data in their buffers + * and may do all work independently. + * Here we use ports from conf->active_listener[] + * whose lifetime is garanteed at the moment of adding + * port to active_listener[] by incrementing + * grp_lock->ref_counter. + */ + + listener_counter = /*conf->*/listener_counter + 1; + + + PRAGMA_OMP(parallel) + { + /* Perform any queued operations that need to be synchronized with + * the clock such as connect, disonnect, remove. + * All those operations performed on the single thread + * but perhaps not on the master thread + */ + PRAGMA_OMP(single nowait) + { + if (!pj_list_empty(conf->op_queue)) { + + /* register omp thread with pjsip */ + register_omp_thread(conf); +#ifdef CONF_DEBUG + inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); +#endif //CONF_DEBUG + + pj_log_push_indent(); + /* Calling any callback while a mutex is locked can result in a deadlock, + * since operations can lock other mutexes in an arbitrary order. + * At least OP_REMOVE_PORT invokes grp_lock handlers callbacks. + * We should move lock inside each operations + */ + //pj_mutex_lock(conf->mutex); + handle_op_queue(conf); + //pj_mutex_unlock(conf->mutex); + pj_log_pop_indent(); + } + } //pragma omp single + + /* Step 3 + * Time for all ports to transmit whatever they have in their + * buffer. + */ + PRAGMA_OMP(for nowait) + for (i = 0; i < listener_counter; ++i) { + + pjmedia_frame_type frm_type; + pj_status_t status; + + //sound device (from port[0]) has 0 idx here too + struct conf_port* conf_port = conf->active_listener[i]; + conf->active_listener[i] = NULL; + if (!conf_port) + continue; + + /* register omp thread with pjsip */ + register_omp_thread(conf); +#ifdef CONF_DEBUG + inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); +#endif //CONF_DEBUG + + + status = write_port(conf, conf_port, &frame->timestamp, + &frm_type); + +#if 0 + 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; + } +#endif + /* Set the type of frame to be returned to sound playback + * device. + */ + if (status == PJ_SUCCESS && i == 0) { + speaker_frame_type = frm_type; + sound_port = conf_port; + } + + //At least "Master/sound" may have no media_port + if (conf_port->port && conf_port->port->grp_lock) + pj_grp_lock_dec_ref(conf_port->port->grp_lock);// conf_port may be destroyed here + + } + + } //pragma omp parallel + + /* Return sound playback frame. */ + if (sound_port != NULL) + { + if (sound_port->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 *)sound_port->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; + +#else + + if (upper_bound) + { + pj_atomic_set(conf->active_ports_idx, upper_bound); + + /* Force frame type NONE */ + frame->type = PJMEDIA_FRAME_TYPE_NONE; + conf->frame = frame; + conf->sound_port = NULL; + + /* Step 2-3 + * Get frames from all ports, and "mix" the signal + * to mix_buf of all listeners of the port and + * transmit whatever listeners have in their buffer + */ + if (conf->is_parallel) + { + /* Start the parallel team + * all threads have reached the barrier already. + * Should be a very short waiting. + */ + pj_status_t status; + status = pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_SPIN_ONLY); + pj_assert(status == PJ_TRUE || status == PJ_FALSE); + } + + perform_get_frame(conf); + pj_assert(pj_atomic_get(conf->active_ports_idx) == -PJ_CONF_BRIDGE_MAX_THREADS); + + /* Return sound playback frame. */ + if (conf->sound_port != NULL) + { + 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->sound_port->mix_buf, + conf->samples_per_frame); + /* MUST set frame type */ + pj_assert(frame->type != PJMEDIA_FRAME_TYPE_NONE); + } + + } + + /* Perform any queued operations that need to be synchronized with + * the clock such as connect, disonnect, remove. + */ + if (!pj_list_empty(conf->op_queue)) + { + pj_log_push_indent(); + handle_op_queue(conf); + pj_log_pop_indent(); + } + +#endif + + TRACE_( (THIS_FILE, "Ports processed by omp team's threads 0:%d, 1:%d, 2:%d, 3:%d, 4:%d, 5:%d, 6:%d, 7:%d, 8:%d, 9:%d, 10:%d, 11:%d, 12:%d, 13:%d, 14:%d, 15:%d.", + threads[0], threads[1], threads[2], threads[3], threads[4], threads[5], threads[6], threads[7], + threads[8], threads[9], threads[10], threads[11], threads[12], threads[13], threads[14], threads[15]) ); + +#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; +} + + +/* register omp thread with pjsip */ +static inline void register_omp_thread(pjmedia_conf *conf) +{ + /* the compiler should potentially optimize this away + * for the serial conference bridge + */ + if (IS_PARALLEL && !pj_thread_is_registered()) + { +#ifdef _OPENMP + /* Open MP support */ + pj_assert(conf->omp_threads_idx && conf->omp_threads); + pj_thread_t *p; + int num = pj_atomic_inc_and_get(conf->omp_threads_idx); + pj_assert(num < 2 * conf->omp_max_threads); + char obj_name[PJ_MAX_OBJ_NAME]; + pj_ansi_snprintf(obj_name, sizeof(obj_name), "omp_conf_%d", num); + pj_thread_register(obj_name, conf->omp_threads[num], &p); +#else + PJ_UNUSED_ARG(conf); +#endif + } + pj_assert(pj_thread_is_registered()); +} + +#ifdef CONF_DEBUG +static inline void inc_omp_thread_usage(int *threads, pj_ssize_t threads_sz) { +# ifdef _OPENMP + int num = omp_get_thread_num(); + if (num < threads_sz) + threads[num]++; +# else + PJ_UNUSED_ARG(threads); + PJ_UNUSED_ARG(threads_sz); +# endif //_OPENMP +} +#endif //CONF_DEBUG + +static pj_status_t thread_pool_start(pjmedia_conf *conf) { + pj_status_t status; + int i; + pj_assert(conf->is_parallel); + + status = pj_barrier_create(conf->pool, + PJ_CONF_BRIDGE_MAX_THREADS, + &conf->active_thread); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pj_barrier_create(conf->pool, + PJ_CONF_BRIDGE_MAX_THREADS, + &conf->barrier); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Thread description's to register threads with pjsip */ + conf->pool_threads = (pj_thread_t **)pj_pool_calloc(conf->pool, PJ_CONF_BRIDGE_MAX_THREADS - 1, sizeof(pj_thread_t *)); + PJ_ASSERT_RETURN(conf->pool_threads, PJ_ENOMEM); + + for (i = 0; i < PJ_CONF_BRIDGE_MAX_THREADS - 1; i++) + { + char obj_name[PJ_MAX_OBJ_NAME]; + pj_ansi_snprintf(obj_name, sizeof(obj_name), "conf_pool_%d", i); + + status = pj_thread_create(conf->pool, obj_name, &conf_thread, conf, 0, 0, &conf->pool_threads[i]); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + conf->running = PJ_TRUE; + + return PJ_SUCCESS; +} + +/* + * Conf thread pool's thread function. + */ +static int conf_thread(void *arg) { + pjmedia_conf *conf = (pjmedia_conf *)arg; + pj_status_t status; + pj_assert(conf->is_parallel); + + /* don't go to the barrier while thread pool is creating + * if we can not create all threads, + * we should not go to the barrier because we can not leave it + */ + while (!conf->quit_flag && !conf->running) + pj_thread_sleep(0); + + while (!conf->quit_flag) + { + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread at barrier", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1)); + + /* long waiting for next timer tick. if supported, blocks immediately */ + status = pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_BLOCK_ONLY); + pj_assert(status == PJ_TRUE || status == PJ_FALSE); + + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread activated, return = %d", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1, + status)); + + if (!conf->quit_flag) + perform_get_frame(conf); + } + + return 0; +} + +static void perform_get_frame(pjmedia_conf *conf) { + + pj_status_t status; + pj_atomic_value_t i; + pjmedia_frame *frame = conf->frame; + + while ((i = pj_atomic_dec_and_get(conf->active_ports_idx)) >= 0) + { + pj_int32_t port_idx = conf->active_ports[i]; + pj_assert((unsigned)port_idx < conf->max_ports); + struct conf_port *conf_port = conf->ports[port_idx]; + PJ_ASSERT_ON_FAIL(conf_port, continue); + + pj_int16_t *p_in; + p_in = get_read_buffer(conf_port, frame); + + /* 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) + { + + /* Check that correct size is specified. */ + pj_assert(frame->size == conf/*_port*/->rx_frame_buf_cap); + /* read data to different buffers to different conf_port's parallel processing */ + status = pjmedia_delay_buf_get(conf_port->delay_buf, p_in); + if (status != PJ_SUCCESS) + { + conf_port->rx_level = 0; + TRACE_EX((THIS_FILE, "%s: No frame from the passive port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); + continue; + } + + } + else + { + + pjmedia_frame_type frame_type; + + /* Check that correct size is specified. */ + pj_assert(frame->size == conf/*_port */->rx_frame_buf_cap); + /* read data to different buffers to different conf_port's parallel processing */ + status = read_port(conf, conf_port, p_in, + conf->samples_per_frame, &frame_type); + + /* Check that the port is not removed when we call get_frame() */ + /* + * if port is removed old conf_port may point to not authorized memory + * We can not call conf_port->rx_level = 0; here! + * "Port is not removed" check should take priority over the return code check + * + * However this check is not necessary for async conference bridge, + * because application can not remove port while we are in get_frame() callback. + * The only thing that can happen is that port removing will be sheduled + * there but still will processed later (see Step 3). + */ + if (conf->ports[port_idx] != conf_port) + { + //conf_port->rx_level = 0; + PJ_LOG(4, (THIS_FILE, "Port %d is removed when we call get_frame()", port_idx)); + continue; + } + + if (status != PJ_SUCCESS) + { + + /* check status and disable port here. + * Prevent multiply eof callback invoke, + * if fileplayer has reached EOF (i.e. status == PJ_EEOF) + */ + if (status == PJ_EEOF) + { + TRACE_((THIS_FILE, "Port %.*s reached EOF and is now disabled", + (int)conf_port->name.slen, + conf_port->name.ptr)); + conf_port->rx_setting = PJMEDIA_PORT_DISABLE; + } + + + /* 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; + */ + conf_port->rx_level = 0; + + TRACE_EX((THIS_FILE, "%s: No frame from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); + + continue; + } + +#if 0 + /* Check that the port is not removed when we call get_frame() */ + if (conf->ports[i] == NULL) + { + /* if port is removed old conf_port may point to not authorized memory */ + conf_port->rx_level = 0; + continue; + } +#endif + + /* Ignore if we didn't get any frame */ + if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) + { + conf_port->rx_level = 0; + TRACE_EX((THIS_FILE, "%s: frame_type %d != PJMEDIA_FRAME_TYPE_AUDIO from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + frame_type, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); + continue; + } + } + + pj_int32_t level = 0; + unsigned j; + + /* 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; + + pj_int32_t cj, listener_cnt; //parallelization requires signed int + + /* Add the signal to all listeners. */ + for (cj = 0, listener_cnt = conf_port->listener_cnt; cj < listener_cnt; ++cj) + { + struct conf_port *listener; + pj_int16_t *p_in_conn_leveled; + SLOT_TYPE listener_slot = conf_port->listener_slots[cj]; + + listener = conf->ports[listener_slot]; + + /* Skip if this listener doesn't want to receive audio */ + if (listener->tx_setting != PJMEDIA_PORT_ENABLE) + { + TRACE_EX((THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) doesn't want to receive audio from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + (int)listener->name.slen, + listener->name.ptr, + listener_slot, + listener->transmitter_cnt, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); + continue; + } + + /* apply connection level, if not normal */ + if (conf_port->listener_adj_level[cj] != NORMAL_LEVEL) + { + unsigned k = 0; + for (; k < conf->samples_per_frame; ++k) + { + /* 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[k]; + /*itemp = itemp * adj / NORMAL_LEVEL;*/ + /* bad code (signed/unsigned badness): + * itemp = (itemp * conf_port->listsener_adj_level) >> 7; + */ + itemp *= conf_port->listener_adj_level[cj]; + 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; + + conf_port->adj_level_buf[k] = (pj_int16_t)itemp; + } + + /* take the leveled frame */ + p_in_conn_leveled = conf_port->adj_level_buf; + } + else + { + /* take the frame as-is */ + p_in_conn_leveled = p_in; + } + + pj_int32_t *mix_buf; + mix_buf = listener->mix_buf; + pj_bool_t ready_to_transmit = PJ_FALSE; + + if (listener->transmitter_cnt > 1) + { + /* Mixing signals, + * and calculate appropriate level adjustment if there is + * any overflowed level in the mixed signal. + */ + unsigned k, samples_per_frame = conf->samples_per_frame; + pj_int32_t mix_buf_min = 0; + pj_int32_t mix_buf_max = 0; + + pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); + //protect listener->mix_buf, listener->mix_adj, listener->last_timestamp + if (IS_PARALLEL) + pj_lock_acquire(listener->tx_Lock); + + if (listener->last_timestamp.u64 == frame->timestamp.u64) + { + //this frame is NOT from the first transmitter + for (k = 0; k < samples_per_frame; ++k) + { + mix_buf[k] += p_in_conn_leveled[k]; // not the first - sum + if (mix_buf[k] < mix_buf_min) + mix_buf_min = mix_buf[k]; + if (mix_buf[k] > mix_buf_max) + mix_buf_max = mix_buf[k]; + } + TRACE_EX((THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) get (sum) audio from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + (int)listener->name.slen, + listener->name.ptr, + listener_slot, + listener->transmitter_cnt, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); + + } + else + { + //this frame is from the first transmitter + listener->last_timestamp = frame->timestamp; + + /* We do not want to reset buffer, we just copy the first frame there. */ + for (k = 0; k < samples_per_frame; ++k) + { + mix_buf[k] = p_in_conn_leveled[k]; // the first - copy + if (mix_buf[k] < mix_buf_min) + mix_buf_min = mix_buf[k]; + if (mix_buf[k] > mix_buf_max) + mix_buf_max = mix_buf[k]; + } + TRACE_EX((THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d) get (copy) audio from the port %p (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + listener, + (int)listener->name.slen, + listener->name.ptr, + listener_slot, + listener->transmitter_cnt, + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); + } + + /* Check if normalization adjustment needed. */ + if (mix_buf_min < MIN_LEVEL || mix_buf_max > MAX_LEVEL) + { + int tmp_adj; + + if (-mix_buf_min > mix_buf_max) + mix_buf_max = -mix_buf_min; + + /* NORMAL_LEVEL * MAX_LEVEL / mix_buf_max; */ + tmp_adj = (MAX_LEVEL << 7) / mix_buf_max; + if (tmp_adj < listener->mix_adj) + listener->mix_adj = tmp_adj; + } + + if (listener->transmitter_cnt == listener->mixed_cnt + 1) + { + ready_to_transmit = PJ_TRUE; + listener->mixed_cnt = 0; + } + else + ++listener->mixed_cnt; + + pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); + if (IS_PARALLEL) + pj_lock_release(listener->tx_Lock); + + } + else + { + //this frame is from the only transmitter + pj_assert(listener->transmitter_cnt == 1 && listener->last_timestamp.u64 != frame->timestamp.u64); + listener->last_timestamp = frame->timestamp; + + /* Only 1 transmitter: + * just copy the samples to the mix buffer + * no mixing and level adjustment needed + */ + unsigned k, samples_per_frame = conf->samples_per_frame; + + for (k = 0; k < samples_per_frame; ++k) + { + mix_buf[k] = p_in_conn_leveled[k]; // here copying 16 bit value to 32 bit dst + } + TRACE_EX((THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d)" + " get audio from the (only) port %p (%.*s, %d, listener_cnt=%d) last_timestamp=%llu, timestamp=%llu", + pj_thread_get_name(pj_thread_this()), + listener, + (int)listener->name.slen, + listener->name.ptr, + listener->slot, + listener->transmitter_cnt, + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + conf_port->slot, conf_port->listener_cnt, + listener->last_timestamp.u64, frame->timestamp.u64)); + + ready_to_transmit = PJ_TRUE; + + } + + if (ready_to_transmit) { + pjmedia_frame_type frm_type; + status = write_port(conf, listener, &frame->timestamp, &frm_type); +#if 0 + 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; + } +#endif + /* Set the type of frame to be returned to sound playback + * device. + */ + if (status == PJ_SUCCESS && listener_slot == 0 && listener->tx_level) + { + /* MUST set frame type */ + conf->frame->type = frm_type; + conf->sound_port = listener; + } + } + + } /* loop the listeners of conf port */ + + } /* loop of all conf ports */ + + if (conf->is_parallel) + { + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, ARRIVE AT BARRIER", + pj_thread_get_name(pj_thread_this()), + frame->timestamp.u64)); + + /* If we carefully balance the work, we won't have to wait long here. + * let it be the default waiting (spin then block) + */ + status = pj_barrier_wait(conf->barrier, PJ_BARRIER_FLAGS_NO_DELETE); + pj_assert(status == PJ_TRUE || status == PJ_FALSE); + + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, BARRIER OVERCOME, return = %d", + pj_thread_get_name(pj_thread_this()), + frame->timestamp.u64, + status)); + } + +} + + +#if !DEPRECATED_FOR_TICKET_2234 +/* + * 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; +} +#endif + + +/* + * 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; +} + + +/* + * Add destructor handler. + */ +PJ_DEF(pj_status_t) pjmedia_conf_add_destroy_handler( + pjmedia_conf* conf, + unsigned slot, + void* member, + pj_grp_lock_handler handler) +{ + struct conf_port *cport; + pj_grp_lock_t *grp_lock; + + PJ_ASSERT_RETURN(conf && handler && slot < conf->max_ports, PJ_EINVAL); + + pj_mutex_lock(conf->mutex); + + /* Port must be valid and has group lock. */ + cport = conf->ports[slot]; + if (!cport || !cport->port || !cport->port->grp_lock) { + pj_mutex_unlock(conf->mutex); + return cport? PJ_EINVALIDOP : PJ_EINVAL; + } + grp_lock = cport->port->grp_lock; + + pj_mutex_unlock(conf->mutex); + + return pj_grp_lock_add_handler(grp_lock, NULL, member, handler); +} + + +/* + * Remove previously registered destructor handler. + */ +PJ_DEF(pj_status_t) pjmedia_conf_del_destroy_handler( + pjmedia_conf* conf, + unsigned slot, + void* member, + pj_grp_lock_handler handler) +{ + struct conf_port* cport; + pj_grp_lock_t* grp_lock; + + PJ_ASSERT_RETURN(conf && handler && slot < conf->max_ports, PJ_EINVAL); + + pj_mutex_lock(conf->mutex); + + /* Port must be valid and has group lock. */ + cport = conf->ports[slot]; + if (!cport || !cport->port || !cport->port->grp_lock) { + pj_mutex_unlock(conf->mutex); + return cport ? PJ_EINVALIDOP : PJ_EINVAL; + } + grp_lock = cport->port->grp_lock; + + pj_mutex_unlock(conf->mutex); + + return pj_grp_lock_del_handler(grp_lock, member, handler); +} + + +#endif //PJMEDIA_CONF_USE_OPENMP diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index ed740faa9b..923b47065d 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -16,6 +16,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* MP related modification by Leonid Goltsblat 2021-2025 */ + #include #include #include @@ -35,44 +38,34 @@ #include #endif -#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0 +#if (!defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0) && \ + (!defined(PJMEDIA_CONF_USE_OPENMP) || PJMEDIA_CONF_USE_OPENMP == 0) - -/* Open MP related modification by Leonid Goltsblat 2021-2025 - * - * Enabling OpenMP support makes it possible to parallelize the processing of audio frames. - * To activate OpenMP support you must perform the appropriate development environment setup. - * For example, in Visual Studio, you must enable OpenMP support in the project settings. - * see: https://learn.microsoft.com/en-us/cpp/build/reference/openmp-enable-openmp-2-0-support?view=msvc-170 - * In GCC, you must use the appropriate compiler options. +#ifndef PJ_CONF_BRIDGE_MAX_THREADS +/** + * The maximum number of threads that can be used by the conference bridge. + * This value is used to determine if the conference bridge should be + * implemented as a parallel bridge or not. + * If this value is set to 1, the conference bridge will be implemented as a + * serial bridge, otherwise it will be implemented as a parallel bridge. * - * Current implementation uses only basic subset of OpenMP features corresponding to OpenMP 2.0 - * and should be compatible with any OpenMP 2.0 compliant compiler. + * DEFAULT: 1 - serial bridge + * Please set this macro in the config_site.h to a value greater than 1 + * to enable parallel bridge. */ +# define PJ_CONF_BRIDGE_MAX_THREADS 1 +#endif -#ifdef _OPENMP -# include -# define PRAGMA(clause) _Pragma(#clause) -# define PRAGMA_OMP(clause) PRAGMA(omp clause) - -# ifndef PJ_OPENMP_FOR_CLAUSES -# define PJ_OPENMP_FOR_CLAUSES -/* for example - * #define PJSIP_OMP_CHUNK_SIZE 16 - * #define PJ_OPENMP_FOR_CLAUSES schedule(dynamic, PJSIP_OMP_CHUNK_SIZE) if (max_thread > 1) num_threads(max_thread) - * will produce - * #pragma omp parallel for schedule(dynamic, PJSIP_OMP_CHUNK_SIZE) if (max_thread > 1) num_threads(max_thread) - */ -# endif -# define IS_PARALLEL PJ_TRUE // OpenMP is enabled +#if PJ_CONF_BRIDGE_MAX_THREADS > 1 + +# define IS_PARALLEL PJ_TRUE // pj_thread MP is enabled -#else //_OPENMP +#else // PJ_CONF_BRIDGE_MAX_THREADS <= 1 -# define PRAGMA_OMP(clause) -# define IS_PARALLEL PJ_FALSE // OpenMP is disabled +# define IS_PARALLEL PJ_FALSE // MP is disabled -#endif //_OPENMP +#endif // PJ_CONF_BRIDGE_MAX_THREADS > 1 /* CONF_DEBUG enables detailed operation of the conference bridge. * Beware that it prints large amounts of logs (several lines per frame). @@ -290,7 +283,7 @@ struct conf_port * parallel bridge implementation. */ pj_lock_t *tx_Lock; /**< Lock to protect mix_buf,mix_adj, - * last_timestamp */ + * last_timestamp, mixed_cnt */ pj_timestamp last_timestamp;/**< last transmited packet * timestamp. We set this when @@ -299,6 +292,10 @@ struct conf_port * If this time stamp is equals to * current frame timestamp, * we have data to transmite */ + unsigned mixed_cnt; /**rx_frame_buf */ - pj_thread_desc *omp_threads; /**< Thread description's - * to register omp threads - * with pjsip */ - int omp_max_threads; /**< omp_threads[] dimension */ - pj_atomic_t *omp_threads_idx; /**< omp_threads[] current idx */ pj_bool_t is_parallel; /**< parallel bridge flag */ + unsigned rx_frame_buf_cap;/**< size in bytes to allocate + * conf_port->rx_frame_buf */ + + /* native pjsip multithreading */ + pj_thread_t **pool_threads; /**< Thread pool's threads */ + pj_barrier_t *active_thread; /**< entry barrier */ + pj_barrier_t *barrier; /**< exit barrier */ + pj_bool_t quit_flag; /**< quit flag for threads */ + pj_bool_t running; /**< thread pool is running */ + pj_atomic_t *active_ports_idx;/**< index of the element of the + * active_ports[] array processed + * by the current thread */ + pjmedia_frame *frame; /**< Frame buffer for conference + * bridge at the current tick. */ + struct conf_port *sound_port; + }; @@ -407,12 +410,11 @@ static pj_status_t destroy_port_pasv(pjmedia_port *this_port); #endif -/* register omp thread with pjsip */ -static inline void register_omp_thread(pjmedia_conf *conf); -#ifdef CONF_DEBUG -static inline void inc_omp_thread_usage(int *threads, pj_ssize_t threads_sz); -#endif //CONF_DEBUG static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_frame *frame); +static pj_status_t thread_pool_start(pjmedia_conf *conf); +static void perform_get_frame(pjmedia_conf *conf); +/* Conf thread pool's thread function.*/ +static int conf_thread(void *arg); static void destroy_conf_port(struct conf_port *conf_port); @@ -962,6 +964,10 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, /* Can only accept 16bits per sample, for now.. */ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); +#if defined(CONF_DEBUG_EX) || defined(CONF_DEBUG) + pj_log_set_level(5); +#endif + PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports", max_ports)); @@ -983,9 +989,6 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, conf->active_ports = pj_pool_calloc(pool, max_ports, sizeof(pj_int32_t) ); PJ_ASSERT_ON_FAIL( conf->active_ports, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); - conf->active_listener = pj_pool_calloc(pool, max_ports, sizeof(struct conf_port*)); - PJ_ASSERT_ON_FAIL( conf->active_listener, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); - conf->options = options; conf->max_ports = max_ports; conf->clock_rate = clock_rate; @@ -1079,25 +1082,27 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, } } -#ifdef _OPENMP - conf->omp_max_threads = omp_get_max_threads(); - /* Thread description's to register omp threads with pjsip */ - conf->omp_threads = (pj_thread_desc *)pj_pool_calloc( pool, 2 * conf->omp_max_threads, sizeof(pj_thread_desc) ); - status = pj_atomic_create(pool, 0, &conf->omp_threads_idx); - if (status != PJ_SUCCESS) + conf->is_parallel = IS_PARALLEL; + + status = pj_atomic_create(conf->pool, 0, &conf->active_ports_idx); + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, goto on_return); + + if (conf->is_parallel) { - pjmedia_conf_destroy(conf); - return status; + status = thread_pool_start(conf); + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, goto on_return); } -#endif //_OPENMP - - conf->is_parallel = IS_PARALLEL; /* Done */ - *p_conf = conf; return PJ_SUCCESS; + +on_return: + pjmedia_conf_destroy(conf); + + return status; + } @@ -1133,6 +1138,39 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) pj_log_push_indent(); + /* Signal threads to quit */ + conf->quit_flag = PJ_TRUE; + + /* all threads have reached the barrier and the conference bridge thread no longer exists. + * Should be a very short waiting. + * + * If we couldn't create all the threads from the pool, we shouldn't get close to the barrier. + */ + if (conf->running && conf->active_thread) + pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_SPIN_ONLY); + + /* Destroy thread pool */ + if (conf->pool_threads) + { + pj_thread_t **threads = conf->pool_threads; + pj_thread_t **end = threads + (PJ_CONF_BRIDGE_MAX_THREADS - 1); + while (threads < end) + { + if (*threads) + { + pj_thread_join(*threads); + pj_thread_destroy(*threads); + *threads = NULL; + } + ++threads; + } + } + if (conf->active_thread) + pj_barrier_destroy(conf->active_thread); + if (conf->barrier) + pj_barrier_destroy(conf->barrier); + + /* Destroy sound device port. */ if (conf->snd_dev_port) { pjmedia_snd_port_destroy(conf->snd_dev_port); @@ -1163,8 +1201,8 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) pj_mutex_destroy(conf->mutex); /* Destroy atomic */ - if (conf->omp_threads_idx) - pj_atomic_destroy(conf->omp_threads_idx); + if (conf->active_ports_idx) + pj_atomic_destroy(conf->active_ports_idx); /* Destroy pool */ if (conf->pool) @@ -2894,7 +2932,6 @@ 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; //parallelization requires signed int pj_int32_t i, @@ -2928,9 +2965,6 @@ static pj_status_t get_frame(pjmedia_port *this_port, begin = conf->lower_bound; end = conf->upper_bound; - conf->listener_counter = 0; - pj_int32_t listener_counter = 0; - #ifdef CONF_DEBUG int threads[IS_PARALLEL ? 64 : 1] = {0}; #endif //CONF_DEBUG @@ -2960,26 +2994,6 @@ static pj_status_t get_frame(pjmedia_port *this_port, #endif if (conf_port->transmitter_cnt && conf_port->tx_setting != PJMEDIA_PORT_DISABLE) { - /* In the final loop of the get_frame() function, - * we might call write_port() either concurrently with - * or even after the OP_PORT_REMOVE operation. - * Therefore, ports that are being written to - * (i.e., those with conf_port->transmitter_cnt != 0) - * require protection from deletion. - * For such ports, we must increment the ref_count - * (and decrement it after transmission, - * which may result in deferred deletion of the port). - * - * At least "Master/sound" has no media_port. - * We don't manage the lifetime of such ports, - * their lifetime is the conference bridge lifetime. - */ - if (conf_port->port && conf_port->port->grp_lock) - pj_grp_lock_add_ref(conf_port->port->grp_lock); - - //sound device (from 0 SLOT) to 0 idx, other to next idx - conf->active_listener[i == 0 ? 0 : ++conf->listener_counter] = conf_port; - /* We need not reset mix_buf, we just want to copy the first * (and probably only) frame there. * The criteria for "this frame is from the first transmitter" @@ -3014,64 +3028,193 @@ static pj_status_t get_frame(pjmedia_port *this_port, } } - struct conf_port *sound_port = NULL; - PRAGMA_OMP(parallel for PJ_OPENMP_FOR_CLAUSES) - /* Step 2 - * Get frames from all ports, and "mix" the signal - * to mix_buf of all listeners of the port. - * - * Here we use the current switching states, - * which must be stable during this cycle, - * i.e. must not change in parallel. - * - * To receive frames from all ports in parallel, - * we receive data from each port into separate - * buffers conf_port->rx_frame_buf + if (upper_bound) + { + pj_atomic_set(conf->active_ports_idx, upper_bound); + + /* Force frame type NONE */ + frame->type = PJMEDIA_FRAME_TYPE_NONE; + conf->frame = frame; + conf->sound_port = NULL; + + /* Step 2-3 + * Get frames from all ports, and "mix" the signal + * to mix_buf of all listeners of the port and + * transmit whatever listeners have in their buffer + */ + if (conf->is_parallel) + { + /* Start the parallel team + * all threads have reached the barrier already. + * Should be a very short waiting. + */ + pj_status_t status; + status = pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_SPIN_ONLY); + pj_assert(status == PJ_TRUE || status == PJ_FALSE); + } + + perform_get_frame(conf); + pj_assert(pj_atomic_get(conf->active_ports_idx) == -PJ_CONF_BRIDGE_MAX_THREADS); + + /* Return sound playback frame. */ + if (conf->sound_port != NULL) + { + 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->sound_port->mix_buf, + conf->samples_per_frame); + /* MUST set frame type */ + pj_assert(frame->type != PJMEDIA_FRAME_TYPE_NONE); + } + + } + + /* Perform any queued operations that need to be synchronized with + * the clock such as connect, disonnect, remove. */ - for (i = 0; i < upper_bound; ++i) { + if (!pj_list_empty(conf->op_queue)) + { + pj_log_push_indent(); + handle_op_queue(conf); + pj_log_pop_indent(); + } + + TRACE_( (THIS_FILE, "Ports processed by omp team's threads 0:%d, 1:%d, 2:%d, 3:%d, 4:%d, 5:%d, 6:%d, 7:%d, 8:%d, 9:%d, 10:%d, 11:%d, 12:%d, 13:%d, 14:%d, 15:%d.", + threads[0], threads[1], threads[2], threads[3], threads[4], threads[5], threads[6], threads[7], + threads[8], threads[9], threads[10], threads[11], threads[12], threads[13], threads[14], threads[15]) ); + +#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; +} + + +static pj_status_t thread_pool_start(pjmedia_conf *conf) { + pj_status_t status; + int i; + pj_assert(conf->is_parallel); + + status = pj_barrier_create(conf->pool, + PJ_CONF_BRIDGE_MAX_THREADS, + &conf->active_thread); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pj_barrier_create(conf->pool, + PJ_CONF_BRIDGE_MAX_THREADS, + &conf->barrier); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Thread description's to register threads with pjsip */ + conf->pool_threads = (pj_thread_t **)pj_pool_calloc(conf->pool, PJ_CONF_BRIDGE_MAX_THREADS - 1, sizeof(pj_thread_t *)); + PJ_ASSERT_RETURN(conf->pool_threads, PJ_ENOMEM); + + for (i = 0; i < PJ_CONF_BRIDGE_MAX_THREADS - 1; i++) + { + char obj_name[PJ_MAX_OBJ_NAME]; + pj_ansi_snprintf(obj_name, sizeof(obj_name), "conf_pool_%d", i); + + status = pj_thread_create(conf->pool, obj_name, &conf_thread, conf, 0, 0, &conf->pool_threads[i]); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + conf->running = PJ_TRUE; + + return PJ_SUCCESS; +} + +/* + * Conf thread pool's thread function. + */ +static int conf_thread(void *arg) { + pjmedia_conf *conf = (pjmedia_conf *)arg; + pj_status_t status; + pj_assert(conf->is_parallel); + + /* don't go to the barrier while thread pool is creating + * if we can not create all threads, + * we should not go to the barrier because we can not leave it + */ + while (!conf->quit_flag && !conf->running) + pj_thread_sleep(0); + + while (!conf->quit_flag) + { + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread at barrier", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1)); + + /* long waiting for next timer tick. if supported, blocks immediately */ + status = pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_BLOCK_ONLY); + pj_assert(status == PJ_TRUE || status == PJ_FALSE); + + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread activated, return = %d", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1, + status)); + + if (!conf->quit_flag) + perform_get_frame(conf); + } + + return 0; +} + +static void perform_get_frame(pjmedia_conf *conf) { + + pj_status_t status; + pj_atomic_value_t i; + pjmedia_frame *frame = conf->frame; + + while ((i = pj_atomic_dec_and_get(conf->active_ports_idx)) >= 0) + { pj_int32_t port_idx = conf->active_ports[i]; - pj_assert( (unsigned)port_idx < conf->max_ports ); + pj_assert((unsigned)port_idx < conf->max_ports); struct conf_port *conf_port = conf->ports[port_idx]; + PJ_ASSERT_ON_FAIL(conf_port, continue); - /* register omp thread with pjsip */ - register_omp_thread( conf ); - -#ifdef CONF_DEBUG - inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); -#endif //CONF_DEBUG + pj_int16_t *p_in; + p_in = get_read_buffer(conf_port, frame); /* Get frame from this port. * For passive ports, get the frame from the delay_buf. - * For other ports, get the frame from the port. + * For other ports, get the frame from the port. */ - if (conf_port->delay_buf != NULL) { - pj_status_t status; + if (conf_port->delay_buf != NULL) + { /* Check that correct size is specified. */ - pj_assert( frame->size == conf/*_port*/->rx_frame_buf_cap); + pj_assert(frame->size == conf/*_port*/->rx_frame_buf_cap); /* read data to different buffers to different conf_port's parallel processing */ - status = pjmedia_delay_buf_get( conf_port->delay_buf, get_read_buffer(conf_port, frame) ); - if (status != PJ_SUCCESS) { + status = pjmedia_delay_buf_get(conf_port->delay_buf, p_in); + if (status != PJ_SUCCESS) + { conf_port->rx_level = 0; - TRACE_EX( (THIS_FILE, "%s: No frame from the passive port (%.*s, %d, listener_cnt=%d)", - pj_thread_get_name( pj_thread_this() ), - (int)conf_port->name.slen, - conf_port->name.ptr, - port_idx, conf_port->listener_cnt) ); + TRACE_EX((THIS_FILE, "%s: No frame from the passive port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); continue; - } + } - } else { + } + else + { - pj_status_t status; pjmedia_frame_type frame_type; /* Check that correct size is specified. */ - pj_assert( frame->size == conf/*_port */->rx_frame_buf_cap); + pj_assert(frame->size == conf/*_port */->rx_frame_buf_cap); /* read data to different buffers to different conf_port's parallel processing */ - status = read_port(conf, conf_port, get_read_buffer(conf_port, frame), - conf->samples_per_frame, &frame_type ); + status = read_port(conf, conf_port, p_in, + conf->samples_per_frame, &frame_type); /* Check that the port is not removed when we call get_frame() */ /* @@ -3081,25 +3224,28 @@ static pj_status_t get_frame(pjmedia_port *this_port, * * However this check is not necessary for async conference bridge, * because application can not remove port while we are in get_frame() callback. - * The only thing that can happen is that port removing will be sheduled + * The only thing that can happen is that port removing will be sheduled * there but still will processed later (see Step 3). */ - if (conf->ports[port_idx] != conf_port) { + if (conf->ports[port_idx] != conf_port) + { //conf_port->rx_level = 0; - PJ_LOG( 4, (THIS_FILE, "Port %d is removed when we call get_frame()", port_idx) ); + PJ_LOG(4, (THIS_FILE, "Port %d is removed when we call get_frame()", port_idx)); continue; } - if (status != PJ_SUCCESS) { + if (status != PJ_SUCCESS) + { /* check status and disable port here. - * Prevent multiply eof callback invoke, + * Prevent multiply eof callback invoke, * if fileplayer has reached EOF (i.e. status == PJ_EEOF) */ - if (status == PJ_EEOF) { - TRACE_( (THIS_FILE, "Port %.*s reached EOF and is now disabled", - (int)conf_port->name.slen, - conf_port->name.ptr) ); + if (status == PJ_EEOF) + { + TRACE_((THIS_FILE, "Port %.*s reached EOF and is now disabled", + (int)conf_port->name.slen, + conf_port->name.ptr)); conf_port->rx_setting = PJMEDIA_PORT_DISABLE; } @@ -3115,18 +3261,19 @@ static pj_status_t get_frame(pjmedia_port *this_port, */ conf_port->rx_level = 0; - TRACE_EX( (THIS_FILE, "%s: No frame from the port (%.*s, %d, listener_cnt=%d)", - pj_thread_get_name( pj_thread_this() ), - (int)conf_port->name.slen, - conf_port->name.ptr, - port_idx, conf_port->listener_cnt) ); + TRACE_EX((THIS_FILE, "%s: No frame from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); continue; } #if 0 /* Check that the port is not removed when we call get_frame() */ - if (conf->ports[i] == NULL) { + if (conf->ports[i] == NULL) + { /* if port is removed old conf_port may point to not authorized memory */ conf_port->rx_level = 0; continue; @@ -3134,29 +3281,29 @@ static pj_status_t get_frame(pjmedia_port *this_port, #endif /* Ignore if we didn't get any frame */ - if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) { + if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) + { conf_port->rx_level = 0; - TRACE_EX( (THIS_FILE, "%s: frame_type %d != PJMEDIA_FRAME_TYPE_AUDIO from the port (%.*s, %d, listener_cnt=%d)", - pj_thread_get_name( pj_thread_this() ), - frame_type, - (int)conf_port->name.slen, - conf_port->name.ptr, - port_idx, conf_port->listener_cnt) ); + TRACE_EX((THIS_FILE, "%s: frame_type %d != PJMEDIA_FRAME_TYPE_AUDIO from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + frame_type, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); continue; - } + } } pj_int32_t level = 0; - pj_int16_t *p_in; unsigned j; - p_in = get_read_buffer(conf_port, frame); - /* 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; jsamples_per_frame; ++j) { + 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. @@ -3175,12 +3322,15 @@ static pj_status_t get_frame(pjmedia_port *this_port, 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]); + p_in[j] = (pj_int16_t)itemp; + level += (p_in[j] >= 0 ? p_in[j] : -p_in[j]); } - } else { - for (j=0; jsamples_per_frame; ++j) { - 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]); } } @@ -3205,28 +3355,31 @@ static pj_status_t get_frame(pjmedia_port *this_port, { struct conf_port *listener; pj_int16_t *p_in_conn_leveled; + SLOT_TYPE listener_slot = conf_port->listener_slots[cj]; - listener = conf->ports[conf_port->listener_slots[cj]]; + listener = conf->ports[listener_slot]; /* Skip if this listener doesn't want to receive audio */ if (listener->tx_setting != PJMEDIA_PORT_ENABLE) { - TRACE_EX( (THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) doesn't want to receive audio from the port (%.*s, %d, listener_cnt=%d)", - pj_thread_get_name( pj_thread_this() ), - (int)listener->name.slen, - listener->name.ptr, - conf_port->listener_slots[cj], - listener->transmitter_cnt, - (int)conf_port->name.slen, - conf_port->name.ptr, - port_idx, conf_port->listener_cnt) ); + TRACE_EX((THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) doesn't want to receive audio from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + (int)listener->name.slen, + listener->name.ptr, + listener_slot, + listener->transmitter_cnt, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); continue; } /* apply connection level, if not normal */ - if (conf_port->listener_adj_level[cj] != NORMAL_LEVEL) { + if (conf_port->listener_adj_level[cj] != NORMAL_LEVEL) + { unsigned k = 0; - for (; k < conf->samples_per_frame; ++k) { + for (; k < conf->samples_per_frame; ++k) + { /* For the level adjustment, we need to store the sample to * a temporary 32bit integer value to avoid overflowing the * 16bit sample storage. @@ -3250,15 +3403,19 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* take the leveled frame */ p_in_conn_leveled = conf_port->adj_level_buf; - } else { + } + else + { /* take the frame as-is */ p_in_conn_leveled = p_in; } pj_int32_t *mix_buf; mix_buf = listener->mix_buf; + pj_bool_t ready_to_transmit = PJ_FALSE; - if (listener->transmitter_cnt > 1) { + if (listener->transmitter_cnt > 1) + { /* Mixing signals, * and calculate appropriate level adjustment if there is * any overflowed level in the mixed signal. @@ -3272,70 +3429,86 @@ static pj_status_t get_frame(pjmedia_port *this_port, if (IS_PARALLEL) pj_lock_acquire(listener->tx_Lock); - if (listener->last_timestamp.u64 == frame->timestamp.u64) { + if (listener->last_timestamp.u64 == frame->timestamp.u64) + { //this frame is NOT from the first transmitter - for (k = 0; k < samples_per_frame; ++k) { + for (k = 0; k < samples_per_frame; ++k) + { mix_buf[k] += p_in_conn_leveled[k]; // not the first - sum if (mix_buf[k] < mix_buf_min) mix_buf_min = mix_buf[k]; if (mix_buf[k] > mix_buf_max) mix_buf_max = mix_buf[k]; } - TRACE_EX( (THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) get (sum) audio from the port (%.*s, %d, listener_cnt=%d)", - pj_thread_get_name( pj_thread_this() ), - (int)listener->name.slen, - listener->name.ptr, - conf_port->listener_slots[cj], - listener->transmitter_cnt, - (int)conf_port->name.slen, - conf_port->name.ptr, - port_idx, conf_port->listener_cnt) ); - - } else { + TRACE_EX((THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) get (sum) audio from the port (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + (int)listener->name.slen, + listener->name.ptr, + listener_slot, + listener->transmitter_cnt, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); + + } + else + { //this frame is from the first transmitter listener->last_timestamp = frame->timestamp; /* We do not want to reset buffer, we just copy the first frame there. */ - for (k = 0; k < samples_per_frame; ++k) { + for (k = 0; k < samples_per_frame; ++k) + { mix_buf[k] = p_in_conn_leveled[k]; // the first - copy if (mix_buf[k] < mix_buf_min) mix_buf_min = mix_buf[k]; if (mix_buf[k] > mix_buf_max) mix_buf_max = mix_buf[k]; } - TRACE_EX( (THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d) get (copy) audio from the port %p (%.*s, %d, listener_cnt=%d)", - pj_thread_get_name( pj_thread_this() ), - listener, - (int)listener->name.slen, - listener->name.ptr, - conf_port->listener_slots[cj], - listener->transmitter_cnt, - conf_port, - (int)conf_port->name.slen, - conf_port->name.ptr, - port_idx, conf_port->listener_cnt) ); + TRACE_EX((THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d) get (copy) audio from the port %p (%.*s, %d, listener_cnt=%d)", + pj_thread_get_name(pj_thread_this()), + listener, + (int)listener->name.slen, + listener->name.ptr, + listener_slot, + listener->transmitter_cnt, + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + port_idx, conf_port->listener_cnt)); } /* Check if normalization adjustment needed. */ - if (mix_buf_min < MIN_LEVEL || mix_buf_max > MAX_LEVEL) { + if (mix_buf_min < MIN_LEVEL || mix_buf_max > MAX_LEVEL) + { int tmp_adj; if (-mix_buf_min > mix_buf_max) mix_buf_max = -mix_buf_min; /* NORMAL_LEVEL * MAX_LEVEL / mix_buf_max; */ - tmp_adj = (MAX_LEVEL<<7) / mix_buf_max; + tmp_adj = (MAX_LEVEL << 7) / mix_buf_max; if (tmp_adj < listener->mix_adj) listener->mix_adj = tmp_adj; } + if (listener->transmitter_cnt == listener->mixed_cnt + 1) + { + ready_to_transmit = PJ_TRUE; + listener->mixed_cnt = 0; + } + else + ++listener->mixed_cnt; + pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); if (IS_PARALLEL) pj_lock_release(listener->tx_Lock); - } else { + } + else + { //this frame is from the only transmitter - pj_assert( listener->transmitter_cnt == 1 && listener->last_timestamp.u64 != frame->timestamp.u64 ); + pj_assert(listener->transmitter_cnt == 1 && listener->last_timestamp.u64 != frame->timestamp.u64); listener->last_timestamp = frame->timestamp; /* Only 1 transmitter: @@ -3344,200 +3517,92 @@ static pj_status_t get_frame(pjmedia_port *this_port, */ unsigned k, samples_per_frame = conf->samples_per_frame; - for (k = 0; k < samples_per_frame; ++k) { + for (k = 0; k < samples_per_frame; ++k) + { mix_buf[k] = p_in_conn_leveled[k]; // here copying 16 bit value to 32 bit dst } - TRACE_EX( (THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d)" - " get audio from the (only) port %p (%.*s, %d, listener_cnt=%d) last_timestamp=%llu, timestamp=%llu", - pj_thread_get_name( pj_thread_this() ), - listener, - (int)listener->name.slen, - listener->name.ptr, - listener->slot, - listener->transmitter_cnt, - conf_port, - (int)conf_port->name.slen, - conf_port->name.ptr, - conf_port->slot, conf_port->listener_cnt, - listener->last_timestamp.u64, frame->timestamp.u64) ); + TRACE_EX((THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d)" + " get audio from the (only) port %p (%.*s, %d, listener_cnt=%d) last_timestamp=%llu, timestamp=%llu", + pj_thread_get_name(pj_thread_this()), + listener, + (int)listener->name.slen, + listener->name.ptr, + listener->slot, + listener->transmitter_cnt, + conf_port, + (int)conf_port->name.slen, + conf_port->name.ptr, + conf_port->slot, conf_port->listener_cnt, + listener->last_timestamp.u64, frame->timestamp.u64)); + + ready_to_transmit = PJ_TRUE; } - } /* loop the listeners of conf port */ - } /* loop of all conf ports */ - - /* all ports have data in their buffers - * and may do all work independently. - * Here we use ports from conf->active_listener[] - * whose lifetime is garanteed at the moment of adding - * port to active_listener[] by incrementing - * grp_lock->ref_counter. - */ - - listener_counter = conf->listener_counter + 1; - - - PRAGMA_OMP(parallel) - { - /* Perform any queued operations that need to be synchronized with - * the clock such as connect, disonnect, remove. - * All those operations performed on the single thread - * but perhaps not on the master thread - */ - PRAGMA_OMP(single nowait) - { - if (!pj_list_empty(conf->op_queue)) { - - /* register omp thread with pjsip */ - register_omp_thread(conf); -#ifdef CONF_DEBUG - inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); -#endif //CONF_DEBUG - - pj_log_push_indent(); - /* Calling any callback while a mutex is locked can result in a deadlock, - * since operations can lock other mutexes in an arbitrary order. - * At least OP_REMOVE_PORT invokes grp_lock handlers callbacks. - * We should move lock inside each operations - */ - //pj_mutex_lock(conf->mutex); - handle_op_queue(conf); - //pj_mutex_unlock(conf->mutex); - pj_log_pop_indent(); - } - } //pragma omp single - - /* Step 3 - * Time for all ports to transmit whatever they have in their - * buffer. - */ - PRAGMA_OMP(for nowait) - for (i = 0; i < listener_counter; ++i) { - - pjmedia_frame_type frm_type; - pj_status_t status; - - //sound device (from port[0]) has 0 idx here too - struct conf_port* conf_port = conf->active_listener[i]; - conf->active_listener[i] = NULL; - if (!conf_port) - continue; - - /* register omp thread with pjsip */ - register_omp_thread(conf); -#ifdef CONF_DEBUG - inc_omp_thread_usage(threads, PJ_ARRAY_SIZE(threads)); -#endif //CONF_DEBUG - - - status = write_port(conf, conf_port, &frame->timestamp, - &frm_type); - + if (ready_to_transmit) { + pjmedia_frame_type frm_type; + status = write_port(conf, listener, &frame->timestamp, &frm_type); #if 0 - 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; - } + 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; + } #endif - /* Set the type of frame to be returned to sound playback - * device. - */ - if (status == PJ_SUCCESS && i == 0) { - speaker_frame_type = frm_type; - sound_port = conf_port; + /* Set the type of frame to be returned to sound playback + * device. + */ + if (status == PJ_SUCCESS && listener_slot == 0 && listener->tx_level) + { + /* MUST set frame type */ + conf->frame->type = frm_type; + conf->sound_port = listener; + } } - //At least "Master/sound" may have no media_port - if (conf_port->port && conf_port->port->grp_lock) - pj_grp_lock_dec_ref(conf_port->port->grp_lock);// conf_port may be destroyed here - - } - - } //pragma omp parallel - - /* Return sound playback frame. */ - if (sound_port != NULL) { - if (sound_port->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 *)sound_port->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; - - TRACE_( (THIS_FILE, "Ports processed by omp team's threads 0:%d, 1:%d, 2:%d, 3:%d, 4:%d, 5:%d, 6:%d, 7:%d, 8:%d, 9:%d, 10:%d, 11:%d, 12:%d, 13:%d, 14:%d, 15:%d.", - threads[0], threads[1], threads[2], threads[3], threads[4], threads[5], threads[6], threads[7], - threads[8], threads[9], threads[10], threads[11], threads[12], threads[13], threads[14], threads[15]) ); + } /* loop the listeners of conf port */ -#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 + } /* loop of all conf ports */ - return PJ_SUCCESS; -} + if (conf->is_parallel) + { + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, ARRIVE AT BARRIER", + pj_thread_get_name(pj_thread_this()), + frame->timestamp.u64)); + /* If we carefully balance the work, we won't have to wait long here. + * let it be the default waiting (spin then block) + */ + status = pj_barrier_wait(conf->barrier, PJ_BARRIER_FLAGS_NO_DELETE); + pj_assert(status == PJ_TRUE || status == PJ_FALSE); -/* register omp thread with pjsip */ -static inline void register_omp_thread(pjmedia_conf *conf) -{ - /* the compiler should potentially optimize this away - * for the serial conference bridge - */ - if (IS_PARALLEL && !pj_thread_is_registered()) - { - pj_assert(conf->omp_threads_idx && conf->omp_threads); - pj_thread_t *p; - int num = pj_atomic_inc_and_get(conf->omp_threads_idx); - pj_assert(num < 2 * conf->omp_max_threads); - char obj_name[PJ_MAX_OBJ_NAME]; - pj_ansi_snprintf(obj_name, sizeof(obj_name), "omp_conf_%d", num); - pj_thread_register(obj_name, conf->omp_threads[num], &p); + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, BARRIER OVERCOME, return = %d", + pj_thread_get_name(pj_thread_this()), + frame->timestamp.u64, + status)); } - pj_assert(pj_thread_is_registered()); -} -#ifdef CONF_DEBUG -static inline void inc_omp_thread_usage(int *threads, pj_ssize_t threads_sz) { -# ifdef _OPENMP - int num = omp_get_thread_num(); - if (num < threads_sz) - threads[num]++; -# else - PJ_UNUSED_ARG(threads); - PJ_UNUSED_ARG(threads_sz); -# endif //_OPENMP } -#endif //CONF_DEBUG #if !DEPRECATED_FOR_TICKET_2234 /* * get_frame() for passive port */ -static pj_status_t get_frame_pasv(pjmedia_port *this_port, - pjmedia_frame *frame) +static pj_status_t get_frame_pasv(pjmedia_port *this_port, + pjmedia_frame *frame ) { pj_assert(0); PJ_UNUSED_ARG(this_port); @@ -3550,7 +3615,7 @@ static pj_status_t get_frame_pasv(pjmedia_port *this_port, /* * Recorder (or passive port) callback. */ -static pj_status_t put_frame(pjmedia_port *this_port, +static pj_status_t put_frame(pjmedia_port *this_port, pjmedia_frame *frame) { pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; @@ -3560,7 +3625,7 @@ static pj_status_t put_frame(pjmedia_port *this_port, /* Check for correct size. */ PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame * conf->bits_per_sample / 8, - PJMEDIA_ENCSAMPLESPFRAME); + PJMEDIA_ENCSAMPLESPFRAME ); /* Check existance of delay_buf instance */ PJ_ASSERT_RETURN( port->delay_buf, PJ_EBUG ); From 8e38b1ab6800fcca5a0b9f96d62cb504af81ea93 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sun, 26 Jan 2025 20:23:40 +0100 Subject: [PATCH 06/16] added && _POSIX_BARRIERS >= 200112L --- pjlib/src/pj/os_core_unix.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pjlib/src/pj/os_core_unix.c b/pjlib/src/pj/os_core_unix.c index 7d5eb2b1f5..495cc6898c 100644 --- a/pjlib/src/pj/os_core_unix.c +++ b/pjlib/src/pj/os_core_unix.c @@ -146,7 +146,7 @@ struct pj_event_t }; #endif /* PJ_HAS_EVENT_OBJ */ -#if defined(_POSIX_BARRIERS) +#if defined(_POSIX_BARRIERS) && _POSIX_BARRIERS >= 200112L /* pthread_barrier is supported. */ struct pj_barrier_t { pthread_barrier_t barrier; @@ -2097,7 +2097,7 @@ PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) #endif /* PJ_HAS_EVENT_OBJ */ /////////////////////////////////////////////////////////////////////////////// -#if defined(_POSIX_BARRIERS) +#if defined(_POSIX_BARRIERS) && _POSIX_BARRIERS >= 200112L /* pthread_barrier is supported. */ /** From 29201a85e31ef2e1798d112547dd06d679552d84 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sun, 26 Jan 2025 20:48:25 +0100 Subject: [PATCH 07/16] pthread_mutex_t -> pj_mutex_t --- pjlib/src/pj/os_core_unix.c | 45 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/pjlib/src/pj/os_core_unix.c b/pjlib/src/pj/os_core_unix.c index 495cc6898c..3fd276d3d3 100644 --- a/pjlib/src/pj/os_core_unix.c +++ b/pjlib/src/pj/os_core_unix.c @@ -146,22 +146,18 @@ struct pj_event_t }; #endif /* PJ_HAS_EVENT_OBJ */ -#if defined(_POSIX_BARRIERS) && _POSIX_BARRIERS >= 200112L -/* pthread_barrier is supported. */ struct pj_barrier_t { +#if defined(_POSIX_BARRIERS) && _POSIX_BARRIERS >= 200112L + /* pthread_barrier is supported. */ pthread_barrier_t barrier; -}; - #else -/* pthread_barrier is not supported. */ -struct pj_barrier_t { - pthread_mutex_t mutex; - pthread_cond_t cond; - unsigned count; - unsigned trip_count; -}; - + /* pthread_barrier is not supported. */ + pj_mutex_t mutex; + pthread_cond_t cond; + unsigned count; + unsigned trip_count; #endif +}; /* * Flag and reference counter for PJLIB instance. @@ -2154,14 +2150,16 @@ pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { pj_barrier_t *barrier; int rc; + pj_status_t PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); barrier = (pj_barrier_t *)pj_pool_zalloc(pool, sizeof(pj_barrier_t)); if (barrier == NULL) return PJ_ENOMEM; - rc = pthread_mutex_init(&barrier->mutex, &attr); - if (rc != 0) - return PJ_RETURN_OS_ERROR(rc); + status = init_mutex(&barrier->mutex, "barrier%p", PJ_MUTEX_SIMPLE); + if (status != PJ_SUCCESS) + return status; + pthread_cond_init(&barrier->cond, NULL); barrier->count = 0; barrier->trip_count = trip_count; @@ -2175,18 +2173,23 @@ pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { PJ_UNUSED_ARG(flags); - pthread_mutex_lock(&barrier->mutex); + pj_bool_t is_last = PJ_FALSE; + + pthread_mutex_lock(&barrier->mutex.mutex); barrier->count++; if (barrier->count >= barrier->trip_count) { barrier->count = 0; pthread_cond_broadcast(&barrier->cond); + is_last = PJ_TRUE; } else { - pthread_cond_wait(&barrier->cond, &barrier->mutex); + pthread_cond_wait(&barrier->cond, &barrier->mutex.mutex); } - pthread_mutex_unlock(&barrier->mutex); + pthread_mutex_unlock(&barrier->mutex.mutex); + + rerutn is_last; } /** @@ -2194,11 +2197,7 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { */ pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { pthread_cond_destroy(&barrier->cond); - int status = pthread_mutex_destroy(&barrier->mutex); - if (status == 0) - return PJ_SUCCESS; - else - return PJ_RETURN_OS_ERROR(status); + return pj_mutex_destroy(&barrier->mutex); } #endif // _POSIX_BARRIERS From eec77e5508110508bc75be385e1f1e522a4c4511 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sun, 26 Jan 2025 20:52:03 +0100 Subject: [PATCH 08/16] fix compile error --- pjlib/src/pj/os_core_unix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pjlib/src/pj/os_core_unix.c b/pjlib/src/pj/os_core_unix.c index 3fd276d3d3..1ab904f964 100644 --- a/pjlib/src/pj/os_core_unix.c +++ b/pjlib/src/pj/os_core_unix.c @@ -2150,7 +2150,7 @@ pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { pj_barrier_t *barrier; int rc; - pj_status_t + pj_status_t status; PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); barrier = (pj_barrier_t *)pj_pool_zalloc(pool, sizeof(pj_barrier_t)); From 60e5b02894e806115e0c34fb0cb2aaecb034727d Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sun, 26 Jan 2025 20:54:27 +0100 Subject: [PATCH 09/16] fix compile error --- pjlib/src/pj/os_core_unix.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pjlib/src/pj/os_core_unix.c b/pjlib/src/pj/os_core_unix.c index 1ab904f964..1ebd7d00e8 100644 --- a/pjlib/src/pj/os_core_unix.c +++ b/pjlib/src/pj/os_core_unix.c @@ -2149,7 +2149,6 @@ pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { */ pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { pj_barrier_t *barrier; - int rc; pj_status_t status; PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); @@ -2189,7 +2188,7 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { } pthread_mutex_unlock(&barrier->mutex.mutex); - rerutn is_last; + return is_last; } /** From 97cccb003eb8cf4c706574ea645daf9eadc06339 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sun, 26 Jan 2025 22:09:26 +0100 Subject: [PATCH 10/16] last minute fixes --- pjlib/src/pj/os_core_win32.c | 40 +++++++++++++++----------------- pjmedia/src/pjmedia/conference.c | 19 +++------------ 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/pjlib/src/pj/os_core_win32.c b/pjlib/src/pj/os_core_win32.c index a4f6bc1f57..1b4a77a0a8 100644 --- a/pjlib/src/pj/os_core_win32.c +++ b/pjlib/src/pj/os_core_win32.c @@ -131,16 +131,16 @@ struct pj_barrier_t { }; #elif PJ_WIN32_WINNT >= _WIN32_WINNT_VISTA struct pj_barrier_t { - CRITICAL_SECTION mutex; - CONDITION_VARIABLE cond; - unsigned count; - unsigned waiting; + CRITICAL_SECTION mutex; + CONDITION_VARIABLE cond; + unsigned count; + unsigned waiting; }; #else struct pj_barrier_t { - HANDLE cond; /* Semaphore */ - LONG count; /* Number of threads required to pass the barrier */ - LONG waiting; /* Number of threads waiting at the barrier */ + HANDLE cond; /* Semaphore */ + LONG count; /* Number of threads required to pass the barrier */ + LONG waiting;/* Number of threads waiting at the barrier */ }; #endif @@ -1596,8 +1596,8 @@ pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t return PJ_SUCCESS; #else barrier->cond = CreateSemaphore(NULL, - 0, /* initial count */ - count, /* max count */ + 0, /* initial count */ + trip_count, /* max count */ NULL); if (!barrier->cond) return pj_get_os_error(); @@ -1640,12 +1640,11 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { #elif PJ_WIN32_WINNT >= _WIN32_WINNT_VISTA PJ_UNUSED_ARG(flags); EnterCriticalSection(&barrier->mutex); - barrier->waiting++; - if (barrier->waiting == barrier->count) + if (++barrier->waiting == barrier->count) { barrier->waiting = 0; - WakeAllConditionVariable(&barrier->cond); LeaveCriticalSection(&barrier->mutex); + WakeAllConditionVariable(&barrier->cond); return PJ_TRUE; } else @@ -1659,12 +1658,13 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { } #else PJ_UNUSED_ARG(flags); + if (InterlockedIncrement(&barrier->waiting) == barrier->count) { - LONG previousCount; + LONG previousCount = 0; barrier->waiting = 0; /* Release all threads waiting on the semaphore */ - if (ReleaseSemaphore(barrier->cond, barrier->count, &previousCount)) + if (barrier->count == 1 || ReleaseSemaphore(barrier->cond, barrier->count - 1, &previousCount)) { PJ_ASSERT_RETURN(previousCount == 0, PJ_EBUG); return PJ_TRUE; @@ -1672,14 +1672,12 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { else return pj_get_os_error(); } + + DWORD rc = WaitForSingleObject(barrier->cond, INFINITE); + if (rc == WAIT_OBJECT_0) + return PJ_FALSE; else - { - DWORD rc = WaitForSingleObject(barrier->cond, INFINITE); - if (rc == WAIT_OBJECT_0) - return PJ_FALSE; - else - return pj_get_os_error(); - } + return pj_get_os_error(); #endif } diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 923b47065d..f1a5dbc687 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -2965,10 +2965,6 @@ static pj_status_t get_frame(pjmedia_port *this_port, begin = conf->lower_bound; end = conf->upper_bound; -#ifdef CONF_DEBUG - int threads[IS_PARALLEL ? 64 : 1] = {0}; -#endif //CONF_DEBUG - /* Step 1 initialization * Single threaded loop to get the active_ports[] (transmitters) * and active_listener[] (receivers) arrays. @@ -2985,13 +2981,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, { /* Reset auto adjustment level for mixed signal. */ conf_port->mix_adj = NORMAL_LEVEL; -#if 0 - /* We need not reset buffer, we just want to copy the first (and probably only) frame there. */ - if (conf_port->transmitter_cnt > 1) { - pj_bzero( conf_port->mix_buf, - conf->samples_per_frame * sizeof(conf_port->mix_buf[0]) ); - } -#endif + if (conf_port->transmitter_cnt && conf_port->tx_setting != PJMEDIA_PORT_DISABLE) { /* We need not reset mix_buf, we just want to copy the first @@ -3067,8 +3057,9 @@ static pj_status_t get_frame(pjmedia_port *this_port, conf->samples_per_frame); /* MUST set frame type */ pj_assert(frame->type != PJMEDIA_FRAME_TYPE_NONE); + conf->sound_port = NULL; } - + conf->frame = NULL; } /* Perform any queued operations that need to be synchronized with @@ -3081,10 +3072,6 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_log_pop_indent(); } - TRACE_( (THIS_FILE, "Ports processed by omp team's threads 0:%d, 1:%d, 2:%d, 3:%d, 4:%d, 5:%d, 6:%d, 7:%d, 8:%d, 9:%d, 10:%d, 11:%d, 12:%d, 13:%d, 14:%d, 15:%d.", - threads[0], threads[1], threads[2], threads[3], threads[4], threads[5], threads[6], threads[7], - threads[8], threads[9], threads[10], threads[11], threads[12], threads[13], threads[14], threads[15]) ); - #ifdef REC_FILE if (fhnd_rec == NULL) fhnd_rec = fopen(REC_FILE, "wb"); From a3caf2cfc3c7a3201cf93f0a15267672ac0a8d32 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sat, 8 Feb 2025 01:59:05 +0300 Subject: [PATCH 11/16] barriers changes on code review --- pjlib/include/pj/os.h | 59 ++++++++++++++++++++++-------------- pjlib/src/pj/os_core_unix.c | 26 ++++++++-------- pjlib/src/pj/os_core_win32.c | 29 ++++++++---------- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/pjlib/include/pj/os.h b/pjlib/include/pj/os.h index 3e8039e35d..e35cbf1513 100644 --- a/pjlib/include/pj/os.h +++ b/pjlib/include/pj/os.h @@ -1113,16 +1113,21 @@ PJ_DECL(pj_status_t) pj_event_destroy(pj_event_t *event); * Otherwize, the flags are ignored. */ enum pj_barrier_flags { - /* Specifies that the thread entering the barrier should block - * immediately until the last thread enters the barrier. */ + /** + * Specifies that the thread entering the barrier should block + * immediately until the last thread enters the barrier. + */ PJ_BARRIER_FLAGS_BLOCK_ONLY = 1, - /* Specifies that the thread entering the barrier should spin until + /** + * Specifies that the thread entering the barrier should spin until * the last thread enters the barrier, - * even if the spinning thread exceeds the barrier's maximum spin count.*/ + * even if the spinning thread exceeds the barrier's maximum spin count. + */ PJ_BARRIER_FLAGS_SPIN_ONLY = 2, - /* Specifies that the function can skip the work required to ensure + /** + * Specifies that the function can skip the work required to ensure * that it is safe to delete the barrier, which can improve performance. * All threads that enter this barrier must specify the flag; * otherwise, the flag is ignored. @@ -1134,43 +1139,51 @@ enum pj_barrier_flags { /** * Create a barrier object. - * pj_barrier_create() creates a barrier object that can be used to synchronize threads. - * The barrier object is initialized with a trip count that specifies the number of threads - * that must call pj_barrier_wait() before any are allowed to proceed. + * pj_barrier_create() creates a barrier object that can be used to synchronize + * threads. The barrier object is initialized with a thread count that + * specifies the number of threads that must call pj_barrier_wait() + * before any are allowed to proceed. * - * @param pool The pool to allocate the barrier object. - * @param trip_count The number of threads that must call pj_barrier_wait() before any are allowed to proceed. - * @param p_barrier Pointer to hold the barrier object upon return. + * @param pool The pool to allocate the barrier object. + * @param thread_count The number of threads that must call pj_barrier_wait() + * before any are allowed to proceed. + * The behavi�r of the barrier is undefined if more + * threads than thread_count arrive at the barrier. + * @param p_barrier Pointer to hold the barrier object upon return. * * @return PJ_SUCCESS on success, or the error code. */ -pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier); +PJ_DECL(pj_status_t) pj_barrier_create(pj_pool_t *pool, unsigned thread_count, + pj_barrier_t **p_barrier); /** * Destroy a barrier object. - * pj_barrier_destroy() destroys a barrier object and releases any resources associated with the barrier. + * pj_barrier_destroy() destroys a barrier object and releases any resources + * associated with the barrier. * - * @param barrier The barrier to destroy. + * @param barrier The barrier to destroy. * * @return PJ_SUCCESS on success, or the error code. */ -pj_status_t pj_barrier_destroy(pj_barrier_t *barrier); +PJ_DECL(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier); /** - * Wait for all threads to reach the barrier - * pj_barrier_wait() allows threads to block until all participating threads have reached the barrier, - * ensuring synchronization at specific points in execution. - * It provides a barrier mechanism for synchronizing threads in a multithreaded application, - * similar to the POSIX pthread_barrier_wait or Windows EnterSynchronizationBarrier. + * Wait for all threads to reach the barrier. + * pj_barrier_wait() allows threads to block until all participating threads + * have reached the barrier, ensuring synchronization at specific points in + * execution. It provides a barrier mechanism for synchronizing threads in + * a multithreaded application, similar to the POSIX pthread_barrier_wait + * or Windows EnterSynchronizationBarrier. * - * @param barrier The barrier to wait on - * @param flags Flags that control the behavior of the barrier (combination of pj_barrier_flags) + * @param barrier The barrier to wait on. + * @param flags Flags that control the behavior of the barrier + * (combination of pj_barrier_flags), default 0. * * @return Returns PJ_TRUE for a single (arbitrary) thread synchronized * at the barrier and PJ_FALSE for each of the other threads. * Otherwise, an error number shall be returned to indicate the error. */ -pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags); +PJ_DECL(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags); /** * @} diff --git a/pjlib/src/pj/os_core_unix.c b/pjlib/src/pj/os_core_unix.c index 1ebd7d00e8..ab25b8572f 100644 --- a/pjlib/src/pj/os_core_unix.c +++ b/pjlib/src/pj/os_core_unix.c @@ -2099,7 +2099,8 @@ PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) /** * Barrier object. */ -pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { +PJ_DEF(pj_status_t) pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) +{ pj_barrier_t *barrier; int rc; PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); @@ -2116,7 +2117,8 @@ pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t /** * Wait on the barrier. */ -pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { +PJ_DEF(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) +{ PJ_UNUSED_ARG(flags); int rc = pthread_barrier_wait(&barrier->barrier); switch (rc) @@ -2133,7 +2135,8 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { /** * Destroy the barrier. */ -pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { +PJ_DEF(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier) +{ int status = pthread_barrier_destroy(&barrier->barrier); if (status == 0) return PJ_SUCCESS; @@ -2147,7 +2150,8 @@ pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { /** * Barrier object. */ -pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { +PJ_DEF(pj_status_t) pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) +{ pj_barrier_t *barrier; pj_status_t status; @@ -2169,21 +2173,18 @@ pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t /** * Wait on the barrier. */ -pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { +PJ_DEF(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) +{ PJ_UNUSED_ARG(flags); pj_bool_t is_last = PJ_FALSE; pthread_mutex_lock(&barrier->mutex.mutex); - barrier->count++; - if (barrier->count >= barrier->trip_count) - { + if (++barrier->count >= barrier->trip_count) { barrier->count = 0; pthread_cond_broadcast(&barrier->cond); is_last = PJ_TRUE; - } - else - { + } else { pthread_cond_wait(&barrier->cond, &barrier->mutex.mutex); } pthread_mutex_unlock(&barrier->mutex.mutex); @@ -2194,7 +2195,8 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { /** * Destroy the barrier. */ -pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { +PJ_DEF(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier) +{ pthread_cond_destroy(&barrier->cond); return pj_mutex_destroy(&barrier->mutex); } diff --git a/pjlib/src/pj/os_core_win32.c b/pjlib/src/pj/os_core_win32.c index 1b4a77a0a8..6f28521c6c 100644 --- a/pjlib/src/pj/os_core_win32.c +++ b/pjlib/src/pj/os_core_win32.c @@ -1573,19 +1573,18 @@ PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) /* * pj_barrier_create() */ -pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) { +PJ_DEF(pj_status_t) pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t **p_barrier) +{ pj_barrier_t *barrier; PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); barrier = (pj_barrier_t *)pj_pool_zalloc(pool, sizeof(pj_barrier_t)); if (barrier == NULL) return PJ_ENOMEM; #if PJ_WIN32_WINNT >= _WIN32_WINNT_WIN8 - if (InitializeSynchronizationBarrier(&barrier->sync_barrier, trip_count, -1)) - { + if (InitializeSynchronizationBarrier(&barrier->sync_barrier, trip_count, -1)) { *p_barrier = barrier; return PJ_SUCCESS; - } - else + } else return pj_get_os_error(); #elif PJ_WIN32_WINNT >= _WIN32_WINNT_VISTA InitializeCriticalSection(&barrier->mutex); @@ -1611,7 +1610,8 @@ pj_status_t pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_barrier_t /* * pj_barrier_destroy() */ -pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { +PJ_DEF(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier) +{ PJ_ASSERT_RETURN(barrier, PJ_EINVAL); #if PJ_WIN32_WINNT >= _WIN32_WINNT_WIN8 DeleteSynchronizationBarrier(&barrier->sync_barrier); @@ -1630,7 +1630,8 @@ pj_status_t pj_barrier_destroy(pj_barrier_t *barrier) { /* * pj_barrier_wait() */ -pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { +PJ_DEF(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) +{ PJ_ASSERT_RETURN(barrier, PJ_EINVAL); #if PJ_WIN32_WINNT >= _WIN32_WINNT_WIN8 DWORD dwFlags = ((flags & PJ_BARRIER_FLAGS_BLOCK_ONLY) ? SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY : 0) | @@ -1640,15 +1641,12 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { #elif PJ_WIN32_WINNT >= _WIN32_WINNT_VISTA PJ_UNUSED_ARG(flags); EnterCriticalSection(&barrier->mutex); - if (++barrier->waiting == barrier->count) - { + if (++barrier->waiting == barrier->count) { barrier->waiting = 0; LeaveCriticalSection(&barrier->mutex); WakeAllConditionVariable(&barrier->cond); return PJ_TRUE; - } - else - { + } else { BOOL rc = SleepConditionVariableCS(&barrier->cond, &barrier->mutex, INFINITE); LeaveCriticalSection(&barrier->mutex); if (rc) @@ -1659,13 +1657,12 @@ pj_status_t pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { #else PJ_UNUSED_ARG(flags); - if (InterlockedIncrement(&barrier->waiting) == barrier->count) - { + if (InterlockedIncrement(&barrier->waiting) == barrier->count) { LONG previousCount = 0; barrier->waiting = 0; /* Release all threads waiting on the semaphore */ - if (barrier->count == 1 || ReleaseSemaphore(barrier->cond, barrier->count - 1, &previousCount)) - { + if (barrier->count == 1 || + ReleaseSemaphore(barrier->cond, barrier->count-1, &previousCount)) { PJ_ASSERT_RETURN(previousCount == 0, PJ_EBUG); return PJ_TRUE; } From a20ed8cf1657f0832bf506def4c2fca18324b961 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Sat, 8 Feb 2025 18:47:24 +0300 Subject: [PATCH 12/16] documentation and code improvements related to barriers --- pjlib/include/pj/os.h | 20 +++++++++----- pjlib/src/pj/os_core_unix.c | 52 ++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/pjlib/include/pj/os.h b/pjlib/include/pj/os.h index e35cbf1513..35d44fe2f8 100644 --- a/pjlib/include/pj/os.h +++ b/pjlib/include/pj/os.h @@ -1147,11 +1147,12 @@ enum pj_barrier_flags { * @param pool The pool to allocate the barrier object. * @param thread_count The number of threads that must call pj_barrier_wait() * before any are allowed to proceed. - * The behavi�r of the barrier is undefined if more - * threads than thread_count arrive at the barrier. * @param p_barrier Pointer to hold the barrier object upon return. + * + * @return PJ_SUCCESS on success, or the error code. * - * @return PJ_SUCCESS on success, or the error code. + * @warning The behavior of the barrier is undefined if more + * threads than thread_count arrive at the barrier. */ PJ_DECL(pj_status_t) pj_barrier_create(pj_pool_t *pool, unsigned thread_count, pj_barrier_t **p_barrier); @@ -1163,7 +1164,7 @@ PJ_DECL(pj_status_t) pj_barrier_create(pj_pool_t *pool, unsigned thread_count, * * @param barrier The barrier to destroy. * - * @return PJ_SUCCESS on success, or the error code. + * @return PJ_SUCCESS on success, or the error code. */ PJ_DECL(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier); @@ -1179,9 +1180,14 @@ PJ_DECL(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier); * @param flags Flags that control the behavior of the barrier * (combination of pj_barrier_flags), default 0. * - * @return Returns PJ_TRUE for a single (arbitrary) thread synchronized - * at the barrier and PJ_FALSE for each of the other threads. - * Otherwise, an error number shall be returned to indicate the error. + * @return Returns PJ_TRUE for a single (arbitrary) thread + * synchronized at the barrier and PJ_FALSE for each + * of the other threads. Otherwise, an error number + * shall be returned to indicate the error. + * + * @warning The behavior of the barrier is undefined if more + * threads arrive at the barrier than the thread_count + * specified when the barrier was created. */ PJ_DECL(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags); diff --git a/pjlib/src/pj/os_core_unix.c b/pjlib/src/pj/os_core_unix.c index ab25b8572f..14c63a07a3 100644 --- a/pjlib/src/pj/os_core_unix.c +++ b/pjlib/src/pj/os_core_unix.c @@ -2108,10 +2108,9 @@ PJ_DEF(pj_status_t) pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_b if (barrier == NULL) return PJ_ENOMEM; rc = pthread_barrier_init(&barrier->barrier, NULL, trip_count); - if (rc != 0) - return PJ_RETURN_OS_ERROR(rc); - *p_barrier = barrier; - return PJ_SUCCESS; + if (rc == 0) + *p_barrier = barrier; + return PJ_STATUS_FROM_OS(rc); } /** @@ -2121,14 +2120,13 @@ PJ_DEF(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) { PJ_UNUSED_ARG(flags); int rc = pthread_barrier_wait(&barrier->barrier); - switch (rc) - { + switch (rc) { case 0: return PJ_FALSE; case PTHREAD_BARRIER_SERIAL_THREAD: return PJ_TRUE; default: - return PJ_RETURN_OS_ERROR(rc); + return PJ_STATUS_FROM_OS(rc); } } @@ -2138,10 +2136,7 @@ PJ_DEF(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) PJ_DEF(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier) { int status = pthread_barrier_destroy(&barrier->barrier); - if (status == 0) - return PJ_SUCCESS; - else - return PJ_RETURN_OS_ERROR(status); + return PJ_STATUS_FROM_OS(status); } #else // _POSIX_BARRIERS @@ -2154,20 +2149,26 @@ PJ_DEF(pj_status_t) pj_barrier_create(pj_pool_t *pool, unsigned trip_count, pj_b { pj_barrier_t *barrier; pj_status_t status; + int rc; PJ_ASSERT_RETURN(pool && p_barrier, PJ_EINVAL); - barrier = (pj_barrier_t *)pj_pool_zalloc(pool, sizeof(pj_barrier_t)); + barrier = (pj_barrier_t*)pj_pool_zalloc(pool, sizeof(pj_barrier_t)); if (barrier == NULL) return PJ_ENOMEM; - status = init_mutex(&barrier->mutex, "barrier%p", PJ_MUTEX_SIMPLE); - if (status != PJ_SUCCESS) - return status; - pthread_cond_init(&barrier->cond, NULL); - barrier->count = 0; - barrier->trip_count = trip_count; - *p_barrier = barrier; - return PJ_SUCCESS; + rc = pthread_cond_init(&barrier->cond, NULL); + if ((status = PJ_STATUS_FROM_OS(rc)) == PJ_SUCCESS) { + status = init_mutex(&barrier->mutex, "barrier%p", PJ_MUTEX_SIMPLE); + if (status != PJ_SUCCESS) { + rc = pthread_cond_destroy(&barrier->cond); + pj_assert(!rc); + } else { + barrier->count = 0; + barrier->trip_count = trip_count; + *p_barrier = barrier; + } + } + return status; } /** @@ -2178,18 +2179,19 @@ PJ_DEF(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) PJ_UNUSED_ARG(flags); pj_bool_t is_last = PJ_FALSE; + int status; pthread_mutex_lock(&barrier->mutex.mutex); if (++barrier->count >= barrier->trip_count) { barrier->count = 0; - pthread_cond_broadcast(&barrier->cond); + status = pthread_cond_broadcast(&barrier->cond); is_last = PJ_TRUE; } else { - pthread_cond_wait(&barrier->cond, &barrier->mutex.mutex); + status = pthread_cond_wait(&barrier->cond, &barrier->mutex.mutex); } pthread_mutex_unlock(&barrier->mutex.mutex); - return is_last; + return !status ? is_last : PJ_STATUS_FROM_OS(status); } /** @@ -2197,7 +2199,9 @@ PJ_DEF(pj_int32_t) pj_barrier_wait(pj_barrier_t *barrier, pj_uint32_t flags) */ PJ_DEF(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier) { - pthread_cond_destroy(&barrier->cond); + int status = pthread_cond_destroy(&barrier->cond); + pj_assert(!status); + PJ_UNUSED_ARG(status); return pj_mutex_destroy(&barrier->mutex); } From 1746b44e9801885738650f71f8d4e89bb1a91de2 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Mon, 10 Feb 2025 16:44:51 +0300 Subject: [PATCH 13/16] pjmedia_conf_param introduced --- pjmedia/include/pjmedia/conference.h | 33 ++ pjmedia/src/pjmedia/conf_openmp.c | 10 + pjmedia/src/pjmedia/conf_switch.c | 10 + pjmedia/src/pjmedia/conference.c | 496 ++++++++++++++------------- pjmedia/src/test/mips_test.c | 23 +- 5 files changed, 330 insertions(+), 242 deletions(-) diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h index e0a273e6e3..2d0e1b5a8b 100644 --- a/pjmedia/include/pjmedia/conference.h +++ b/pjmedia/include/pjmedia/conference.h @@ -104,6 +104,32 @@ enum pjmedia_conf_option based. */ }; +/** + * Conference bridge creation parameters. +//TODO + */ +typedef struct pjmedia_conf_param +{ + unsigned max_slots; + unsigned sampling_rate; + unsigned channel_count; + unsigned samples_per_frame; + unsigned bits_per_sample; + unsigned options; + /** The number of worker threads to use, from 0-N. + * Zero means the operations will be done only by get_frame() thread. + */ + unsigned worker_threads; +} pjmedia_conf_param; + + +/** + * Initialize conference bridge creation parameters. + */ +PJ_INLINE(void) pjmedia_conf_param_default(pjmedia_conf_param *param) +{ + pj_bzero(param, sizeof(pjmedia_conf_param)); +} /** * Create conference bridge with the specified parameters. The sampling rate, @@ -170,6 +196,13 @@ PJ_DECL(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, unsigned options, pjmedia_conf **p_conf ); +/** +//TODO + */ +PJ_DECL(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool, + pjmedia_conf_param *param, + pjmedia_conf **p_conf); + /** * Destroy conference bridge. diff --git a/pjmedia/src/pjmedia/conf_openmp.c b/pjmedia/src/pjmedia/conf_openmp.c index a6bf75cde0..59d3d9811c 100644 --- a/pjmedia/src/pjmedia/conf_openmp.c +++ b/pjmedia/src/pjmedia/conf_openmp.c @@ -975,6 +975,16 @@ static pj_status_t create_sound_port( pj_pool_t *pool, return PJ_SUCCESS; } +PJ_DEF(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool, + pjmedia_conf_param *param, + pjmedia_conf **p_conf) +{ + return pjmedia_conf_create(pool, + param.max_slots, param.sampling_rate, + param.channel_count, param.samples_per_frame, + param.bits_per_sample, param.options, p_conf); +} + /* * Create conference bridge. */ diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c index 90f1739cef..43cec652b4 100644 --- a/pjmedia/src/pjmedia/conf_switch.c +++ b/pjmedia/src/pjmedia/conf_switch.c @@ -209,6 +209,16 @@ static pj_status_t create_sound_port( pj_pool_t *pool, return PJ_SUCCESS; } +PJ_DEF(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool, + pjmedia_conf_param *param, + pjmedia_conf **p_conf) +{ + return pjmedia_conf_create(pool, + param.max_slots, param.sampling_rate, + param.channel_count, param.samples_per_frame, + param.bits_per_sample, param.options, p_conf); +} + /* * Create conference bridge. */ diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index f1a5dbc687..ddb3240195 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -57,16 +57,6 @@ #endif -#if PJ_CONF_BRIDGE_MAX_THREADS > 1 - -# define IS_PARALLEL PJ_TRUE // pj_thread MP is enabled - -#else // PJ_CONF_BRIDGE_MAX_THREADS <= 1 - -# define IS_PARALLEL PJ_FALSE // MP is disabled - -#endif // PJ_CONF_BRIDGE_MAX_THREADS > 1 - /* CONF_DEBUG enables detailed operation of the conference bridge. * Beware that it prints large amounts of logs (several lines per frame). */ @@ -118,7 +108,7 @@ static FILE *fhnd_rec; * in the port does not cause misaligned signal (which causes noise). */ #if defined(PJMEDIA_CONF_USE_AGC) && PJMEDIA_CONF_USE_AGC != 0 -# define ATTACK_A ((conf->clock_rate / conf->samples_per_frame) >> 4) +# define ATTACK_A ((conf->sampling_rate / conf->samples_per_frame) >> 4) # define ATTACK_B 1 # define DECAY_A 0 # define DECAY_B 1 @@ -168,7 +158,7 @@ struct conf_port unsigned transmitter_cnt;/**rx_frame_buf */ /* native pjsip multithreading */ + pj_atomic_value_t threads; /**< The number of threads to use. + * 1 means the operations will be + * done only by get_frame() thread.*/ pj_thread_t **pool_threads; /**< Thread pool's threads */ pj_barrier_t *active_thread; /**< entry barrier */ pj_barrier_t *barrier; /**< exit barrier */ @@ -680,12 +673,12 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, 1); conf_port->port = port; - conf_port->clock_rate = afd->clock_rate; + conf_port->sampling_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->sampling_rate = conf->sampling_rate; conf_port->samples_per_frame = conf->samples_per_frame; conf_port->channel_count = conf->channel_count; } @@ -699,7 +692,7 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, /* If port's clock rate is different than conference's clock rate, * create a resample sessions. */ - if (conf_port->clock_rate != conf->clock_rate) { + if (conf_port->sampling_rate != conf->sampling_rate) { pj_bool_t high_quality; pj_bool_t large_filter; @@ -712,11 +705,11 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, high_quality, large_filter, conf->channel_count, - conf_port->clock_rate,/* Rate in */ - conf->clock_rate, /* Rate out */ + conf_port->sampling_rate,/* Rate in */ + conf->sampling_rate, /* Rate out */ conf->samples_per_frame * - conf_port->clock_rate / - conf->clock_rate, + conf_port->sampling_rate / + conf->sampling_rate, &conf_port->rx_resample); if (status != PJ_SUCCESS) goto on_return; @@ -727,8 +720,8 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, high_quality, large_filter, conf->channel_count, - conf->clock_rate, /* Rate in */ - conf_port->clock_rate, /* Rate out */ + conf->sampling_rate, /* Rate in */ + conf_port->sampling_rate, /* Rate out */ conf->samples_per_frame, &conf_port->tx_resample); if (status != PJ_SUCCESS) @@ -740,16 +733,16 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, * port's clock rate or channel number is different then the conference * bridge settings. */ - if (conf_port->clock_rate != conf->clock_rate || + if (conf_port->sampling_rate != conf->sampling_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; + 1000 / conf_port->sampling_rate; conf_ptime = conf->samples_per_frame / conf->channel_count * - 1000 / conf->clock_rate; + 1000 / conf->sampling_rate; /* Calculate the size (in ptime) for the port buffer according to * this formula: @@ -773,7 +766,7 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, // 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; + conf_port->rx_buf_cap = conf_port->sampling_rate * buff_ptime / 1000; if (conf_port->channel_count > conf->channel_count) conf_port->rx_buf_cap *= conf_port->channel_count; else @@ -805,10 +798,7 @@ static pj_status_t create_conf_port( pj_pool_t *parent_pool, {status = PJ_ENOMEM; goto on_return;}); conf_port->last_mix_adj = NORMAL_LEVEL; - if (IS_PARALLEL) { - /* the compiler should potentially optimize this "if" away - * for the serial conference bridge - */ + if (conf->is_parallel) { conf_port->rx_frame_buf = (pj_int16_t *)pj_pool_zalloc(pool, conf/*_port*/->rx_frame_buf_cap); status = pj_lock_create_simple_mutex(pool, "tx_Lock", &conf_port->tx_Lock); if (status != PJ_SUCCESS) @@ -849,10 +839,10 @@ static pj_status_t create_pasv_port( pjmedia_conf *conf, pool = conf_port->pool; /* Passive port has delay buf. */ - ptime = conf->samples_per_frame * 1000 / conf->clock_rate / + ptime = conf->samples_per_frame * 1000 / conf->sampling_rate / conf->channel_count; status = pjmedia_delay_buf_create(pool, name->ptr, - conf->clock_rate, + conf->sampling_rate, conf->samples_per_frame, conf->channel_count, RX_BUF_COUNT * ptime, /* max delay */ @@ -896,7 +886,7 @@ static pj_status_t create_sound_port( pj_pool_t *pool, * Otherwise create bidirectional sound device port. */ if (conf->options & PJMEDIA_CONF_NO_MIC) { - status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate, + status = pjmedia_snd_port_create_player(pool, -1, conf->sampling_rate, conf->channel_count, conf->samples_per_frame, conf->bits_per_sample, @@ -904,7 +894,7 @@ static pj_status_t create_sound_port( pj_pool_t *pool, &conf->snd_dev_port); } else { - status = pjmedia_snd_port_create( pool, -1, -1, conf->clock_rate, + status = pjmedia_snd_port_create( pool, -1, -1, conf->sampling_rate, conf->channel_count, conf->samples_per_frame, conf->bits_per_sample, @@ -946,30 +936,51 @@ static pj_status_t create_sound_port( pj_pool_t *pool, /* * 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 ) +PJ_DEF(pj_status_t) pjmedia_conf_create(pj_pool_t *pool, + unsigned max_slots, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned options, + pjmedia_conf **p_conf) +{ + pjmedia_conf_param param; + + pjmedia_conf_param_default(¶m); + + param.max_slots = max_slots; + param.sampling_rate = sampling_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = bits_per_sample; + param.options = options; + param.worker_threads = PJ_CONF_BRIDGE_MAX_THREADS - 1; + + return pjmedia_conf_create2(pool, ¶m, p_conf); +} + +PJ_DEF(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool_, + pjmedia_conf_param *param, + pjmedia_conf **p_conf) { pj_pool_t *pool; pjmedia_conf *conf; - const pj_str_t name = { "Conf", 4 }; + const pj_str_t name = {"Conf", 4}; pj_status_t status; - PJ_ASSERT_RETURN(samples_per_frame > 0, PJ_EINVAL); + PJ_ASSERT_RETURN(param && p_conf, PJ_EINVAL); + + PJ_ASSERT_RETURN(param->samples_per_frame > 0, PJ_EINVAL); /* Can only accept 16bits per sample, for now.. */ - PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); + PJ_ASSERT_RETURN(param->bits_per_sample == 16, PJ_EINVAL); #if defined(CONF_DEBUG_EX) || defined(CONF_DEBUG) pj_log_set_level(5); #endif - PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports", - max_ports)); + PJ_LOG(5, (THIS_FILE, "Creating conference bridge with %d ports", + param->max_slots)); /* Create own pool */ pool = pj_pool_create(pool_->factory, name.ptr, 512, 512, NULL); @@ -980,33 +991,45 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, /* Create and init conf structure. */ conf = PJ_POOL_ZALLOC_T(pool, pjmedia_conf); - PJ_ASSERT_RETURN(conf, PJ_ENOMEM); + PJ_ASSERT_ON_FAIL(conf, + {pj_pool_release(pool); return PJ_ENOMEM;}); conf->pool = pool; - conf->ports = pj_pool_calloc(pool, max_ports, sizeof(void*)); - PJ_ASSERT_ON_FAIL( conf->ports, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + conf->options = param->options; + conf->max_ports = param->max_slots; + conf->sampling_rate = param->sampling_rate; + conf->channel_count = param->channel_count; + conf->samples_per_frame = param->samples_per_frame; + conf->bits_per_sample = param->bits_per_sample; + conf->threads = param->worker_threads + 1; + conf->is_parallel = (param->worker_threads>0); + + /* loading and storing a properly aligned pointer should be atomic + * at the processor level and not require mutex protection + */ + conf->ports = + pj_pool_aligned_alloc(pool, sizeof(struct conf_port*), + conf->max_ports * sizeof(struct conf_port*)); + PJ_ASSERT_ON_FAIL(conf->ports, + {pjmedia_conf_destroy(conf); return PJ_ENOMEM;}); + pj_bzero(conf->ports, conf->max_ports * sizeof(struct conf_port*)); - conf->active_ports = pj_pool_calloc(pool, max_ports, sizeof(pj_int32_t) ); - PJ_ASSERT_ON_FAIL( conf->active_ports, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + conf->active_ports = + pj_pool_calloc(pool, conf->max_ports, sizeof(pj_int32_t)); + PJ_ASSERT_ON_FAIL(conf->active_ports, + {pjmedia_conf_destroy(conf); return 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; + conf->lower_bound = conf->max_ports; /* no connected ports yet */ + conf->upper_bound = 0; /* no connected ports yet */ - conf->lower_bound = max_ports; // no connected ports yet - conf->upper_bound = 0; // no connected ports yet - /* 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->sampling_rate, conf->channel_count, + conf->bits_per_sample, conf->samples_per_frame); conf->master_port->port_data.pdata = conf; conf->master_port->port_data.ldata = 0; @@ -1018,7 +1041,9 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, /* Get the bytes_per_frame value, to determine the size of the * buffer. */ - conf->rx_frame_buf_cap = PJMEDIA_AFD_AVG_FSZ(pjmedia_format_get_audio_format_detail(&conf->master_port->info.fmt, PJ_TRUE)); + conf->rx_frame_buf_cap = + PJMEDIA_AFD_AVG_FSZ(pjmedia_format_get_audio_format_detail( + &conf->master_port->info.fmt, PJ_TRUE)); /* Create port zero for sound device. */ status = create_sound_port(pool, conf); @@ -1038,8 +1063,8 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, * master port. */ if (conf->snd_dev_port) { - status = pjmedia_snd_port_connect( conf->snd_dev_port, - conf->master_port ); + status = pjmedia_snd_port_connect(conf->snd_dev_port, + conf->master_port); if (status != PJ_SUCCESS) { pjmedia_conf_destroy(conf); return status; @@ -1060,35 +1085,32 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool_, #if defined(PJ_STACK_IMPLEMENTATION) - status = pj_stack_create( pool, &conf->unused_slots ); + status = pj_stack_create(pool, &conf->unused_slots); if (status != PJ_SUCCESS) { - pjmedia_conf_destroy( conf ); + pjmedia_conf_destroy(conf); return status; } #else conf->unused_slots = PJ_POOL_ZALLOC_T(pool, unused_slots_cache); pj_list_init(conf->unused_slots); #endif - conf->free_port_slots = pj_pool_calloc(pool, max_ports, sizeof(port_slot)); - PJ_ASSERT_ON_FAIL( conf->free_port_slots, { pjmedia_conf_destroy( conf ); return PJ_ENOMEM; } ); + conf->free_port_slots = pj_pool_calloc(pool, conf->max_ports, sizeof(port_slot)); + PJ_ASSERT_ON_FAIL(conf->free_port_slots, {pjmedia_conf_destroy(conf); return PJ_ENOMEM;}); unsigned i = conf->max_ports; while (i--) { /* prepare unused slots to later reservation, reverse order due to FILO */ if (!conf->ports[i]) { /* If sound device was created, skip it's slot */ - status = conf_release_port( conf, i ); + status = conf_release_port(conf, i); if (status != PJ_SUCCESS) { - pjmedia_conf_destroy( conf ); + pjmedia_conf_destroy(conf); return status; } } } - conf->is_parallel = IS_PARALLEL; - status = pj_atomic_create(conf->pool, 0, &conf->active_ports_idx); PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, goto on_return); - if (conf->is_parallel) - { + if (conf->is_parallel) { status = thread_pool_start(conf); PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, goto on_return); } @@ -1130,9 +1152,10 @@ static pj_status_t resume_sound( pjmedia_conf *conf ) /** * Destroy conference bridge. */ -PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) +PJ_DEF(pj_status_t) pjmedia_conf_destroy(pjmedia_conf *conf) { unsigned i; + pj_int32_t rc; PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL); @@ -1143,21 +1166,31 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) /* all threads have reached the barrier and the conference bridge thread no longer exists. * Should be a very short waiting. - * + * * If we couldn't create all the threads from the pool, we shouldn't get close to the barrier. */ - if (conf->running && conf->active_thread) - pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_SPIN_ONLY); + if (conf->running && conf->active_thread) { + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread at barrier. quit_flag = %d.", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1, + conf->quit_flag)); + + rc = pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_SPIN_ONLY); + pj_assert(rc == PJ_TRUE || rc == PJ_FALSE); + + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, barrier passed with return = %d. quit_flag = %d.", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1, + rc, conf->quit_flag)); + PJ_UNUSED_ARG(rc); + } /* Destroy thread pool */ - if (conf->pool_threads) - { + if (conf->pool_threads) { pj_thread_t **threads = conf->pool_threads; - pj_thread_t **end = threads + (PJ_CONF_BRIDGE_MAX_THREADS - 1); - while (threads < end) - { - if (*threads) - { + pj_thread_t **end = threads + (conf->threads - 1); + while (threads < end) { + if (*threads) { pj_thread_join(*threads); pj_thread_destroy(*threads); *threads = NULL; @@ -1182,7 +1215,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) handle_op_queue(conf); /* Remove all ports (may destroy them too). */ - for (i=0; imax_ports; ++i) { + for (i = 0; i < conf->max_ports; ++i) { if (conf->ports[i]) { op_param oprm = {0}; oprm.remove_port.port = i; @@ -2306,7 +2339,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf, 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->clock_rate = conf_port->sampling_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; @@ -2563,7 +2596,7 @@ static pj_status_t read_port( pjmedia_conf *conf, */ samples_req = (unsigned) (count * 1.0 * - cport->clock_rate / conf->clock_rate + 0.5); + cport->sampling_rate / conf->sampling_rate + 0.5); while (cport->rx_buf_count < samples_req) { @@ -2625,7 +2658,7 @@ static pj_status_t read_port( pjmedia_conf *conf, * If port's clock_rate is different, resample. * Otherwise just copy. */ - if (cport->clock_rate != conf->clock_rate) { + if (cport->sampling_rate != conf->sampling_rate) { unsigned src_count; @@ -2634,8 +2667,8 @@ static pj_status_t read_port( pjmedia_conf *conf, pjmedia_resample_run( cport->rx_resample,cport->rx_buf, frame); - src_count = (unsigned)(count * 1.0 * cport->clock_rate / - conf->clock_rate + 0.5); + src_count = (unsigned)(count * 1.0 * cport->sampling_rate / + conf->sampling_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, @@ -2708,13 +2741,13 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, 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->tx_heart_beat += conf->samples_per_frame * cport->sampling_rate / + conf->sampling_rate * cport->channel_count / conf->channel_count; /* Set frame timestamp */ - frame.timestamp.u64 = timestamp->u64 * cport->clock_rate / - conf->clock_rate; + frame.timestamp.u64 = timestamp->u64 * cport->sampling_rate / + conf->sampling_rate; frame.type = PJMEDIA_FRAME_TYPE_NONE; frame.buf = NULL; frame.size = 0; @@ -2804,7 +2837,7 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, * number of channels as the conference bridge, transmit the * frame as is. */ - if (cport->clock_rate == conf->clock_rate && + if (cport->sampling_rate == conf->sampling_rate && cport->samples_per_frame == conf->samples_per_frame && cport->channel_count == conf->channel_count) { @@ -2830,11 +2863,11 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, } /* If it has different clock_rate, must resample. */ - if (cport->clock_rate != conf->clock_rate) { + if (cport->sampling_rate != conf->sampling_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); + cport->sampling_rate / conf->sampling_rate + 0.5); } else { /* Same clock rate. * Just copy the samples to tx_buffer. @@ -2884,8 +2917,8 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, /* Adjust timestamp as port may have different clock rate * than the bridge. */ - frame.timestamp.u64 = timestamp->u64 * cport->clock_rate / - conf->clock_rate; + frame.timestamp.u64 = timestamp->u64 * cport->sampling_rate / + conf->sampling_rate; /* Add timestamp for individual frame */ frame.timestamp.u64 += ts; @@ -2914,12 +2947,9 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, return status; } -static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_frame *frame) { - pj_assert(IS_PARALLEL == (conf_port->rx_frame_buf != NULL)); - /* the compiler should potentially optimize this away - * for the serial conference bridge - */ - if (IS_PARALLEL) +static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_frame *frame) +{ + if (conf_port->rx_frame_buf) return conf_port->rx_frame_buf; // parallel conference bridge else return (pj_int16_t *)frame->buf; // sequential conference bridge @@ -2928,13 +2958,13 @@ static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_f /* * Player callback. */ -static pj_status_t get_frame(pjmedia_port *this_port, +static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { - pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + pjmedia_conf *conf = (pjmedia_conf *)this_port->port_data.pdata; //parallelization requires signed int - pj_int32_t i, + pj_int32_t i, rc, begin, end, /* this is lower_bound and upper_bound for conf->ports[] array */ upper_bound; /* this is upper_bound for conf->active_ports[] array */ @@ -2942,7 +2972,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Check that correct size is specified. */ pj_assert(frame->size == conf->samples_per_frame * - conf->bits_per_sample / 8); + conf->bits_per_sample / 8); #if 0 /* Perform any queued operations that need to be synchronized with @@ -2965,38 +2995,36 @@ static pj_status_t get_frame(pjmedia_port *this_port, begin = conf->lower_bound; end = conf->upper_bound; - /* Step 1 initialization - * Single threaded loop to get the active_ports[] (transmitters) + /* Step 1 initialization + * Single threaded loop to get the active_ports[] (transmitters) * and active_listener[] (receivers) arrays. */ for (i = begin, upper_bound = 0; i < end; ++i) { - pj_assert( (unsigned)i < conf->max_ports ); + pj_assert((unsigned)i < conf->max_ports); struct conf_port *conf_port = conf->ports[i]; - /* Skip empty port. - * Newly added ports are not connected yet + /* Skip empty port. + * Newly added ports are not connected yet * and so we skip them as not active */ - if (is_port_active( conf_port )) - { + if (is_port_active(conf_port)) { /* Reset auto adjustment level for mixed signal. */ conf_port->mix_adj = NORMAL_LEVEL; if (conf_port->transmitter_cnt && conf_port->tx_setting != PJMEDIA_PORT_DISABLE) { - /* We need not reset mix_buf, we just want to copy the first + /* We need not reset mix_buf, we just want to copy the first * (and probably only) frame there. * The criteria for "this frame is from the first transmitter" * condition is: * (conf_port->last_timestamp.u64 != frame->timestamp.u64) */ - if (conf_port->last_timestamp.u64 == frame->timestamp.u64) - { //this port have not yet received data on this timer tick + if (conf_port->last_timestamp.u64 == frame->timestamp.u64) { //this port have not yet received data on this timer tick // enforce "this frame is from the first transmitter" condition //we usually shouldn't come here conf_port->last_timestamp.u64 = (frame->timestamp.u64 ? PJ_UINT64(0) : (pj_uint64_t)-1); } - pj_assert( conf_port->last_timestamp.u64 != frame->timestamp.u64 ); + pj_assert(conf_port->last_timestamp.u64 != frame->timestamp.u64); } /* Skip if we're not allowed to receive from this port. */ @@ -3013,14 +3041,13 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* compacted transmitter's array should help OpenMP to distribute task throught team's threads */ conf->active_ports[upper_bound++] = i; - pj_assert( upper_bound <= (end - begin) ); + pj_assert(upper_bound <= (end - begin)); } } - - if (upper_bound) - { + /* This optimization is mainly intended for debugging. */ + if (upper_bound) { pj_atomic_set(conf->active_ports_idx, upper_bound); /* Force frame type NONE */ @@ -3033,23 +3060,31 @@ static pj_status_t get_frame(pjmedia_port *this_port, * to mix_buf of all listeners of the port and * transmit whatever listeners have in their buffer */ - if (conf->is_parallel) - { - /* Start the parallel team + if (conf->is_parallel) { + /* Start the parallel team * all threads have reached the barrier already. * Should be a very short waiting. */ - pj_status_t status; - status = pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_SPIN_ONLY); - pj_assert(status == PJ_TRUE || status == PJ_FALSE); + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread at barrier", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1)); + + rc = pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_SPIN_ONLY); + pj_assert(rc == PJ_TRUE || rc == PJ_FALSE); + + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread activated, return = %d", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1, + rc)); + PJ_UNUSED_ARG(rc); + } perform_get_frame(conf); - pj_assert(pj_atomic_get(conf->active_ports_idx) == -PJ_CONF_BRIDGE_MAX_THREADS); + pj_assert(pj_atomic_get(conf->active_ports_idx) == -conf->threads); /* Return sound playback frame. */ - if (conf->sound_port != NULL) - { + if (conf->sound_port != NULL) { TRACE_((THIS_FILE, "write to audio, count=%d", conf->samples_per_frame)); pjmedia_copy_samples((pj_int16_t *)frame->buf, @@ -3065,8 +3100,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Perform any queued operations that need to be synchronized with * the clock such as connect, disonnect, remove. */ - if (!pj_list_empty(conf->op_queue)) - { + if (!pj_list_empty(conf->op_queue)) { pj_log_push_indent(); handle_op_queue(conf); pj_log_pop_indent(); @@ -3083,27 +3117,27 @@ static pj_status_t get_frame(pjmedia_port *this_port, } -static pj_status_t thread_pool_start(pjmedia_conf *conf) { +static pj_status_t thread_pool_start(pjmedia_conf *conf) +{ pj_status_t status; int i; pj_assert(conf->is_parallel); status = pj_barrier_create(conf->pool, - PJ_CONF_BRIDGE_MAX_THREADS, + conf->threads, &conf->active_thread); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); status = pj_barrier_create(conf->pool, - PJ_CONF_BRIDGE_MAX_THREADS, + conf->threads, &conf->barrier); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); /* Thread description's to register threads with pjsip */ - conf->pool_threads = (pj_thread_t **)pj_pool_calloc(conf->pool, PJ_CONF_BRIDGE_MAX_THREADS - 1, sizeof(pj_thread_t *)); + conf->pool_threads = (pj_thread_t **)pj_pool_calloc(conf->pool, conf->threads - 1, sizeof(pj_thread_t *)); PJ_ASSERT_RETURN(conf->pool_threads, PJ_ENOMEM); - for (i = 0; i < PJ_CONF_BRIDGE_MAX_THREADS - 1; i++) - { + for (i = 0; i < conf->threads - 1; i++) { char obj_name[PJ_MAX_OBJ_NAME]; pj_ansi_snprintf(obj_name, sizeof(obj_name), "conf_pool_%d", i); @@ -3119,48 +3153,67 @@ static pj_status_t thread_pool_start(pjmedia_conf *conf) { /* * Conf thread pool's thread function. */ -static int conf_thread(void *arg) { +static int conf_thread(void *arg) +{ pjmedia_conf *conf = (pjmedia_conf *)arg; - pj_status_t status; + pj_int32_t rc; pj_assert(conf->is_parallel); - /* don't go to the barrier while thread pool is creating - * if we can not create all threads, + /* don't go to the barrier while thread pool is creating + * if we can not create all threads, * we should not go to the barrier because we can not leave it */ - while (!conf->quit_flag && !conf->running) + while (!conf->running && !conf->quit_flag) { pj_thread_sleep(0); + //TODO classic option for using condition variable + } - while (!conf->quit_flag) - { - TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread at barrier", - pj_thread_get_name(pj_thread_this()), - conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1)); + if (conf->running) { - /* long waiting for next timer tick. if supported, blocks immediately */ - status = pj_barrier_wait(conf->active_thread, PJ_BARRIER_FLAGS_NO_DELETE | PJ_BARRIER_FLAGS_BLOCK_ONLY); - pj_assert(status == PJ_TRUE || status == PJ_FALSE); + while (1) { + TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread at barrier", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1)); - TRACE_EX((THIS_FILE, "%s: timestamp=%llu, thread activated, return = %d", - pj_thread_get_name(pj_thread_this()), - conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1, - status)); + /* long waiting for next timer tick. if supported, blocks immediately*/ + rc = pj_barrier_wait(conf->active_thread, + PJ_BARRIER_FLAGS_NO_DELETE | + PJ_BARRIER_FLAGS_BLOCK_ONLY); + pj_assert(rc == PJ_TRUE || rc == PJ_FALSE); + + /* quit_flag should be checked only once per loop and strictly + * after the active_thread barrier is crossed + */ + if (conf->quit_flag) { + TRACE_EX((THIS_FILE, + "%s: timestamp=%llu, thread exiting, barrier return = %d, quit_flag = %d", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1, + rc, conf->quit_flag)); + break; + } else { + TRACE_EX((THIS_FILE, + "%s: timestamp=%llu, thread activated, barrier return = %d", + pj_thread_get_name(pj_thread_this()), + conf->frame ? conf->frame->timestamp.u64 : (pj_uint64_t)-1, + rc)); + } - if (!conf->quit_flag) perform_get_frame(conf); + } } return 0; } -static void perform_get_frame(pjmedia_conf *conf) { +static void perform_get_frame(pjmedia_conf *conf) +{ pj_status_t status; pj_atomic_value_t i; pjmedia_frame *frame = conf->frame; - while ((i = pj_atomic_dec_and_get(conf->active_ports_idx)) >= 0) - { + while ((i = pj_atomic_dec_and_get(conf->active_ports_idx)) >= 0) { pj_int32_t port_idx = conf->active_ports[i]; pj_assert((unsigned)port_idx < conf->max_ports); struct conf_port *conf_port = conf->ports[port_idx]; @@ -3173,15 +3226,13 @@ static void perform_get_frame(pjmedia_conf *conf) { * 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) - { + if (conf_port->delay_buf != NULL) { /* Check that correct size is specified. */ pj_assert(frame->size == conf/*_port*/->rx_frame_buf_cap); /* read data to different buffers to different conf_port's parallel processing */ status = pjmedia_delay_buf_get(conf_port->delay_buf, p_in); - if (status != PJ_SUCCESS) - { + if (status != PJ_SUCCESS) { conf_port->rx_level = 0; TRACE_EX((THIS_FILE, "%s: No frame from the passive port (%.*s, %d, listener_cnt=%d)", pj_thread_get_name(pj_thread_this()), @@ -3191,9 +3242,7 @@ static void perform_get_frame(pjmedia_conf *conf) { continue; } - } - else - { + } else { pjmedia_frame_type frame_type; @@ -3214,22 +3263,19 @@ static void perform_get_frame(pjmedia_conf *conf) { * The only thing that can happen is that port removing will be sheduled * there but still will processed later (see Step 3). */ - if (conf->ports[port_idx] != conf_port) - { + if (conf->ports[port_idx] != conf_port) { //conf_port->rx_level = 0; PJ_LOG(4, (THIS_FILE, "Port %d is removed when we call get_frame()", port_idx)); continue; } - if (status != PJ_SUCCESS) - { + if (status != PJ_SUCCESS) { /* check status and disable port here. * Prevent multiply eof callback invoke, * if fileplayer has reached EOF (i.e. status == PJ_EEOF) */ - if (status == PJ_EEOF) - { + if (status == PJ_EEOF) { TRACE_((THIS_FILE, "Port %.*s reached EOF and is now disabled", (int)conf_port->name.slen, conf_port->name.ptr)); @@ -3259,8 +3305,7 @@ static void perform_get_frame(pjmedia_conf *conf) { #if 0 /* Check that the port is not removed when we call get_frame() */ - if (conf->ports[i] == NULL) - { + if (conf->ports[i] == NULL) { /* if port is removed old conf_port may point to not authorized memory */ conf_port->rx_level = 0; continue; @@ -3268,8 +3313,7 @@ static void perform_get_frame(pjmedia_conf *conf) { #endif /* Ignore if we didn't get any frame */ - if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) - { + if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) { conf_port->rx_level = 0; TRACE_EX((THIS_FILE, "%s: frame_type %d != PJMEDIA_FRAME_TYPE_AUDIO from the port (%.*s, %d, listener_cnt=%d)", pj_thread_get_name(pj_thread_this()), @@ -3287,10 +3331,8 @@ static void perform_get_frame(pjmedia_conf *conf) { /* 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) - { + 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. @@ -3312,11 +3354,8 @@ static void perform_get_frame(pjmedia_conf *conf) { 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) - { + } else { + for (j = 0; j < conf->samples_per_frame; ++j) { level += (p_in[j] >= 0 ? p_in[j] : -p_in[j]); } } @@ -3338,8 +3377,7 @@ static void perform_get_frame(pjmedia_conf *conf) { pj_int32_t cj, listener_cnt; //parallelization requires signed int /* Add the signal to all listeners. */ - for (cj = 0, listener_cnt = conf_port->listener_cnt; cj < listener_cnt; ++cj) - { + for (cj = 0, listener_cnt = conf_port->listener_cnt; cj < listener_cnt; ++cj) { struct conf_port *listener; pj_int16_t *p_in_conn_leveled; SLOT_TYPE listener_slot = conf_port->listener_slots[cj]; @@ -3347,8 +3385,7 @@ static void perform_get_frame(pjmedia_conf *conf) { listener = conf->ports[listener_slot]; /* Skip if this listener doesn't want to receive audio */ - if (listener->tx_setting != PJMEDIA_PORT_ENABLE) - { + if (listener->tx_setting != PJMEDIA_PORT_ENABLE) { TRACE_EX((THIS_FILE, "%s: listener (%.*s, %d, transmitter_cnt=%d) doesn't want to receive audio from the port (%.*s, %d, listener_cnt=%d)", pj_thread_get_name(pj_thread_this()), (int)listener->name.slen, @@ -3362,11 +3399,9 @@ static void perform_get_frame(pjmedia_conf *conf) { } /* apply connection level, if not normal */ - if (conf_port->listener_adj_level[cj] != NORMAL_LEVEL) - { + if (conf_port->listener_adj_level[cj] != NORMAL_LEVEL) { unsigned k = 0; - for (; k < conf->samples_per_frame; ++k) - { + for (; k < conf->samples_per_frame; ++k) { /* For the level adjustment, we need to store the sample to * a temporary 32bit integer value to avoid overflowing the * 16bit sample storage. @@ -3390,9 +3425,7 @@ static void perform_get_frame(pjmedia_conf *conf) { /* take the leveled frame */ p_in_conn_leveled = conf_port->adj_level_buf; - } - else - { + } else { /* take the frame as-is */ p_in_conn_leveled = p_in; } @@ -3401,8 +3434,7 @@ static void perform_get_frame(pjmedia_conf *conf) { mix_buf = listener->mix_buf; pj_bool_t ready_to_transmit = PJ_FALSE; - if (listener->transmitter_cnt > 1) - { + if (listener->transmitter_cnt > 1) { /* Mixing signals, * and calculate appropriate level adjustment if there is * any overflowed level in the mixed signal. @@ -3411,16 +3443,14 @@ static void perform_get_frame(pjmedia_conf *conf) { pj_int32_t mix_buf_min = 0; pj_int32_t mix_buf_max = 0; - pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); + pj_assert(conf->is_parallel == (listener->tx_Lock != NULL)); //protect listener->mix_buf, listener->mix_adj, listener->last_timestamp - if (IS_PARALLEL) + if (listener->tx_Lock) pj_lock_acquire(listener->tx_Lock); - if (listener->last_timestamp.u64 == frame->timestamp.u64) - { + if (listener->last_timestamp.u64 == frame->timestamp.u64) { //this frame is NOT from the first transmitter - for (k = 0; k < samples_per_frame; ++k) - { + for (k = 0; k < samples_per_frame; ++k) { mix_buf[k] += p_in_conn_leveled[k]; // not the first - sum if (mix_buf[k] < mix_buf_min) mix_buf_min = mix_buf[k]; @@ -3437,15 +3467,12 @@ static void perform_get_frame(pjmedia_conf *conf) { conf_port->name.ptr, port_idx, conf_port->listener_cnt)); - } - else - { + } else { //this frame is from the first transmitter listener->last_timestamp = frame->timestamp; /* We do not want to reset buffer, we just copy the first frame there. */ - for (k = 0; k < samples_per_frame; ++k) - { + for (k = 0; k < samples_per_frame; ++k) { mix_buf[k] = p_in_conn_leveled[k]; // the first - copy if (mix_buf[k] < mix_buf_min) mix_buf_min = mix_buf[k]; @@ -3466,8 +3493,7 @@ static void perform_get_frame(pjmedia_conf *conf) { } /* Check if normalization adjustment needed. */ - if (mix_buf_min < MIN_LEVEL || mix_buf_max > MAX_LEVEL) - { + if (mix_buf_min < MIN_LEVEL || mix_buf_max > MAX_LEVEL) { int tmp_adj; if (-mix_buf_min > mix_buf_max) @@ -3479,21 +3505,17 @@ static void perform_get_frame(pjmedia_conf *conf) { listener->mix_adj = tmp_adj; } - if (listener->transmitter_cnt == listener->mixed_cnt + 1) - { + if (listener->transmitter_cnt == listener->mixed_cnt + 1) { ready_to_transmit = PJ_TRUE; listener->mixed_cnt = 0; - } - else + } else ++listener->mixed_cnt; - pj_assert(IS_PARALLEL == (listener->tx_Lock != NULL)); - if (IS_PARALLEL) + pj_assert(conf->is_parallel == (listener->tx_Lock != NULL)); + if (listener->tx_Lock) pj_lock_release(listener->tx_Lock); - } - else - { + } else { //this frame is from the only transmitter pj_assert(listener->transmitter_cnt == 1 && listener->last_timestamp.u64 != frame->timestamp.u64); listener->last_timestamp = frame->timestamp; @@ -3504,8 +3526,7 @@ static void perform_get_frame(pjmedia_conf *conf) { */ unsigned k, samples_per_frame = conf->samples_per_frame; - for (k = 0; k < samples_per_frame; ++k) - { + for (k = 0; k < samples_per_frame; ++k) { mix_buf[k] = p_in_conn_leveled[k]; // here copying 16 bit value to 32 bit dst } TRACE_EX((THIS_FILE, "%s: listener %p (%.*s, %d, transmitter_cnt=%d)" @@ -3530,8 +3551,7 @@ static void perform_get_frame(pjmedia_conf *conf) { pjmedia_frame_type frm_type; status = write_port(conf, listener, &frame->timestamp, &frm_type); #if 0 - if (status != PJ_SUCCESS) - { + 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 @@ -3551,8 +3571,7 @@ static void perform_get_frame(pjmedia_conf *conf) { /* Set the type of frame to be returned to sound playback * device. */ - if (status == PJ_SUCCESS && listener_slot == 0 && listener->tx_level) - { + if (status == PJ_SUCCESS && listener_slot == 0 && listener->tx_level) { /* MUST set frame type */ conf->frame->type = frm_type; conf->sound_port = listener; @@ -3563,8 +3582,8 @@ static void perform_get_frame(pjmedia_conf *conf) { } /* loop of all conf ports */ - if (conf->is_parallel) - { + if (conf->is_parallel) { + pj_int32_t rc; TRACE_EX((THIS_FILE, "%s: timestamp=%llu, ARRIVE AT BARRIER", pj_thread_get_name(pj_thread_this()), frame->timestamp.u64)); @@ -3572,13 +3591,14 @@ static void perform_get_frame(pjmedia_conf *conf) { /* If we carefully balance the work, we won't have to wait long here. * let it be the default waiting (spin then block) */ - status = pj_barrier_wait(conf->barrier, PJ_BARRIER_FLAGS_NO_DELETE); - pj_assert(status == PJ_TRUE || status == PJ_FALSE); + rc = pj_barrier_wait(conf->barrier, PJ_BARRIER_FLAGS_NO_DELETE); + pj_assert(rc == PJ_TRUE || rc == PJ_FALSE); TRACE_EX((THIS_FILE, "%s: timestamp=%llu, BARRIER OVERCOME, return = %d", pj_thread_get_name(pj_thread_this()), frame->timestamp.u64, - status)); + rc)); + PJ_UNUSED_ARG(rc); } } diff --git a/pjmedia/src/test/mips_test.c b/pjmedia/src/test/mips_test.c index c2a2e93e74..1feb235dab 100644 --- a/pjmedia/src/test/mips_test.c +++ b/pjmedia/src/test/mips_test.c @@ -492,6 +492,7 @@ static pjmedia_port* init_conf_port(unsigned nb_participant, pjmedia_conf *conf; unsigned i, nport = 1; pj_status_t status; + pjmedia_conf_param param; PJ_UNUSED_ARG(flags); @@ -499,9 +500,21 @@ static pjmedia_port* init_conf_port(unsigned nb_participant, pj_bzero(te->pdata, sizeof(te->pdata)); /* Create conf */ - status = pjmedia_conf_create(pool, 2+nb_participant*2, clock_rate, - channel_count, samples_per_frame, 16, - PJMEDIA_CONF_NO_DEVICE, &conf); + pjmedia_conf_param_default(¶m); + + param.max_slots = 2+nb_participant*2; + param.sampling_rate = clock_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = 16; + param.options = PJMEDIA_CONF_NO_DEVICE; + param.worker_threads = 0; + + status = pjmedia_conf_create2(pool, ¶m, &conf); + + //status = pjmedia_conf_create(pool, 2+nb_participant*2, clock_rate, + // channel_count, samples_per_frame, 16, + // PJMEDIA_CONF_NO_DEVICE, &conf); if (status != PJ_SUCCESS) return NULL; te->pdata[0] = conf; @@ -2349,6 +2362,7 @@ static pj_timestamp run_entry(unsigned clock_rate, struct test_entry *e) pj_int16_t pcm[32000 * PTIME / 1000]; pjmedia_port *gen_port; pj_status_t status; + pj_uint64_t u64; samples_per_frame = clock_rate * PTIME / 1000; @@ -2373,8 +2387,9 @@ static pj_timestamp run_entry(unsigned clock_rate, struct test_entry *e) } pj_get_timestamp(&t0); - for (j=0; jvalid_op==OP_GET_PUT) { frm.buf = (void*)pcm; From b1dbb767ef71f6ea4019e2596d7d617829cfd5a2 Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Mon, 17 Feb 2025 19:57:19 +0300 Subject: [PATCH 14/16] PJMEDIA_CONF_THREADS macro --- pjmedia/include/pjmedia/conference.h | 115 +++++++++++++++++++++++++-- pjmedia/src/pjmedia/conference.c | 20 +---- 2 files changed, 114 insertions(+), 21 deletions(-) diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h index 2d0e1b5a8b..d7cf575a46 100644 --- a/pjmedia/include/pjmedia/conference.h +++ b/pjmedia/include/pjmedia/conference.h @@ -60,6 +60,22 @@ PJ_BEGIN_DECL */ #define PJMEDIA_CONF_SWITCH_SIGNATURE PJMEDIA_SIG_PORT_CONF_SWITCH +/** + * Total number of threads that can be used by the conference bridge + * including get_frame() thread. + * This value is used to determine if the conference bridge should be + * implemented as a parallel bridge or not. + * If this value is set to 1, the conference bridge will be implemented as a + * serial bridge, otherwise it will be implemented as a parallel bridge. + * PJMEDIA_CONF_THREADS should not be less than 1. + * + * DEFAULT: 1 - serial bridge + * Please set this macro in the config_site.h to a value greater than 1 + * to enable parallel bridge. + */ +#ifndef PJMEDIA_CONF_THREADS +# define PJMEDIA_CONF_THREADS 1 +#endif /** * Opaque type for conference bridge. @@ -105,19 +121,72 @@ enum pjmedia_conf_option }; /** - * Conference bridge creation parameters. -//TODO + * This structure specifies the conference bridge creation parameters. */ typedef struct pjmedia_conf_param { + /** + * Maximum number of slots/ports to be created in + * the bridge. Note that the bridge internally uses + * one port for the sound device, so the actual + * maximum number of ports will be less one than + * this value. + */ unsigned max_slots; + + /** + * Set the sampling rate of the bridge. This value + * is also used to set the sampling rate of the + * sound device. + */ unsigned sampling_rate; + + /** + * Number of channels in the PCM stream. Normally + * the value will be 1 for mono, but application may + * specify a value of 2 for stereo. Note that all + * ports that will be connected to the bridge MUST + * have the same number of channels as the bridge. + */ unsigned channel_count; + + /** + * Set the number of samples per frame. This value + * is also used to set the sound device. + */ unsigned samples_per_frame; + + /** + * Set the number of bits per sample. This value + * is also used to set the sound device. Currently + * only 16bit per sample is supported. + */ unsigned bits_per_sample; + + /** + * Bitmask options to be set for the bridge. The + * options are constructed from #pjmedia_conf_option + * enumeration. + * The default value is zero. + */ unsigned options; - /** The number of worker threads to use, from 0-N. - * Zero means the operations will be done only by get_frame() thread. + + /** + * The number of worker threads to use. + * Zero means the operations will be done only by get_frame() thread, + * i.e. conference bridge will be sequential. + * Set this parameter to non-zero value to enable parallel processing. + * The number of worker threads should be less than or equal to the number + * of the processor cores. However, the optimal number of worker threads + * is application and hardware dependent. + * The default value is zero - sequential conference bridge. + * This value is compatible with previous behavior. + * At compile time application developer can change the default value by + * setting #PJMEDIA_CONF_THREADS macro in the config_site.h. + * PJMEDIA_CONF_THREADS is total number of conference bridge threads + * including get_frame() thread. worker_threads is the number of conference + * bridge threads excluding get_frame() thread. + * As a general rule worker_threads is 1 less than PJMEDIA_CONF_THREADS. */ unsigned worker_threads; } pjmedia_conf_param; @@ -129,6 +198,8 @@ typedef struct pjmedia_conf_param PJ_INLINE(void) pjmedia_conf_param_default(pjmedia_conf_param *param) { pj_bzero(param, sizeof(pjmedia_conf_param)); + /* Set the default values */ + param->worker_threads = PJMEDIA_CONF_THREADS-1; } /** @@ -197,7 +268,41 @@ PJ_DECL(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, pjmedia_conf **p_conf ); /** -//TODO + * 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(), or to a master port (pjmedia_master_port) + * if application doesn't want to instantiate any sound devices. + * + * The sound device or master port are 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. + * @param param The conference bridge creation parameters. + * See #pjmedia_conf_param for more information. + * @param p_conf Pointer to receive the conference bridge instance. + * + * @return PJ_SUCCESS if conference bridge can be created. */ PJ_DECL(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool, pjmedia_conf_param *param, diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index ddb3240195..ea98a790f6 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -41,21 +41,6 @@ #if (!defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0) && \ (!defined(PJMEDIA_CONF_USE_OPENMP) || PJMEDIA_CONF_USE_OPENMP == 0) -#ifndef PJ_CONF_BRIDGE_MAX_THREADS -/** - * The maximum number of threads that can be used by the conference bridge. - * This value is used to determine if the conference bridge should be - * implemented as a parallel bridge or not. - * If this value is set to 1, the conference bridge will be implemented as a - * serial bridge, otherwise it will be implemented as a parallel bridge. - * - * DEFAULT: 1 - serial bridge - * Please set this macro in the config_site.h to a value greater than 1 - * to enable parallel bridge. - */ -# define PJ_CONF_BRIDGE_MAX_THREADS 1 -#endif - /* CONF_DEBUG enables detailed operation of the conference bridge. * Beware that it prints large amounts of logs (several lines per frame). @@ -955,7 +940,10 @@ PJ_DEF(pj_status_t) pjmedia_conf_create(pj_pool_t *pool, param.samples_per_frame = samples_per_frame; param.bits_per_sample = bits_per_sample; param.options = options; - param.worker_threads = PJ_CONF_BRIDGE_MAX_THREADS - 1; + /* Let's skip setting the parameter for the number of worker threads here + * to use the default number of worker threads. + * param.worker_threads = PJMEDIA_CONF_THREADS-1; + */ return pjmedia_conf_create2(pool, ¶m, p_conf); } From 90ec90328d3cc360c6a1c88c2477eaa130e61aea Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Mon, 17 Feb 2025 20:52:41 +0300 Subject: [PATCH 15/16] pjsua, pjsua2 runtime setting for the conference bridge threads --- pjmedia/include/pjmedia/conference.h | 8 +++----- pjsip-apps/src/python/_pjsua.h | 14 ++++++++++++++ pjsip/include/pjsua-lib/pjsua.h | 13 +++++++++++++ pjsip/include/pjsua2/endpoint.hpp | 13 +++++++++++++ pjsip/src/pjsua-lib/pjsua_aud.c | 26 +++++++++++++++++++------- pjsip/src/pjsua-lib/pjsua_core.c | 1 + pjsip/src/pjsua-lib/pjsua_media.c | 4 ++++ pjsip/src/pjsua2/endpoint.cpp | 4 ++++ 8 files changed, 71 insertions(+), 12 deletions(-) diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h index d7cf575a46..c9ab57ccd4 100644 --- a/pjmedia/include/pjmedia/conference.h +++ b/pjmedia/include/pjmedia/conference.h @@ -61,17 +61,15 @@ PJ_BEGIN_DECL #define PJMEDIA_CONF_SWITCH_SIGNATURE PJMEDIA_SIG_PORT_CONF_SWITCH /** - * Total number of threads that can be used by the conference bridge - * including get_frame() thread. + * The default value for the total number of threads, including get_frame() + * thread, that can be used by the conference bridge. * This value is used to determine if the conference bridge should be * implemented as a parallel bridge or not. * If this value is set to 1, the conference bridge will be implemented as a * serial bridge, otherwise it will be implemented as a parallel bridge. * PJMEDIA_CONF_THREADS should not be less than 1. * - * DEFAULT: 1 - serial bridge - * Please set this macro in the config_site.h to a value greater than 1 - * to enable parallel bridge. + * Default value: 1 - serial bridge */ #ifndef PJMEDIA_CONF_THREADS # define PJMEDIA_CONF_THREADS 1 diff --git a/pjsip-apps/src/python/_pjsua.h b/pjsip-apps/src/python/_pjsua.h index d0e2e0b525..fd51d143b9 100644 --- a/pjsip-apps/src/python/_pjsua.h +++ b/pjsip-apps/src/python/_pjsua.h @@ -471,6 +471,7 @@ typedef struct unsigned audio_frame_ptime; int snd_auto_close_time; unsigned max_media_ports; + unsigned conf_threads; int has_ioqueue; unsigned thread_cnt; unsigned quality; @@ -535,6 +536,17 @@ static PyMemberDef PyObj_pjsua_media_config_members[] = "support all of them. However, the larger the value, the more " "computations are performed." }, + { + "conf_threads", T_INT, + offsetof(PyObj_pjsua_media_config, conf_threads), 0, + "Total number of threads that can be used by the conference bridge " + "including get_frame() thread. " + "This value is used to determine if the conference bridge should be " + "implemented as a parallel bridge or not. " + "If this value is set to 1, the conference bridge will be implemented " + "as a serial bridge, otherwise it will be implemented as a parallel " + "bridge. Should not be less than 1." + }, { "has_ioqueue", T_INT, offsetof(PyObj_pjsua_media_config, has_ioqueue), 0, @@ -685,6 +697,7 @@ static void PyObj_pjsua_media_config_import(PyObj_pjsua_media_config *obj, obj->audio_frame_ptime = cfg->audio_frame_ptime; obj->snd_auto_close_time= cfg->snd_auto_close_time; obj->max_media_ports = cfg->max_media_ports; + obj->conf_threads = cfg->conf_threads; obj->has_ioqueue = cfg->has_ioqueue; obj->thread_cnt = cfg->thread_cnt; obj->quality = cfg->quality; @@ -732,6 +745,7 @@ static void PyObj_pjsua_media_config_export(pjsua_media_config *cfg, cfg->audio_frame_ptime = obj->audio_frame_ptime; cfg->snd_auto_close_time=obj->snd_auto_close_time; cfg->max_media_ports = obj->max_media_ports; + cfg->conf_threads = obj->conf_threads; cfg->has_ioqueue = obj->has_ioqueue; cfg->thread_cnt = obj->thread_cnt; cfg->quality = obj->quality; diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 7c4092e102..97133b0f2d 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -7380,6 +7380,19 @@ struct pjsua_media_config */ unsigned max_media_ports; + /** + * Total number of threads that can be used by the conference bridge + * including get_frame() thread. + * This value is used to determine if the conference bridge should be + * implemented as a parallel bridge or not. + * If this value is set to 1, the conference bridge will be implemented as a + * serial bridge, otherwise it will be implemented as a parallel bridge. + * Should not be less than 1. + * + * Default value: PJMEDIA_CONF_THREADS + */ + unsigned conf_threads; + /** * Specify whether the media manager should manage its own * ioqueue for the RTP/RTCP sockets. If yes, ioqueue will be created diff --git a/pjsip/include/pjsua2/endpoint.hpp b/pjsip/include/pjsua2/endpoint.hpp index cb78cc3b6c..3afc38ee52 100644 --- a/pjsip/include/pjsua2/endpoint.hpp +++ b/pjsip/include/pjsua2/endpoint.hpp @@ -981,6 +981,19 @@ struct MediaConfig : public PersistentObject */ unsigned maxMediaPorts; + /** + * Total number of threads that can be used by the conference bridge + * including get_frame() thread. + * This value is used to determine if the conference bridge should be + * implemented as a parallel bridge or not. + * If this value is set to 1, the conference bridge will be implemented as a + * serial bridge, otherwise it will be implemented as a parallel bridge. + * Should not be less than 1. + * + * Default value: PJMEDIA_CONF_THREADS + */ + unsigned confThreads; + /** * Specify whether the media manager should manage its own * ioqueue for the RTP/RTCP sockets. If yes, ioqueue will be created diff --git a/pjsip/src/pjsua-lib/pjsua_aud.c b/pjsip/src/pjsua-lib/pjsua_aud.c index 24003b4f18..3321d97ccd 100644 --- a/pjsip/src/pjsua-lib/pjsua_aud.c +++ b/pjsip/src/pjsua-lib/pjsua_aud.c @@ -407,14 +407,26 @@ pj_status_t pjsua_aud_subsys_init() opt |= PJMEDIA_CONF_USE_LINEAR; } + pjmedia_conf_param param; + pjmedia_conf_param_default(¶m); + + param.max_slots = pjsua_var.media_cfg.max_media_ports; + param.sampling_rate = pjsua_var.media_cfg.clock_rate; + param.channel_count = pjsua_var.mconf_cfg.channel_count; + param.samples_per_frame = pjsua_var.mconf_cfg.samples_per_frame; + param.bits_per_sample = pjsua_var.mconf_cfg.bits_per_sample; + param.options = opt; + param.worker_threads = pjsua_var.media_cfg.conf_threads-1; + /* Init conference bridge. */ - status = pjmedia_conf_create(pjsua_var.pool, - pjsua_var.media_cfg.max_media_ports, - pjsua_var.media_cfg.clock_rate, - pjsua_var.mconf_cfg.channel_count, - pjsua_var.mconf_cfg.samples_per_frame, - pjsua_var.mconf_cfg.bits_per_sample, - opt, &pjsua_var.mconf); + status = pjmedia_conf_create2(pjsua_var.pool, ¶m, &pjsua_var.mconf); + //status = pjmedia_conf_create(pjsua_var.pool, + // pjsua_var.media_cfg.max_media_ports, + // pjsua_var.media_cfg.clock_rate, + // pjsua_var.mconf_cfg.channel_count, + // pjsua_var.mconf_cfg.samples_per_frame, + // pjsua_var.mconf_cfg.bits_per_sample, + // opt, &pjsua_var.mconf); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error creating conference bridge", status); diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 176e8d6309..95c6f0a581 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -419,6 +419,7 @@ PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg) cfg->channel_count = 1; cfg->audio_frame_ptime = PJSUA_DEFAULT_AUDIO_FRAME_PTIME; cfg->max_media_ports = PJSUA_MAX_CONF_PORTS; + cfg->conf_threads = PJMEDIA_CONF_THREADS; cfg->has_ioqueue = PJ_TRUE; cfg->thread_cnt = 1; cfg->quality = PJSUA_DEFAULT_CODEC_QUALITY; diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index bfcd5d0b24..e3beaec75b 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -83,6 +83,10 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2; } + if (pjsua_var.media_cfg.conf_threads < 1) { + pjsua_var.media_cfg.conf_threads = 1; + } + /* Create media endpoint. */ status = pjmedia_endpt_create(&pjsua_var.cp.factory, pjsua_var.media_cfg.has_ioqueue? NULL : diff --git a/pjsip/src/pjsua2/endpoint.cpp b/pjsip/src/pjsua2/endpoint.cpp index cdfbb0cf73..7f0d5d6cac 100644 --- a/pjsip/src/pjsua2/endpoint.cpp +++ b/pjsip/src/pjsua2/endpoint.cpp @@ -437,6 +437,7 @@ void MediaConfig::fromPj(const pjsua_media_config &mc) this->channelCount = mc.channel_count; this->audioFramePtime = mc.audio_frame_ptime; this->maxMediaPorts = mc.max_media_ports; + this->confThreads = mc.conf_threads; this->hasIoqueue = PJ2BOOL(mc.has_ioqueue); this->threadCnt = mc.thread_cnt; this->quality = mc.quality; @@ -470,6 +471,7 @@ pjsua_media_config MediaConfig::toPj() const mcfg.channel_count = this->channelCount; mcfg.audio_frame_ptime = this->audioFramePtime; mcfg.max_media_ports = this->maxMediaPorts; + mcfg.conf_threads = this->confThreads; mcfg.has_ioqueue = this->hasIoqueue; mcfg.thread_cnt = this->threadCnt; mcfg.quality = this->quality; @@ -502,6 +504,7 @@ void MediaConfig::readObject(const ContainerNode &node) PJSUA2_THROW(Error) NODE_READ_UNSIGNED( this_node, channelCount); NODE_READ_UNSIGNED( this_node, audioFramePtime); NODE_READ_UNSIGNED( this_node, maxMediaPorts); + NODE_READ_UNSIGNED( this_node, confThreads); NODE_READ_BOOL ( this_node, hasIoqueue); NODE_READ_UNSIGNED( this_node, threadCnt); NODE_READ_UNSIGNED( this_node, quality); @@ -533,6 +536,7 @@ void MediaConfig::writeObject(ContainerNode &node) const PJSUA2_THROW(Error) NODE_WRITE_UNSIGNED( this_node, channelCount); NODE_WRITE_UNSIGNED( this_node, audioFramePtime); NODE_WRITE_UNSIGNED( this_node, maxMediaPorts); + NODE_WRITE_UNSIGNED( this_node, confThreads); NODE_WRITE_BOOL ( this_node, hasIoqueue); NODE_WRITE_UNSIGNED( this_node, threadCnt); NODE_WRITE_UNSIGNED( this_node, quality); From 5f9f2ab4b23a32c912c94451a4bb91947c33b90c Mon Sep 17 00:00:00 2001 From: Leonid Goltsblat Date: Wed, 26 Feb 2025 01:27:00 +0300 Subject: [PATCH 16/16] resolve on code review --- pjlib/include/pj/os.h | 4 +- pjmedia/include/pjmedia/conference.h | 3 +- pjmedia/src/pjmedia/conference.c | 59 ++++++++++++++-------------- pjsip/include/pjsua-lib/pjsua.h | 1 + pjsip/include/pjsua2/endpoint.hpp | 1 + 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/pjlib/include/pj/os.h b/pjlib/include/pj/os.h index 35d44fe2f8..c8287a91d4 100644 --- a/pjlib/include/pj/os.h +++ b/pjlib/include/pj/os.h @@ -1108,8 +1108,8 @@ PJ_DECL(pj_status_t) pj_event_destroy(pj_event_t *event); */ /** - * Flags that control the behavior of the barrier - * Supported on Windows platform starting from Windows 8 + * Flags that control the behavior of the barrier. + * Only supported on Windows platform starting from Windows 8. * Otherwize, the flags are ignored. */ enum pj_barrier_flags { diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h index c9ab57ccd4..00d9999b30 100644 --- a/pjmedia/include/pjmedia/conference.h +++ b/pjmedia/include/pjmedia/conference.h @@ -170,7 +170,7 @@ typedef struct pjmedia_conf_param unsigned options; /** - * The number of worker threads to use. + * The number of worker threads to use by conference bridge. * Zero means the operations will be done only by get_frame() thread, * i.e. conference bridge will be sequential. * Set this parameter to non-zero value to enable parallel processing. @@ -185,6 +185,7 @@ typedef struct pjmedia_conf_param * including get_frame() thread. worker_threads is the number of conference * bridge threads excluding get_frame() thread. * As a general rule worker_threads is 1 less than PJMEDIA_CONF_THREADS. + * This value will not be used for switchboard. */ unsigned worker_threads; } pjmedia_conf_param; diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index ea98a790f6..e557ce519b 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -909,7 +909,7 @@ static pj_status_t create_sound_port( pj_pool_t *pool, conf_port->slot = 0; #endif //CONF_DEBUG_EX - /* Add the port to the bridge */ + /* Add the port to the bridge */ conf->ports[0] = conf_port; #if 0 conf->port_cnt++; // the port will become active only when connected @@ -921,14 +921,14 @@ static pj_status_t create_sound_port( pj_pool_t *pool, /* * Create conference bridge. */ -PJ_DEF(pj_status_t) pjmedia_conf_create(pj_pool_t *pool, - unsigned max_slots, - unsigned sampling_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - unsigned options, - pjmedia_conf **p_conf) +PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, + unsigned max_slots, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned options, + pjmedia_conf **p_conf ) { pjmedia_conf_param param; @@ -954,8 +954,9 @@ PJ_DEF(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool_, { pj_pool_t *pool; pjmedia_conf *conf; - const pj_str_t name = {"Conf", 4}; + const pj_str_t name = { "Conf", 4 }; pj_status_t status; + unsigned i; PJ_ASSERT_RETURN(param && p_conf, PJ_EINVAL); @@ -1051,8 +1052,8 @@ PJ_DEF(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool_, * master port. */ if (conf->snd_dev_port) { - status = pjmedia_snd_port_connect(conf->snd_dev_port, - conf->master_port); + status = pjmedia_snd_port_connect( conf->snd_dev_port, + conf->master_port ); if (status != PJ_SUCCESS) { pjmedia_conf_destroy(conf); return status; @@ -1084,7 +1085,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool_, #endif conf->free_port_slots = pj_pool_calloc(pool, conf->max_ports, sizeof(port_slot)); PJ_ASSERT_ON_FAIL(conf->free_port_slots, {pjmedia_conf_destroy(conf); return PJ_ENOMEM;}); - unsigned i = conf->max_ports; + i = conf->max_ports; while (i--) { /* prepare unused slots to later reservation, reverse order due to FILO */ if (!conf->ports[i]) { /* If sound device was created, skip it's slot */ status = conf_release_port(conf, i); @@ -1140,7 +1141,7 @@ static pj_status_t resume_sound( pjmedia_conf *conf ) /** * Destroy conference bridge. */ -PJ_DEF(pj_status_t) pjmedia_conf_destroy(pjmedia_conf *conf) +PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) { unsigned i; pj_int32_t rc; @@ -1203,7 +1204,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy(pjmedia_conf *conf) handle_op_queue(conf); /* Remove all ports (may destroy them too). */ - for (i = 0; i < conf->max_ports; ++i) { + for (i=0; imax_ports; ++i) { if (conf->ports[i]) { op_param oprm = {0}; oprm.remove_port.port = i; @@ -2946,7 +2947,7 @@ static inline pj_int16_t *get_read_buffer(struct conf_port *conf_port, pjmedia_f /* * Player callback. */ -static pj_status_t get_frame(pjmedia_port *this_port, +static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { pjmedia_conf *conf = (pjmedia_conf *)this_port->port_data.pdata; @@ -2960,7 +2961,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Check that correct size is specified. */ pj_assert(frame->size == conf->samples_per_frame * - conf->bits_per_sample / 8); + conf->bits_per_sample / 8); #if 0 /* Perform any queued operations that need to be synchronized with @@ -3212,7 +3213,7 @@ static void perform_get_frame(pjmedia_conf *conf) /* Get frame from this port. * For passive ports, get the frame from the delay_buf. - * For other ports, get the frame from the port. + * For other ports, get the frame from the port. */ if (conf_port->delay_buf != NULL) { @@ -3320,7 +3321,7 @@ static void perform_get_frame(pjmedia_conf *conf) * 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 (j=0; jsamples_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. @@ -3339,12 +3340,12 @@ static void perform_get_frame(pjmedia_conf *conf) 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]); + 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]); + for (j=0; jsamples_per_frame; ++j) { + level += (p_in[j]>=0 ? p_in[j] : -p_in[j]); } } @@ -3366,6 +3367,7 @@ static void perform_get_frame(pjmedia_conf *conf) /* Add the signal to all listeners. */ for (cj = 0, listener_cnt = conf_port->listener_cnt; cj < listener_cnt; ++cj) { + pj_int32_t *mix_buf; struct conf_port *listener; pj_int16_t *p_in_conn_leveled; SLOT_TYPE listener_slot = conf_port->listener_slots[cj]; @@ -3418,7 +3420,6 @@ static void perform_get_frame(pjmedia_conf *conf) p_in_conn_leveled = p_in; } - pj_int32_t *mix_buf; mix_buf = listener->mix_buf; pj_bool_t ready_to_transmit = PJ_FALSE; @@ -3488,7 +3489,7 @@ static void perform_get_frame(pjmedia_conf *conf) mix_buf_max = -mix_buf_min; /* NORMAL_LEVEL * MAX_LEVEL / mix_buf_max; */ - tmp_adj = (MAX_LEVEL << 7) / mix_buf_max; + tmp_adj = (MAX_LEVEL<<7) / mix_buf_max; if (tmp_adj < listener->mix_adj) listener->mix_adj = tmp_adj; } @@ -3596,8 +3597,8 @@ static void perform_get_frame(pjmedia_conf *conf) /* * get_frame() for passive port */ -static pj_status_t get_frame_pasv(pjmedia_port *this_port, - pjmedia_frame *frame ) +static pj_status_t get_frame_pasv(pjmedia_port *this_port, + pjmedia_frame *frame) { pj_assert(0); PJ_UNUSED_ARG(this_port); @@ -3610,7 +3611,7 @@ static pj_status_t get_frame_pasv(pjmedia_port *this_port, /* * Recorder (or passive port) callback. */ -static pj_status_t put_frame(pjmedia_port *this_port, +static pj_status_t put_frame(pjmedia_port *this_port, pjmedia_frame *frame) { pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; @@ -3620,7 +3621,7 @@ static pj_status_t put_frame(pjmedia_port *this_port, /* Check for correct size. */ PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame * conf->bits_per_sample / 8, - PJMEDIA_ENCSAMPLESPFRAME ); + PJMEDIA_ENCSAMPLESPFRAME); /* Check existance of delay_buf instance */ PJ_ASSERT_RETURN( port->delay_buf, PJ_EBUG ); diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 97133b0f2d..9baeecb8ac 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -7388,6 +7388,7 @@ struct pjsua_media_config * If this value is set to 1, the conference bridge will be implemented as a * serial bridge, otherwise it will be implemented as a parallel bridge. * Should not be less than 1. + * This value will not be used for switchboard. * * Default value: PJMEDIA_CONF_THREADS */ diff --git a/pjsip/include/pjsua2/endpoint.hpp b/pjsip/include/pjsua2/endpoint.hpp index 3afc38ee52..a3d5d5cedb 100644 --- a/pjsip/include/pjsua2/endpoint.hpp +++ b/pjsip/include/pjsua2/endpoint.hpp @@ -989,6 +989,7 @@ struct MediaConfig : public PersistentObject * If this value is set to 1, the conference bridge will be implemented as a * serial bridge, otherwise it will be implemented as a parallel bridge. * Should not be less than 1. + * This value will not be used for switchboard. * * Default value: PJMEDIA_CONF_THREADS */