diff --git a/.gitignore b/.gitignore index a3589c66f3..da11d5f021 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ tests/pjsua/logs */docs/xml/ */docs/latex/ */docs/*.tag +*.bak diff --git a/pjlib/include/pj/assert.h b/pjlib/include/pj/assert.h index 7df702c229..0ecdcf4222 100644 --- a/pjlib/include/pj/assert.h +++ b/pjlib/include/pj/assert.h @@ -35,20 +35,53 @@ * Assertion and other helper macros for sanity checking. */ + /** + * @hideinitializer + * Check during debug build that an expression is true. If the expression + * computes to false during run-time, then the program will stop at the + * offending statements. + * For release build, this macro only print assert expression on the log. + * + * @param expr The expression to be evaluated. + */ +#ifndef _DEBUG +#ifndef pj_assert +#include "pj/log.h" +#include +#ifdef _WIN32 +#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#else +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#endif +#define pj_assert(expr) \ + do { \ + if (!(expr)) { PJ_LOG(1,(__FILENAME__, "Assert failed: " #expr)); } \ + } while (0) +#endif +#else +#ifndef pj_assert +# define pj_assert(expr) assert(expr) +#endif +#endif + /** * @hideinitializer + * for all buils log the message * Check during debug build that an expression is true. If the expression * computes to false during run-time, then the program will stop at the * offending statements. - * For release build, this macro will not do anything. - * - * @param expr The expression to be evaluated. + * For release build, this macro only print message on the log. + * @param expr The expression to be evaluated. + * @param ... file name,The format string for the log message ("config.c", " PJ_VERSION: %s", PJ_VERSION) */ -#ifndef pj_assert -# define pj_assert(expr) assert(expr) -#endif - +#ifndef PJ_ASSERT_LOG +#include "pj/log.h" +#define PJ_ASSERT_LOG(expr,...) \ + do { \ + if (!(expr)) { PJ_LOG(1,(__VA_ARGS__)); assert(expr); } \ + } while (0) +#endif /** * @hideinitializer * If the expression yields false, assertion will be triggered diff --git a/pjlib/src/pj/os_core_win32.c b/pjlib/src/pj/os_core_win32.c index 566c364cb8..62d9324c48 100644 --- a/pjlib/src/pj/os_core_win32.c +++ b/pjlib/src/pj/os_core_win32.c @@ -978,6 +978,10 @@ PJ_DEF(void*) pj_thread_local_get(long index) //Can't check stack because this function is called //by PJ_CHECK_STACK() itself!!! //PJ_CHECK_STACK(); +if (index == -1) + { + return 0; + } #if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 return TlsGetValueRT(index); #else @@ -1093,10 +1097,18 @@ PJ_DEF(pj_status_t) pj_mutex_lock(pj_mutex_t *mutex) status = PJ_STATUS_FROM_OS(GetLastError()); #endif +if (status == PJ_SUCCESS) + { LOG_MUTEX((mutex->obj_name, - (status==PJ_SUCCESS ? "Mutex acquired by thread %s" : "FAILED by %s"), + "Mutex acquired by thread %s", pj_thread_this()->obj_name)); - + } + else + { + LOG_MUTEX_WARN((mutex->obj_name, + "FAILED by %s", + pj_thread_this()->obj_name)); + } #if PJ_DEBUG if (status == PJ_SUCCESS) { mutex->owner = pj_thread_this(); @@ -1296,7 +1308,7 @@ static pj_status_t pj_sem_wait_for(pj_sem_t *sem, unsigned timeout) LOG_MUTEX((sem->obj_name, "Semaphore acquired by thread %s", pj_thread_this()->obj_name)); } else { - LOG_MUTEX((sem->obj_name, "Semaphore: thread %s FAILED to acquire", + LOG_MUTEX_WARN((sem->obj_name, "Semaphore: thread %s FAILED to acquire", pj_thread_this()->obj_name)); } diff --git a/pjmedia/include/pjmedia/circbuf.h b/pjmedia/include/pjmedia/circbuf.h index d6caabfa24..89981446e6 100644 --- a/pjmedia/include/pjmedia/circbuf.h +++ b/pjmedia/include/pjmedia/circbuf.h @@ -84,9 +84,14 @@ PJ_INLINE(pj_status_t) pjmedia_circ_buf_create(pj_pool_t *pool, { pjmedia_circ_buf *cbuf; + *p_cb = NULL; + cbuf = PJ_POOL_ZALLOC_T(pool, pjmedia_circ_buf); - cbuf->buf = (pj_int16_t*) pj_pool_calloc(pool, capacity, - sizeof(pj_int16_t)); + if (!cbuf) + return PJ_ENOMEM; + cbuf->buf = (pj_int16_t*) pj_pool_calloc(pool, capacity, sizeof(pj_int16_t)); + if (!cbuf->buf) + return PJ_ENOMEM; cbuf->capacity = capacity; cbuf->start = cbuf->buf; cbuf->len = 0; diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h index 9c1b4b4535..f73557eb11 100644 --- a/pjmedia/include/pjmedia/conference.h +++ b/pjmedia/include/pjmedia/conference.h @@ -213,8 +213,20 @@ PJ_DECL(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf); PJ_DECL(pj_status_t) pjmedia_conf_set_port0_name(pjmedia_conf *conf, const pj_str_t *name); - /** + * compare signature of the port with the signature of the port in the conference bridge + * + * if the conf_slot is not found, return PJ_FALSE + * + * @param conf The conference bridge. + * @param conf_slot conference bridge slot + * @param signature signature of the port + * + * @return PJ_TRUE if the signature of the port is the same as the signature of the port in the conference bridge + */ + +PJ_DECL(pj_bool_t) pjmedia_conf_compare_port_signature(pjmedia_conf* conf, unsigned conf_slot, pj_uint32_t signature); +/** * Add media port to the conference bridge. * * By default, the new conference port will have both TX and RX enabled, @@ -243,7 +255,33 @@ PJ_DECL(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, const pj_str_t *name, unsigned *p_slot ); - +/** + * Replace existing media port in the conference bridge (that was + * initially added using #pjmedia_conf_add_port()) with a new port + * in the same slot whilst maintaining any connections established + * by invoking #pjmedia_conf_connect_port(). + * + * By default, the new conference port will have both TX and RX enabled, + * but it is not connected to any other ports. Application SHOULD call + * #pjmedia_conf_connect_port() to enable audio transmission and receipt + * to/from this port. + * + * Once the media port is connected to other port(s) in the bridge, + * the bridge will continuosly call get_frame() and put_frame() to the + * port, allowing media to flow to/from the port. + * + * @param conf The conference bridge. + * @param pool Pool to allocate buffers for this port. + * @param strm_port Stream port interface. + * @param slot Slot index of the existing port in + * the conference bridge. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_conf_replace_port( pjmedia_conf *conf, + pj_pool_t *pool, + pjmedia_port *strm_port, + unsigned slot ); #if !DEPRECATED_FOR_TICKET_2234 /** * Warning: This API has been deprecated since 1.3 and will be @@ -433,7 +471,18 @@ PJ_DECL(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf, PJ_DECL(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf, unsigned ports[], unsigned *count ); - +/** + * Get port info. + * + * @param conf The conference bridge. + * @param slot Port index. + * @param info Pointer to receive the info. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_conf_get_media_port_info(pjmedia_conf* conf, + unsigned slot, + pjmedia_port_info* info); /** * Get port info. diff --git a/pjmedia/include/pjmedia/port.h b/pjmedia/include/pjmedia/port.h index fa69794e6b..2d9da0aa6c 100644 --- a/pjmedia/include/pjmedia/port.h +++ b/pjmedia/include/pjmedia/port.h @@ -231,7 +231,14 @@ typedef enum pjmedia_port_op /** * Enable TX and RX to/from this port. */ - PJMEDIA_PORT_ENABLE + PJMEDIA_PORT_ENABLE, + + /** + * Enable TX and RX to/from this port and invoke get_frame() and + * put_frame() even if there are no connections/listeners or non- + * silence audio frames respectively. + */ + PJMEDIA_PORT_ENABLE_ALWAYS } pjmedia_port_op; diff --git a/pjmedia/include/pjmedia/stream.h b/pjmedia/include/pjmedia/stream.h index 05231f4197..df7dbe7476 100644 --- a/pjmedia/include/pjmedia/stream.h +++ b/pjmedia/include/pjmedia/stream.h @@ -413,6 +413,18 @@ PJ_DECL(pj_status_t) pjmedia_stream_resume(pjmedia_stream *stream, PJ_DECL(pj_status_t) pjmedia_stream_dial_dtmf(pjmedia_stream *stream, const pj_str_t *ascii_digit); +/** + * Get the number of DTMF digits currently in the RFC 2833 transmit + * queue for this stream (valid only for audio streams). + * + * @param stream The media stream. + * @param digits Receives the number of queued digits. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_get_queued_dtmf_digits(pjmedia_stream *stream, + unsigned *digits); + /** * Check if the stream has incoming DTMF digits in the incoming DTMF diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c index 74a3ffd7c1..d83d57f387 100644 --- a/pjmedia/src/pjmedia/conf_switch.c +++ b/pjmedia/src/pjmedia/conf_switch.c @@ -1380,7 +1380,8 @@ static pj_status_t get_frame(pjmedia_port *this_port, while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_tx) > 0) { - if (cport->tx_setting == PJMEDIA_PORT_ENABLE) { + if ((cport->tx_setting == PJMEDIA_PORT_ENABLE) || + (cport->tx_setting == PJMEDIA_PORT_ENABLE_ALWAYS)) { pjmedia_frame tmp_f; tmp_f.timestamp = cport->ts_tx; diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 8b85da5049..84d8a7b8b9 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -820,12 +820,214 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, if (p_port) { *p_port = index; } - pj_mutex_unlock(conf->mutex); return PJ_SUCCESS; } +/* + * Replace stream port in the conference bridge. + */ +PJ_DEF(pj_status_t) pjmedia_conf_replace_port( pjmedia_conf *conf, + pj_pool_t *pool, + pjmedia_port *strm_port, + unsigned slot ) +{ + struct conf_port *conf_port; + pjmedia_port *old_port; + pj_status_t status; + PJ_LOG(4, (THIS_FILE, "pjmedia_conf_replace_port [conf_slot:%d]", slot)); + PJ_ASSERT_RETURN(conf && pool && strm_port && slotmax_ports, PJ_EINVAL); + + /* 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"); + return PJMEDIA_ENCCHANNEL; + } + + pj_mutex_lock(conf->mutex); + + /* Old port must be valid. */ + conf_port = conf->ports[slot]; + if (conf_port == NULL) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + + /* Disable port whilst configuration is updated */ + conf_port->tx_setting = PJMEDIA_PORT_DISABLE; + conf_port->rx_setting = PJMEDIA_PORT_DISABLE; + + // Save old media port + old_port = conf_port->port; + conf_port->port = NULL; + + /* Save some port's infos, for convenience. */ + if (strm_port) { + pjmedia_audio_format_detail *afd; + afd = pjmedia_format_get_audio_format_detail(&strm_port->info.fmt, 1); + 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->clock_rate = conf->clock_rate; + conf_port->samples_per_frame = conf->samples_per_frame; + conf_port->channel_count = conf->channel_count; + } + + /* 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; + } + else { + conf_port->rx_resample = NULL; + conf_port->tx_resample = NULL; + } + + /* + * 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])); + if (!conf_port->rx_buf) { + status = PJ_ENOMEM; + PJ_LOG(2, (THIS_FILE, "pjmedia_conf_replace_port: conf_port->rx_buf alloc failed [conf_slot:%d]", slot)); + 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])); + if (!conf_port->tx_buf) { + status = PJ_ENOMEM; + PJ_LOG(2, (THIS_FILE, "pjmedia_conf_replace_port: conf_port->tx_buf alloc failed [conf_slot:%d]", slot)); + goto on_return; + } + } else { + conf_port->rx_buf_cap = 0; + conf_port->rx_buf_count = 0; + conf_port->rx_buf = NULL; + conf_port->tx_buf_cap = 0; + conf_port->tx_buf_count = 0; + conf_port->tx_buf = NULL; + } + + /* Put the port. */ + conf_port->port = strm_port; + + /* Default has tx and rx enabled. */ + conf_port->rx_setting = PJMEDIA_PORT_ENABLE; + conf_port->tx_setting = PJMEDIA_PORT_ENABLE; + + /* Done. */ + status = PJ_SUCCESS; + +on_return: + /* Destroy old pjmedia port if this conf port not a passive port, + * i.e: has delay buf. + */ + if (old_port ) + { + if (/*conf_port->delay_buf ||*/ old_port->info.signature != PJMEDIA_SIG_PORT_NULL) + { + PJ_LOG(4, (THIS_FILE, "pjmedia_conf_replace_port::distroy old port ")); + pjmedia_port_destroy(old_port); + old_port= NULL; + } + } + + pj_mutex_unlock(conf->mutex); + + if(status!=PJ_SUCCESS) + { + PJ_LOG(2, (THIS_FILE, "pjmedia_conf_replace_port failed [conf_slot:%d]", slot)); + } + return status; +} #if !DEPRECATED_FOR_TICKET_2234 /* @@ -1293,7 +1495,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf, { unsigned i, count=0; - PJ_ASSERT_RETURN(conf && p_count && ports, PJ_EINVAL); + PJ_ASSERT_RETURN(conf && p_count, PJ_EINVAL); /* Lock mutex */ pj_mutex_lock(conf->mutex); @@ -1301,8 +1503,10 @@ PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf, for (i=0; imax_ports && count<*p_count; ++i) { if (!conf->ports[i]) continue; - + if (ports) ports[count++] = i; + else + count++; } /* Unlock mutex */ @@ -1311,7 +1515,59 @@ PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf, *p_count = count; return PJ_SUCCESS; } +PJ_DECL(pj_bool_t) pjmedia_conf_compare_port_signature(pjmedia_conf* conf,unsigned conf_slot, pj_uint32_t signature) +{ + /* Check arguments */ + PJ_ASSERT_RETURN(conf, PJ_FALSE); + if (conf_slot<0 || conf_slot >= conf->max_ports) + { + PJ_LOG(4, (THIS_FILE, " pjmedia_conf_compare_port_signature : [conf_slot : %d], [conf->max_ports:%d]",conf_slot, conf->max_ports)); + return PJ_FALSE; + } + pjmedia_port_info port_info; + if (pjmedia_conf_get_media_port_info(conf, conf_slot, &port_info) != PJ_SUCCESS) + { + return PJ_FALSE; + } + return port_info.signature == signature; +} +PJ_DEF(pj_status_t) pjmedia_conf_get_media_port_info(pjmedia_conf* conf,unsigned slot, pjmedia_port_info* info) +{ + struct conf_port* conf_port; + /* Check arguments */ + if (slot < 0 || slot >= conf->max_ports) + { + PJ_LOG(2, (THIS_FILE, " pjmedia_conf_get_media_port_info : [slot : %d], [conf->max_ports:%d]", slot, conf->max_ports)); + return PJ_EINVAL; + } + PJ_ASSERT_RETURN(conf && slot >=0 && slot < conf->max_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 (!conf_port->port) { + pj_mutex_unlock(conf->mutex); + return PJ_EINVAL; + } + pjmedia_port_info * port_info = &conf_port->port->info; + pjmedia_format_copy(&info->fmt, &port_info->fmt); + info->name= port_info->name; + info->dir= port_info->dir; + info->signature= port_info->signature; + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} /* * Get port info */ @@ -1718,7 +1974,8 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, *frm_type = PJMEDIA_FRAME_TYPE_AUDIO; /* Skip port if it is disabled */ - if (cport->tx_setting != PJMEDIA_PORT_ENABLE) { + if (cport->tx_setting == PJMEDIA_PORT_DISABLE && + (cport->tx_setting != PJMEDIA_PORT_ENABLE_ALWAYS)) { cport->tx_level = 0; *frm_type = PJMEDIA_FRAME_TYPE_NONE; return PJ_SUCCESS; @@ -2006,7 +2263,8 @@ static pj_status_t get_frame(pjmedia_port *this_port, } /* Also skip if this port doesn't have listeners. */ - if (conf_port->listener_cnt == 0) { + if ((conf_port->listener_cnt == 0) && + (conf_port->rx_setting != PJMEDIA_PORT_ENABLE_ALWAYS)) { conf_port->rx_level = 0; continue; } @@ -2119,7 +2377,8 @@ static pj_status_t get_frame(pjmedia_port *this_port, 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) + if ((listener->tx_setting != PJMEDIA_PORT_ENABLE) && + (listener->tx_setting != PJMEDIA_PORT_ENABLE_ALWAYS)) continue; mix_buf = listener->mix_buf; @@ -2301,12 +2560,15 @@ static pj_status_t put_frame(pjmedia_port *this_port, PJ_ASSERT_RETURN( port->delay_buf, PJ_EBUG ); /* Skip if this port is muted/disabled. */ - if (port->rx_setting != PJMEDIA_PORT_ENABLE) { + if ((port->rx_setting != PJMEDIA_PORT_ENABLE) && + (port->rx_setting != PJMEDIA_PORT_ENABLE_ALWAYS)) { return PJ_SUCCESS; } /* Skip if no port is listening to the microphone */ - if (port->listener_cnt == 0) { + if ((port->listener_cnt == 0) && + (port->rx_setting != PJMEDIA_PORT_ENABLE_ALWAYS)) + { return PJ_SUCCESS; } diff --git a/pjmedia/src/pjmedia/null_port.c b/pjmedia/src/pjmedia/null_port.c index fb5dfccf4d..1c00e32aef 100644 --- a/pjmedia/src/pjmedia/null_port.c +++ b/pjmedia/src/pjmedia/null_port.c @@ -82,7 +82,11 @@ static pj_status_t null_get_frame(pjmedia_port *this_port, { frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); +#if PJ_HAS_INT64 + frame->timestamp.u64 += PJMEDIA_PIA_SPF(&this_port->info); +#else frame->timestamp.u32.lo += PJMEDIA_PIA_SPF(&this_port->info); +#endif pjmedia_zero_samples((pj_int16_t*)frame->buf, PJMEDIA_PIA_SPF(&this_port->info)); diff --git a/pjmedia/src/pjmedia/silencedet.c b/pjmedia/src/pjmedia/silencedet.c index a2f8964c46..375780e5da 100644 --- a/pjmedia/src/pjmedia/silencedet.c +++ b/pjmedia/src/pjmedia/silencedet.c @@ -235,9 +235,11 @@ PJ_DEF(pj_bool_t) pjmedia_silence_det_apply( pjmedia_silence_det *sd, /* Voiced for long time (>recalc_on_voiced), current * threshold seems to be too low. */ + unsigned old = sd->threshold; sd->threshold = (avg_recent_level + sd->threshold) >> 1; - TRACE_((THIS_FILE,"Re-adjust threshold (in talk burst)" - "to %d", sd->threshold)); + if (sd->threshold != old) + TRACE_((THIS_FILE,"%s re-adjust threshold (in talk burst) to %d (was %d)", + sd->objname, sd->threshold, old)); sd->voiced_timer = 0; @@ -248,8 +250,8 @@ PJ_DEF(pj_bool_t) pjmedia_silence_det_apply( pjmedia_silence_det *sd, break; case STATE_SILENCE: - TRACE_((THIS_FILE,"Starting talk burst (level=%d threshold=%d)", - level, sd->threshold)); + TRACE_((THIS_FILE,"%s starting talk burst (level=%d threshold=%d)", + sd->objname, level, sd->threshold)); case STATE_START_SILENCE: sd->state = STATE_VOICED; @@ -271,9 +273,11 @@ PJ_DEF(pj_bool_t) pjmedia_silence_det_apply( pjmedia_silence_det *sd, switch(sd->state) { case STATE_SILENCE: if (sd->silence_timer >= sd->recalc_on_silence) { + unsigned old = sd->threshold; sd->threshold = avg_recent_level << 1; - TRACE_((THIS_FILE,"Re-adjust threshold (in silence)" - "to %d", sd->threshold)); + if (sd->threshold != old) + TRACE_((THIS_FILE,"%s re-adjust threshold (in silence) to %d (was %d)", + sd->objname, sd->threshold, old)); sd->silence_timer = 0; @@ -294,8 +298,8 @@ PJ_DEF(pj_bool_t) pjmedia_silence_det_apply( pjmedia_silence_det *sd, if (sd->silence_timer >= sd->before_silence) { sd->state = STATE_SILENCE; sd->threshold = avg_recent_level << 1; - TRACE_((THIS_FILE,"Starting silence (level=%d " - "threshold=%d)", level, sd->threshold)); + TRACE_((THIS_FILE,"%s starting silence (level=%d threshold=%d)", + sd->objname, level, sd->threshold)); /* Reset sig_level */ sd->sum_level = avg_recent_level; @@ -320,6 +324,9 @@ PJ_DEF(pj_bool_t) pjmedia_silence_det_detect( pjmedia_silence_det *sd, { pj_uint32_t level; + if (!p_level && (sd->mode == VAD_MODE_NONE)) + return PJ_FALSE; + /* Calculate average signal level. */ level = pjmedia_calc_avg_signal(samples, count); diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c index acdb052170..25cb41d4cc 100644 --- a/pjmedia/src/pjmedia/stream.c +++ b/pjmedia/src/pjmedia/stream.c @@ -1546,7 +1546,7 @@ static pj_status_t put_frame_imp( pjmedia_port *port, pjmedia_rtp_hdr *rtp = (pjmedia_rtp_hdr*) channel->out_pkt; rtp->m = 1; - PJ_LOG(5,(stream->port.info.name.ptr,"Start talksprut..")); + PJ_LOG(5,(stream->port.info.name.ptr,"Starting talksprut..")); } stream->is_streaming = PJ_TRUE; @@ -3331,6 +3331,26 @@ PJ_DEF(pj_status_t) pjmedia_stream_dial_dtmf( pjmedia_stream *stream, return status; } +/* + * Get number of DTMF digits in the stream's transmit queue. + */ +PJ_DEF(pj_status_t) pjmedia_get_queued_dtmf_digits(pjmedia_stream *stream, + unsigned *digits) +{ + /* By convention we use jitter buffer mutex to access DTMF + * queue. + */ + PJ_ASSERT_RETURN(stream && digits, PJ_EINVAL); + + pj_mutex_lock(stream->jb_mutex); + + *digits = (unsigned)stream->tx_dtmf_count; + + pj_mutex_unlock(stream->jb_mutex); + + return PJ_SUCCESS; +} + /* * See if we have DTMF digits in the rx buffer. diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 2a88aefa89..7a1424682c 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -270,6 +270,10 @@ typedef int pjsua_buddy_id; /** File player identification */ typedef int pjsua_player_id; +/** Media port identification */ +typedef int pjsua_mport_id; + + /** File recorder identification */ typedef int pjsua_recorder_id; @@ -376,20 +380,34 @@ typedef struct pj_stun_resolve_result pj_stun_resolve_result; # define PJSUA_VID_REQ_KEYFRAME_INTERVAL 3000 #endif + /** + * Specify whether timer heap events will be polled by a separate worker + * thread. If this is set/enabled, a worker thread will be dedicated to + * poll timer heap events only, and the rest worker thread(s) will poll + * ioqueue/network events only. + * + * Note that if worker thread count setting (i.e: pjsua_config.thread_cnt) + * is set to zero, this setting will be ignored. + * + * Default: 0 (disabled) + */ +#ifndef PJSUA_SEPARATE_WORKER_FOR_TIMER +# define PJSUA_SEPARATE_WORKER_FOR_TIMER 0 +#endif /** - * Specify whether timer heap events will be polled by a separate worker - * thread. If this is set/enabled, a worker thread will be dedicated to - * poll timer heap events only, and the rest worker thread(s) will poll - * ioqueue/network events only. - * - * Note that if worker thread count setting (i.e: pjsua_config.thread_cnt) - * is set to zero, this setting will be ignored. - * - * Default: 0 (disabled) + * Size of internal media port record buffer (in ms) */ -#ifndef PJSUA_SEPARATE_WORKER_FOR_TIMER -# define PJSUA_SEPARATE_WORKER_FOR_TIMER 0 +#ifndef PJSUA_MPORT_RECORD_BUFFER_SIZE +# define PJSUA_MPORT_RECORD_BUFFER_SIZE 1250 +#endif + + +/** + * Size of internal media port replay buffer (in ms) + */ +#ifndef PJSUA_MPORT_REPLAY_BUFFER_SIZE +# define PJSUA_MPORT_REPLAY_BUFFER_SIZE 1250 #endif @@ -2468,7 +2486,15 @@ PJ_DECL(pjsua_msg_data*) pjsua_msg_data_clone(pj_pool_t *pool, * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsua_create(void); - +/** + * Instantiate pjsua application. Application must call this function before + * calling any other functions, to make sure that the underlying libraries + * are properly initialized. Once this function has returned success, + * application must call pjsua_destroy() before quitting. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_create2(const pjsua_logging_config *log_cfg); /** Forward declaration */ typedef struct pjsua_media_config pjsua_media_config; @@ -4944,7 +4970,7 @@ PJ_DECL(pj_status_t) pjsua_acc_set_transport(pjsua_acc_id acc_id, * Maximum simultaneous calls. */ #ifndef PJSUA_MAX_CALLS -# define PJSUA_MAX_CALLS 4 +# define PJSUA_MAX_CALLS 64 #endif /** @@ -5510,6 +5536,33 @@ PJ_DECL(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, pjsua_call_id *p_call_id); +/** + * Make outgoing call to the specified URI using the specified account. + * + * @param acc_id The account to be used. + * @param dst_uri URI to be put in the To header (normally is the same + * as the target URI). + * @param opt Optional call setting. This should be initialized + * using #pjsua_call_setting_default(). + * @param user_data Arbitrary user data to be attached to the call, and + * can be retrieved later. + * @param msg_data Optional headers etc to be added to outgoing INVITE + * request, or NULL if no custom header is desired. + * @param p_call_id Pointer to receive call identification. + * @param src_uri Optional From URI to override the local account uri. + * @param contact Optional Constact URI to override the account's default. + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_call_make_call2(pjsua_acc_id acc_id, + const pj_str_t *dst_uri, + const pjsua_call_setting *opt, + void *user_data, + const pjsua_msg_data *msg_data, + pjsua_call_id *p_call_id, + const pj_str_t* src_uri, + const pj_str_t* contact_uri); + + /** * Check if the specified call has active INVITE session and the INVITE * session has not been disconnected. @@ -5983,6 +6036,18 @@ PJ_DECL(pj_status_t) pjsua_call_xfer_replaces(pjsua_call_id call_id, PJ_DECL(pj_status_t) pjsua_call_dial_dtmf(pjsua_call_id call_id, const pj_str_t *digits); +/** +* Get number of digits in the DTMF transmit queue. + * + * @param call_id Call identification. + * @param digits Receives the number of digits currently queued for + * transmission using RFC 2833 payload formats + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_call_get_queued_dtmf_digits(pjsua_call_id call_id, + unsigned *digits); + /** * Send DTMF digits to remote. Use this method to send DTMF using the method in * \a pjsua_dtmf_method. This method will call #pjsua_call_dial_dtmf() when @@ -6845,6 +6910,20 @@ struct pjsua_media_config */ unsigned max_media_ports; + /** + * Default size of internal media port record buffer (in ms). + * + * Default value: PJSUA_MPORT_RECORD_BUFFER_SIZE + */ + unsigned mport_record_buffer_size; + + /** + * Default size of internal media port play buffer (in ms). + * + * Default value: PJSUA_MPORT_REPLAY_BUFFER_SIZE + */ + unsigned mport_replay_buffer_size; + /** * Specify whether the media manager should manage its own * ioqueue for the RTP/RTCP sockets. If yes, ioqueue will be created @@ -7386,8 +7465,6 @@ PJ_DECL(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[], */ PJ_DECL(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id port_id, pjsua_conf_port_info *info); - - /** * Add arbitrary media port to PJSUA's conference bridge. Application * can use this function to add the media port that it creates. For @@ -7711,6 +7788,498 @@ PJ_DECL(pj_status_t) pjsua_recorder_get_port(pjsua_recorder_id id, */ PJ_DECL(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id); +/***************************************************************************** +* +* +* +* + * Low-level media/audio port/channel API (similar to memory players and recorders). + */ + +/** + * Create/allocate a media port, and automatically add this port to + * the conference bridge so that its connectivity can be controlled + * and play, record and/or conferencing operations can be initiated + * as necessary. + * + * @param dir Media port's direction. + * @param enable_vad Set to true to enable VAD features during record. + * @param record_buffer_size Desired size of internal record buffer (in ms). + * If 0, the default value in mport_record_buffer_size + * will be used instead. + * @param record_data_threshold Threshold, in ms, that should be used to signal + * the record event so that the application can collect + * the recorded data. The record event will signaled + * whilst the available/free space in the record buffer + * is below this threshold. If 0, the default value of + * 250 will be used; + * @param play_buffer_size Desired size of internal play buffer (in ms). + * If 0, the default value in mport_replay_buffer_size + * will be used instead. + * @param play_data_threshold Threshold, in ms, that should be used to signal + * the play event so that the application can supply + * additional data. The play event will signaled + * whilst the available/unplayed data in the play buffer + * is below this threshold. If 0, the default value of + * 250 will be used; + * @param p_id Pointer to receive media port ID. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) pjsua_mport_alloc(pjmedia_dir dir, + pj_bool_t enable_vad, + pj_size_t record_buffer_size, + pj_size_t record_data_threshold, + pj_size_t play_buffer_size, + pj_size_t play_data_threshold, + pjsua_mport_id *p_id); + + +/** + * Destroy/free media port, remove it from the bridge, and free + * associated resources. + * + * @param id The media port ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_free(pjsua_mport_id id); + +/** + * Get conference port ID associated with media port. + * + * @param id The media port ID. + * + * @return Conference port ID associated with this media port. + */ +PJ_DECL(pjsua_conf_port_id) pjsua_mport_get_conf_port(pjsua_mport_id id); + +/** + * Get the underlying pjmedia_port for the media port. + * + * @param id The media port ID. + * @param p_port The media port associated with the player. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsua_mport_get_port(pjsua_mport_id id, + pjmedia_port **p_port); + +/** + * Get the media port's direction. + * + * @param id The media port ID. + * + * @return Direction. + */ +PJ_DECL(pjmedia_dir) pjsua_mport_get_dir(pjsua_mport_id id); + +/** + * Get media port's replay event object. The returned event object is + * valid only until pjsua_mport_free() is invoked and must therefore + * not be accessed after that point. + * + * @param id The media port ID. + * + * @return Event object that is signaled when the status of the + * replay operation changes indicating that the + * application should invoke pjsua_mport_play_status(). + */ +PJ_DECL(pj_event_t *) pjsua_mport_get_play_event(pjsua_mport_id id); + +/** + * Get media port's record event object. The returned event object is + * valid only until pjsua_mport_free() is invoked and must therefore + * not be accessed after that point. + * + * @param id The media port ID. + * + * @return Event object that is signaled when the status of the + * record operation changes indicating that the + * application should invoke pjsua_mport_record_status(). + */ +PJ_DECL(pj_event_t *) pjsua_mport_get_record_event(pjsua_mport_id id); + +/** + * Get media port's recognition event object. The returned event object + * is valid only until pjsua_mport_free() is invoked and must therefore + * not be accessed after that point. + * + * @param id The media port ID. + * + * @return Event object that is signaled when a recognition + * event is available for collection via + * pjsua_mport_get_recognised(). + */ +PJ_DECL(pj_event_t *) pjsua_mport_get_recognition_event(pjsua_mport_id id); + +/** + * Initiate a replay operation. If successful, the replay will continue + * until pjsua_mport_play_status() returns with the 'completed' flag set + * after all the supplied data is played. Additional data can be periodically + * supplied by invoking pjsua_mport_play_put_data() once the play event is + * signaled. + * + * @param id The media port ID. + * @param fmt Format/ecoding details for the replay data. + * @param data Optional address of initial block of data that should be + * replayed. + * @param size Number of bytes/octets of replay data provided in the + * initial block. + * @param count Address of count that will receive the number of bytes + * of replay data that were copied from the supplied initial + * data block to the internal replay buffer. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_play_start(pjsua_mport_id id, + const pjmedia_format *fmt, + const void *data, + pj_size_t size, + pj_size_t *count); + +/** + * Status information about the play operation. + */ +typedef struct pjsua_mport_play_info +{ + /** + * Number of samples actually played thus far. + */ + pj_uint64_t samples_played; + + /** + * Flag which is set to true if the replay has completed. + */ + pj_bool_t completed; + + /** + * Flag which is set to true if an underrun has occurred + * since the last invokation of pjsua_mport_play_status(). + */ + pj_bool_t underrun; + + /** + * Space, in samples, available in the internal buffer + * for new replay data. + */ + pj_uint32_t free_buffer_size; + +} pjs_mport_play_info; + +/** + * Get status info about the current play operation. + * + * @param id The media port ID. + * @param info The status. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_play_status(pjsua_mport_id id, + pjs_mport_play_info *info); + +/** + * Supply the next chunk of data to be replayed. If no data is supplied, + * then this taken as an idication that no more data is available and + * that the replay should complete when the already buffered data is + * replayed. + * + * @param id The media port ID. + * @param data Address of next block of data that should be replayed. + * @param size Number of bytes/octets of replay data provided in the + * data block. + * @param count Address of count that will receive the number of bytes + * of replay data that were copied from the supplied data + * block to the internal replay buffer. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_play_put_data(pjsua_mport_id id, + const void *data, + pj_size_t size, + pj_size_t *count); + +/** + * Stop/abort the current play operation. + * + * @param id The file player ID. + * @param discard Flag which should be set to discard any unplayed data + * in the internal replay buffer (i.e., stop immediately). + * If not set, the replay operation will continue until + * all the already internally buffered data is played. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_play_stop(pjsua_mport_id id, pj_bool_t discard); + + +/** + * Initiate a record operation. If successful, the record will continue + * until pjsua_mport_record_status() returns with the 'completed' flag + * set after a completion condition has been met. The recorded data can be + * retreived periodically (when the record event is signaled) by invoking + * pjsua_mport_record_get_data(). + * + * @param id The media port ID. + * @param fmt Format/ecoding details for the recorded data. + * @param rec_output Flag which should be set to PJ_TRUE if the + * port's output (player or transmit direction) + * should be recorded. Otherwise, the port's input + * will be recorded. + * @param max_duration Maximum duration, in ms, for the recording. 0 + * if no limit. + * @param max_samples Maximum number of samples to record. 0 if no + * limit. + * @param max_silence Max period of silence, in ms, before the + * recording is terminated. 0 if no limit. + *@param eliminate_silence The maximum duration, in ms, of silence to + * record. Silences longer than this are truncated + * to this length. 0 disables silence elimination. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) pjsua_mport_record_start(pjsua_mport_id id, + const pjmedia_format *fmt, + pj_bool_t rec_output, + pj_size_t max_duration, + pj_size_t max_samples, + pj_size_t max_silence, + pj_size_t eliminate_silence); + +/** +* Status information about the record operation. +*/ +typedef enum pjsua_record_end_reason +{ + PJSUA_REC_ER_NONE, + PJSUA_REC_ER_MAX_SAMPLES, + PJSUA_REC_ER_MAX_DURATION, + PJSUA_REC_ER_MAX_SILENCE, + PJSUA_REC_ER_STOP +} pjs_record_end_reason; +typedef struct pjsua_mport_record_info +{ + /** + * Total number of samples actually recorded thus far. + */ + pj_uint64_t samples_recorded; + + /** + * Flag which is set to true if the record has completed. + */ + pj_bool_t completed; + + /** + * Identifies the reason the recording was completed. + */ + pjs_record_end_reason end_reason; + + /** + * Flag which is set to true if an overrun has occurred + * since the last invokation of pjsua_mport_record_status(). + */ + pj_bool_t overrun; + + /** + * Number of samples available for immediate retreival from + * the internal record buffer. + */ + pj_uint32_t samples_available; + +} pjs_mport_record_info; + +/** + * Get the status of the current record operation. + * + * @param id The media port ID. + * @param info The info. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_record_status(pjsua_mport_id id, + pjs_mport_record_info *info); + +/** + * Get the next chunk of data that was recorded. + * + * @param id The media port ID. + * @param data Address of buffer that should receive the data. + * @param size Size of data buffer in bytes. + * @param count Address of count that will receive the number of bytes + * of record data that were copied to the supplied buffer. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_record_get_data(pjsua_mport_id id, + void *data, + pj_size_t size, + pj_size_t *count); + +/** + * Stop/abort the current record operation. + * + * @param id The media port ID. + * @param discard Flag which should be set to discard any uncollected + * data in the internal record buffer. + * The media port will continue to be 'reserved' for + * recording purposes until pjsua_mport_record_status() + * returns with the 'completed' flag set. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_record_stop(pjsua_mport_id id, pj_bool_t discard); + + +/** + * Initiate a conference operation. If successful, the port's output will + * contain the mixed result of the inputs of all the conference's + * participants. A play operation can not be active at the same time as + * a conference on the same port/channel. The conference will continue + * until #pjsua_mport_conf_stop() is invoked. Participants can be added + * and removed using #pjsua_mport_conf_add() and + * #pjsua_mport_conf_remove() respectively. + * + * @param id The media port ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_conf_start(pjsua_mport_id id); + +/** + * Add new participant to the conference on the media port. + * + * @param id The media port ID. + * @param pid The media port ID of the participant to add. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_conf_add(pjsua_mport_id id, pjsua_mport_id pid); + +/** + * Remove participant from the conference on the media port. + * + * @param id The media port ID. + * @param pid The media port ID of the participant to remove. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_conf_remove(pjsua_mport_id id, pjsua_mport_id pid); + +/** + * Stop conference operation. + * + * @param id The media port ID. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_conf_stop(pjsua_mport_id id); + + +/** +* Recognition event types +*/ +typedef enum pjsua_recognition_type +{ + PJSUA_RCG_NONE = 0 + ,PJSUA_RCG_DTMF_RFC2833 = 1 + ,PJSUA_RCG_DTMF_TONE = 2 + ,PJSUA_RCG_DTMF = (PJSUA_RCG_DTMF_RFC2833 | PJSUA_RCG_DTMF_TONE) + //,PJSUA_RCG_GRUNT = 4 + //,PJSUA_RCG_CALL_PROGRESS_TONE = 8 + //,PJSUA_RCG_TONE = 16 + //,PJSUA_RCG_LIVE_SPEAKER = 32 +} pjs_recognition_type; + +typedef struct pjsua_listen_for_parms { + /** + * Bitmask of the detector types that should be activated or + * PJSUA_RCG_NONE to disable all detectors. + * + * PJSUA_RCG_DTMF_RFC2833 does not represent a detector that can + * be activated on the audio stream, however, it allows the + * application's #on_dtmf_digit() callback handler to use the + * same consistent event delivery mechanism for RFC2833 received + * DTMF digits as those received directly as inband tones from + * peers that do not support RFC2833. RFC2833 digits can be + * queued by the application by invoking + * #pjsua_mport_add_rfc2833_dtmf_digit(). If desired, + * PJSUA_RCG_DTMF_TONE can be added to detect inband tones. If + * both RFC2833 and tone based DTMF tone detectors are activated + * (i.e., PJSUA_RCG_DTMF), then the first event queued of either + * type will automatically deactivate the other detector to + * prevent the potential of digit duplication. + * + * PJSUA_RCG_GRUNT activates a grunt detector that generates an + * event whenever the silence state of the audio stream changes + * based on an adaptive noise threshold algorithm. + */ + unsigned types; + + //int grunt_latency; + //double min_noise_level; + //double grunt_threshold; +} pjs_listen_for_parms; + +/** + * Start or stop one or more of a series of detectors that can be + * activated on a media port. Once a detector is activated, detection + * events can be retreived by invoking #pjsua_mport_get_recognised() + * after the recognition event, which can be retreived by invoking + * #pjsua_mport_get_recognition_event(), is signaled. + * + * @param id The media port ID. + * @param params Details of detectors to be activated or NULL to + * disable all detectors. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_mport_listen_for(pjsua_mport_id id, pjs_listen_for_parms* params); + +typedef struct pjsua_recognition_info { + pjs_recognition_type type; + unsigned timestamp; + unsigned param0; + unsigned param1; +} pjs_recognition_info; + +/** + * Get the next available detection event. + * + * @param id The media port ID. + * @param info Details of detected event. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ + +PJ_DECL(pj_status_t) pjsua_mport_get_recognised(pjsua_mport_id id, pjs_recognition_info* info); + +/** + * Discard any recognition events currently queued in the internal buffer. + * + * @param id The media port ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ + +PJ_DECL(pj_status_t) pjsua_mport_discard_recognised(pjsua_mport_id id); + +/** + * Add a RFC2833 DTMF digit to the internal recognition event queue. + * + * @param id The media port ID. + * @param digit The actual digit. + * @param timestamp The RTP timestamp associated with the digit. + * @param duration The duration, in ms, of the digit. + * + * @return PJ_SUCCESS on success PJ_EIGNORED if the port is + * not currently listening for RFC2833 digits, or + * the appropriate error code. + */ + +PJ_DECL(pj_status_t) pjsua_mport_add_rfc2833_dtmf_digit(pjsua_mport_id id, char digit, unsigned timestamp, unsigned duration); + /***************************************************************************** * Sound devices. diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index 2e849b45d2..91c61ff363 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -173,6 +173,10 @@ struct pjsua_call /**< Is media update successful? */ pj_bool_t hanging_up;/**< Is call in the process of hangup? */ + + pjmedia_port* null_port; + pjsua_conf_port_id conf_slot; /**< Slot # in conference bridge. */ + int conf_idx; /**< Index of audio stream that was added to the conference. */ int audio_idx; /**< First active audio media. */ pj_mutex_t *med_ch_mutex;/**< Media channel callback's mutex. */ pjsua_med_tp_state_cb med_ch_cb;/**< Media channel callback. */ @@ -397,6 +401,85 @@ typedef struct pjsua_file_data } pjsua_file_data; +/** + * Media port data. + */ +typedef enum pjsua_record_status +{ + PJSUA_REC_IDLE, + PJSUA_REC_RUNNING, + PJSUA_REC_STOPPING +} pjsua_record_status; +typedef struct pjsua_mport_record_data +{ + pjsua_record_status status; + pjmedia_format_id fmt_id; + pj_size_t max_samples; + pj_size_t max_duration; + pj_size_t max_silence; + pj_size_t eliminate_silence; + pj_uint64_t samples_seen; + pj_uint64_t samples_recorded; + pj_timestamp vad_timestamp; + pjmedia_silence_det *vad; + pjmedia_circ_buf *buffer; + pj_event_t *event; + pj_size_t buffer_size; + pj_size_t threshold; + pj_bool_t signaled; + pj_bool_t overrun; + pj_bool_t is_silence; + pj_bool_t rec_output; + pjs_record_end_reason er; +} pjsua_mport_record_data; +typedef struct pjsua_mport_recognition_data +{ + pj_event_t *event; + pj_uint32_t event_cnt; + pjs_listen_for_parms params; + pjs_recognition_info events[32]; + pj_bool_t signaled; + pj_bool_t overrun; +} pjsua_mport_recognition_data; +typedef enum pjsua_replay_status +{ + PJSUA_REP_IDLE, + PJSUA_REP_RUNNING, + PJSUA_REP_STOPPING, + PJSUA_REP_CONFERENCING +} pjsua_replay_status; +typedef struct pjsua_mport_replay_data +{ + pjsua_replay_status status; + pjmedia_format_id fmt_id; + pj_uint64_t samples_played; + pj_timestamp timestamp; + pjmedia_circ_buf *buffer; + pj_event_t *event; + pj_size_t buffer_size; + pj_size_t threshold; + pj_bool_t signaled; + pj_bool_t underrun; +} pjsua_mport_replay_data; +typedef struct pjsua_mport_data +{ + pjmedia_port base; + pj_pool_t *pool; + pjsua_conf_port_id slot; + pj_uint32_t participant_cnt; + pjsua_mport_id *participants; + pj_uint32_t listener_cnt; + pjsua_mport_id *listeners; + pj_uint32_t mix_cnt; + int mix_adj; + int last_mix_adj; + pj_int32_t *mix_buf; + pjsua_mport_record_data record_data; + pjsua_mport_replay_data play_data; + pjsua_mport_recognition_data recogntion_data; +} pjsua_mport_data; + + /** * Additional parameters for conference bridge. */ @@ -448,6 +531,7 @@ typedef struct pjsua_vid_win pjmedia_vid_port *vp_rend; /**< Renderer vidport */ pjsua_conf_port_id cap_slot; /**< Capturer conf slot */ pjsua_conf_port_id rend_slot; /**< Renderer conf slot */ + pjmedia_port *tee; /**< Video tee */ pjmedia_vid_dev_index preview_cap_id;/**< Capture dev id */ pj_bool_t preview_running;/**< Preview is started*/ pj_bool_t is_native; /**< Preview is by dev */ @@ -530,7 +614,7 @@ struct pjsua_data /* Calls: */ pjsua_config ua_cfg; /**< UA config. */ unsigned call_cnt; /**< Call counter. */ - pjsua_call calls[PJSUA_MAX_CALLS];/**< Calls array. */ + pjsua_call *calls; /**< Calls array. */ pjsua_call_id next_call_id; /**< Next call id to use*/ /* Buddy; */ @@ -582,6 +666,11 @@ struct pjsua_data unsigned rec_cnt; /**< Number of file recorders. */ pjsua_file_data recorder[PJSUA_MAX_RECORDERS];/**< Array of recs.*/ + /* Media ports/channels */ + unsigned mport_cnt; /**< Number of media channels. */ + pjsua_mport_id mport_id; /**< Id of last media port that was allocated */ + pjsua_mport_data *mport; /**< Array of media channels.*/ + /* Video windows */ #if PJSUA_HAS_VIDEO pjsua_vid_win win[PJSUA_MAX_VID_WINS]; /**< Array of windows */ @@ -854,6 +943,11 @@ pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg); */ pj_status_t pjsua_call_subsys_start(void); +/** + * Destroy pjsua call subsystem. + */ +pj_status_t pjsua_call_subsys_destroy(void); + /** * Init media subsystems. */ diff --git a/pjsip/src/pjsip-ua/sip_100rel.c b/pjsip/src/pjsip-ua/sip_100rel.c index 9fa1b9bfd2..f739538228 100644 --- a/pjsip/src/pjsip-ua/sip_100rel.c +++ b/pjsip/src/pjsip-ua/sip_100rel.c @@ -582,7 +582,7 @@ static void on_retransmit(pj_timer_heap_t *timer_heap, clear_all_responses(dd); /* Send 500 response */ - status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata); + status = pjsip_inv_end_session(dd->inv, 504, &reason, &tdata); if (status == PJ_SUCCESS && tdata) { pjsip_dlg_send_response(dd->inv->dlg, dd->inv->invite_tsx, @@ -608,7 +608,10 @@ static void on_retransmit(pj_timer_heap_t *timer_heap, return; } } else { + if (dd->inv->invite_tsx) pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata); + else + pjsip_tx_data_dec_ref(tdata); } if (final) { diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c index bba63b78cd..9ca567e783 100644 --- a/pjsip/src/pjsip/sip_dialog.c +++ b/pjsip/src/pjsip/sip_dialog.c @@ -965,8 +965,8 @@ PJ_DEF(void) pjsip_dlg_dec_lock(pjsip_dialog *dlg) { PJ_ASSERT_ON_FAIL(dlg!=NULL, return); - PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_dec_lock(), sess_count=%d", - dlg->sess_count)); + PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_dec_lock(), sess_count=%d, tsx_count=%d", + dlg->sess_count, dlg->tsx_count)); pj_assert(dlg->sess_count > 0); --dlg->sess_count; @@ -1820,7 +1820,7 @@ static void dlg_update_routeset(pjsip_dialog *dlg, const pjsip_rx_data *rdata) //msg_cseq = rdata->msg_info.cseq->cseq; /* Ignore if route set has been frozen */ - if (dlg->route_set_frozen) + if (dlg->route_set_frozen) return; /* Ignore if the message is an UPDATE response (see ticket #1781) */ diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c index cf9795dbbf..5607d61476 100644 --- a/pjsip/src/pjsip/sip_transaction.c +++ b/pjsip/src/pjsip/sip_transaction.c @@ -1952,6 +1952,7 @@ static void send_msg_callback( pjsip_send_state *send_state, tsx_update_transport(tsx, send_state->cur_transport); /* Update remote address. */ + pj_assert(tdata->dest_info.cur_addr < _countof(tdata->dest_info.addr.entry)); tsx->addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len; pj_memcpy(&tsx->addr, &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr, @@ -2378,6 +2379,7 @@ PJ_DEF(pj_status_t) pjsip_tsx_retransmit_no_state(pjsip_transaction *tsx, { pj_status_t status; + if (tsx) { pj_grp_lock_acquire(tsx->grp_lock); if (tdata == NULL) { tdata = tsx->last_tx; @@ -2392,7 +2394,11 @@ PJ_DEF(pj_status_t) pjsip_tsx_retransmit_no_state(pjsip_transaction *tsx, if (status == PJ_SUCCESS) { pjsip_tx_data_dec_ref(tdata); } - + } else { + status = PJ_EBUG; + if (tdata) + pjsip_tx_data_dec_ref(tdata); + } return status; } diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c index 4f483faa1b..57c0614b32 100644 --- a/pjsip/src/pjsip/sip_transport.c +++ b/pjsip/src/pjsip/sip_transport.c @@ -624,26 +624,38 @@ PJ_DEF(pj_bool_t) pjsip_tx_data_is_valid( pjsip_tx_data *tdata ) static char *get_msg_info(pj_pool_t *pool, const char *obj_name, const pjsip_msg *msg) { - char info_buf[128], *info; + char info_buf[256], to_uri[PJSIP_MAX_URL_SIZE], *info; const pjsip_cseq_hdr *cseq; + const pjsip_cid_hdr *cid; + const pjsip_to_hdr *to; int len; cseq = (const pjsip_cseq_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL); PJ_ASSERT_RETURN(cseq != NULL, "INVALID MSG"); + cid = (const pjsip_cid_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CALL_ID, NULL); + to = (const pjsip_to_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_TO, NULL); + len = (to && to->uri) ? pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, to->uri, to_uri, sizeof(to_uri)) : 0; + to_uri[((len > 0) && (len < sizeof(to_uri))) ? len : 0] = '\0'; if (msg->type == PJSIP_REQUEST_MSG) { len = pj_ansi_snprintf(info_buf, sizeof(info_buf), - "Request msg %.*s/cseq=%d (%s)", + "Request msg %.*s/cseq=%d%s%.*s%s%s (%s)", (int)msg->line.req.method.name.slen, msg->line.req.method.name.ptr, - cseq->cseq, obj_name); + cseq->cseq, + cid?"/cid=":"", cid?(int)cid->id.slen:0, cid?cid->id.ptr:"", + to_uri[0]?"/to=":"", to_uri, + obj_name); } else { len = pj_ansi_snprintf(info_buf, sizeof(info_buf), - "Response msg %d/%.*s/cseq=%d (%s)", + "Response msg %d/%.*s/cseq=%d%s%.*s%s%s (%s)", msg->line.status.code, (int)cseq->method.name.slen, cseq->method.name.ptr, - cseq->cseq, obj_name); + cseq->cseq, + cid?"/cid=":"", cid?(int)cid->id.slen:0, cid?cid->id.ptr:"", + to_uri[0]?"/to=":"", to_uri, + obj_name); } if (len < 1 || len >= (int)sizeof(info_buf)) { @@ -1593,7 +1605,7 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_create( pj_pool_t *pool, if (!mgr->table) return PJ_ENOMEM; - status = pj_lock_create_recursive_mutex(mgr->pool, "tmgr%p", &mgr->lock); + status = pj_lock_create_recursive_mutex(pool, "tmgr%p", &mgr->lock); if (status != PJ_SUCCESS) return status; diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index 86853f4f5e..b5a028f9f4 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -1227,7 +1227,8 @@ static void stateless_send_transport_cb( void *token, if (tdata->via_addr.host.slen > 0 && (!tdata->via_tp || - tdata->via_tp == (void *)stateless_data->cur_transport)) + tdata->via_tp == (void *)stateless_data->cur_transport|| + tdata->msg->line.req.method.id == PJSIP_CANCEL_METHOD)) { via->sent_by = tdata->via_addr; diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index 9982b03c73..eea9b4f1fb 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -1456,6 +1456,7 @@ PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id, if (update_reg) { /* If accounts has registration enabled, start registration */ if (acc->cfg.reg_uri.slen) { + pjsip_regc_destroy(acc->regc); status = pjsua_acc_set_registration(acc->index, PJ_TRUE); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Failed to register with new account " @@ -2866,6 +2867,7 @@ PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, goto on_return; } +on_retry_reg: status = pjsip_regc_register(pjsua_var.acc[acc_id].regc, PJSUA_REG_AUTO_REG_REFRESH, &tdata); @@ -2903,6 +2905,7 @@ PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, status = pjsip_regc_unregister(pjsua_var.acc[acc_id].regc, &tdata); } +on_retry_unreg: if (status == PJ_SUCCESS) { pjsip_regc *regc = pjsua_var.acc[acc_id].regc; @@ -2933,7 +2936,25 @@ PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, //pjsua_process_msg_data(tdata, NULL); status = pjsip_regc_send( regc, tdata ); - + if (status == PJSIP_EBUSY) + { + PJ_LOG(3, (THIS_FILE, "Acc %d: %s could not be sent using the current registration client because it is busy; creating a new client", acc_id, renew ? "Registration" : "Unregistration")); + status = pjsua_regc_init(acc_id); + if (status == PJ_SUCCESS) + { + if (!regc) + { + status = PJ_EINVALIDOP; + } + else + { + if (renew) + goto on_retry_reg; + status = pjsip_regc_unregister_all(regc, &tdata); + goto on_retry_unreg; + } + } + } PJSUA_LOCK(); if (pjsip_regc_dec_ref(regc) == PJ_EGONE) { /* regc has been deleted. */ @@ -3392,7 +3413,6 @@ pj_status_t pjsua_acc_get_uac_addr(pjsua_acc_id acc_id, pjsip_sip_uri *sip_uri; pj_status_t status; pjsip_transport_type_e tp_type = PJSIP_TRANSPORT_UNSPECIFIED; - unsigned flag; pjsip_tpselector tp_sel; pjsip_tpmgr *tpmgr; pjsip_tpmgr_fla2_param tfla2_prm; @@ -3442,7 +3462,7 @@ pj_status_t pjsua_acc_get_uac_addr(pjsua_acc_id acc_id, tp_type = (pjsip_transport_type_e)(((int)tp_type) | PJSIP_TRANSPORT_IPV6); - flag = pjsip_transport_get_flag_from_type(tp_type); + unsigned flag = pjsip_transport_get_flag_from_type(tp_type); /* Init transport selector. */ pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); @@ -3462,6 +3482,9 @@ pj_status_t pjsua_acc_get_uac_addr(pjsua_acc_id acc_id, return status; /* Set this as default return value. This may be changed below. */ + if (acc->cfg.rtp_cfg.public_addr.slen > 0) + addr->host = acc->cfg.rtp_cfg.public_addr; + else addr->host = tfla2_prm.ret_addr; addr->port = tfla2_prm.ret_port; @@ -3735,7 +3758,6 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uas_contact( pj_pool_t *pool, pjsip_tpselector tp_sel; pjsip_tpmgr *tpmgr; pjsip_tpmgr_fla2_param tfla2_prm; - unsigned flag; int secure; int local_port; const char *beginquote, *endquote; @@ -3821,7 +3843,7 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uas_contact( pj_pool_t *pool, (((int)tp_type) | PJSIP_TRANSPORT_IPV6); } - flag = pjsip_transport_get_flag_from_type(tp_type); + unsigned flag = pjsip_transport_get_flag_from_type(tp_type); secure = (flag & PJSIP_TRANSPORT_SECURE) != 0; /* Init transport selector. */ diff --git a/pjsip/src/pjsua-lib/pjsua_aud.c b/pjsip/src/pjsua-lib/pjsua_aud.c index d61a4f9b08..193bd64561 100644 --- a/pjsip/src/pjsua-lib/pjsua_aud.c +++ b/pjsip/src/pjsua-lib/pjsua_aud.c @@ -23,6 +23,30 @@ #define THIS_FILE "pjsua_aud.c" +#define SIGNATURE PJMEDIA_SIG_CLASS_PORT_AUD('A','P') + +#define NORMAL_LEVEL 128 + + /* 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). + */ +#define ATTACK_A (pjsua_var.media_cfg.clock_rate / pjsua_var.mconf_cfg.samples_per_frame) +#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) + +#define MAX_LEVEL (32767) +#define MIN_LEVEL (-32768) + +#define IS_OVERFLOW(s) (((s) > MAX_LEVEL) || ((s) < MIN_LEVEL)) + /***************************************************************************** * * Prototypes @@ -40,6 +64,9 @@ static pj_status_t create_aud_param(pjmedia_aud_param *param, unsigned samples_per_frame, unsigned bits_per_sample, pj_bool_t use_default_settings); +static pj_status_t mport_put_frame(pjmedia_port *this_port, pjmedia_frame *frame); +static pj_status_t mport_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); +static pj_status_t mport_on_destroy(pjmedia_port *this_port); /***************************************************************************** * @@ -77,9 +104,21 @@ PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id) goto on_return; call = &pjsua_var.calls[call_id]; - if (call->audio_idx >= 0) - port_id = call->media[call->audio_idx].strm.a.conf_slot; - + port_id = (call->conf_slot != PJSUA_INVALID_ID) ? call->conf_slot : (call->audio_idx >= 0) ? call->media[call->audio_idx].strm.a.conf_slot : PJSUA_INVALID_ID; + if (port_id != PJSUA_INVALID_ID) + { + goto on_return; + } + for (int mi = 0;(unsigned) mi < call->med_cnt; ++mi) { + pjsua_call_media* call_med = &call->media[mi]; + if(call_med->strm.a.conf_slot == PJSUA_INVALID_ID || mi== call->audio_idx) + { + continue; + } + call->conf_slot = call_med->strm.a.conf_slot; + call->audio_idx= mi; + port_id = call->conf_slot; + } on_return: PJSUA_UNLOCK(); @@ -263,12 +302,436 @@ PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, return status; } +PJ_DEF(pj_status_t) pjsua_call_get_queued_dtmf_digits(pjsua_call_id call_id, + unsigned *digits) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(digits, PJ_EINVAL); + *digits = 0U; + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + pj_log_push_indent(); + + status = acquire_call("pjsua_call_get_queued_dtmf_digits()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + if (!pjsua_call_has_media(call_id)) { + PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); + status = PJ_EINVALIDOP; + goto on_return; + } + + status = pjmedia_get_queued_dtmf_digits( + call->media[call->audio_idx].strm.a.stream, digits); + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + /***************************************************************************** * * Audio media with PJMEDIA backend */ +static void mport_rec_frame(pjsua_mport_data *data, pjmedia_frame *frame) +{ + pj_size_t size, avl, count; + pj_bool_t is_silence = PJ_FALSE; + pj_size_t silence_samples = 0; + pj_int16_t *buf = (pj_int16_t *)frame->buf; + + if (data->record_data.status != PJSUA_REC_RUNNING) + return; + + if (frame->type == PJMEDIA_FRAME_TYPE_NONE) + { + count = size = pjsua_var.mconf_cfg.samples_per_frame; + buf = (pj_int16_t *)_alloca(sizeof(*buf) * size); + pj_bzero(buf, sizeof(*buf) * size); + } + else + { + count = size = (frame->size >> 1); + } + + pj_assert(data->record_data.buffer != NULL); + + if (data->record_data.max_samples) + { + pj_assert(data->record_data.max_samples > data->record_data.samples_recorded); + avl = (pj_size_t)((pj_uint64_t)data->record_data.max_samples - data->record_data.samples_recorded); + if (count > avl) + count = avl; + } + if (data->record_data.max_duration) + { + pj_assert(data->record_data.max_duration > data->record_data.samples_seen); + avl = data->record_data.max_duration - (pj_size_t)data->record_data.samples_seen; + if (count > avl) + count = avl; + } + if ((data->record_data.max_silence || data->record_data.eliminate_silence) && data->record_data.vad) + { + is_silence = pjmedia_silence_det_detect(data->record_data.vad, (const pj_int16_t *)buf, size, NULL); + if (is_silence) + { + if (!data->record_data.is_silence) + { + data->record_data.is_silence = PJ_TRUE; + data->record_data.vad_timestamp = frame->timestamp; + } + else + { + pj_assert(frame->timestamp.u64 > data->record_data.vad_timestamp.u64); + } + silence_samples = (pj_size_t)(frame->timestamp.u64 - data->record_data.vad_timestamp.u64); + if (data->record_data.eliminate_silence) + { + if ((silence_samples + count) >= data->record_data.eliminate_silence) + { + count = 0; + } + else + { + avl = (data->record_data.eliminate_silence - silence_samples); + if (count > avl) + count = avl; + } + } + if (data->record_data.max_silence) + { + pj_assert(data->record_data.max_silence > silence_samples); + avl = silence_samples - data->record_data.max_silence; + if (count > avl) + count = avl; + } + } + else if (data->record_data.is_silence) + { + data->record_data.is_silence = PJ_FALSE; + data->record_data.vad_timestamp = frame->timestamp; + } + } + + pj_assert(data->record_data.buffer->capacity >= data->record_data.buffer->len); + avl = (data->record_data.buffer->capacity - data->record_data.buffer->len); + if (count > avl) + { + if (!data->record_data.overrun) + { + PJ_LOG(3, (THIS_FILE, "Record buffer overrun for %s: avl=%lu, size=%lu", data->base.info.name.ptr, avl, size)); + data->record_data.overrun = PJ_TRUE; + } + if ((data->base.info.fmt.type == PJMEDIA_TYPE_AUDIO) && + (data->base.info.fmt.detail_type == PJMEDIA_FORMAT_DETAIL_AUDIO) && + (data->base.info.fmt.det.aud.channel_count > 1)) + { + // Take account of block size and ensure that only complete blocks are buffered + count = avl % data->base.info.fmt.det.aud.channel_count; + if (!count) + count = avl; + else + count = avl - count; + } + else + { + count = avl; + } + } + if (count) + { + pjmedia_circ_buf_write(data->record_data.buffer, buf, (unsigned int)count); + data->record_data.samples_recorded += count; + avl -= count; + } + data->record_data.samples_seen += size; + + do { + if (data->record_data.max_samples && (data->record_data.samples_recorded >= data->record_data.max_samples)) + { + data->record_data.status = PJSUA_REC_STOPPING; + data->record_data.er = PJSUA_REC_ER_MAX_SAMPLES; + break; + } + if (data->record_data.max_duration && (data->record_data.samples_seen >= data->record_data.max_duration)) + { + data->record_data.status = PJSUA_REC_STOPPING; + data->record_data.er = PJSUA_REC_ER_MAX_DURATION; + break; + } + if (data->record_data.max_silence && ((silence_samples + size) >= data->record_data.max_silence)) + { + data->record_data.status = PJSUA_REC_STOPPING; + data->record_data.er = PJSUA_REC_ER_MAX_SILENCE; + break; + } + } while (0); + + // Notify the application to collect the buffered data when the available space + // falls below the data threshold or if we've encountered a termination + // condition + if (!data->record_data.signaled && + ((avl < data->record_data.threshold) || (data->record_data.status == PJSUA_REC_STOPPING))) + { + data->record_data.signaled = PJ_TRUE; + pj_event_set(data->record_data.event); + } + + if (data->record_data.status == PJSUA_REC_STOPPING) + { + if (data->record_data.rec_output) + pjmedia_conf_configure_port(pjsua_var.mconf, data->slot, PJMEDIA_PORT_NO_CHANGE, PJMEDIA_PORT_ENABLE); + else + pjmedia_conf_configure_port(pjsua_var.mconf, data->slot, PJMEDIA_PORT_ENABLE, PJMEDIA_PORT_NO_CHANGE); + } +} + +static pj_status_t mport_put_frame(pjmedia_port *this_port, pjmedia_frame *frame) +{ + PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVALIDOP); + + pjsua_mport_data *data = (pjsua_mport_data*) this_port; + if ((data->record_data.status == PJSUA_REC_RUNNING) && + !data->record_data.rec_output) + { + pj_enter_critical_section(); + mport_rec_frame(data, frame); + pj_leave_critical_section(); + } + + if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO) && + (frame->size > 0U) && + (data->listener_cnt > 0U)) + { + const pj_size_t samples = frame->size >> 1; + pj_assert(samples == pjsua_var.mconf_cfg.samples_per_frame); + pj_enter_critical_section(); + pj_assert(data->listeners != NULL); + for (register pj_uint32_t i = 0; i < data->listener_cnt; ++i) + { + const pjsua_mport_id id = data->listeners[i]; + pjsua_mport_data *listener; + pj_int32_t *mix_buf; + pj_int16_t *buf; + pj_size_t j; + pj_assert((id >= 0) && (id < (pjsua_mport_id)pjsua_var.media_cfg.max_media_ports)); + listener = &pjsua_var.mport[id]; + mix_buf = listener->mix_buf; + buf = (pj_int16_t *)frame->buf; + if (listener->mix_cnt++ > 0) + { + for (j = 0; j < samples; ++j) + { + *mix_buf += *buf++; + if (IS_OVERFLOW(*mix_buf)) + { + int tmp_adj = (MAX_LEVEL << 7) / *mix_buf; + if (tmp_adj < 0) + tmp_adj = -tmp_adj; + if (tmp_adj < listener->mix_adj) + listener->mix_adj = tmp_adj; + } + mix_buf++; + } + } + else + { + for (j = 0; j < samples; ++j) + { + *mix_buf++ = *buf++; + } + } + } + pj_leave_critical_section(); + } + + return PJ_SUCCESS; +} + +static pj_status_t mport_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) +{ + pjsua_mport_data *data; + pj_size_t i, size, avl, count; + + PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVALIDOP); + + data = (pjsua_mport_data*) this_port; + + size = pjsua_var.mconf_cfg.samples_per_frame; + + frame->timestamp.u64 = data->play_data.timestamp.u64; + data->play_data.timestamp.u64 += size; + + pj_enter_critical_section(); + + if (data->play_data.status <= PJSUA_REP_IDLE) + { + frame->size = 0; + frame->type = PJMEDIA_FRAME_TYPE_NONE; + } + else if (data->play_data.status == PJSUA_REP_CONFERENCING) + { + if (data->mix_cnt) + { + pj_int16_t *buf = (pj_int16_t *)frame->buf; + SIMPLE_AGC(data->last_mix_adj, data->mix_adj); + data->last_mix_adj = data->mix_adj; + pj_assert(data->mix_buf != NULL); + if (data->mix_adj != NORMAL_LEVEL) + { + for (i = 0; i < size; ++i) + { + pj_int32_t s = data->mix_buf[i]; + s = (s * data->mix_adj) >> 7; + if (s > MAX_LEVEL) s = MAX_LEVEL; + else if (s < MIN_LEVEL) s = MIN_LEVEL; + *buf++ = (pj_int16_t)s; + } + data->mix_adj = NORMAL_LEVEL; + } + else + { + for (i = 0; i < size; ++i) + { + *buf++ = (pj_int16_t)data->mix_buf[i]; + } + } + data->mix_cnt = 0U; + frame->size = size << 1; + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + } + else + { + frame->size = 0; + frame->type = PJMEDIA_FRAME_TYPE_NONE; + } + } + else + { + pj_assert(data->play_data.buffer != NULL); + avl = data->play_data.buffer->len; + count = size; + if (count > avl) + { + if (!data->play_data.underrun && (avl || data->play_data.samples_played) && (data->play_data.status == PJSUA_REP_RUNNING)) + { + PJ_LOG(3, (THIS_FILE, "Replay buffer underrun for %s: avl=%lu, size=%lu", this_port->info.name.ptr, avl, size)); + data->play_data.underrun = PJ_TRUE; + } + count = avl; + } + if (count) + { + pjmedia_circ_buf_read(data->play_data.buffer, (pj_int16_t*)frame->buf, (unsigned int)count); + avl -= count; + data->play_data.samples_played += count; + } + + // Pad with zeroes if necessary + if (count < size) + pj_bzero((pj_int16_t*)(frame->buf) + count, (size - count) << 1); + + // Notify the application when the remaining data falls below the + // threshold or if the replay has completed + if (!data->play_data.signaled && + (((data->play_data.status == PJSUA_REP_STOPPING) && !avl) || + ((data->play_data.status == PJSUA_REP_RUNNING) && (avl < data->play_data.threshold)))) + { + data->play_data.signaled = PJ_TRUE; + pj_event_set(data->play_data.event); + } + + frame->size = size << 1; + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + } + + if ((data->record_data.status == PJSUA_REC_RUNNING) && + data->record_data.rec_output) + mport_rec_frame(data, frame); + + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + + +static pj_status_t mport_on_destroy(pjmedia_port *this_port) +{ + pjsua_mport_data *data; + + PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVALIDOP); + + data = (pjsua_mport_data*) this_port; + + pj_enter_critical_section(); + + if (data->record_data.buffer) + pjmedia_circ_buf_reset(data->record_data.buffer); + if (data->record_data.status == PJSUA_REC_RUNNING) + data->record_data.status = PJSUA_REC_STOPPING; + if ((data->record_data.status != PJSUA_REC_IDLE) && !data->record_data.signaled && data->record_data.event) + { + data->record_data.signaled = PJ_TRUE; + pj_event_set(data->record_data.event); + } + + if (data->play_data.buffer) + pjmedia_circ_buf_reset(data->play_data.buffer); + if (data->play_data.status == PJSUA_REP_RUNNING) + data->play_data.status = PJSUA_REP_STOPPING; + if ((data->play_data.status == PJSUA_REP_STOPPING) && !data->play_data.signaled && data->play_data.event) + { + data->play_data.signaled = PJ_TRUE; + pj_event_set(data->play_data.event); + } + + pj_leave_critical_section(); + + if (data->play_data.status == PJSUA_REP_CONFERENCING) + pjsua_mport_conf_stop((pjsua_mport_id)(data - pjsua_var.mport)); + + return PJ_SUCCESS; +} + +static pj_status_t init_mport(pjsua_mport_id id) +{ + pj_status_t status; + char buf[PJ_MAX_OBJ_NAME]; + pj_str_t name; + register pjsua_mport_data *p = &pjsua_var.mport[id]; + //pj_bzero(p, sizeof(*p)); + p->slot = PJSUA_INVALID_ID; + _snprintf(buf, sizeof(buf), "mp%d", id); + buf[sizeof(buf)-1] = '\0'; + pj_strdup2_with_null(pjsua_var.pool, &name, buf); + status = pjmedia_port_info_init(&p->base.info, &name, SIGNATURE, pjsua_var.media_cfg.clock_rate, + pjsua_var.mconf_cfg.channel_count, pjsua_var.mconf_cfg.bits_per_sample, pjsua_var.mconf_cfg.samples_per_frame); + if (status == PJ_SUCCESS) + { + p->base.port_data.ldata = id; + p->base.put_frame = &mport_put_frame; + p->base.get_frame = &mport_get_frame; + p->base.on_destroy = &mport_on_destroy; + } + return status; +} + +static void deinit_mport(pjsua_mport_id id) +{ + pjsua_mport_free(id); + register pjsua_mport_data *p = &pjsua_var.mport[id]; + pjmedia_port_destroy(&p->base); + p->base.info.signature = 0; +} + /* Init pjmedia audio subsystem */ pj_status_t pjsua_aud_subsys_init() { @@ -411,7 +874,32 @@ pj_status_t pjsua_aud_subsys_init() pjsua_var.mconf_cfg.samples_per_frame, pjsua_var.mconf_cfg.bits_per_sample, &pjsua_var.null_port); - PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + if (status != PJ_SUCCESS) + { + pjsua_perror(THIS_FILE, "Error creating null port", status); + goto on_error; + } + + /* Initialise the media ports */ + pjsua_var.mport_cnt = 0U; + pjsua_var.mport_id = -1; + pjsua_var.mport = (pjsua_mport_data*)pj_pool_zalloc(pjsua_var.pool, sizeof(pjsua_mport_data) * pjsua_var.media_cfg.max_media_ports); + if (!pjsua_var.mport) + { + status = PJ_ENOMEM; + pjsua_perror(THIS_FILE, "Error allocating media ports", status); + goto on_error; + } + + /* Init media port array. */ + for (unsigned i = 0u; i < pjsua_var.media_cfg.max_media_ports; ++i) + { + status = init_mport(i); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initialising media port", status); + goto on_error; + } + } return status; @@ -507,6 +995,14 @@ pj_status_t pjsua_aud_subsys_destroy() { unsigned i; + if (pjsua_var.mport != NULL) + { + for (i = 0U; i < pjsua_var.media_cfg.max_media_ports; ++i) + deinit_mport(i); + pjsua_var.mport = NULL; + } + pjsua_var.mport_cnt = 0U; + close_snd_dev(); /* Destroy file players */ @@ -539,6 +1035,31 @@ pj_status_t pjsua_aud_subsys_destroy() return PJ_SUCCESS; } +static pj_status_t replace_conference_slot_to_null_port(pjsua_call* call) +{ + if (!call->inv) { + return PJ_EBUSY; + } + if (call->conf_slot == PJSUA_INVALID_ID || !call->null_port) + { + return PJ_EINVAL; + } + return pjmedia_conf_replace_port(pjsua_var.mconf, call->inv->pool, call->null_port, (unsigned)call->conf_slot); +} +void remove_conf_port(pjsua_conf_port_id* id) +{ + if (pjsua_var.mconf) { + if (pjsua_conf_remove_port(*id) == PJ_SUCCESS) + { + PJ_LOG(4, (THIS_FILE, "pjsua_conf_remove_port done id: %d", *id)); + } + else + { + PJ_LOG(2, (THIS_FILE, "pjsua_conf_remove_port failed")); + } + } + *id = PJSUA_INVALID_ID; +} void pjsua_aud_stop_stream(pjsua_call_media *call_med) { @@ -552,12 +1073,27 @@ void pjsua_aud_stop_stream(pjsua_call_media *call_med) pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, strm); pjmedia_stream_send_rtcp_bye(strm); - - if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) { - if (pjsua_var.mconf) { - pjsua_conf_remove_port(call_med->strm.a.conf_slot); + pjsua_call* call = call_med->call; + if(call_med->strm.a.conf_slot != PJSUA_INVALID_ID ) + { + if (call->conf_slot == call_med->strm.a.conf_slot) + { + if(replace_conference_slot_to_null_port(call)==PJ_SUCCESS) + { + PJ_LOG(4, (THIS_FILE, "pjsua_aud_stop_stream: replace_conference_slot_to_null_port [call->conf_slot:%d]", call->conf_slot)); + call_med->strm.a.conf_slot= PJSUA_INVALID_ID; + } + else + { + PJ_LOG(2, (THIS_FILE, "pjsua_aud_stop_stream: replace_conference_slot_to_null_port failed [call->conf_slot:%d]", call->conf_slot)); + } } - call_med->strm.a.conf_slot = PJSUA_INVALID_ID; + else + remove_conf_port(&call_med->strm.a.conf_slot); + } + else + { + PJ_LOG(4, (THIS_FILE, "pjsua_aud_stop_stream::stream is invalid")); } /* Don't check for direction and transmitted packets count as we @@ -592,7 +1128,6 @@ void pjsua_aud_stop_stream(pjsua_call_media *call_med) pjmedia_port_destroy(call_med->strm.a.media_port); call_med->strm.a.media_port = NULL; } - pjmedia_stream_destroy(strm); call_med->strm.a.stream = NULL; } @@ -662,7 +1197,6 @@ static void dtmf_event_callback(pjmedia_stream *strm, void *user_data, pj_log_pop_indent(); } - /* Internal function: update audio channel after SDP negotiation. * Warning: do not use temporary/flip-flop pool, e.g: inv->pool_prov, * for creating stream, etc, as after SDP negotiation and when @@ -683,7 +1217,8 @@ pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med, PJ_UNUSED_ARG(local_sdp); PJ_UNUSED_ARG(remote_sdp); - PJ_LOG(4,(THIS_FILE,"Audio channel update..")); + PJ_LOG(4,(THIS_FILE,"Audio channel update for index %d for call %d...", + call_med->idx, call_med->call->index)); pj_log_push_indent(); si->rtcp_sdes_bye_disabled = pjsua_var.media_cfg.no_rtcp_sdes_bye; @@ -802,30 +1337,72 @@ pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med, &call_med->strm.a.media_port); } - /* - * Add the call to conference bridge. - */ - { - char tmp[PJSIP_MAX_URL_SIZE]; - pj_str_t port_name; - - port_name.ptr = tmp; - port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, - call->inv->dlg->remote.info->uri, - tmp, sizeof(tmp)); - if (port_name.slen < 1) { - port_name = pj_str("call"); - } - status = pjmedia_conf_add_port(pjsua_var.mconf, - call->inv->pool, - call_med->strm.a.media_port, - &port_name, - (unsigned*) - &call_med->strm.a.conf_slot); - if (status != PJ_SUCCESS) { - goto on_return; + if ((call->audio_idx == -1) || ((unsigned)call->audio_idx == strm_idx)) + { + if (call_med->strm.a.conf_slot == PJSUA_INVALID_ID) + { + /* + * Add the stream to conference bridge. + */ + char tmp[PJSIP_MAX_URL_SIZE]; + pj_str_t port_name; + + port_name.ptr = tmp; + port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + call->inv->dlg->remote.info->uri, + tmp, sizeof(tmp)); + if (port_name.slen < 1) { + port_name = pj_str("call"); + } + if (pjmedia_conf_compare_port_signature(pjsua_var.mconf, call->conf_slot, PJMEDIA_SIG_PORT_NULL)) + { + status = pjmedia_conf_replace_port(pjsua_var.mconf, + call->inv->pool, + call_med->strm.a.media_port, + (unsigned)call->conf_slot); + if (status != PJ_SUCCESS) + goto on_return; + call_med->strm.a.conf_slot= call->conf_slot; + } + else + { + status = pjmedia_conf_add_port(pjsua_var.mconf, + call->inv->pool, + call_med->strm.a.media_port, + &port_name, + (unsigned*)&call_med->strm.a.conf_slot); + if (status != PJ_SUCCESS) + goto on_return; + PJ_LOG(4, (THIS_FILE, "pjsua_aud_channel_update::pjmedia_conf_add_port [call_med->strm.a.conf_slot:%d]", call_med->strm.a.conf_slot)); + } + if (call->conf_slot == PJSUA_INVALID_ID) + call->conf_slot = call_med->strm.a.conf_slot; + PJ_LOG(4, (THIS_FILE, " pjsua_aud_channel_update:[call->conf_slot:%d] [call->index:%d]", call->conf_slot, call->index)); + } + else + { + /* + * Replace the old/null port in the conference bridge. + */ + status = pjmedia_conf_replace_port(pjsua_var.mconf, + call->inv->pool, + call_med->strm.a.media_port, + (unsigned)call_med->strm.a.conf_slot); + if (status != PJ_SUCCESS) + goto on_return; + PJ_LOG(4, (THIS_FILE, "pjsua_aud_channel_update::pjmedia_conf_replace_port [call_med->strm.a.conf_slot:%d]", call_med->strm.a.conf_slot)); } - } + if (call->audio_idx == -1) + call->audio_idx = (int)strm_idx; + call->conf_idx = call->audio_idx; + } + else + { + PJ_LOG(2, (THIS_FILE, "Ignoring audio channel update for index %d for call %d because the selected index is %d!", + call_med->idx, call_med->call->index, call->audio_idx)); + goto on_return; + } + /* Subscribe to stream events */ pjmedia_event_subscribe(NULL, &call_media_on_event, call_med, @@ -834,6 +1411,8 @@ pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med, on_return: pj_log_pop_indent(); + if (status != PJ_SUCCESS) + PJ_LOG(2, (THIS_FILE, "pjsua_aud_channel_update failed")); return status; } @@ -864,11 +1443,10 @@ PJ_DEF(unsigned) pjsua_conf_get_max_ports(void) */ PJ_DEF(unsigned) pjsua_conf_get_active_ports(void) { - unsigned ports[PJSUA_MAX_CONF_PORTS]; - unsigned count = PJ_ARRAY_SIZE(ports); + unsigned count = pjsua_var.media_cfg.max_media_ports; pj_status_t status; - status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count); + status = pjmedia_conf_enum_ports(pjsua_var.mconf, NULL, &count); if (status != PJ_SUCCESS) count = 0; @@ -915,14 +1493,12 @@ PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id, /* Build array of listeners */ info->listener_cnt = cinfo.listener_cnt; - for (i=0; ilisteners); ++i) { info->listeners[i] = cinfo.listener_slots[i]; } return PJ_SUCCESS; } - - /* * Add arbitrary media port to PJSUA's conference bridge. */ @@ -1706,6 +2282,1371 @@ PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id) return PJ_SUCCESS; } +/***************************************************************************** + * Media port + */ + +PJ_DEF(pj_status_t) pjsua_mport_alloc(pjmedia_dir dir, + pj_bool_t enable_vad, pj_size_t record_buffer_size, + pj_size_t record_data_threshold, pj_size_t play_buffer_size, + pj_size_t play_data_threshold, pjsua_mport_id *p_id) +{ + char name[PJ_MAX_OBJ_NAME]; + + if ((dir != PJMEDIA_DIR_CAPTURE) && (dir != PJMEDIA_DIR_PLAYBACK) && + (dir != PJMEDIA_DIR_CAPTURE_PLAYBACK)) + return PJ_EINVAL; + + PJSUA_LOCK(); + + if (pjsua_var.mport_cnt >= pjsua_var.media_cfg.max_media_ports) + { + PJSUA_UNLOCK(); + return PJ_ETOOMANY; + } + + unsigned int id; + for (id = 0; id= pjsua_var.media_cfg.max_media_ports) + tmp -= pjsua_var.media_cfg.max_media_ports; + if (pjsua_var.mport[tmp].slot == PJSUA_INVALID_ID) + { + pjsua_var.mport_id = id = tmp; + break; + } + } + if (id == pjsua_var.media_cfg.max_media_ports) + { + /* This is unexpected */ + pj_assert(0); + PJSUA_UNLOCK(); + return PJ_EBUG; + } + + pj_log_push_indent(); + + pj_status_t status = PJ_SUCCESS; + register pjsua_mport_data *const p = &pjsua_var.mport[id]; + p->pool = pjsua_pool_create(p->base.info.name.ptr, 1000, 1000); + if (p->pool == NULL) + { + status = PJ_ENOMEM; + goto on_error; + } + + name[sizeof(name) - 1] = '\0'; + + p->listener_cnt = p->participant_cnt = 0U; + p->listeners = p->participants = NULL; + p->mix_buf = NULL; + p->last_mix_adj = p->mix_adj = NORMAL_LEVEL; + + p->record_data.vad = NULL; + p->record_data.buffer_size = 0U; + p->record_data.buffer = NULL; + p->record_data.event = NULL; + p->record_data.signaled = PJ_FALSE; + p->record_data.status = PJSUA_REC_IDLE; + if (dir & PJMEDIA_DIR_CAPTURE) + { + if (enable_vad) + { + status = pjmedia_silence_det_create(p->pool, pjsua_var.media_cfg.clock_rate, pjsua_var.mconf_cfg.samples_per_frame, &p->record_data.vad); + if (status != PJ_SUCCESS) + goto on_error; + status = pjmedia_silence_det_set_name(p->record_data.vad, p->base.info.name.ptr); + } + if (!record_buffer_size) + { + record_buffer_size = pjsua_var.media_cfg.mport_record_buffer_size; + if (!record_buffer_size) + record_buffer_size = 1250; + } + if (record_buffer_size < 40) + record_buffer_size = 40; + else if (record_buffer_size > 5000) + record_buffer_size = 5000; + record_buffer_size = ((record_buffer_size + (pjsua_var.media_cfg.audio_frame_ptime - 1)) / pjsua_var.media_cfg.audio_frame_ptime); + p->record_data.buffer_size = record_buffer_size * pjsua_var.mconf_cfg.samples_per_frame; + _snprintf(name, sizeof(name)-1, "mp_rec%d", id); + status = pj_event_create(p->pool, name, PJ_TRUE, PJ_FALSE, &p->record_data.event); + if (status != PJ_SUCCESS) + goto on_error; + if (!record_data_threshold) + record_data_threshold = 250; + record_data_threshold = ((record_data_threshold + (pjsua_var.media_cfg.audio_frame_ptime - 1)) / pjsua_var.media_cfg.audio_frame_ptime); + if (record_data_threshold > record_buffer_size) + record_data_threshold = record_buffer_size; + p->record_data.threshold = record_data_threshold * pjsua_var.mconf_cfg.samples_per_frame; + _snprintf(name, sizeof(name)-1, "mp_rgn%d", id); + status = pj_event_create(p->pool, name, PJ_TRUE, PJ_FALSE, &p->recogntion_data.event); + if (status != PJ_SUCCESS) + goto on_error; + } + + p->play_data.buffer_size = 0U; + p->play_data.timestamp.u64 = 0; + p->play_data.buffer = NULL; + p->play_data.event = NULL; + p->play_data.signaled = PJ_FALSE; + p->play_data.status = PJSUA_REP_IDLE; + if (dir & PJMEDIA_DIR_PLAYBACK) + { + if (!play_buffer_size) + { + play_buffer_size = pjsua_var.media_cfg.mport_replay_buffer_size; + if (!play_buffer_size) + play_buffer_size = 1250; + } + if (play_buffer_size < 40) + play_buffer_size = 40; + else if (play_buffer_size > 5000) + play_buffer_size = 5000; + play_buffer_size = ((play_buffer_size + (pjsua_var.media_cfg.audio_frame_ptime - 1)) / pjsua_var.media_cfg.audio_frame_ptime); + p->play_data.buffer_size = play_buffer_size * pjsua_var.mconf_cfg.samples_per_frame; + _snprintf(name, sizeof(name)-1, "mp_pla%d", id); + status = pj_event_create(p->pool, name, PJ_TRUE, PJ_FALSE, &p->play_data.event); + if (status != PJ_SUCCESS) + goto on_error; + if (!play_data_threshold) + play_data_threshold = 250; + play_data_threshold = ((play_data_threshold + (pjsua_var.media_cfg.audio_frame_ptime - 1)) / pjsua_var.media_cfg.audio_frame_ptime); + if (play_data_threshold > play_buffer_size) + play_data_threshold = play_buffer_size; + p->play_data.threshold = play_data_threshold * pjsua_var.mconf_cfg.samples_per_frame; + } + + status = pjsua_conf_add_port(p->pool, &p->base, &p->slot); + if (status != PJ_SUCCESS) + { + pjsua_perror(THIS_FILE, "Unable to add media port to conference bridge", status); + goto on_error; + } + + p->base.info.dir = dir; + + ++pjsua_var.mport_cnt; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Media port %d allocated: slot=%d", id, p->slot)); + + if (p_id) + *p_id = id; + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + p->listeners = p->participants = NULL; + p->mix_buf = NULL; + if (p->recogntion_data.event != NULL) + { + pj_event_destroy(p->recogntion_data.event); + p->recogntion_data.event = NULL; + } + if (p->record_data.event != NULL) + { + pj_event_destroy(p->record_data.event); + p->record_data.event = NULL; + } + p->record_data.vad = NULL; + p->record_data.buffer = NULL; + if (p->play_data.event != NULL) + { + pj_event_destroy(p->play_data.event); + p->play_data.event = NULL; + } + p->play_data.buffer = NULL; + if (p->pool != NULL) + { + pj_pool_release(p->pool); + p->pool = NULL; + } + p->base.info.dir = PJMEDIA_DIR_NONE; + p->slot = PJSUA_INVALID_ID; + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; +} + +PJ_DEF(pj_status_t) pjsua_mport_free(pjsua_mport_id id) +{ + if ((id < 0) || ((unsigned int)id >= pjsua_var.media_cfg.max_media_ports)) + { + pj_assert(0); + return PJ_EINVAL; + } + + PJSUA_LOCK(); + + pjsua_mport_data *p = &pjsua_var.mport[id]; + + if (!p->pool) + { + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } + + pjsua_mport_conf_stop(id); + while (p->listener_cnt > 0) + pjsua_mport_conf_remove(p->listeners[p->listener_cnt - 1], id); + + pj_enter_critical_section(); + + p->listeners = p->participants = NULL; + p->mix_buf = NULL; + + if (p->recogntion_data.event_cnt > 0U) + { + if (p->recogntion_data.signaled) + { + p->recogntion_data.signaled = PJ_FALSE; + pj_event_reset(p->recogntion_data.event); + } + p->recogntion_data.event_cnt = 0U; + } + if (p->record_data.buffer) + { + pjmedia_circ_buf_reset(p->record_data.buffer); + p->record_data.buffer = NULL; + } + if (p->record_data.status != PJSUA_REC_IDLE) + { + if ((p->record_data.event != NULL) && + !p->record_data.signaled) + { + p->record_data.signaled = PJ_TRUE; + pj_event_set(p->record_data.event); + } + p->record_data.status = PJSUA_REC_IDLE; + } + p->record_data.vad = NULL; + + if (p->play_data.buffer) + { + pjmedia_circ_buf_reset(p->play_data.buffer); + p->play_data.buffer = NULL; + } + if (p->play_data.status != PJSUA_REP_IDLE) + { + if ((p->play_data.status != PJSUA_REP_CONFERENCING) && + (p->play_data.event != NULL) && + !p->play_data.signaled) + { + p->play_data.signaled = PJ_TRUE; + pj_event_set(p->play_data.event); + } + p->play_data.status = PJSUA_REP_IDLE; + } + + pj_leave_critical_section(); + + if (p->slot != PJSUA_INVALID_ID) + { + pjsua_conf_remove_port(p->slot); + p->slot = PJSUA_INVALID_ID; + } + + if (p->recogntion_data.event != NULL) + { + pj_event_destroy(p->recogntion_data.event); + p->recogntion_data.event = NULL; + p->recogntion_data.signaled = PJ_FALSE; + } + + if (p->record_data.event != NULL) + { + pj_event_destroy(p->record_data.event); + p->record_data.event = NULL; + p->record_data.signaled = PJ_FALSE; + } + + if (p->play_data.event != NULL) + { + pj_event_destroy(p->play_data.event); + p->play_data.event = NULL; + p->play_data.signaled = PJ_FALSE; + } + + if (p->pool != NULL) + { + pj_pool_release(p->pool); + p->pool = NULL; + } + + p->base.info.dir = PJMEDIA_DIR_NONE; + + pj_assert(pjsua_var.mport_cnt > 0); + --pjsua_var.mport_cnt; + + PJSUA_UNLOCK(); + + PJ_LOG(4, (THIS_FILE, "Media port %d freed", id)); + + return PJ_SUCCESS; +} + +PJ_DEF(pjsua_conf_port_id) pjsua_mport_get_conf_port(pjsua_mport_id id) +{ + PJ_ASSERT_RETURN(id>=0&&(unsigned int)id=0&&(unsigned int)id=0&&(unsigned int)id=0&&(unsigned int)id=0&&(unsigned int)id=0&&(unsigned int)idcapacity - buffer->len; + if (avl) + { + if (fmt_id == PJMEDIA_FORMAT_PCM) + { + size = size >> 1; + if (size > avl) + size = avl; + pjmedia_circ_buf_write(buffer, (pj_int16_t*)data, (unsigned int)size); + size = size << 1; + } + else + { + if (size > avl) + size = avl; + pjmedia_circ_buf_get_write_regions(buffer, ®1, ®1cnt, ®2, ®2cnt); + if (reg1cnt >= size) + { + switch (fmt_id) + { + case PJMEDIA_FORMAT_PCMA: + pjmedia_alaw_decode(reg1, data, size); + break; + case PJMEDIA_FORMAT_PCMU: + pjmedia_ulaw_decode(reg1, data, size); + break; + default: + pj_assert(0); + break; + } + } + else + { + switch (fmt_id) + { + case PJMEDIA_FORMAT_PCMA: + pjmedia_alaw_decode(reg1, data, reg1cnt); + pjmedia_alaw_decode(reg2, (const pj_uint8_t*)data + reg1cnt, size - reg1cnt); + break; + case PJMEDIA_FORMAT_PCMU: + pjmedia_ulaw_decode(reg1, data, reg1cnt); + pjmedia_ulaw_decode(reg2, (const pj_uint8_t*)data + reg1cnt, size - reg1cnt); + break; + default: + pj_assert(0); + break; + } + } + pjmedia_circ_buf_adv_write_ptr(buffer, (unsigned int)size); + } + *count = size; + } +} + +PJ_DEF(pj_status_t) pjsua_mport_play_start( + pjsua_mport_id id, + const pjmedia_format *fmt, + const void *data, + pj_size_t size, + pj_size_t *count) +{ + pjsua_mport_replay_data *p; + + PJ_ASSERT_RETURN(count != NULL, PJ_EINVAL); + + *count = 0; + + PJ_ASSERT_RETURN(id>=0&&(unsigned int)idstatus != PJSUA_REP_IDLE) + { + PJSUA_UNLOCK(); + return PJ_EBUSY; + } + if (((fmt->id != PJMEDIA_FORMAT_PCM) && (fmt->id != PJMEDIA_FORMAT_PCMA) && (fmt->id != PJMEDIA_FORMAT_PCMU)) || + (fmt->det.aud.clock_rate != pjsua_var.media_cfg.clock_rate) || + (fmt->det.aud.channel_count != pjsua_var.media_cfg.channel_count)) + { + PJSUA_UNLOCK(); + return PJ_ENOTSUP; + } + if (!p->buffer) + { + pj_status_t status = pjmedia_circ_buf_create(pjsua_var.mport[id].pool, (unsigned int)p->buffer_size, &p->buffer); + if (status != PJ_SUCCESS) + { + PJSUA_UNLOCK(); + return status; + } + } + + pjmedia_circ_buf_reset(p->buffer); + if ((data != NULL) && size) + add_replay_data(fmt->id, p->buffer, data, size, count); + p->samples_played = 0; + p->underrun = PJ_FALSE; + if (p->buffer->len < p->threshold) + { + pj_event_set(p->event); + p->signaled = PJ_TRUE; + } + else + { + pj_event_reset(p->event); + p->signaled = PJ_FALSE; + } + p->fmt_id = fmt->id; + p->status = PJSUA_REP_RUNNING; + + PJSUA_UNLOCK(); + + PJ_LOG(4, (THIS_FILE, "Started replay on media port %d", id)); + + pjmedia_conf_configure_port(pjsua_var.mconf, pjsua_var.mport[id].slot, PJMEDIA_PORT_NO_CHANGE, PJMEDIA_PORT_ENABLE_ALWAYS); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_play_status(pjsua_mport_id id, + pjs_mport_play_info *info) +{ + pjsua_mport_replay_data *p; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idstatus != PJSUA_REP_RUNNING) && (p->status != PJSUA_REP_STOPPING)) + { + pj_leave_critical_section(); + return PJ_ENOTFOUND; + } + + info->samples_played = p->samples_played; + info->underrun = p->underrun; + if (info->underrun) + p->underrun = PJ_FALSE; + if (p->status == PJSUA_REP_STOPPING) + { + if (!p->buffer->len) + { + if (p->signaled) + { + pj_event_reset(p->event); + p->signaled = PJ_FALSE; + } + info->completed = PJ_TRUE; + p->status = PJSUA_REP_IDLE; + + PJ_LOG(4, (THIS_FILE, "Replay completed on media port %d - %I64u samples were played", + id, p->samples_played)); + } + } + else + { + info->free_buffer_size = p->buffer->capacity - p->buffer->len; + } + + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_play_put_data(pjsua_mport_id id, + const void *data, + pj_size_t size, + pj_size_t *count) +{ + pjsua_mport_replay_data *p; + + PJ_ASSERT_RETURN(count != NULL, PJ_EINVAL); + + *count = 0; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idstatus != PJSUA_REP_RUNNING) + { + pj_status_t status = (p->status == PJSUA_REP_STOPPING) ? PJ_EEOF : PJ_ENOTFOUND; + pj_leave_critical_section(); + return status; + } + + if ((data != NULL) && size) + { + add_replay_data(p->fmt_id, p->buffer, data, size, count); + if (p->buffer->len >= p->threshold) + { + if (p->signaled) + { + pj_event_reset(p->event); + p->signaled = PJ_FALSE; + } + } + else + { + if (!p->signaled) + { + pj_event_set(p->event); + p->signaled = PJ_TRUE; + } + } + } + else + { + if (p->buffer->len) + { + if (p->signaled) + { + pj_event_reset(p->event); + p->signaled = PJ_FALSE; + } + } + else + { + if (!p->signaled) + { + pj_event_set(p->event); + p->signaled = PJ_TRUE; + } + } + p->status = PJSUA_REP_STOPPING; + } + + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_play_stop(pjsua_mport_id id, pj_bool_t discard) +{ + pjsua_mport_replay_data *p; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idstatus != PJSUA_REP_RUNNING) && (p->status != PJSUA_REP_STOPPING)) + { + pj_leave_critical_section(); + return PJ_ENOTFOUND; + } + + if (discard) + p->buffer->len = 0; + if (p->buffer->len) + { + if (p->signaled) + { + pj_event_reset(p->event); + p->signaled = PJ_FALSE; + } + } + else + { + if (!p->signaled) + { + pj_event_set(p->event); + p->signaled = PJ_TRUE; + } + } + if (p->status == PJSUA_REP_RUNNING) + p->status = PJSUA_REP_STOPPING; + + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_record_start(pjsua_mport_id id, + const pjmedia_format *fmt, + pj_bool_t rec_output, + pj_size_t max_duration, + pj_size_t max_samples, + pj_size_t max_silence, + pj_size_t eliminate_silence) +{ + pjsua_mport_record_data *p; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idstatus != PJSUA_REC_IDLE) + { + PJSUA_UNLOCK(); + return PJ_EBUSY; + } + if (((fmt->id != PJMEDIA_FORMAT_PCM) && (fmt->id != PJMEDIA_FORMAT_PCMA) && (fmt->id != PJMEDIA_FORMAT_PCMU)) || + (fmt->det.aud.clock_rate != pjsua_var.media_cfg.clock_rate) || + (fmt->det.aud.channel_count != pjsua_var.media_cfg.channel_count)) + { + PJSUA_UNLOCK(); + return PJ_ENOTSUP; + } + if (!p->buffer) + { + pj_status_t status = pjmedia_circ_buf_create(pjsua_var.mport[id].pool, (unsigned int)p->buffer_size, &p->buffer); + if (status != PJ_SUCCESS) + { + PJSUA_UNLOCK(); + return status; + } + } + + // Convert all the time based arguments to the corresponding sample counts + // and ensure that they are truncated to complete blocks if necessary + if (max_duration) + { + max_duration = max_duration * fmt->det.aud.clock_rate / 1000; + max_duration -= (max_duration % fmt->det.aud.channel_count); + } + if (max_silence) + { + max_silence = max_silence * fmt->det.aud.clock_rate / 1000; + max_silence -= (max_silence % fmt->det.aud.channel_count); + } + if (eliminate_silence) + { + eliminate_silence = eliminate_silence * fmt->det.aud.clock_rate / 1000; + eliminate_silence -= (eliminate_silence % fmt->det.aud.channel_count); + } + + if (p->signaled) + { + pj_event_reset(p->event); + p->signaled = PJ_FALSE; + } + pjmedia_circ_buf_reset(p->buffer); + p->samples_seen = 0; + p->samples_recorded = 0; + p->vad_timestamp.u64 = 0; + p->is_silence = PJ_FALSE; + p->overrun = PJ_FALSE; + p->er = PJSUA_REC_ER_NONE; + p->fmt_id = fmt->id; + p->rec_output = rec_output; + p->max_samples = max_samples; + p->max_duration = max_duration; + p->max_silence = max_silence; + p->eliminate_silence = eliminate_silence; + p->status = PJSUA_REC_RUNNING; + + PJSUA_UNLOCK(); + + PJ_LOG(4, (THIS_FILE, "Started recording on media port %d; event=%p", id, p->event)); + + if (rec_output) + pjmedia_conf_configure_port(pjsua_var.mconf, pjsua_var.mport[id].slot, PJMEDIA_PORT_NO_CHANGE, PJMEDIA_PORT_ENABLE_ALWAYS); + else + pjmedia_conf_configure_port(pjsua_var.mconf, pjsua_var.mport[id].slot, PJMEDIA_PORT_ENABLE_ALWAYS, PJMEDIA_PORT_NO_CHANGE); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_record_status(pjsua_mport_id id, + pjs_mport_record_info *info) +{ + pjsua_mport_record_data *p; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idstatus != PJSUA_REP_RUNNING) && (p->status != PJSUA_REP_STOPPING)) + { + pj_leave_critical_section(); + return PJ_ENOTFOUND; + } + + info->samples_recorded = p->samples_recorded; + info->overrun = p->overrun; + if (info->overrun) + p->overrun = PJ_FALSE; + info->samples_available = p->buffer->len; + info->end_reason = p->er; + if ((p->status == PJSUA_REC_STOPPING) && !info->samples_available) + { + if (p->signaled) + { + pj_event_reset(p->event); + p->signaled = PJ_FALSE; + } + info->completed = PJ_TRUE; + p->status = PJSUA_REC_IDLE; + + PJ_LOG(4, (THIS_FILE, "Recording completed on media port %d - %I64u samples were recorded", + id, p->samples_recorded)); + } + + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +static void get_record_data(pjmedia_format_id fmt_id, + pjmedia_circ_buf *buffer, + void *data, + pj_size_t size, /* Size of the data buffer in bytes - not samples! */ + pj_size_t *count) +{ + pj_int16_t *reg1, *reg2; + unsigned reg1cnt, reg2cnt; + if (buffer->len) + { + if (fmt_id == PJMEDIA_FORMAT_PCM) + { + if (size == 1U) + { + pj_int16_t buf; + pjmedia_circ_buf_read(buffer, &buf, 1U); + memcpy(data, &buf, 1U); + } + else + { + unsigned int samples = (unsigned int)(size >> 1); + if (samples > buffer->len) + { + samples = buffer->len; + size = samples << 1; + } + pjmedia_circ_buf_read(buffer, (pj_int16_t*)data, samples); + } + } + else + { + if (size > buffer->len) + size = buffer->len; + pjmedia_circ_buf_get_read_regions(buffer, ®1, ®1cnt, ®2, ®2cnt); + if (reg1cnt >= size) + { + switch (fmt_id) + { + case PJMEDIA_FORMAT_PCMA: + pjmedia_alaw_encode((pj_uint8_t*)data, reg1, size); + break; + case PJMEDIA_FORMAT_PCMU: + pjmedia_ulaw_encode((pj_uint8_t*)data, reg1, size); + break; + default: + pj_assert(0); + break; + } + } + else + { + switch (fmt_id) + { + case PJMEDIA_FORMAT_PCMA: + pjmedia_alaw_encode((pj_uint8_t*)data, reg1, reg1cnt); + pjmedia_alaw_encode((pj_uint8_t*)data + reg1cnt, reg2, size - reg1cnt); + break; + case PJMEDIA_FORMAT_PCMU: + pjmedia_ulaw_encode((pj_uint8_t*)data, reg1, reg1cnt); + pjmedia_ulaw_encode((pj_uint8_t*)data + reg1cnt, reg2, size - reg1cnt); + break; + default: + pj_assert(0); + break; + } + } + pjmedia_circ_buf_adv_read_ptr(buffer, (unsigned int)size); + } + *count = size; + } +} + +PJ_DEF(pj_status_t) pjsua_mport_record_get_data(pjsua_mport_id id, + void *buffer, + pj_size_t size, + pj_size_t *count) +{ + pjsua_mport_record_data *p; + + PJ_ASSERT_RETURN(count != NULL, PJ_EINVAL); + + *count = 0; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idstatus != PJSUA_REC_RUNNING) && (p->status != PJSUA_REC_STOPPING)) + { + pj_leave_critical_section(); + return PJ_ENOTFOUND; + } + + get_record_data(p->fmt_id, p->buffer, buffer, size, count); + if ((p->status == PJSUA_REC_RUNNING) && + ((p->buffer->capacity - p->buffer->len) >= p->threshold)) + { + if (p->signaled) + { + pj_event_reset(p->event); + p->signaled = PJ_FALSE; + } + } + else + { + if (!p->signaled) + { + pj_event_set(p->event); + p->signaled = PJ_TRUE; + } + } + + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_record_stop(pjsua_mport_id id, pj_bool_t discard) +{ + pjsua_mport_record_data *p; + int rec_output = -1; + pj_event_t* signaled_event = NULL; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idstatus != PJSUA_REC_RUNNING) && (p->status != PJSUA_REC_STOPPING)) + { + pj_leave_critical_section(); + return PJ_ENOTFOUND; + } + + if (discard) + p->buffer->len = 0; + if (p->status == PJSUA_REC_RUNNING) + { + p->er = PJSUA_REC_ER_STOP; + p->status = PJSUA_REC_STOPPING; + rec_output = p->rec_output; + } + if (!p->signaled) + { + signaled_event = p->event; + pj_event_set(p->event); + p->signaled = PJ_TRUE; + } + + pj_leave_critical_section(); + + if (rec_output >= 0) + { + if (rec_output) + pjmedia_conf_configure_port(pjsua_var.mconf, pjsua_var.mport[id].slot, PJMEDIA_PORT_NO_CHANGE, PJMEDIA_PORT_ENABLE); + else + pjmedia_conf_configure_port(pjsua_var.mconf, pjsua_var.mport[id].slot, PJMEDIA_PORT_ENABLE, PJMEDIA_PORT_NO_CHANGE); + } + + PJ_LOG(4, (THIS_FILE, "rec_output=%d, event=%p", rec_output, signaled_event)); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_conf_start(pjsua_mport_id id) +{ + register pjsua_mport_data *p; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idpool != NULL, PJ_EINVAL); + + PJSUA_LOCK(); + + if (!(p->base.info.dir & PJMEDIA_DIR_PLAYBACK)) + { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + if (p->play_data.status != PJSUA_REP_IDLE) + { + PJSUA_UNLOCK(); + return PJ_EBUSY; + } + if (!p->participants) + { + p->participants = (pjsua_mport_id*)pj_pool_zalloc(p->pool, sizeof(pjsua_mport_id) * PJSUA_MAX_CONF_PORTS); + if (!p->participants) + { + PJSUA_UNLOCK(); + return PJ_ENOMEM; + } + for (register int i = 0; i < PJSUA_MAX_CONF_PORTS; ++i) + p->participants[i] = PJSUA_INVALID_ID; + } + if (!p->mix_buf) + { + p->mix_buf = (pj_int32_t*)pj_pool_zalloc(p->pool, pjsua_var.mconf_cfg.samples_per_frame * sizeof(p->mix_buf[0])); + if (!p->mix_buf) + { + PJSUA_UNLOCK(); + return PJ_ENOMEM; + } + p->last_mix_adj = NORMAL_LEVEL; + } + + p->mix_cnt = 0U; + p->mix_adj = NORMAL_LEVEL; + p->play_data.status = PJSUA_REP_CONFERENCING; + + PJSUA_UNLOCK(); + + PJ_LOG(4, (THIS_FILE, "Started conference on media port %d", id)); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_conf_add(pjsua_mport_id id, pjsua_mport_id pid) +{ + register pjsua_mport_data *p, *pp; + register int i; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idpool != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(pid >= 0 && (unsigned int)pidpool != NULL, PJ_EINVAL); + + PJSUA_LOCK(); + + if (p->play_data.status != PJSUA_REP_CONFERENCING) + { + PJSUA_UNLOCK(); + return PJ_ENOTFOUND; + } + + for (i = (int)p->participant_cnt - 1; i >= 0; --i) + { + if (p->participants[i] == pid) + { + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } + } + + if (p->participant_cnt >= PJSUA_MAX_CONF_PORTS) + { + PJSUA_UNLOCK(); + return PJ_ETOOMANY; + } + + if (!pp->listeners) + { + pp->listeners = (pjsua_mport_id*)pj_pool_zalloc(pp->pool, sizeof(pjsua_mport_id) * pjsua_var.media_cfg.max_media_ports); + if (!pp->listeners) + { + PJSUA_UNLOCK(); + return PJ_ENOMEM; + } + for (i = 0; i < (int)pjsua_var.media_cfg.max_media_ports; ++i) + pp->listeners[i] = PJSUA_INVALID_ID; + } + + pj_enter_critical_section(); + + p->participants[p->participant_cnt++] = pid; + + for (i = (int)pp->listener_cnt - 1; i >= 0; --i) + { + if (pp->listeners[i] == id) + break; + } + if (i < 0) + { + pj_assert(pp->listener_cnt < pjsua_var.media_cfg.max_media_ports); + pp->listeners[pp->listener_cnt++] = id; + } + + pj_leave_critical_section(); + + PJSUA_UNLOCK(); + + PJ_LOG(4, (THIS_FILE, "Added media port %d to conference on media port %d", pid, id)); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_conf_remove(pjsua_mport_id id, pjsua_mport_id pid) +{ + register pjsua_mport_data *p, *pp; + register int i, j; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idpool != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(pid >= 0 && (unsigned int)pidpool != NULL, PJ_EINVAL); + + PJSUA_LOCK(); + + if (p->play_data.status != PJSUA_REP_CONFERENCING) + { + PJSUA_UNLOCK(); + return PJ_ENOTFOUND; + } + + pj_enter_critical_section(); + + for (i = (int)(p->participant_cnt) - 1; i >= 0; --i) + { + if (p->participants[i] != pid) + continue; + for (j = (int)(pp->listener_cnt) - 1; j >= 0; --j) + { + if (pp->listeners[j] == id) + { + pp->listener_cnt--; + if (j == (int)pp->listener_cnt) + { + pp->listeners[j] = PJSUA_INVALID_ID; + } + else + { + pp->listeners[j] = pp->listeners[pp->listener_cnt]; + pp->listeners[pp->listener_cnt] = PJSUA_INVALID_ID; + } + break; + } + } + p->participant_cnt--; + if (i == (int)p->participant_cnt) + { + p->participants[i] = PJSUA_INVALID_ID; + } + else + { + p->participants[i] = p->participants[p->participant_cnt]; + p->participants[p->participant_cnt] = PJSUA_INVALID_ID; + } + break; + } + + pj_leave_critical_section(); + + PJSUA_UNLOCK(); + + if (i < 0) + return PJ_ENOTFOUND; + + PJ_LOG(4, (THIS_FILE, "Removed media port %d from conference on media port %d", pid, id)); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_conf_stop(pjsua_mport_id id) +{ + register pjsua_mport_data *p; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idpool != NULL, PJ_EINVAL); + + PJSUA_LOCK(); + + if (p->play_data.status != PJSUA_REP_CONFERENCING) + { + PJSUA_UNLOCK(); + return PJ_ENOTFOUND; + } + + pj_enter_critical_section(); + + for (register int i = (int)(p->participant_cnt) - 1; i >= 0; --i) + { + register pjsua_mport_data *pp = &pjsua_var.mport[p->participants[i]]; + for (register int j = (int)(pp->listener_cnt) - 1; j >= 0; --j) + { + if (pp->listeners[j] == id) + { + pp->listener_cnt--; + if (j == (int)pp->listener_cnt) + { + pp->listeners[j] = PJSUA_INVALID_ID; + } + else + { + pp->listeners[j] = pp->listeners[pp->listener_cnt]; + pp->listeners[pp->listener_cnt] = PJSUA_INVALID_ID; + } + break; + } + } + p->participants[i] = PJSUA_INVALID_ID; + p->participant_cnt--; + } + + p->play_data.status = PJSUA_REP_IDLE; + + pj_leave_critical_section(); + + PJSUA_UNLOCK(); + + PJ_LOG(4, (THIS_FILE, "Stopped conference on media port %d", id)); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_listen_for(pjsua_mport_id id, pjs_listen_for_parms* params) +{ + register pjsua_mport_data *p; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idpool != NULL, PJ_EINVAL); + + if (params && (params->types == PJSUA_RCG_NONE)) + params = NULL; + + PJSUA_LOCK(); + + if (!(p->base.info.dir & PJMEDIA_DIR_CAPTURE)) + { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + + pj_enter_critical_section(); + + if (params) + p->recogntion_data.params = *params; + else + p->recogntion_data.params.types = PJSUA_RCG_NONE; + + pj_leave_critical_section(); + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_get_recognised(pjsua_mport_id id, pjs_recognition_info* info) +{ + register pjsua_mport_data *p; + + PJ_ASSERT_RETURN(info != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idpool != NULL, PJ_EINVAL); + + pj_enter_critical_section(); + + if (p->recogntion_data.event_cnt > 0U) + { + *info = p->recogntion_data.events[0U]; + pj_array_erase(p->recogntion_data.events, sizeof(p->recogntion_data.events[0]), p->recogntion_data.event_cnt, 0U); + if (!--p->recogntion_data.event_cnt) + { + if (p->recogntion_data.signaled) + { + pj_event_reset(p->recogntion_data.event); + p->recogntion_data.signaled = PJ_FALSE; + } + } + } + else + { + info->type = PJSUA_RCG_NONE; + } + + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsua_mport_discard_recognised(pjsua_mport_id id) +{ + register pjsua_mport_data *p; + + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idpool != NULL, PJ_EINVAL); + + pj_enter_critical_section(); + + if (p->recogntion_data.event_cnt > 0U) + { + p->recogntion_data.event_cnt = 0U; + if (p->recogntion_data.signaled) + { + pj_event_reset(p->recogntion_data.event); + p->recogntion_data.signaled = PJ_FALSE; + } + } + + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +static pj_status_t pjsua_mport_add_recognition_event(pjsua_mport_id id, const pjs_recognition_info* ri) +{ + register pjsua_mport_data *p; + + PJ_ASSERT_RETURN(ri != NULL && ri->type != PJSUA_RCG_NONE, PJ_EINVAL); + PJ_ASSERT_RETURN(id >= 0 && (unsigned int)idpool != NULL, PJ_EINVAL); + + register pj_status_t pje = PJ_SUCCESS; + + pj_enter_critical_section(); + + if (!(ri->type & p->recogntion_data.params.types)) + { + pje = PJ_EIGNORED; + } + else + { + if ((ri->type & PJSUA_RCG_DTMF) && + ((p->recogntion_data.params.types & PJSUA_RCG_DTMF) == PJSUA_RCG_DTMF)) + p->recogntion_data.params.types &= + ~(!(ri->type & PJSUA_RCG_DTMF_RFC2833) ? PJSUA_RCG_DTMF_RFC2833 : PJSUA_RCG_DTMF_TONE); + if (p->recogntion_data.event_cnt >= PJ_ARRAY_SIZE(p->recogntion_data.events)) + { + pje = PJ_ETOOMANY; + } + else + { + p->recogntion_data.events[p->recogntion_data.event_cnt++] = *ri; + if (!p->recogntion_data.signaled) + { + pj_event_set(p->recogntion_data.event); + p->recogntion_data.signaled = PJ_TRUE; + } + } + } + + pj_leave_critical_section(); + + return pje; +} + +PJ_DEF(pj_status_t) pjsua_mport_add_rfc2833_dtmf_digit(pjsua_mport_id id, char digit, unsigned timestamp, unsigned duration) +{ + pjs_recognition_info ri; + ri.type = PJSUA_RCG_DTMF_RFC2833; + ri.timestamp = timestamp; + ri.param0 = (unsigned)(pj_uint8_t)digit; + ri.param1 = duration; + return pjsua_mport_add_recognition_event(id, &ri); +} /***************************************************************************** * Sound devices. diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 1faf16e0d4..cfce8c807c 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -148,14 +148,66 @@ static pj_status_t call_inv_end_session(pjsua_call *call, unsigned code, const pj_str_t *reason, const pjsua_msg_data *msg_data); - +/* + * Allocate conference slot for call. + */ +static pj_status_t allocate_conference_slot(pjsua_call *call) +{ + register pj_status_t status = PJ_SUCCESS; + if (call->inv) { + if ((call->conf_slot == PJSUA_INVALID_ID) && call->null_port) { + char tmp[PJSIP_MAX_URL_SIZE]; + pj_str_t port_name; + port_name.ptr = tmp; + port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + call->inv->dlg->remote.info->uri, + tmp, sizeof(tmp)); + if (port_name.slen < 1) + port_name = pj_str("call"); + pjsip_dlg_inc_session(call->inv->dlg,&pjsua_var.mod); + status = pjmedia_conf_add_port(pjsua_var.mconf, + call->inv->pool, + call->null_port, + &port_name, + (unsigned*)&call->conf_slot); + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error adding null port to conference for call", status); + } + } else { + status = PJ_EBUSY; + } + return status; +} +static pj_status_t deallocate_conference_slot(pjsua_call* call) +{ + if (!call->inv) + { + return PJ_EBUSY; + } + + if (pjsua_var.mconf && call->conf_slot != PJSUA_INVALID_ID) + { + pj_status_t status = pjsua_conf_remove_port(call->conf_slot); + pjsip_dlg_dec_session(call->inv->dlg, &pjsua_var.mod); + if (status== PJ_SUCCESS) + { + PJ_LOG(4, (THIS_FILE, "deallocate_conference_slot::pjsua_conf_remove_port done [conf_slot: %d]", call->conf_slot)); + } + else + { + PJ_LOG(2, (THIS_FILE, "deallocate_conference_slot::pjsua_conf_remove_port failed")); + return status; + } + } + call->conf_slot = PJSUA_INVALID_ID; + return pjmedia_port_destroy(call->null_port); +} /* * Reset call descriptor. */ static void reset_call(pjsua_call_id id) { pjsua_call *call = &pjsua_var.calls[id]; - unsigned i; if (call->incoming_data) { pjsip_rx_data_free_cloned(call->incoming_data); @@ -163,10 +215,13 @@ static void reset_call(pjsua_call_id id) } pj_bzero(call, sizeof(*call)); call->index = id; + call->null_port = NULL; + call->conf_idx = -1; + call->conf_slot = PJSUA_INVALID_ID; call->last_text.ptr = call->last_text_buf_; call->cname.ptr = call->cname_buf; call->cname.slen = sizeof(call->cname_buf); - for (i=0; imedia); ++i) { + for (unsigned i=0; imedia); ++i) { pjsua_call_media *call_med = &call->media[i]; call_med->ssrc = pj_rand(); call_med->strm.a.conf_slot = PJSUA_INVALID_ID; @@ -209,17 +264,19 @@ pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg) const pj_str_t str_trickle_ice = { "trickle-ice", 11 }; pj_status_t status; - /* Init calls array. */ - for (i=0; i= PJSUA_MAX_CALLS) { + if (pjsua_var.ua_cfg.max_calls > PJSUA_MAX_CALLS) pjsua_var.ua_cfg.max_calls = PJSUA_MAX_CALLS; - } + pjsua_var.calls = (pjsua_call*)pj_pool_zalloc(pjsua_var.pool, sizeof(pjsua_call) * pjsua_var.ua_cfg.max_calls); + if (!pjsua_var.calls) + return PJ_ENOMEM; + + /* Init calls array. */ + for (i=0U; imedia_dir[i] = PJMEDIA_DIR_ENCODING_DECODING; } } @@ -830,6 +895,17 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, void *user_data, const pjsua_msg_data *msg_data, pjsua_call_id *p_call_id) +{ + return pjsua_call_make_call2(acc_id,dest_uri,opt,user_data,msg_data,p_call_id,NULL,NULL); +} +PJ_DEF(pj_status_t) pjsua_call_make_call2(pjsua_acc_id acc_id, + const pj_str_t *dest_uri, + const pjsua_call_setting *opt, + void *user_data, + const pjsua_msg_data *msg_data, + pjsua_call_id *p_call_id, + const pj_str_t *src_uri, + const pj_str_t *contact_uri) { pj_pool_t *tmp_pool = NULL; pjsip_dialog *dlg = NULL; @@ -849,6 +925,15 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, PJ_LOG(4,(THIS_FILE, "Making call with acc #%d to %.*s", acc_id, (int)dest_uri->slen, dest_uri->ptr)); + if (src_uri) + { + PJ_LOG(4,(THIS_FILE, "src_uri %.*s", (int)src_uri->slen, src_uri->ptr)); + } + if (contact_uri) + { + PJ_LOG(4,(THIS_FILE, "contact_uri %.*s", (int)contact_uri->slen, contact_uri->ptr)); + } + pj_log_push_indent(); PJSUA_LOCK(); @@ -923,6 +1008,26 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, status = PJSIP_EINVALIDREQURI; goto on_error; } + + if (src_uri && src_uri->slen) { + pj_strdup_with_null(tmp_pool, &dup, src_uri); + uri = pjsip_parse_uri(tmp_pool, dup.ptr, dup.slen, 0); + if (uri == NULL) + { + PJ_LOG(2, (THIS_FILE, "invalid src_uri")); + src_uri = NULL; + } + } + + if (contact_uri && contact_uri->slen) { + pj_strdup_with_null(tmp_pool, &dup, contact_uri); + uri = pjsip_parse_uri(tmp_pool, dup.ptr, dup.slen, 0); + if (uri == NULL) + { + PJ_LOG(2, (THIS_FILE, "invalid contact_uri")); + contact_uri = NULL; + } + } } /* Mark call start time. */ @@ -934,7 +1039,9 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, /* Create suitable Contact header unless a Contact header has been * set in the account. */ - if (acc->contact.slen) { + if (contact_uri && contact_uri->slen) { + contact = *contact_uri; + } else if (acc->contact.slen) { contact = acc->contact; } else { status = pjsua_acc_create_uac_contact(tmp_pool, &contact, @@ -946,9 +1053,19 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, } } + pj_str_t from; + if(src_uri && src_uri->slen) + { + from = *src_uri; + } + else + { + from = acc->cfg.id; + } + /* Create outgoing dialog: */ status = pjsip_dlg_create_uac( pjsip_ua_instance(), - &acc->cfg.id, &contact, + &from, &contact, dest_uri, (msg_data && msg_data->target_uri.slen? &msg_data->target_uri: dest_uri), @@ -988,6 +1105,11 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, if ((call->opt.flag & PJSUA_CALL_NO_SDP_OFFER) == 0) { /* Init media channel */ + pjmedia_null_port_create(dlg->pool, 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, + &call->null_port); status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, call->secure_level, dlg->pool, NULL, NULL, PJ_TRUE, @@ -1003,6 +1125,11 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, goto on_error; } + /* Allocate conference slot for the call. */ + status = allocate_conference_slot(call); + if (status != PJ_SUCCESS) + goto on_error; + /* Done. */ if (p_call_id) @@ -1168,6 +1295,15 @@ static pj_status_t process_pending_call_hangup(pjsua_call *call) } pjsua_media_channel_deinit(call->index); + + if(deallocate_conference_slot(call) ==PJ_SUCCESS) + { + PJ_LOG(4,(THIS_FILE, "Call %d: conference slot deallocated", call->index)); + } + else + { + PJ_LOG(2,(THIS_FILE, "Call %d: conference slot deallocation failed", call->index)); + } pjsua_check_snd_dev_idle(); if (call->inv) @@ -1922,6 +2058,11 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) call->async_call.dlg = NULL; goto on_return; } + pjmedia_null_port_create(dlg->pool, 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, + &call->null_port); status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS, call->secure_level, rdata->tp_info.pool, @@ -2023,6 +2164,19 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) update_remote_nat_type(call, remote_sdp); } + /* Allocate conference slot for the call. */ + status = allocate_conference_slot(call); + if (status != PJ_SUCCESS) { + pjsip_dlg_respond(dlg, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); + pjsip_inv_terminate(inv, PJSIP_SC_INTERNAL_SERVER_ERROR, PJ_FALSE); + + pjsua_media_channel_deinit(call->index); + call->inv = NULL; + call->async_call.dlg = NULL; + + goto on_return; + } + /* Must answer with some response to initial INVITE. We'll do this before * attaching the call to the invite session/dialog, so that the application * will not get notification about this event (on another scenario, it is @@ -2106,7 +2260,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) */ if (call->async_call.call_var.inc_call.hangup) { process_pending_call_hangup(call); - } else if (call->med_ch_cb == NULL && call->inv) { + } else if (call->med_ch_cb == NULL && call->inv){ process_pending_call_answer(call); } } else { @@ -2381,12 +2535,15 @@ PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id, } ++info->media_cnt; } - - if (call->audio_idx != -1) { + info->conf_slot = call->conf_slot; + if (call->audio_idx >=0) { info->media_status = call->media[call->audio_idx].state; info->media_dir = call->media[call->audio_idx].dir; + if (info->conf_slot == PJSUA_INVALID_ID ) + { info->conf_slot = call->media[call->audio_idx].strm.a.conf_slot; } + } /* Build array of provisional media info */ info->prov_media_cnt = 0; @@ -2698,7 +2855,7 @@ PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id, */ if (!call->med_ch_cb && (call->opt_inited || (code==183 || code/100==2)) && - (!call->inv->neg || + (!call->inv->neg || pjmedia_sdp_neg_get_state(call->inv->neg) == PJMEDIA_SDP_NEG_STATE_NULL)) { @@ -2710,7 +2867,7 @@ PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id, status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, call->secure_level, dlg->pool, - NULL, NULL, PJ_TRUE, + NULL, NULL, PJ_TRUE, &on_answer_call_med_tp_complete); if (status == PJ_SUCCESS) { status = on_answer_call_med_tp_complete(call->index, NULL); @@ -2751,8 +2908,13 @@ PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id, answer->msg_data = pjsua_msg_data_clone(call->inv->pool_prov, msg_data); } + if (call->async_call.call_var.inc_call.answers.prev) { pj_list_push_back(&call->async_call.call_var.inc_call.answers, answer); + } else { + if (status == PJ_SUCCESS) status = PJ_EBUG; + pjsua_perror(THIS_FILE, "Error answering call without a media transport", status); + } PJSUA_UNLOCK(); if (dlg) pjsip_dlg_dec_lock(dlg); @@ -3038,6 +3200,14 @@ PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id, } else { /* Destroy media session. */ pjsua_media_channel_deinit(call_id); + if (deallocate_conference_slot(call) == PJ_SUCCESS) + { + PJ_LOG(4, (THIS_FILE, "Call %d: conference slot deallocated", call->index)); + } + else + { + PJ_LOG(2, (THIS_FILE, "Call %d: conference slot deallocation failed", call->index)); + } call->hanging_up = PJ_TRUE; pjsua_check_snd_dev_idle(); } diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 5425bd489f..07b83be1b4 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -408,6 +408,8 @@ 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->mport_record_buffer_size = PJSUA_MPORT_RECORD_BUFFER_SIZE; + cfg->mport_replay_buffer_size = PJSUA_MPORT_REPLAY_BUFFER_SIZE; cfg->has_ioqueue = PJ_TRUE; cfg->thread_cnt = 1; cfg->quality = PJSUA_DEFAULT_CODEC_QUALITY; @@ -728,7 +730,7 @@ PJ_DEF(pj_status_t) pjsua_reconfigure_logging(const pjsua_logging_config *cfg) pjsua_logging_config_dup(pjsua_var.pool, &pjsua_var.log_cfg, cfg); /* Redirect log function to ours */ - pj_log_set_log_func( &log_writer ); + pj_log_set_log_func( (cfg && cfg->cb && !cfg->log_filename.slen) ? cfg->cb : &log_writer ); /* Set decor */ pj_log_set_decor(pjsua_var.log_cfg.decor); @@ -890,15 +892,28 @@ static void init_random_seed(void) /* * Instantiate pjsua application. */ -PJ_DEF(pj_status_t) pjsua_create(void) +PJ_DECL(pj_status_t) pjsua_create(void) +{ + return pjsua_create2(NULL); +} +PJ_DEF(pj_status_t) pjsua_create2(const pjsua_logging_config *log_cfg) { pj_status_t status; /* Init pjsua data */ init_data(); + if (log_cfg) { + /* Save custom logging config */ + pj_memcpy(&pjsua_var.log_cfg, log_cfg, sizeof(*log_cfg)); + pj_bzero(&pjsua_var.log_cfg.log_filename, sizeof(pjsua_var.log_cfg.log_filename)); + } else { /* Set default logging settings */ pjsua_logging_config_default(&pjsua_var.log_cfg); + } + pj_log_set_log_func((log_cfg && log_cfg->cb && !log_cfg->log_filename.slen) ? log_cfg->cb : &log_writer); + pj_log_set_decor(pjsua_var.log_cfg.decor); + pj_log_set_level(pjsua_var.log_cfg.level); /* Init PJLIB: */ status = pj_init(); @@ -2084,6 +2099,9 @@ PJ_DEF(pj_status_t) pjsua_destroy2(unsigned flags) } } + /* Destroy call subsystem (to free call array, etc) */ + pjsua_call_subsys_destroy(); + #if defined(PJNATH_HAS_UPNP) && (PJNATH_HAS_UPNP != 0) /* Deinitialize UPnP */ if (pjsua_var.ua_cfg.enable_upnp) { diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 72814bd2ca..809f09df3c 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -79,10 +79,6 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) pjsua_var.media_cfg.thread_cnt = 1; } - if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) { - pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2; - } - /* Create media endpoint. */ status = pjmedia_endpt_create(&pjsua_var.cp.factory, pjsua_var.media_cfg.has_ioqueue? NULL : @@ -2788,7 +2784,7 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, const pjmedia_sdp_session *s_; pjmedia_sdp_neg_get_active_local(call->inv->neg, &s_); - if (mi < s_->media_count) { + if (s_ && mi < s_->media_count) { m = pjmedia_sdp_media_clone(pool, s_->media[mi]); m->desc.port = 0; } else { @@ -3096,7 +3092,7 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, static void stop_media_stream(pjsua_call *call, unsigned med_idx) { pjsua_call_media *call_med; - + if (pjsua_call_media_is_changing(call)) { call_med = &call->media_prov[med_idx]; if (med_idx >= call->med_prov_cnt) @@ -3165,9 +3161,7 @@ static void stop_media_stream(pjsua_call *call, unsigned med_idx) static void stop_media_session(pjsua_call_id call_id) { pjsua_call *call = &pjsua_var.calls[call_id]; - unsigned mi; - - for (mi=0; mimed_cnt; ++mi) { + for (unsigned mi=0; mimed_cnt; ++mi) { stop_media_stream(call, mi); } }