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);