diff options
Diffstat (limited to 'pjmedia/src/pjmedia/sound_port.c')
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 624 |
1 files changed, 335 insertions, 289 deletions
diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 3a21ee5d..7117c82b 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -18,16 +18,15 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <pjmedia/sound_port.h> +#include <pjmedia/alaw_ulaw.h> #include <pjmedia/delaybuf.h> #include <pjmedia/echo.h> #include <pjmedia/errno.h> -#include <pjmedia/plc.h> #include <pj/assert.h> #include <pj/log.h> #include <pj/rand.h> #include <pj/string.h> /* pj_memset() */ -//#define SIMULATE_LOST_PCT 20 #define AEC_TAIL 128 /* default AEC length in ms */ #define AEC_SUSPEND_LIMIT 5 /* seconds of no activity */ @@ -35,119 +34,55 @@ //#define TEST_OVERFLOW_UNDERFLOW -enum -{ - PJMEDIA_PLC_ENABLED = 1, -}; - -//#define DEFAULT_OPTIONS PJMEDIA_PLC_ENABLED -#define DEFAULT_OPTIONS 0 - - struct pjmedia_snd_port { int rec_id; int play_id; - pjmedia_snd_stream *snd_stream; + pj_uint32_t aud_caps; + pjmedia_aud_param aud_param; + pjmedia_aud_stream *aud_stream; pjmedia_dir dir; pjmedia_port *port; - unsigned options; - - pjmedia_echo_state *ec_state; - unsigned aec_tail_len; - - pj_bool_t ec_suspended; - unsigned ec_suspend_count; - unsigned ec_suspend_limit; - - pjmedia_plc *plc; unsigned clock_rate; unsigned channel_count; unsigned samples_per_frame; unsigned bits_per_sample; -#if PJMEDIA_SOUND_USE_DELAYBUF - pjmedia_delay_buf *delay_buf; -#endif + /* software ec */ + pjmedia_echo_state *ec_state; + unsigned ec_options; + unsigned ec_tail_len; + pj_bool_t ec_suspended; + unsigned ec_suspend_count; + unsigned ec_suspend_limit; }; /* * The callback called by sound player when it needs more samples to be * played. */ -static pj_status_t play_cb(/* in */ void *user_data, - /* in */ pj_uint32_t timestamp, - /* out */ void *output, - /* out */ unsigned size) +static pj_status_t play_cb(void *user_data, pjmedia_frame *frame) { pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port; - pjmedia_frame frame; + unsigned required_size = frame->size; pj_status_t status; - /* We're risking accessing the port without holding any mutex. - * It's possible that port is disconnected then destroyed while - * we're trying to access it. - * But in the name of performance, we'll try this approach until - * someone complains when it crashes. - */ port = snd_port->port; if (port == NULL) goto no_frame; - frame.buf = output; - frame.size = size; - frame.timestamp.u32.hi = 0; - frame.timestamp.u32.lo = timestamp; - -#if PJMEDIA_SOUND_USE_DELAYBUF - if (snd_port->delay_buf) { - status = pjmedia_delay_buf_get(snd_port->delay_buf, (pj_int16_t*)output); - if (status != PJ_SUCCESS) - pj_bzero(output, size); - - frame.type = PJMEDIA_FRAME_TYPE_AUDIO; - pjmedia_port_put_frame(port, &frame); - -#ifdef TEST_OVERFLOW_UNDERFLOW - { - static int count = 1; - if (++count % 10 == 0) { - status = pjmedia_delay_buf_get(snd_port->delay_buf, - (pj_int16_t*)output); - if (status != PJ_SUCCESS) - pj_bzero(output, size); - - frame.type = PJMEDIA_FRAME_TYPE_AUDIO; - pjmedia_port_put_frame(port, &frame); - } - } -#endif - - } -#endif - - status = pjmedia_port_get_frame(port, &frame); + status = pjmedia_port_get_frame(port, frame); if (status != PJ_SUCCESS) goto no_frame; - if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) + if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) goto no_frame; /* Must supply the required samples */ - pj_assert(frame.size == size); - -#ifdef SIMULATE_LOST_PCT - /* Simulate packet lost */ - if (pj_rand() % 100 < SIMULATE_LOST_PCT) { - PJ_LOG(4,(THIS_FILE, "Frame dropped")); - goto no_frame; - } -#endif - - if (snd_port->plc) - pjmedia_plc_save(snd_port->plc, (pj_int16_t*) output); + PJ_UNUSED_ARG(required_size); + pj_assert(frame->size == required_size); if (snd_port->ec_state) { if (snd_port->ec_suspended) { @@ -156,7 +91,7 @@ static pj_status_t play_cb(/* in */ void *user_data, PJ_LOG(4,(THIS_FILE, "EC activated")); } snd_port->ec_suspend_count = 0; - pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)output); + pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf); } @@ -172,22 +107,10 @@ no_frame: } if (snd_port->ec_state) { /* To maintain correct delay in EC */ - pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)output); + pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf); } } - /* Apply PLC */ - if (snd_port->plc) { - - pjmedia_plc_generate(snd_port->plc, (pj_int16_t*) output); -#ifdef SIMULATE_LOST_PCT - PJ_LOG(4,(THIS_FILE, "Lost frame generated")); -#endif - } else { - pj_bzero(output, size); - } - - return PJ_SUCCESS; } @@ -196,49 +119,59 @@ no_frame: * The callback called by sound recorder when it has finished capturing a * frame. */ -static pj_status_t rec_cb(/* in */ void *user_data, - /* in */ pj_uint32_t timestamp, - /* in */ void *input, - /* in*/ unsigned size) +static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame) { pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port; - pjmedia_frame frame; - /* We're risking accessing the port without holding any mutex. - * It's possible that port is disconnected then destroyed while - * we're trying to access it. - * But in the name of performance, we'll try this approach until - * someone complains when it crashes. - */ port = snd_port->port; if (port == NULL) return PJ_SUCCESS; /* Cancel echo */ if (snd_port->ec_state && !snd_port->ec_suspended) { - pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) input, 0); + pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0); } -#if PJMEDIA_SOUND_USE_DELAYBUF - if (snd_port->delay_buf) { - pjmedia_delay_buf_put(snd_port->delay_buf, (pj_int16_t*)input); - } else { - frame.buf = (void*)input; - frame.size = size; - frame.type = PJMEDIA_FRAME_TYPE_AUDIO; - frame.timestamp.u32.lo = timestamp; + pjmedia_port_put_frame(port, frame); + + return PJ_SUCCESS; +} - pjmedia_port_put_frame(port, &frame); +/* + * The callback called by sound player when it needs more samples to be + * played. This version is for non-PCM data. + */ +static pj_status_t play_cb_ext(void *user_data, pjmedia_frame *frame) +{ + pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; + pjmedia_port *port = snd_port->port; + + if (port == NULL) { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; } -#else - frame.buf = (void*)input; - frame.size = size; - frame.type = PJMEDIA_FRAME_TYPE_AUDIO; - frame.timestamp.u32.lo = timestamp; - pjmedia_port_put_frame(port, &frame); -#endif + pjmedia_port_get_frame(port, frame); + + return PJ_SUCCESS; +} + + +/* + * The callback called by sound recorder when it has finished capturing a + * frame. This version is for non-PCM data. + */ +static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame) +{ + pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; + pjmedia_port *port; + + port = snd_port->port; + if (port == NULL) + return PJ_SUCCESS; + + pjmedia_port_put_frame(port, frame); return PJ_SUCCESS; } @@ -250,84 +183,102 @@ static pj_status_t rec_cb(/* in */ void *user_data, static pj_status_t start_sound_device( pj_pool_t *pool, pjmedia_snd_port *snd_port ) { + pjmedia_aud_rec_cb snd_rec_cb; + pjmedia_aud_play_cb snd_play_cb; + pjmedia_aud_param param_copy; pj_status_t status; /* Check if sound has been started. */ - if (snd_port->snd_stream != NULL) + if (snd_port->aud_stream != NULL) return PJ_SUCCESS; - /* Open sound stream. */ - if (snd_port->dir == PJMEDIA_DIR_CAPTURE) { - status = pjmedia_snd_open_rec( snd_port->rec_id, - snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - snd_port->bits_per_sample, - &rec_cb, - snd_port, - &snd_port->snd_stream); + PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE || + snd_port->dir == PJMEDIA_DIR_PLAYBACK || + snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, + PJ_EBUG); - } else if (snd_port->dir == PJMEDIA_DIR_PLAYBACK) { - status = pjmedia_snd_open_player( snd_port->play_id, - snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - snd_port->bits_per_sample, - &play_cb, - snd_port, - &snd_port->snd_stream); - - } else if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { - status = pjmedia_snd_open( snd_port->rec_id, - snd_port->play_id, - snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - snd_port->bits_per_sample, - &rec_cb, - &play_cb, - snd_port, - &snd_port->snd_stream); + /* Get device caps */ + if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) { + pjmedia_aud_dev_info dev_info; + + status = pjmedia_aud_dev_get_info(snd_port->aud_param.rec_id, + &dev_info); + if (status != PJ_SUCCESS) + return status; + + snd_port->aud_caps = dev_info.caps; + } else { + snd_port->aud_caps = 0; + } + + /* Process EC settings */ + pj_memcpy(¶m_copy, &snd_port->aud_param, sizeof(param_copy)); + if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) { + /* EC is wanted */ + if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { + /* Device supports EC */ + /* Nothing to do */ + } else { + /* Device doesn't support EC, remove EC settings from + * device parameters + */ + param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC | + PJMEDIA_AUD_DEV_CAP_EC_TAIL); + } + } + + /* Use different callback if format is not PCM */ + if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) { + snd_rec_cb = &rec_cb; + snd_play_cb = &play_cb; } else { - pj_assert(!"Invalid dir"); - status = PJ_EBUG; + snd_rec_cb = &rec_cb_ext; + snd_play_cb = &play_cb_ext; } + /* Open the device */ + status = pjmedia_aud_stream_create(¶m_copy, + snd_rec_cb, + snd_play_cb, + snd_port, + &snd_port->aud_stream); + if (status != PJ_SUCCESS) return status; + /* Inactivity limit before EC is suspended. */ + snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT * + (snd_port->clock_rate / + snd_port->samples_per_frame); -#ifdef SIMULATE_LOST_PCT - snd_port->options |= PJMEDIA_PLC_ENABLED; -#endif - - /* If we have player components, allocate buffer to save the last - * frame played to the speaker. The last frame is used for packet - * lost concealment (PLC) algorithm. + /* Create software EC if parameter specifies EC but device + * doesn't support EC. Only do this if the format is PCM! */ - if ((snd_port->dir & PJMEDIA_DIR_PLAYBACK) && - (snd_port->options & PJMEDIA_PLC_ENABLED)) + if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) && + (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 && + param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM) { - status = pjmedia_plc_create(pool, snd_port->clock_rate, - snd_port->samples_per_frame * - snd_port->channel_count, - 0, &snd_port->plc); + if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { + snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL; + snd_port->aud_param.ec_tail_ms = AEC_TAIL; + PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms", + snd_port->aud_param.ec_tail_ms)); + } + + status = pjmedia_snd_port_set_ec(snd_port, pool, + snd_port->aud_param.ec_tail_ms, 0); if (status != PJ_SUCCESS) { - PJ_LOG(4,(THIS_FILE, "Unable to create PLC")); - snd_port->plc = NULL; + pjmedia_aud_stream_destroy(snd_port->aud_stream); + snd_port->aud_stream = NULL; + return status; } } - /* Inactivity limit before EC is suspended. */ - snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT * - (snd_port->clock_rate / - snd_port->samples_per_frame); - /* Start sound stream. */ - status = pjmedia_snd_stream_start(snd_port->snd_stream); + status = pjmedia_aud_stream_start(snd_port->aud_stream); if (status != PJ_SUCCESS) { - pjmedia_snd_stream_close(snd_port->snd_stream); - snd_port->snd_stream = NULL; + pjmedia_aud_stream_destroy(snd_port->aud_stream); + snd_port->aud_stream = NULL; return status; } @@ -342,10 +293,10 @@ static pj_status_t start_sound_device( pj_pool_t *pool, static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port ) { /* Check if we have sound stream device. */ - if (snd_port->snd_stream) { - pjmedia_snd_stream_stop(snd_port->snd_stream); - pjmedia_snd_stream_close(snd_port->snd_stream); - snd_port->snd_stream = NULL; + if (snd_port->aud_stream) { + pjmedia_aud_stream_stop(snd_port->aud_stream); + pjmedia_aud_stream_destroy(snd_port->aud_stream); + snd_port->aud_stream = NULL; } /* Destroy AEC */ @@ -371,47 +322,24 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, unsigned options, pjmedia_snd_port **p_port) { - pjmedia_snd_port *snd_port; - - PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); - - snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); - PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); + pjmedia_aud_param param; + pj_status_t status; - snd_port->rec_id = rec_id; - snd_port->play_id = play_id; - snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; - snd_port->options = options | DEFAULT_OPTIONS; - snd_port->clock_rate = clock_rate; - snd_port->channel_count = channel_count; - snd_port->samples_per_frame = samples_per_frame; - snd_port->bits_per_sample = bits_per_sample; - -#if PJMEDIA_SOUND_USE_DELAYBUF - do { - pj_status_t status; - unsigned ptime; - - ptime = samples_per_frame * 1000 / (clock_rate * channel_count); - - status = pjmedia_delay_buf_create(pool, "snd_buff", - clock_rate, samples_per_frame, - channel_count, - PJMEDIA_SOUND_BUFFER_COUNT * ptime, - 0, &snd_port->delay_buf); - PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - } while (0); -#endif + PJ_UNUSED_ARG(options); - *p_port = snd_port; + status = pjmedia_aud_dev_default_param(rec_id, ¶m); + if (status != PJ_SUCCESS) + return status; + param.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param.rec_id = rec_id; + param.play_id = play_id; + param.clock_rate = clock_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = bits_per_sample; - /* Start sound device immediately. - * If there's no port connected, the sound callback will return - * empty signal. - */ - return start_sound_device( pool, snd_port ); - + return pjmedia_snd_port_create2(pool, ¶m, p_port); } /* @@ -426,28 +354,23 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool, unsigned options, pjmedia_snd_port **p_port) { - pjmedia_snd_port *snd_port; - - PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); + pjmedia_aud_param param; + pj_status_t status; - snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); - PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); + PJ_UNUSED_ARG(options); - snd_port->rec_id = dev_id; - snd_port->dir = PJMEDIA_DIR_CAPTURE; - snd_port->options = options | DEFAULT_OPTIONS; - snd_port->clock_rate = clock_rate; - snd_port->channel_count = channel_count; - snd_port->samples_per_frame = samples_per_frame; - snd_port->bits_per_sample = bits_per_sample; + status = pjmedia_aud_dev_default_param(dev_id, ¶m); + if (status != PJ_SUCCESS) + return status; - *p_port = snd_port; + param.dir = PJMEDIA_DIR_CAPTURE; + param.rec_id = dev_id; + param.clock_rate = clock_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = bits_per_sample; - /* Start sound device immediately. - * If there's no port connected, the sound callback will return - * empty signal. - */ - return start_sound_device( pool, snd_port ); + return pjmedia_snd_port_create2(pool, ¶m, p_port); } @@ -463,28 +386,63 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool, unsigned options, pjmedia_snd_port **p_port) { + pjmedia_aud_param param; + pj_status_t status; + + PJ_UNUSED_ARG(options); + + status = pjmedia_aud_dev_default_param(dev_id, ¶m); + if (status != PJ_SUCCESS) + return status; + + param.dir = PJMEDIA_DIR_PLAYBACK; + param.play_id = dev_id; + param.clock_rate = clock_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = bits_per_sample; + + return pjmedia_snd_port_create2(pool, ¶m, p_port); +} + + +/* + * Create sound port. + */ +PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, + const pjmedia_aud_param *prm, + pjmedia_snd_port **p_port) +{ pjmedia_snd_port *snd_port; + pj_status_t status; - PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); + PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL); snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); - snd_port->play_id = dev_id; - snd_port->dir = PJMEDIA_DIR_PLAYBACK; - snd_port->options = options | DEFAULT_OPTIONS; - snd_port->clock_rate = clock_rate; - snd_port->channel_count = channel_count; - snd_port->samples_per_frame = samples_per_frame; - snd_port->bits_per_sample = bits_per_sample; - - *p_port = snd_port; - + snd_port->dir = prm->dir; + snd_port->rec_id = prm->rec_id; + snd_port->play_id = prm->play_id; + snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + snd_port->clock_rate = prm->clock_rate; + snd_port->channel_count = prm->channel_count; + snd_port->samples_per_frame = prm->samples_per_frame; + snd_port->bits_per_sample = prm->bits_per_sample; + pj_memcpy(&snd_port->aud_param, prm, sizeof(*prm)); + /* Start sound device immediately. * If there's no port connected, the sound callback will return * empty signal. */ - return start_sound_device( pool, snd_port ); + status = start_sound_device( pool, snd_port ); + if (status != PJ_SUCCESS) { + pjmedia_snd_port_destroy(snd_port); + return status; + } + + *p_port = snd_port; + return PJ_SUCCESS; } @@ -502,23 +460,23 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port) /* * Retrieve the sound stream associated by this sound device port. */ -PJ_DEF(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream( +PJ_DEF(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream( pjmedia_snd_port *snd_port) { PJ_ASSERT_RETURN(snd_port, NULL); - return snd_port->snd_stream; + return snd_port->aud_stream; } /* - * Enable AEC + * Change EC settings. */ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, pj_pool_t *pool, unsigned tail_ms, unsigned options) { - pjmedia_snd_stream_info si; + pjmedia_aud_param prm; pj_status_t status; /* Sound must be opened in full-duplex mode */ @@ -526,43 +484,101 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, PJ_EINVALIDOP); - /* Sound port must have 16bits per sample */ - PJ_ASSERT_RETURN(snd_port->bits_per_sample == 16, - PJ_EINVALIDOP); + /* Determine whether we use device or software EC */ + if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { + /* We use device EC */ + pj_bool_t ec_enabled; - /* Destroy AEC */ - if (snd_port->ec_state) { - pjmedia_echo_destroy(snd_port->ec_state); - snd_port->ec_state = NULL; - } + /* Query EC status */ + status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC, + &ec_enabled); + if (status != PJ_SUCCESS) + return status; - snd_port->aec_tail_len = tail_ms; + if (tail_ms != 0) { + /* Change EC setting */ - if (tail_ms != 0) { - unsigned delay_ms; + if (!ec_enabled) { + /* Enable EC first */ + pj_bool_t value = PJ_TRUE; + status = pjmedia_aud_stream_set_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC, + &value); + if (status != PJ_SUCCESS) + return status; + } - status = pjmedia_snd_stream_get_info(snd_port->snd_stream, &si); - if (status != PJ_SUCCESS) - si.rec_latency = si.play_latency = 0; - - //No need to add input latency in the latency calculation, - //since actual input latency should be zero. - //delay_ms = (si.rec_latency + si.play_latency) * 1000 / - // snd_port->clock_rate; - delay_ms = si.play_latency * 1000 / snd_port->clock_rate; - status = pjmedia_echo_create2(pool, snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - tail_ms, delay_ms, - options, &snd_port->ec_state); + if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { + /* Device does not support setting EC tail */ + return PJMEDIA_EAUD_INVCAP; + } + + return pjmedia_aud_stream_set_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC_TAIL, + &tail_ms); + + } else if (ec_enabled) { + /* Disable EC */ + pj_bool_t value = PJ_FALSE; + return pjmedia_aud_stream_set_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC, + &value); + } else { + /* Request to disable EC but EC has been disabled */ + /* Do nothing */ + return PJ_SUCCESS; + } + + } else { + /* We use software EC */ + + /* Check if there is change in parameters */ + if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) { + PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no " + "change in settings")); + return PJ_SUCCESS; + } + + status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm); if (status != PJ_SUCCESS) + return status; + + /* Audio stream must be in PCM format */ + PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM, + PJ_EINVALIDOP); + + /* Destroy AEC */ + if (snd_port->ec_state) { + pjmedia_echo_destroy(snd_port->ec_state); snd_port->ec_state = NULL; - else - snd_port->ec_suspended = PJ_FALSE; - } else { - PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the " - "sound port")); - status = PJ_SUCCESS; + } + + if (tail_ms != 0) { + unsigned delay_ms; + + //No need to add input latency in the latency calculation, + //since actual input latency should be zero. + //delay_ms = (si.rec_latency + si.play_latency) * 1000 / + // snd_port->clock_rate; + delay_ms = prm.output_latency_ms; + status = pjmedia_echo_create2(pool, snd_port->clock_rate, + snd_port->channel_count, + snd_port->samples_per_frame, + tail_ms, delay_ms, + options, &snd_port->ec_state); + if (status != PJ_SUCCESS) + snd_port->ec_state = NULL; + else + snd_port->ec_suspended = PJ_FALSE; + } else { + PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the " + "sound port")); + status = PJ_SUCCESS; + } + + snd_port->ec_options = options; + snd_port->ec_tail_len = tail_ms; } return status; @@ -574,12 +590,42 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port, unsigned *p_length) { PJ_ASSERT_RETURN(snd_port && p_length, PJ_EINVAL); - *p_length = snd_port->ec_state ? snd_port->aec_tail_len : 0; + + /* Determine whether we use device or software EC */ + if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { + /* We use device EC */ + pj_bool_t ec_enabled; + pj_status_t status; + + /* Query EC status */ + status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC, + &ec_enabled); + if (status != PJ_SUCCESS) + return status; + + if (!ec_enabled) { + *p_length = 0; + } else if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) { + /* Get device EC tail */ + status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC_TAIL, + p_length); + if (status != PJ_SUCCESS) + return status; + } else { + /* Just use default */ + *p_length = AEC_TAIL; + } + + } else { + /* We use software EC */ + *p_length = snd_port->ec_state ? snd_port->ec_tail_len : 0; + } return PJ_SUCCESS; } - /* * Connect a port. */ |