diff --git a/pjlib/include/pj/os.h b/pjlib/include/pj/os.h index b913781383..c8287a91d4 100644 --- a/pjlib/include/pj/os.h +++ b/pjlib/include/pj/os.h @@ -1093,6 +1093,108 @@ 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. + * Only 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 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 thread_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. + * + * @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); + +/** + * 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_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. + * + * @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. + * + * @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); + + /** + * @} + */ + /* **************************************************************************/ /** * @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..14c63a07a3 100644 --- a/pjlib/src/pj/os_core_unix.c +++ b/pjlib/src/pj/os_core_unix.c @@ -146,6 +146,18 @@ struct pj_event_t }; #endif /* PJ_HAS_EVENT_OBJ */ +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. */ + pj_mutex_t mutex; + pthread_cond_t cond; + unsigned count; + unsigned trip_count; +#endif +}; /* * Flag and reference counter for PJLIB instance. @@ -2080,6 +2092,122 @@ PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) #endif /* PJ_HAS_EVENT_OBJ */ +/////////////////////////////////////////////////////////////////////////////// +#if defined(_POSIX_BARRIERS) && _POSIX_BARRIERS >= 200112L + /* pthread_barrier is supported. */ + +/** + * Barrier object. + */ +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); + 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) + *p_barrier = barrier; + return PJ_STATUS_FROM_OS(rc); +} + +/** + * Wait on the barrier. + */ +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) { + case 0: + return PJ_FALSE; + case PTHREAD_BARRIER_SERIAL_THREAD: + return PJ_TRUE; + default: + return PJ_STATUS_FROM_OS(rc); + } +} + +/** + * Destroy the barrier. + */ +PJ_DEF(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier) +{ + int status = pthread_barrier_destroy(&barrier->barrier); + return PJ_STATUS_FROM_OS(status); +} + +#else // _POSIX_BARRIERS + /* pthread_barrier is not supported. */ + +/** + * Barrier object. + */ +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; + 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_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; +} + +/** + * Wait on the barrier. + */ +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; + status = pthread_cond_broadcast(&barrier->cond); + is_last = PJ_TRUE; + } else { + status = pthread_cond_wait(&barrier->cond, &barrier->mutex.mutex); + } + pthread_mutex_unlock(&barrier->mutex.mutex); + + return !status ? is_last : PJ_STATUS_FROM_OS(status); +} + +/** + * Destroy the barrier. + */ +PJ_DEF(pj_status_t) pj_barrier_destroy(pj_barrier_t *barrier) +{ + int status = pthread_cond_destroy(&barrier->cond); + pj_assert(!status); + PJ_UNUSED_ARG(status); + return pj_mutex_destroy(&barrier->mutex); +} + +#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..6f28521c6c 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,116 @@ PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) #endif /* PJ_HAS_EVENT_OBJ */ +/////////////////////////////////////////////////////////////////////////////// + +/* + * pj_barrier_create() + */ +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)) { + *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 */ + trip_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_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); + 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_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) | + ((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); + if (++barrier->waiting == barrier->count) { + barrier->waiting = 0; + LeaveCriticalSection(&barrier->mutex); + WakeAllConditionVariable(&barrier->cond); + 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 = 0; + barrier->waiting = 0; + /* Release all threads waiting on the semaphore */ + if (barrier->count == 1 || + ReleaseSemaphore(barrier->cond, barrier->count-1, &previousCount)) { + PJ_ASSERT_RETURN(previousCount == 0, PJ_EBUG); + return PJ_TRUE; + } + else + return pj_get_os_error(); + } + + 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/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h index e0a273e6e3..00d9999b30 100644 --- a/pjmedia/include/pjmedia/conference.h +++ b/pjmedia/include/pjmedia/conference.h @@ -60,6 +60,20 @@ PJ_BEGIN_DECL */ #define PJMEDIA_CONF_SWITCH_SIGNATURE PJMEDIA_SIG_PORT_CONF_SWITCH +/** + * 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 value: 1 - serial bridge + */ +#ifndef PJMEDIA_CONF_THREADS +# define PJMEDIA_CONF_THREADS 1 +#endif /** * Opaque type for conference bridge. @@ -104,6 +118,88 @@ enum pjmedia_conf_option based. */ }; +/** + * 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 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. + * 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. + * This value will not be used for switchboard. + */ + 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)); + /* Set the default values */ + param->worker_threads = PJMEDIA_CONF_THREADS-1; +} /** * Create conference bridge with the specified parameters. The sampling rate, @@ -170,6 +266,47 @@ PJ_DECL(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, unsigned options, pjmedia_conf **p_conf ); +/** + * 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, + pjmedia_conf **p_conf); + /** * Destroy conference bridge. diff --git a/pjmedia/src/pjmedia/conf_openmp.c b/pjmedia/src/pjmedia/conf_openmp.c new file mode 100644 index 0000000000..59d3d9811c --- /dev/null +++ b/pjmedia/src/pjmedia/conf_openmp.c @@ -0,0 +1,4321 @@ +/* + * 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; +} + +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. + */ +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/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 594d17bf50..a74e8eccf7 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 @@ -30,8 +33,171 @@ #include #include #include +#include + +#if defined(PJ_ATOMIC_SLIST_IMPLEMENTATION) +# include +#else + +//TODO until #4330 is approved, we'll have to include a generic implementation of atomic_slist directly + +# define PJ_ATOMIC_SLIST_ALIGN_PREFIX +# define PJ_ATOMIC_SLIST_ALIGN_SUFFIX +# define PJ_DECL_ATOMIC_SLIST_MEMBER(type) \ + /** Slist @a next. */ \ + type *next + +/** + * The opaque data type for atomic slist, which is used as arguments throughout + * the atomic slist operations. + */ +typedef struct pj_atomic_slist pj_atomic_slist; + +/** + * The opaque data type for atomic slist item, which is used as item argument + * throughout the atomic slist operations. + * Real atomic slist's item should have PJ_DECL_ATOMIC_SLIST_MEMBER(type) + * as the first member. + */ +typedef void pj_atomic_slist_node_t; + +/** + * This structure describes generic slist node. + */ +typedef struct pj_atomic_slist_node +{ + PJ_DECL_ATOMIC_SLIST_MEMBER(struct pj_atomic_slist_node); +} PJ_ATTR_MAY_ALIAS pj_atomic_slist_node; /* may_alias avoids warning with gcc-4.4 -Wall -O2 */ + + +struct pj_atomic_slist +{ + pj_atomic_slist_node head; + pj_mutex_t *mutex; +}; + +PJ_DEF(pj_status_t) pj_atomic_slist_create(pj_pool_t *pool, pj_atomic_slist **slist) +{ + pj_atomic_slist *p_slist; + pj_status_t rc; + + PJ_ASSERT_RETURN(pool && slist, PJ_EINVAL); + + p_slist = PJ_POOL_ZALLOC_T(pool, pj_atomic_slist); + if (!p_slist) + return PJ_ENOMEM; + + char name[PJ_MAX_OBJ_NAME]; + /* Set name. */ + pj_ansi_snprintf(name, PJ_MAX_OBJ_NAME, "slst%p", p_slist); + + rc = pj_mutex_create_simple(pool, name, &p_slist->mutex); + if (rc != PJ_SUCCESS) + return rc; + + + p_slist->head.next = &p_slist->head; + *slist = p_slist; + + PJ_LOG(6, (THIS_FILE, "Atomic slist created slst%p", p_slist)); + return PJ_SUCCESS; + +} + + +PJ_DEF(pj_status_t) pj_atomic_slist_destroy(pj_atomic_slist *slist) +{ + pj_status_t rc; + + PJ_ASSERT_RETURN(slist, PJ_EINVAL); + rc = pj_mutex_destroy(slist->mutex); + if (rc == PJ_SUCCESS) + PJ_LOG(6, (THIS_FILE, "Atomic slist destroyed slst%p", slist)); + return rc; +} + + +PJ_DEF(pj_status_t) pj_atomic_slist_push(pj_atomic_slist *slist, pj_atomic_slist_node_t *node) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(node && slist, PJ_EINVAL); + if ((status = pj_mutex_lock(slist->mutex)) != PJ_SUCCESS) { + PJ_PERROR(1, ("pj_atomic_slist_push", status, "Error locking mutex for slist slst%p", slist)); + return status; + } + ((pj_atomic_slist_node*)node)->next = slist->head.next; + slist->head.next = node; + if ((status = pj_mutex_unlock(slist->mutex)) != PJ_SUCCESS) + PJ_PERROR(1, ("pj_atomic_slist_push", status, "Error unlocking mutex for slist slst%p", slist)); + + return status; +} + + +PJ_DEF(pj_atomic_slist_node_t*) pj_atomic_slist_pop(pj_atomic_slist *slist) +{ + pj_status_t status; + pj_atomic_slist_node *node; + + PJ_ASSERT_RETURN(slist, NULL); + if ((status = pj_mutex_lock(slist->mutex)) != PJ_SUCCESS) { + PJ_PERROR(1, ("pj_atomic_slist_pop", status, "Error locking mutex for slist slst%p", slist)); + return NULL; + } + + if ((node = slist->head.next) != &slist->head) { + slist->head.next = node->next; + node->next = NULL; + } else + node = NULL; + + if ((status = pj_mutex_unlock(slist->mutex)) != PJ_SUCCESS) + PJ_PERROR(1, ("pj_atomic_slist_pop", status, "Error unlocking mutex for slist slst%p", slist)); + + return node; +} + + +/** +* Traverse the slist and get it's elements quantity. +* The return value of pj_atomic_slist_size should not be relied upon in multithreaded applications +* because the item count can be changed at any time by another thread. +*/ +PJ_DEF(pj_size_t) pj_atomic_slist_size(/*const*/ pj_atomic_slist *slist) +{ + const pj_atomic_slist_node *node; + pj_size_t count; + pj_status_t status; + + PJ_ASSERT_RETURN(slist, 0); + + if ((status = pj_mutex_lock(slist->mutex)) != PJ_SUCCESS) { + PJ_PERROR(1, ("pj_atomic_slist_size", status, "Error locking mutex for slist slst%p", slist)); + return 0; + } + + for (node = slist->head.next, count = 0; node != &slist->head; node = node->next) { + pj_assert(node); + ++count; + } + + if ((status = pj_mutex_unlock(slist->mutex)) != PJ_SUCCESS) + PJ_PERROR(1, ("pj_atomic_slist_size", status, "Error unlocking mutex for slist slst%p", slist)); + + return count; +} + +PJ_DEF(void*) pj_atomic_slist_calloc(pj_pool_t *pool, pj_size_t count, pj_size_t elem) +{ + return pj_pool_calloc(pool, count, elem); +} + +#endif + +#if (!defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0) && \ + (!defined(PJMEDIA_CONF_USE_OPENMP) || PJMEDIA_CONF_USE_OPENMP == 0) -#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0 /* CONF_DEBUG enables detailed operation of the conference bridge. * Beware that it prints large amounts of logs (several lines per frame). @@ -45,6 +211,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. @@ -75,7 +250,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 @@ -104,13 +279,33 @@ static FILE *fhnd_rec; * port. */ +/** + * src port RX buffer to mix audiodata at the dest side + */ +typedef struct PJ_ATOMIC_SLIST_ALIGN_PREFIX rx_buffer_slist_node { + PJ_DECL_ATOMIC_SLIST_MEMBER(struct rx_buffer_slist_node); + + pj_int16_t *rx_frame_buf;/**< source data to mix */ + unsigned listener_adj_level; /**< adjustment level for current + * TX from current RX port */ + struct conf_port *rx_port; /**< source (RX) port (for debuging) */ + pjmedia_frame_type frame_type; /**< we can't ignore the listener even + * if the transmitter doesn't have a + * frame to transmit. The heartbeat + * frame must be sent anyway. */ + +} PJ_ATOMIC_SLIST_ALIGN_SUFFIX rx_buffer_slist_node; + + /** * This is a port connected to conference bridge. */ 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 */ @@ -123,7 +318,7 @@ struct conf_port unsigned transmitter_cnt;/**rx_frame_buf_cap used in + * parallel bridge implementation. + */ + pj_lock_t *tx_lock; /**< Lock to protect memory + * allocation */ + + 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 */ + + /* 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 */ +#if 0 + pj_barrier_t *barrier; /**< exit barrier */ +#endif + pj_atomic_t *active_thread_cnt;/**< active worker thread counter*/ + pj_event_t *barrier_evt; /**< 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; + }; @@ -271,6 +559,23 @@ static pj_status_t destroy_port_pasv(pjmedia_port *this_port); #endif +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); + +/* mix and perhaps transmit data for listener from conf_port */ +static void mix_and_transmit(pjmedia_conf *conf, struct conf_port *listener, + SLOT_TYPE listener_slot, + unsigned listener_adj_level, + struct conf_port *conf_port, + pj_int16_t *p_in, + pjmedia_frame_type frame_type, + const pj_timestamp *timestamp); + +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 @@ -395,10 +700,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. @@ -409,16 +768,18 @@ 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; + rx_buffer_slist_node *free_node; /* 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; @@ -477,12 +838,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; } @@ -496,7 +857,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; @@ -509,11 +870,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; @@ -524,8 +885,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) @@ -537,16 +898,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: @@ -570,7 +931,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 @@ -602,12 +963,32 @@ 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 (conf->is_parallel) { + PJ_TEST_NOT_NULL(conf_port->rx_frame_buf = (pj_int16_t*)pj_pool_zalloc(pool, conf->rx_frame_buf_cap), NULL, + {status = PJ_ENOMEM;goto on_return;}); + PJ_TEST_SUCCESS(status=pj_lock_create_simple_mutex(pool, "tx_lock", &conf_port->tx_lock), NULL, goto on_return); + } + + PJ_TEST_SUCCESS(status=pj_atomic_slist_create(pool, &conf_port->free_node_cache), NULL, goto on_return); + PJ_TEST_NOT_NULL(free_node = pj_atomic_slist_calloc(pool, 1, sizeof(rx_buffer_slist_node)), NULL, + {status = PJ_ENOMEM;goto on_return;}); + PJ_TEST_SUCCESS(status=pj_atomic_slist_push(conf_port->free_node_cache, free_node), NULL, goto on_return); + PJ_TEST_SUCCESS(status=pj_atomic_slist_create(pool, &conf_port->buff_to_mix), NULL, goto on_return); + PJ_TEST_SUCCESS(status=pj_atomic_create(pool, 0, &conf_port->requests_to_mix), NULL, goto on_return); /* Done */ *p_conf_port = conf_port; on_return: if (status != PJ_SUCCESS) { + if (conf_port->tx_lock) + PJ_TEST_SUCCESS(pj_lock_destroy(conf_port->tx_lock),NULL,(void)0); + if (conf_port->free_node_cache) + PJ_TEST_SUCCESS(pj_atomic_slist_destroy(conf_port->free_node_cache),NULL,(void)0); + if (conf_port->buff_to_mix) + PJ_TEST_SUCCESS(pj_atomic_slist_destroy(conf_port->buff_to_mix),NULL,(void)0); + if (conf_port->requests_to_mix) + PJ_TEST_SUCCESS(pj_atomic_destroy(conf_port->requests_to_mix),NULL,(void)0); if (pool) pj_pool_release(pool); } @@ -634,18 +1015,22 @@ 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 / + 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 */ 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; @@ -680,7 +1065,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, @@ -688,7 +1073,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, @@ -714,10 +1099,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 */ 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; } @@ -725,26 +1115,55 @@ 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, +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; + /* 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); +} + +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 }; pj_status_t status; + unsigned i; + + PJ_ASSERT_RETURN(param && p_conf, PJ_EINVAL); - PJ_ASSERT_RETURN(samples_per_frame > 0, 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); @@ -755,28 +1174,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 = (struct conf_port**) - pj_pool_zalloc(pool, max_ports*sizeof(void*)); - PJ_ASSERT_RETURN(conf->ports, 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, conf->max_ports, sizeof(pj_int32_t)); + PJ_ASSERT_ON_FAIL(conf->active_ports, + {pjmedia_conf_destroy(conf); return PJ_ENOMEM;}); + + conf->lower_bound = conf->max_ports; /* no connected ports yet */ + conf->upper_bound = 0; /* no connected ports yet */ - conf->options = options; - conf->max_ports = max_ports; - conf->clock_rate = clock_rate; - conf->channel_count = channel_count; - conf->samples_per_frame = samples_per_frame; - conf->bits_per_sample = bits_per_sample; - /* Create and initialize the master port interface. */ conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port); PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM); - + pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE, - clock_rate, channel_count, bits_per_sample, - samples_per_frame); + conf->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; @@ -785,6 +1221,12 @@ 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); @@ -824,11 +1266,42 @@ 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); - /* Done */ + status = pj_atomic_slist_create(pool, &conf->unused_slots); + if (status != PJ_SUCCESS) { + pjmedia_conf_destroy(conf); + return status; + } + conf->free_port_slots = pj_atomic_slist_calloc(pool, conf->max_ports, sizeof(port_slot)); + PJ_ASSERT_ON_FAIL(conf->free_port_slots, {pjmedia_conf_destroy(conf); return PJ_ENOMEM;}); + 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; + } + } + } + + 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); + } + + /* Done */ *p_conf = conf; return PJ_SUCCESS; + +on_return: + pjmedia_conf_destroy(conf); + + return status; + } @@ -859,11 +1332,63 @@ static pj_status_t resume_sound( 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); 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) { + 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) { + pj_thread_t **threads = conf->pool_threads; + pj_thread_t **end = threads + (conf->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 0 + if (conf->barrier) + pj_barrier_destroy(conf->barrier); +#endif + /* active worker thread counter*/ + if (conf->active_thread_cnt) + PJ_TEST_SUCCESS(pj_atomic_destroy(conf->active_thread_cnt), NULL, (void)0); + /* exit barrier event */ + if (conf->barrier_evt) + PJ_TEST_SUCCESS(pj_event_destroy(conf->barrier_evt), NULL, (void)0); + + /* Destroy sound device port. */ if (conf->snd_dev_port) { pjmedia_snd_port_destroy(conf->snd_dev_port); @@ -871,7 +1396,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) { @@ -882,10 +1408,18 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) } } + /* Destroy slist */ + if (conf->unused_slots) + pj_atomic_slist_destroy(conf->unused_slots); + /* Destroy mutex */ if (conf->mutex) pj_mutex_destroy(conf->mutex); + /* 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); @@ -972,8 +1506,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); @@ -997,44 +1530,58 @@ 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; - /* Audio data flow is not protected, avoid processing this newly - * added port. + 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; } @@ -1045,12 +1592,44 @@ 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; + pslot = pj_atomic_slist_pop(conf->unused_slots); + 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 ); + return pj_atomic_slist_push( conf->unused_slots, conf->free_port_slots + slot ); +} + static void op_add_port(pjmedia_conf *conf, const op_param *prm) { @@ -1063,10 +1642,12 @@ 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 } @@ -1087,7 +1668,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; @@ -1111,21 +1692,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; @@ -1153,14 +1726,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) @@ -1168,8 +1741,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 @@ -1327,7 +1898,11 @@ 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)", + 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, @@ -1397,9 +1972,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; @@ -1413,11 +1988,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; @@ -1435,6 +2010,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, @@ -1452,49 +2031,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; i<(int)conf->max_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 */ @@ -1627,7 +2214,11 @@ pjmedia_conf_disconnect_port_from_sinks( pjmedia_conf *conf, */ PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf) { + return conf->max_ports - pj_atomic_slist_size(conf->unused_slots); + +#if 0 return conf->port_cnt; +#endif } /* @@ -1777,11 +2368,50 @@ 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); - /* 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; - } + 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; @@ -1794,28 +2424,16 @@ 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. */ - pj_mutex_lock(conf->mutex); - conf->ports[port] = NULL; - pj_mutex_unlock(conf->mutex); - - if (!conf_port->is_new) - --conf->port_cnt; + if (conf_port->tx_lock != NULL) + pj_lock_destroy(conf_port->tx_lock); - 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)); - - /* 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); } @@ -1882,7 +2500,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; @@ -2139,7 +2757,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) { @@ -2201,7 +2819,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; @@ -2210,8 +2828,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, @@ -2250,6 +2868,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 */ @@ -2262,7 +2882,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 @@ -2271,13 +2902,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; @@ -2367,7 +2998,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) { @@ -2382,9 +3013,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 @@ -2392,11 +3024,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. @@ -2446,8 +3078,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; @@ -2476,6 +3108,13 @@ 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) +{ + 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 +} /* * Player callback. @@ -2483,17 +3122,20 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { - pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; - pjmedia_frame_type speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE; - unsigned ci, cj, i, j; - pj_int16_t *p_in; - + pjmedia_conf *conf = (pjmedia_conf *)this_port->port_data.pdata; + + //parallelization requires signed int + 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 */ + 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. */ @@ -2502,6 +3144,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, 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. @@ -2510,82 +3153,324 @@ 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; + + /* 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; - /* Var "ci" is to count how many ports have been visited so far. */ - ++ci; + if (conf_port->transmitter_cnt && conf_port->tx_setting != PJMEDIA_PORT_DISABLE) { - /* 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. + /* Force frame type NONE */ + frame->type = PJMEDIA_FRAME_TYPE_NONE; + + /* This optimization is mainly intended for debugging. */ + if (upper_bound) { + pj_atomic_set(conf->active_ports_idx, upper_bound); + + 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 */ - 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])); + if (conf->is_parallel) { + /* Start the parallel team + * all threads have reached the barrier already. + * Should be a very short waiting. + */ + 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); + + if (conf->is_parallel) { + /* wait until all worker threads have completed their work */ + PJ_TEST_SUCCESS(pj_event_wait(conf->barrier_evt),NULL,(void)0); + pj_atomic_set(conf->active_thread_cnt, conf->threads-1); } + pj_assert(pj_atomic_get(conf->active_ports_idx) == -conf->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); + conf->sound_port = NULL; + } + conf->frame = NULL; + } - /* Get frames from all ports, and "mix" the signal - * to mix_buf of all listeners of the port. + /* Perform any queued operations that need to be synchronized with + * the clock such as connect, disonnect, remove. */ - 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; + if (!pj_list_empty(conf->op_queue)) { + pj_log_push_indent(); + handle_op_queue(conf); + pj_log_pop_indent(); + } - /* Skip empty or new port. */ - if (!conf_port || conf_port->is_new) - continue; +#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 - /* Var "ci" is to count how many ports have been visited so far. */ - ++ci; + return PJ_SUCCESS; +} - /* 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; +static pj_status_t thread_pool_start(pjmedia_conf *conf) +{ + int i; + pj_assert(conf->is_parallel); + + PJ_TEST_SUCCESS(pj_barrier_create(conf->pool, + conf->threads, + &conf->active_thread), + NULL, return tmp_status_); + +#if 0 + PJ_TEST_SUCCESS(pj_barrier_create(conf->pool, + conf->threads, + &conf->barrier), + NULL, return tmp_status_); +#endif + + pj_atomic_value_t worker_threads = conf->threads-1; + /* active worker thread counter*/ + PJ_TEST_SUCCESS(pj_atomic_create(conf->pool, worker_threads, &conf->active_thread_cnt), + NULL, return tmp_status_); + /* exit barrier event */ + PJ_TEST_SUCCESS(pj_event_create(conf->pool, "barrier_evt", PJ_FALSE, PJ_FALSE, &conf->barrier_evt), + NULL, return tmp_status_); + + + /* Thread description's to register threads with pjsip */ + conf->pool_threads = (pj_thread_t **)pj_pool_calloc(conf->pool, worker_threads, sizeof(pj_thread_t *)); + PJ_ASSERT_RETURN(conf->pool_threads, PJ_ENOMEM); + + for (i = 0; i < worker_threads; i++) { + char obj_name[PJ_MAX_OBJ_NAME]; + pj_ansi_snprintf(obj_name, sizeof(obj_name), "conf_pool_%d", i); + + PJ_TEST_SUCCESS(pj_thread_create(conf->pool, obj_name, &conf_thread, conf, 0, 0, &conf->pool_threads[i]), + NULL, return tmp_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_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, + * we should not go to the barrier because we can not leave it + */ + while (!conf->running && !conf->quit_flag) { + pj_thread_sleep(0); + //TODO classic option for using condition variable + } + + if (conf->running) { + + 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)); + + /* 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)); + } + + perform_get_frame(conf); + + /* signal to the get_frame() thread if all worker threads have completed their work */ + if (!pj_atomic_dec_and_get(conf->active_thread_cnt)) + PJ_TEST_SUCCESS(pj_event_set(conf->barrier_evt), NULL, (void)0); + //TODO a conditional variable would be preferable here } + } + + 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_int16_t *p_in; + unsigned j; + pj_int32_t cj, listener_cnt; //parallelization requires signed int + pjmedia_frame_type frame_type; + pj_int32_t level = 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); + + 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) { - pj_status_t status; - - status = pjmedia_delay_buf_get(conf_port->delay_buf, - (pj_int16_t*)frame->buf); + + /* Check that correct size is specified. */ + pj_assert(frame->size == conf->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; - continue; - } + //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)); + frame_type = PJMEDIA_FRAME_TYPE_NONE; + //continue; + } else + frame_type = PJMEDIA_FRAME_TYPE_AUDIO; } else { - pj_status_t status; - pjmedia_frame_type frame_type; - - status = read_port(conf, conf_port, (pj_int16_t*)frame->buf, + /* 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. " @@ -2595,228 +3480,357 @@ static pj_status_t get_frame(pjmedia_port *this_port, status)); conf_port->rx_setting = PJMEDIA_PORT_DISABLE; */ - conf_port->rx_level = 0; - continue; - } + //conf_port->rx_level = 0; - /* Check that the port is not removed when we call get_frame() */ - if (conf->ports[i] == NULL) { - conf_port->rx_level = 0; - continue; + 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)); + + frame_type = PJMEDIA_FRAME_TYPE_NONE; + //continue; } - /* Ignore if we didn't get any frame */ if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) { - conf_port->rx_level = 0; - continue; - } + //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; - - /* 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; + /* Ignore if we didn't get any frame */ + if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) + conf_port->rx_level = 0; + else { + /* 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; + 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; + /* 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]); + 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; + level /= conf->samples_per_frame; - /* Convert level to 8bit complement ulaw */ - level = pjmedia_linear2ulaw(level) ^ 0xff; + /* 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; + /* 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; + // Ticket #671: Skipping very low audio signal may cause noise + // to be generated in the remote end by some hardphones. + /* Skip processing frame if level is zero */ + //if (level == 0) + // continue; + } /* Add the signal to all listeners. */ - for (cj=0; cj < conf_port->listener_cnt; ++cj) - { + 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; + rx_buffer_slist_node *mix_node; + 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) + 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; + } - mix_buf = listener->mix_buf; + mix_node = pj_atomic_slist_pop(listener->free_node_cache); + pj_assert(mix_node || listener->transmitter_cnt > 1 && conf->is_parallel); + pj_assert(conf->is_parallel == (listener->tx_lock != NULL)); + if (mix_node == NULL && listener->tx_lock) { + pj_lock_acquire(listener->tx_lock); + /* tx_lock protects listener->pool. + * The only operation that requires lock protection. + */ + mix_node = pj_atomic_slist_calloc(listener->pool, 1, sizeof(rx_buffer_slist_node)); + pj_lock_release(listener->tx_lock); + } - /* 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; + PJ_ASSERT_ON_FAIL(mix_node, continue); - 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; + mix_node->rx_frame_buf = p_in; + mix_node->rx_port = conf_port; + mix_node->listener_adj_level = conf_port->listener_adj_level[cj]; + mix_node->frame_type = frame_type; + pj_atomic_slist_push(listener->buff_to_mix, mix_node); - /* Clip the signal if it's too loud */ - if (itemp > MAX_LEVEL) itemp = MAX_LEVEL; - else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL; + if (pj_atomic_inc_and_get(listener->requests_to_mix) == 1) { + /* the first mixer appeared here - mix all for this listener! + * This thread has monopolized the mixing operations on that listener port. + * Other threads can asynchronously push their data + * into this listener's buff_to_mix. + */ + do { + while ((mix_node = pj_atomic_slist_pop(listener->buff_to_mix)) != NULL) { + + unsigned listener_adj_level = mix_node->listener_adj_level; + struct conf_port *rx_port = mix_node->rx_port; + pj_int16_t *rx_buf = mix_node->rx_frame_buf; + pjmedia_frame_type rx_frame_type = mix_node->frame_type; + + /* return node into free_node_cache ASAP, + * other threads may reuse it now + */ + pj_atomic_slist_push(listener->free_node_cache, mix_node); + + /* only one thread at time call mix_and_transmit() for the + * same listener, no addition lock protection required here */ + mix_and_transmit(conf, + listener, listener_slot, listener_adj_level, + rx_port, rx_buf, rx_frame_type, + &frame->timestamp); + } + } while (pj_atomic_dec_and_get(listener->requests_to_mix)); + } - conf_port->adj_level_buf[k] = (pj_int16_t)itemp; - } + } /* loop the listeners of conf port */ - /* 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; + } /* loop of all conf ports */ + +#if 0 + 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)); + + /* If we carefully balance the work, we won't have to wait long here. + * let it be the default waiting (spin then block) + */ + 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, + rc)); + PJ_UNUSED_ARG(rc); + } +#endif //0 +} + +static void mix_and_transmit(pjmedia_conf *conf, struct conf_port *listener, + SLOT_TYPE listener_slot, + unsigned listener_adj_level, + struct conf_port *conf_port, + pj_int16_t *p_in, + pjmedia_frame_type frame_type, + const pj_timestamp *timestamp) +{ + PJ_UNUSED_ARG(conf_port); + + if (frame_type != PJMEDIA_FRAME_TYPE_NONE) { + pj_int16_t *p_in_conn_leveled; + + pj_int32_t *mix_buf = listener->mix_buf; + + /* apply connection level, if not normal */ + if (listener_adj_level != 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 *= listener_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; + + listener->adj_level_buf[k] = (pj_int16_t)itemp; } - 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; + /* take the leveled frame */ + p_in_conn_leveled = listener->adj_level_buf; + } else { + /* take the frame as-is */ + p_in_conn_leveled = p_in; + } + + 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; + if (listener->last_timestamp.u64 == 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]; + 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)); - /* 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; - } } else { - /* 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; + //this frame is from the first transmitter + listener->last_timestamp = *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]; + 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)); } - } /* 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; + /* Check if normalization adjustment needed. */ + if (mix_buf_min < MIN_LEVEL || mix_buf_max > MAX_LEVEL) { + int tmp_adj; - if (!conf_port || conf_port->is_new) - continue; + 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; + } + + } else { + //this frame is from the only transmitter + pj_assert(listener->transmitter_cnt == 1 && listener->last_timestamp.u64 != timestamp->u64); + listener->last_timestamp = *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, timestamp->u64)); + + } + } - /* Var "ci" is to count how many ports have been visited. */ - ++ci; + if (++listener->mixed_cnt == listener->transmitter_cnt) { + listener->mixed_cnt = 0; - status = write_port( conf, conf_port, &frame->timestamp, - &frm_type); + pj_status_t status = write_port(conf, listener, timestamp, &frame_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. + 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)); + "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 (i == 0) - speaker_frame_type = frm_type; - } - - /* Return sound playback frame. */ - if (conf->ports[0]->tx_level) { - TRACE_((THIS_FILE, "write to audio, count=%d", - conf->samples_per_frame)); - pjmedia_copy_samples( (pj_int16_t*)frame->buf, - (const pj_int16_t*)conf->ports[0]->mix_buf, - conf->samples_per_frame); - } else { - /* Force frame type NONE */ - speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE; + * device. + */ + if (status == PJ_SUCCESS && listener_slot == 0 && listener->tx_level) { + /* MUST set frame type */ + conf->frame->type = frame_type; + conf->sound_port = listener; + } } - /* MUST set frame type */ - frame->type = speaker_frame_type; - -#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; } - #if !DEPRECATED_FOR_TICKET_2234 /* * get_frame() for passive port 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; diff --git a/pjsip-apps/src/pjsystest/systest.c b/pjsip-apps/src/pjsystest/systest.c index 50c2008f3a..71608df632 100644 --- a/pjsip-apps/src/pjsystest/systest.c +++ b/pjsip-apps/src/pjsystest/systest.c @@ -34,6 +34,7 @@ static void systest_display_settings(void); static void systest_play_tone(void); static void systest_play_wav1(void); static void systest_play_wav2(void); +static void systest_play_wav_conf(void); static void systest_rec_audio(void); static void systest_audio_test(void); static void systest_latency_test(void); @@ -47,6 +48,7 @@ static gui_menu menu_wizard = { "Run test wizard", &systest_wizard }; static gui_menu menu_playtn = { "Play Tone", &systest_play_tone }; static gui_menu menu_playwv1 = { "Play WAV File1", &systest_play_wav1 }; static gui_menu menu_playwv2 = { "Play WAV File2", &systest_play_wav2 }; +static gui_menu menu_playwv_conf = { "Play WAV Files 1+2 (conf)", &systest_play_wav_conf }; static gui_menu menu_recaud = { "Record Audio", &systest_rec_audio }; static gui_menu menu_audtest = { "Device Test", &systest_audio_test }; static gui_menu menu_calclat = { "Latency Test", &systest_latency_test }; @@ -64,10 +66,11 @@ static gui_menu menu_tests = { &menu_playtn, &menu_playwv1, &menu_playwv2, + &menu_playwv_conf, &menu_recaud, &menu_calclat, &menu_sndaec, - NULL, + //NULL, &menu_exit } }; @@ -285,9 +288,11 @@ static pj_status_t create_player(unsigned path_cnt, const char *paths[], /***************************************************************************** * test: play WAV file */ -static void systest_play_wav(unsigned path_cnt, const char *paths[]) +static void systest_play_wav_impl(unsigned path_cnt, const char *paths[], + unsigned path_cnt2, const char *paths2[]) { pjsua_player_id play_id = PJSUA_INVALID_ID; + pjsua_player_id play_id2 = PJSUA_INVALID_ID; enum gui_key key; test_item_t *ti; const char *title = "WAV File Playback Test"; @@ -297,13 +302,22 @@ static void systest_play_wav(unsigned path_cnt, const char *paths[]) if (!ti) return; - pj_ansi_snprintf(textbuf, sizeof(textbuf), - "This test will play %s file to " - "the speaker. Please listen carefully for audio " - "impairments such as stutter. Let this test run " - "for a while to make sure that everything is okay." - " Press OK to start, CANCEL to skip", - paths[0]); + if (path_cnt2) + pj_ansi_snprintf(textbuf, sizeof(textbuf), + "This test will play the sum of %s and %s files to " + "the speaker. Please listen carefully for audio " + "impairments such as stutter. Let this test run " + "for a while to make sure that everything is okay." + " Press OK to start, CANCEL to skip", + paths[0], paths2[0]); + else + pj_ansi_snprintf(textbuf, sizeof(textbuf), + "This test will play %s file to " + "the speaker. Please listen carefully for audio " + "impairments such as stutter. Let this test run " + "for a while to make sure that everything is okay." + " Press OK to start, CANCEL to skip", + paths[0]); key = gui_msgbox(title, textbuf, WITH_OKCANCEL); @@ -323,6 +337,16 @@ static void systest_play_wav(unsigned path_cnt, const char *paths[]) if (status != PJ_SUCCESS) goto on_return; + if (path_cnt2) { + status = create_player(path_cnt2, paths2, &play_id2); + if (status != PJ_SUCCESS) + goto on_return; + + status = pjsua_conf_connect(pjsua_player_get_conf_port(play_id2), 0); + if (status != PJ_SUCCESS) + goto on_return; + } + key = gui_msgbox(title, "WAV file should be playing now in the " "speaker. Press OK to stop. ", WITH_OK); @@ -334,6 +358,9 @@ static void systest_play_wav(unsigned path_cnt, const char *paths[]) if (play_id != -1) pjsua_player_destroy(play_id); + if (play_id2 != -1) + pjsua_player_destroy(play_id2); + if (status != PJ_SUCCESS) { systest_perror("Sorry we've encountered error", status); ti->success = PJ_FALSE; @@ -347,6 +374,11 @@ static void systest_play_wav(unsigned path_cnt, const char *paths[]) return; } +static void systest_play_wav(unsigned path_cnt, const char *paths[]) +{ + systest_play_wav_impl(path_cnt, paths, 0, NULL); +} + static void systest_play_wav1(void) { const char *paths[] = { add_path(res_path, WAV_PLAYBACK_PATH), @@ -361,6 +393,15 @@ static void systest_play_wav2(void) systest_play_wav(PJ_ARRAY_SIZE(paths), paths); } +static void systest_play_wav_conf(void) +{ + const char *paths[] = { add_path(res_path, WAV_PLAYBACK_PATH), + ALT_PATH1 WAV_PLAYBACK_PATH }; + const char *paths2[] = { add_path(res_path, WAV_TOCK8_PATH), + ALT_PATH1 WAV_TOCK8_PATH}; + systest_play_wav_impl(PJ_ARRAY_SIZE(paths), paths, + PJ_ARRAY_SIZE(paths2), paths2); +} /***************************************************************************** * test: record audio @@ -1288,6 +1329,7 @@ static void systest_wizard(void) systest_display_settings(); systest_play_tone(); systest_play_wav1(); + systest_play_wav_conf(); systest_rec_audio(); systest_audio_test(); systest_latency_test(); 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..9baeecb8ac 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -7380,6 +7380,20 @@ 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. + * This value will not be used for switchboard. + * + * 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..a3d5d5cedb 100644 --- a/pjsip/include/pjsua2/endpoint.hpp +++ b/pjsip/include/pjsua2/endpoint.hpp @@ -981,6 +981,20 @@ 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. + * This value will not be used for switchboard. + * + * 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);