diff options
author | Benny Prijono <bennylp@teluu.com> | 2008-08-09 05:40:22 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2008-08-09 05:40:22 +0000 |
commit | c0970767b422b18bb22e71efac3d6353bba37006 (patch) | |
tree | 13d0469e6fae3a4126f9e23aacd6c54e7f8a08ed /pjmedia/src/pjmedia/echo_common.c | |
parent | 79d6cb738dc41982cdf169d9f346cf8a3ab48865 (diff) |
Ticket #588: Improvements to echo cancellation framework
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2198 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src/pjmedia/echo_common.c')
-rw-r--r-- | pjmedia/src/pjmedia/echo_common.c | 252 |
1 files changed, 213 insertions, 39 deletions
diff --git a/pjmedia/src/pjmedia/echo_common.c b/pjmedia/src/pjmedia/echo_common.c index a7e6b1bb..633e78a8 100644 --- a/pjmedia/src/pjmedia/echo_common.c +++ b/pjmedia/src/pjmedia/echo_common.c @@ -17,36 +17,56 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <pjmedia/config.h> #include <pjmedia/echo.h> +#include <pjmedia/delaybuf.h> +#include <pjmedia/errno.h> #include <pj/assert.h> +#include <pj/list.h> +#include <pj/log.h> #include <pj/pool.h> #include "echo_internal.h" +#define THIS_FILE "echo_common.c" + typedef struct ec_operations ec_operations; +struct frame +{ + PJ_DECL_LIST_MEMBER(struct frame); + short buf[1]; +}; + struct pjmedia_echo_state { + pj_pool_t *pool; + char *obj_name; + unsigned samples_per_frame; void *state; ec_operations *op; + + pj_bool_t lat_ready; /* lat_buf has been filled in. */ + unsigned lat_target_cnt;/* Target number of frames in lat_buf */ + unsigned lat_buf_cnt; /* Actual number of frames in lat_buf */ + struct frame lat_buf; /* Frame queue for delayed playback */ + struct frame lat_free; /* Free frame list. */ + + pjmedia_delay_buf *delay_buf; }; struct ec_operations { + const char *name; + pj_status_t (*ec_create)(pj_pool_t *pool, - unsigned clock_rate, - unsigned samples_per_frame, - unsigned tail_ms, - unsigned latency_ms, - unsigned options, - void **p_state ); + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned tail_ms, + unsigned options, + void **p_state ); pj_status_t (*ec_destroy)(void *state ); - pj_status_t (*ec_playback)(void *state, - pj_int16_t *play_frm ); - pj_status_t (*ec_capture)(void *state, - pj_int16_t *rec_frm, - unsigned options ); + void (*ec_reset)(void *state ); pj_status_t (*ec_cancel)(void *state, pj_int16_t *rec_frm, const pj_int16_t *play_frm, @@ -57,10 +77,10 @@ struct ec_operations static struct ec_operations echo_supp_op = { + "Echo suppressor", &echo_supp_create, &echo_supp_destroy, - &echo_supp_playback, - &echo_supp_capture, + &echo_supp_reset, &echo_supp_cancel_echo }; @@ -70,20 +90,30 @@ static struct ec_operations echo_supp_op = * Speex AEC prototypes */ #if defined(PJMEDIA_HAS_SPEEX_AEC) && PJMEDIA_HAS_SPEEX_AEC!=0 -static struct ec_operations aec_op = +static struct ec_operations speex_aec_op = { + "AEC", &speex_aec_create, &speex_aec_destroy, - &speex_aec_playback, - &speex_aec_capture, + &speex_aec_reset, &speex_aec_cancel_echo }; - -#else -#define aec_op echo_supp_op #endif +/* + * IPP AEC prototypes + */ +#if defined(PJMEDIA_HAS_INTEL_IPP_AEC) && PJMEDIA_HAS_INTEL_IPP_AEC!=0 +static struct ec_operations ipp_aec_op = +{ + "IPP AEC", + &ipp_aec_create, + &ipp_aec_destroy, + &ipp_aec_reset, + &ipp_aec_cancel_echo +}; +#endif /* * Create the echo canceller. @@ -96,34 +126,106 @@ PJ_DEF(pj_status_t) pjmedia_echo_create( pj_pool_t *pool, unsigned options, pjmedia_echo_state **p_echo ) { + return pjmedia_echo_create2(pool, clock_rate, 1, samples_per_frame, + tail_ms, latency_ms, options, p_echo); +} + +/* + * Create the echo canceller. + */ +PJ_DEF(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned tail_ms, + unsigned latency_ms, + unsigned options, + pjmedia_echo_state **p_echo ) +{ + unsigned ptime; pjmedia_echo_state *ec; pj_status_t status; - /* Force to use simple echo suppressor if AEC is not available */ -#if !defined(PJMEDIA_HAS_SPEEX_AEC) || PJMEDIA_HAS_SPEEX_AEC==0 - options |= PJMEDIA_ECHO_SIMPLE; + /* Create new pool and instantiate and init the EC */ + pool = pj_pool_create(pool->factory, "ec%p", 256, 256, NULL); + ec = PJ_POOL_ZALLOC_T(pool, struct pjmedia_echo_state); + ec->pool = pool; + ec->obj_name = pool->obj_name; + pj_list_init(&ec->lat_buf); + pj_list_init(&ec->lat_free); + + /* Select the backend algorithm */ + if (0) { + /* Dummy */ + ; +#if defined(PJMEDIA_HAS_SPEEX_AEC) && PJMEDIA_HAS_SPEEX_AEC!=0 + } else if ((options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_SPEEX || + (options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_DEFAULT) + { + ec->op = &speex_aec_op; #endif - ec = PJ_POOL_ZALLOC_T(pool, struct pjmedia_echo_state); +#if defined(PJMEDIA_HAS_INTEL_IPP_AEC) && PJMEDIA_HAS_INTEL_IPP_AEC!=0 + } else if ((options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_IPP || + (options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_DEFAULT) + { + ec->op = &ipp_aec_op; + +#endif - if (options & PJMEDIA_ECHO_SIMPLE) { + } else { ec->op = &echo_supp_op; - status = (*echo_supp_op.ec_create)(pool, clock_rate, samples_per_frame, - tail_ms, latency_ms, options, - &ec->state); + } + + PJ_LOG(5,(ec->obj_name, "Creating %s", ec->op->name)); + + /* Instantiate EC object */ + status = (*ec->op->ec_create)(pool, clock_rate, channel_count, + samples_per_frame, tail_ms, + options, &ec->state); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + + /* Create latency buffers */ + ptime = samples_per_frame * 1000 / clock_rate; + if (latency_ms == 0) { + /* Give at least one frame delay to simplify programming */ + latency_ms = ptime; + } + ec->lat_target_cnt = latency_ms / ptime; + if (ec->lat_target_cnt != 0) { + unsigned i; + for (i=0; i < ec->lat_target_cnt; ++i) { + struct frame *frm; + + frm = (struct frame*) pj_pool_alloc(pool, (samples_per_frame<<1) + + sizeof(struct frame)); + pj_list_push_back(&ec->lat_free, frm); + } } else { - ec->op = &aec_op; - status = (*aec_op.ec_create)(pool, clock_rate, - samples_per_frame, - tail_ms, latency_ms, options, - &ec->state); + ec->lat_ready = PJ_TRUE; } - if (status != PJ_SUCCESS) + /* Create delay buffer to compensate drifts */ + status = pjmedia_delay_buf_create(ec->pool, ec->obj_name, clock_rate, + samples_per_frame, channel_count, + (PJMEDIA_SOUND_BUFFER_COUNT+1) * ptime, + 0, &ec->delay_buf); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); return status; + } - pj_assert(ec->state != NULL); + PJ_LOG(4,(ec->obj_name, + "%s created, clock_rate=%d, channel=%d, " + "samples per frame=%d, tail length=%d ms, " + "latency=%d ms", + ec->op->name, clock_rate, channel_count, samples_per_frame, + tail_ms, latency_ms)); + /* Done */ *p_echo = ec; return PJ_SUCCESS; @@ -135,18 +237,63 @@ PJ_DEF(pj_status_t) pjmedia_echo_create( pj_pool_t *pool, */ PJ_DEF(pj_status_t) pjmedia_echo_destroy(pjmedia_echo_state *echo ) { - return (*echo->op->ec_destroy)(echo->state); + (*echo->op->ec_destroy)(echo->state); + pj_pool_release(echo->pool); + return PJ_SUCCESS; } +/* + * Reset the echo canceller. + */ +PJ_DEF(pj_status_t) pjmedia_echo_reset(pjmedia_echo_state *echo ) +{ + while (!pj_list_empty(&echo->lat_buf)) { + struct frame *frm; + frm = echo->lat_buf.next; + pj_list_erase(frm); + pj_list_push_back(&echo->lat_free, frm); + } + echo->lat_ready = PJ_FALSE; + pjmedia_delay_buf_reset(echo->delay_buf); + echo->op->ec_reset(echo->state); + return PJ_SUCCESS; +} + /* - * Let the Echo Canceller knows that a frame has been played to the speaker. + * Let the Echo Canceller know that a frame has been played to the speaker. */ PJ_DEF(pj_status_t) pjmedia_echo_playback( pjmedia_echo_state *echo, pj_int16_t *play_frm ) { - return (*echo->op->ec_playback)(echo->state, play_frm); + if (!echo->lat_ready) { + /* We've not built enough latency in the buffer, so put this frame + * in the latency buffer list. + */ + struct frame *frm; + + if (pj_list_empty(&echo->lat_free)) { + echo->lat_ready = PJ_TRUE; + PJ_LOG(5,(echo->obj_name, "Latency bufferring complete")); + pjmedia_delay_buf_put(echo->delay_buf, play_frm); + return PJ_SUCCESS; + } + + frm = echo->lat_free.prev; + pj_list_erase(frm); + + pjmedia_copy_samples(frm->buf, play_frm, echo->samples_per_frame); + pj_list_push_back(&echo->lat_buf, frm); + + } else { + /* Latency buffer is ready (full), so we put this frame in the + * delay buffer. + */ + pjmedia_delay_buf_put(echo->delay_buf, play_frm); + } + + return PJ_SUCCESS; } @@ -158,7 +305,34 @@ PJ_DEF(pj_status_t) pjmedia_echo_capture( pjmedia_echo_state *echo, pj_int16_t *rec_frm, unsigned options ) { - return (*echo->op->ec_capture)(echo->state, rec_frm, options); + struct frame *oldest_frm; + pj_status_t status, rc; + + if (!echo->lat_ready) { + /* Prefetching to fill in the desired latency */ + PJ_LOG(5,(echo->obj_name, "Prefetching..")); + return PJ_SUCCESS; + } + + /* Retrieve oldest frame from the latency buffer */ + oldest_frm = echo->lat_buf.next; + pj_list_erase(oldest_frm); + + /* Cancel echo using this reference frame */ + status = pjmedia_echo_cancel(echo, rec_frm, oldest_frm->buf, + options, NULL); + + /* Move one frame from delay buffer to the latency buffer. */ + rc = pjmedia_delay_buf_get(echo->delay_buf, oldest_frm->buf); + if (rc != PJ_SUCCESS) { + /* Ooops.. no frame! */ + PJ_LOG(5,(echo->obj_name, + "No frame from delay buffer. This will upset EC later")); + pjmedia_zero_samples(oldest_frm->buf, echo->samples_per_frame); + } + pj_list_push_back(&echo->lat_buf, oldest_frm); + + return status; } |