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 | |
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')
-rw-r--r-- | pjmedia/build/pjmedia.dsp | 4 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/echo.h | 72 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/echo_common.c | 252 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/echo_internal.h | 31 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/echo_port.c | 7 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/echo_speex.c | 404 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/echo_suppress.c | 68 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 16 |
8 files changed, 336 insertions, 518 deletions
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp index f0ce7c3e..5c8a96b1 100644 --- a/pjmedia/build/pjmedia.dsp +++ b/pjmedia/build/pjmedia.dsp @@ -125,6 +125,10 @@ SOURCE=..\src\pjmedia\echo_common.c # End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\echo_internal.h
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\echo_port.c
# End Source File
# Begin Source File
diff --git a/pjmedia/include/pjmedia/echo.h b/pjmedia/include/pjmedia/echo.h index 53e262f3..4a6c28f7 100644 --- a/pjmedia/include/pjmedia/echo.h +++ b/pjmedia/include/pjmedia/echo.h @@ -57,18 +57,37 @@ typedef struct pjmedia_echo_state pjmedia_echo_state; typedef enum pjmedia_echo_flag { /** + * Use any available backend echo canceller algorithm. This is + * the default settings. This setting is mutually exclusive with + * PJMEDIA_ECHO_SIMPLE and PJMEDIA_ECHO_SPEEX. + */ + PJMEDIA_ECHO_DEFAULT= 0, + + /** + * Force to use Speex AEC as the backend echo canceller algorithm. + * This setting is mutually exclusive with PJMEDIA_ECHO_SIMPLE. + */ + PJMEDIA_ECHO_SPEEX = 1, + + /** * If PJMEDIA_ECHO_SIMPLE flag is specified during echo canceller * creation, then a simple echo suppressor will be used instead of - * an accoustic echo cancellation. + * an accoustic echo cancellation. This setting is mutually exclusive + * with PJMEDIA_ECHO_SPEEX. + */ + PJMEDIA_ECHO_SIMPLE = 2, + + /** + * For internal use. */ - PJMEDIA_ECHO_SIMPLE = 1, + PJMEDIA_ECHO_ALGO_MASK = 15, /** * If PJMEDIA_ECHO_NO_LOCK flag is specified, no mutex will be created * for the echo canceller, but application will guarantee that echo * canceller will not be called by different threads at the same time. */ - PJMEDIA_ECHO_NO_LOCK = 2 + PJMEDIA_ECHO_NO_LOCK = 16 } pjmedia_echo_flag; @@ -102,6 +121,34 @@ PJ_DECL(pj_status_t) pjmedia_echo_create(pj_pool_t *pool, unsigned options, pjmedia_echo_state **p_echo ); +/** + * Create multi-channel the echo canceller. + * + * @param pool Pool to allocate memory. + * @param clock_rate Media clock rate/sampling rate. + * @param channel_count Number of channels. + * @param samples_per_frame Number of samples per frame. + * @param tail_ms Tail length, miliseconds. + * @param latency_ms Total lacency introduced by playback and + * recording device. Set to zero if the latency + * is not known. + * @param options Options. If PJMEDIA_ECHO_SIMPLE is specified, + * then a simple echo suppressor implementation + * will be used instead of an accoustic echo + * cancellation. + * See #pjmedia_echo_flag for other options. + * @param p_echo Pointer to receive the Echo Canceller state. + * + * @return PJ_SUCCESS on success, or the appropriate status. + */ +PJ_DECL(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 ); /** * Destroy the Echo Canceller. @@ -114,7 +161,17 @@ PJ_DECL(pj_status_t) pjmedia_echo_destroy(pjmedia_echo_state *echo ); /** - * Let the Echo Canceller knows that a frame has been played to the speaker. + * Reset the echo canceller. + * + * @param echo The Echo Canceller. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_echo_reset(pjmedia_echo_state *echo ); + + +/** + * Let the Echo Canceller know that a frame has been played to the speaker. * The Echo Canceller will keep the frame in its internal buffer, to be used * when cancelling the echo with #pjmedia_echo_capture(). * @@ -131,10 +188,9 @@ PJ_DECL(pj_status_t) pjmedia_echo_playback(pjmedia_echo_state *echo, /** - * Let the Echo Canceller knows that a frame has been captured from - * the microphone. - * The Echo Canceller will cancel the echo from the captured signal, - * using the internal buffer (supplied by #pjmedia_echo_playback()) + * Let the Echo Canceller know that a frame has been captured from the + * microphone. The Echo Canceller will cancel the echo from the captured + * signal, using the internal buffer (supplied by #pjmedia_echo_playback()) * as the FES (Far End Speech) reference. * * @param echo The Echo Canceller. 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; } diff --git a/pjmedia/src/pjmedia/echo_internal.h b/pjmedia/src/pjmedia/echo_internal.h index c382abbe..6b6a4b60 100644 --- a/pjmedia/src/pjmedia/echo_internal.h +++ b/pjmedia/src/pjmedia/echo_internal.h @@ -28,17 +28,13 @@ PJ_BEGIN_DECL */ PJ_DECL(pj_status_t) echo_supp_create(pj_pool_t *pool, unsigned clock_rate, + unsigned channel_count, unsigned samples_per_frame, unsigned tail_ms, - unsigned latency_ms, unsigned options, void **p_state ); PJ_DECL(pj_status_t) echo_supp_destroy(void *state); -PJ_DECL(pj_status_t) echo_supp_playback(void *state, - pj_int16_t *play_frm ); -PJ_DECL(pj_status_t) echo_supp_capture(void *state, - pj_int16_t *rec_frm, - unsigned options ); +PJ_DECL(void) echo_supp_reset(void *state); PJ_DECL(pj_status_t) echo_supp_cancel_echo(void *state, pj_int16_t *rec_frm, const pj_int16_t *play_frm, @@ -47,23 +43,34 @@ PJ_DECL(pj_status_t) echo_supp_cancel_echo(void *state, PJ_DECL(pj_status_t) speex_aec_create(pj_pool_t *pool, unsigned clock_rate, + unsigned channel_count, unsigned samples_per_frame, unsigned tail_ms, - unsigned latency_ms, unsigned options, void **p_state ); PJ_DECL(pj_status_t) speex_aec_destroy(void *state ); -PJ_DECL(pj_status_t) speex_aec_playback(void *state, - pj_int16_t *play_frm ); -PJ_DECL(pj_status_t) speex_aec_capture(void *state, - pj_int16_t *rec_frm, - unsigned options ); +PJ_DECL(void) speex_aec_reset(void *state ); PJ_DECL(pj_status_t) speex_aec_cancel_echo(void *state, pj_int16_t *rec_frm, const pj_int16_t *play_frm, unsigned options, void *reserved ); +PJ_DECL(pj_status_t) ipp_aec_create(pj_pool_t *pool, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned tail_ms, + unsigned options, + void **p_echo ); +PJ_DECL(pj_status_t) ipp_aec_destroy(void *state ); +PJ_DECL(void) ipp_aec_reset(void *state ); +PJ_DECL(pj_status_t) ipp_aec_cancel_echo(void *state, + pj_int16_t *rec_frm, + const pj_int16_t *play_frm, + unsigned options, + void *reserved ); + PJ_END_DECL diff --git a/pjmedia/src/pjmedia/echo_port.c b/pjmedia/src/pjmedia/echo_port.c index 5d36e134..1b1c89c9 100644 --- a/pjmedia/src/pjmedia/echo_port.c +++ b/pjmedia/src/pjmedia/echo_port.c @@ -67,9 +67,10 @@ PJ_DEF(pj_status_t) pjmedia_echo_port_create(pj_pool_t *pool, dn_port->info.bits_per_sample, dn_port->info.samples_per_frame); - status = pjmedia_echo_create(pool, dn_port->info.clock_rate, - dn_port->info.samples_per_frame, - tail_ms, latency_ms, options, &ec->ec); + status = pjmedia_echo_create2(pool, dn_port->info.clock_rate, + dn_port->info.channel_count, + dn_port->info.samples_per_frame, + tail_ms, latency_ms, options, &ec->ec); if (status != PJ_SUCCESS) return status; diff --git a/pjmedia/src/pjmedia/echo_speex.c b/pjmedia/src/pjmedia/echo_speex.c index d7f27507..033597e8 100644 --- a/pjmedia/src/pjmedia/echo_speex.c +++ b/pjmedia/src/pjmedia/echo_speex.c @@ -19,221 +19,13 @@ #include <pjmedia/echo.h> #include <pjmedia/errno.h> -#include <pjmedia/silencedet.h> #include <pj/assert.h> -#include <pj/lock.h> -#include <pj/log.h> -#include <pj/os.h> #include <pj/pool.h> #include <speex/speex_echo.h> #include <speex/speex_preprocess.h> #include "echo_internal.h" -#define THIS_FILE "echo_speex.c" -#define BUF_COUNT PJMEDIA_SOUND_BUFFER_COUNT -#define MIN_PREFETCH 2 -#define MAX_PREFETCH (BUF_COUNT*2/3) - - - -#if 0 -# define TRACE_(expr) PJ_LOG(5,expr) -#else -# define TRACE_(expr) -#endif - - -typedef struct pjmedia_frame_queue pjmedia_frame_queue; - -struct fq_frame -{ - PJ_DECL_LIST_MEMBER(struct fq_frame); - void *buf; - unsigned size; - pj_uint32_t seq; -}; - -struct pjmedia_frame_queue -{ - char obj_name[PJ_MAX_OBJ_NAME]; - unsigned frame_size; - int samples_per_frame; - unsigned count; - unsigned max_count; - struct fq_frame frame_list; - struct fq_frame free_list; - - int seq_delay; - int prefetch_count; -}; - -PJ_DEF(pj_status_t) pjmedia_frame_queue_create( pj_pool_t *pool, - const char *name, - unsigned frame_size, - unsigned samples_per_frame, - unsigned max_count, - pjmedia_frame_queue **p_fq) -{ - pjmedia_frame_queue *fq; - unsigned i; - - fq = PJ_POOL_ZALLOC_T(pool, pjmedia_frame_queue); - - pj_ansi_snprintf(fq->obj_name, sizeof(fq->obj_name), name, fq); - fq->obj_name[sizeof(fq->obj_name)-1] = '\0'; - - fq->max_count = max_count; - fq->frame_size = frame_size; - fq->samples_per_frame = samples_per_frame; - fq->count = 0; - - pj_list_init(&fq->frame_list); - pj_list_init(&fq->free_list); - - for (i=0; i<max_count; ++i) { - struct fq_frame *f; - - f = PJ_POOL_ZALLOC_T(pool, struct fq_frame); - f->buf = pj_pool_alloc(pool, frame_size); - - pj_list_push_back(&fq->free_list, f); - - } - - *p_fq = fq; - return PJ_SUCCESS; -} - -PJ_DEF(pj_status_t) pjmedia_frame_queue_init( pjmedia_frame_queue *fq, - int seq_delay, - int prefetch_count) -{ - if (prefetch_count > MAX_PREFETCH) - prefetch_count = MAX_PREFETCH; - - fq->seq_delay = seq_delay; - fq->prefetch_count = prefetch_count; - fq->count = 0; - pj_list_merge_first(&fq->free_list, &fq->frame_list); - - PJ_LOG(5,(fq->obj_name, "AEC reset, delay=%d, prefetch=%d", - fq->seq_delay, fq->prefetch_count)); - - return PJ_SUCCESS; -} - -PJ_DEF(pj_bool_t) pjmedia_frame_queue_empty( pjmedia_frame_queue *fq ) -{ - return pj_list_empty(&fq->frame_list); -} - -PJ_DEF(int) pjmedia_frame_queue_get_prefetch( pjmedia_frame_queue *fq ) -{ - return fq->prefetch_count; -} - -PJ_DEF(pj_status_t) pjmedia_frame_queue_put( pjmedia_frame_queue *fq, - const void *framebuf, - unsigned size, - pj_uint32_t timestamp ) -{ - struct fq_frame *f; - - TRACE_((fq->obj_name, "PUT seq=%d, count=%d", - timestamp / fq->samples_per_frame, fq->count)); - - if (pj_list_empty(&fq->free_list)) { - PJ_LOG(5,(fq->obj_name, - " AEC info: queue is full, frame discarded " - "[count=%d, seq=%d]", - fq->max_count, timestamp / fq->samples_per_frame)); - //pjmedia_frame_queue_init(fq, fq->seq_delay, fq->prefetch_count); - return PJ_ETOOMANY; - } - - PJ_ASSERT_RETURN(size <= fq->frame_size, PJ_ETOOBIG); - - f = fq->free_list.next; - pj_list_erase(f); - - pj_memcpy(f->buf, framebuf, size); - f->size = size; - f->seq = timestamp / fq->samples_per_frame; - - pj_list_push_back(&fq->frame_list, f); - ++fq->count; - - return PJ_SUCCESS; -} - -PJ_DEF(pj_status_t) pjmedia_frame_queue_get( pjmedia_frame_queue *fq, - pj_uint32_t get_timestamp, - void **framebuf, - unsigned *size ) -{ - pj_uint32_t frame_seq; - struct fq_frame *f; - - frame_seq = get_timestamp/fq->samples_per_frame + fq->seq_delay - - fq->prefetch_count; - - TRACE_((fq->obj_name, "GET seq=%d for seq=%d delay=%d, prefetch=%d", - get_timestamp/fq->samples_per_frame, frame_seq, fq->seq_delay, - fq->prefetch_count)); - - *size = 0; - - /* Remove old frames */ - for (;!pj_list_empty(&fq->frame_list);) { - f = fq->frame_list.next; - if (f->seq >= frame_seq) - break; - - PJ_LOG(5,(fq->obj_name, - " AEC Info: old frame removed (seq=%d, want=%d, count=%d)", - f->seq, frame_seq, fq->count)); - pj_list_erase(f); - --fq->count; - pj_list_push_back(&fq->free_list, f); - } - - if (pj_list_empty(&fq->frame_list)) { - PJ_LOG(5,(fq->obj_name, - " AEC Info: empty queue for seq=%d!", - frame_seq)); - return PJ_ENOTFOUND; - } - - f = fq->frame_list.next; - - if (f->seq > frame_seq) { - PJ_LOG(5,(fq->obj_name, - " AEC Info: prefetching (first seq=%d)", - f->seq)); - return -1; - } - - pj_list_erase(f); - --fq->count; - - *framebuf = (void*)f->buf; - *size = f->size; - - TRACE_((fq->obj_name, " returning frame with seq=%d, count=%d", - f->seq, fq->count)); - - pj_list_push_front(&fq->free_list, f); - return PJ_SUCCESS; -} - -enum -{ - TS_FLAG_PLAY = 1, - TS_FLAG_REC = 2, - TS_FLAG_OK = 3, -}; - typedef struct speex_ec { SpeexEchoState *state; @@ -243,14 +35,6 @@ typedef struct speex_ec unsigned prefetch; unsigned options; pj_int16_t *tmp_frame; - spx_int32_t *residue; - - pj_uint32_t play_ts, - rec_ts, - ts_flag; - - pjmedia_frame_queue *frame_queue; - pj_lock_t *lock; /* To protect buffers, if required */ } speex_ec; @@ -260,43 +44,33 @@ typedef struct speex_ec */ PJ_DEF(pj_status_t) speex_aec_create(pj_pool_t *pool, unsigned clock_rate, + unsigned channel_count, unsigned samples_per_frame, unsigned tail_ms, - unsigned latency_ms, unsigned options, void **p_echo ) { speex_ec *echo; int sampling_rate; - pj_status_t status; *p_echo = NULL; echo = PJ_POOL_ZALLOC_T(pool, speex_ec); PJ_ASSERT_RETURN(echo != NULL, PJ_ENOMEM); - if (options & PJMEDIA_ECHO_NO_LOCK) { - status = pj_lock_create_null_mutex(pool, "aec%p", &echo->lock); - if (status != PJ_SUCCESS) - return status; - } else { - status = pj_lock_create_simple_mutex(pool, "aec%p", &echo->lock); - if (status != PJ_SUCCESS) - return status; - } - echo->samples_per_frame = samples_per_frame; - echo->prefetch = (latency_ms * clock_rate / 1000) / samples_per_frame; - if (echo->prefetch < MIN_PREFETCH) - echo->prefetch = MIN_PREFETCH; - if (echo->prefetch > MAX_PREFETCH) - echo->prefetch = MAX_PREFETCH; echo->options = options; - echo->state = speex_echo_state_init(samples_per_frame, - clock_rate * tail_ms / 1000); +#if 0 + echo->state = speex_echo_state_init_mc(echo->samples_per_frame, + clock_rate * tail_ms / 1000, + channel_count, channel_count); +#else + PJ_ASSERT_RETURN(channel_count==1, PJ_EINVAL); + echo->state = speex_echo_state_init(echo->samples_per_frame, + clock_rate * tail_ms / 1000); +#endif if (echo->state == NULL) { - pj_lock_destroy(echo->lock); return PJ_ENOMEM; } @@ -305,11 +79,10 @@ PJ_DEF(pj_status_t) speex_aec_create(pj_pool_t *pool, speex_echo_ctl(echo->state, SPEEX_ECHO_SET_SAMPLING_RATE, &sampling_rate); - echo->preprocess = speex_preprocess_state_init(samples_per_frame, + echo->preprocess = speex_preprocess_state_init(echo->samples_per_frame, clock_rate); if (echo->preprocess == NULL) { speex_echo_state_destroy(echo->state); - pj_lock_destroy(echo->lock); return PJ_ENOMEM; } @@ -324,7 +97,7 @@ PJ_DEF(pj_status_t) speex_aec_create(pj_pool_t *pool, speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_VAD, &disabled); speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_DEREVERB, - &disabled); + &enabled); #endif /* Control echo cancellation in the preprocessor */ @@ -333,33 +106,11 @@ PJ_DEF(pj_status_t) speex_aec_create(pj_pool_t *pool, /* Create temporary frame for echo cancellation */ - echo->tmp_frame = (pj_int16_t*) pj_pool_zalloc(pool, 2 * samples_per_frame); + echo->tmp_frame = (pj_int16_t*) pj_pool_zalloc(pool, 2*samples_per_frame); PJ_ASSERT_RETURN(echo->tmp_frame != NULL, PJ_ENOMEM); - /* Create temporary frame to receive residue */ - echo->residue = (spx_int32_t*) - pj_pool_zalloc(pool, sizeof(spx_int32_t) * - (samples_per_frame+1)); - PJ_ASSERT_RETURN(echo->residue != NULL, PJ_ENOMEM); - - /* Create frame queue */ - status = pjmedia_frame_queue_create(pool, "aec%p", samples_per_frame*2, - samples_per_frame, BUF_COUNT, - &echo->frame_queue); - if (status != PJ_SUCCESS) { - speex_preprocess_state_destroy(echo->preprocess); - speex_echo_state_destroy(echo->state); - pj_lock_destroy(echo->lock); - return status; - } - /* Done */ *p_echo = echo; - - PJ_LOG(4,(THIS_FILE, "Speex Echo canceller/AEC created, clock_rate=%d, " - "samples per frame=%d, tail length=%d ms, " - "latency=%d ms", - clock_rate, samples_per_frame, tail_ms, latency_ms)); return PJ_SUCCESS; } @@ -374,9 +125,6 @@ PJ_DEF(pj_status_t) speex_aec_destroy(void *state ) PJ_ASSERT_RETURN(echo && echo->state, PJ_EINVAL); - if (echo->lock) - pj_lock_acquire(echo->lock); - if (echo->state) { speex_echo_state_destroy(echo->state); echo->state = NULL; @@ -387,137 +135,17 @@ PJ_DEF(pj_status_t) speex_aec_destroy(void *state ) echo->preprocess = NULL; } - if (echo->lock) { - pj_lock_destroy(echo->lock); - echo->lock = NULL; - } - return PJ_SUCCESS; } /* - * Let the AEC knows that a frame has been played to the speaker. + * Reset AEC */ -PJ_DEF(pj_status_t) speex_aec_playback(void *state, - pj_int16_t *play_frm ) +PJ_DEF(void) speex_aec_reset(void *state ) { speex_ec *echo = (speex_ec*) state; - - /* Sanity checks */ - PJ_ASSERT_RETURN(echo && play_frm, PJ_EINVAL); - - /* The AEC must be configured to support internal playback buffer */ - PJ_ASSERT_RETURN(echo->frame_queue!= NULL, PJ_EINVALIDOP); - - pj_lock_acquire(echo->lock); - - /* Inc timestamp */ - echo->play_ts += echo->samples_per_frame; - - /* Initialize frame delay. */ - if ((echo->ts_flag & TS_FLAG_PLAY) == 0) { - echo->ts_flag |= TS_FLAG_PLAY; - - if (echo->ts_flag == TS_FLAG_OK) { - int seq_delay; - - seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / - (int)echo->samples_per_frame; - pjmedia_frame_queue_init(echo->frame_queue, seq_delay, - echo->prefetch); - } - } - - if (pjmedia_frame_queue_put(echo->frame_queue, play_frm, - echo->samples_per_frame*2, - echo->play_ts) != PJ_SUCCESS) - { - int seq_delay; - - /* On full reset frame queue */ - seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / - (int)echo->samples_per_frame; - pjmedia_frame_queue_init(echo->frame_queue, seq_delay, - echo->prefetch); - - /* And re-put */ - pjmedia_frame_queue_put(echo->frame_queue, play_frm, - echo->samples_per_frame*2, - echo->play_ts); - } - - pj_lock_release(echo->lock); - - return PJ_SUCCESS; -} - - -/* - * Let the AEC knows that a frame has been captured from the microphone. - */ -PJ_DEF(pj_status_t) speex_aec_capture( void *state, - pj_int16_t *rec_frm, - unsigned options ) -{ - speex_ec *echo = (speex_ec*) state; - pj_status_t status = PJ_SUCCESS; - - /* Sanity checks */ - PJ_ASSERT_RETURN(echo && rec_frm, PJ_EINVAL); - - /* The AEC must be configured to support internal playback buffer */ - PJ_ASSERT_RETURN(echo->frame_queue!= NULL, PJ_EINVALIDOP); - - /* Lock mutex */ - pj_lock_acquire(echo->lock); - - /* Inc timestamp */ - echo->rec_ts += echo->samples_per_frame; - - /* Init frame delay. */ - if ((echo->ts_flag & TS_FLAG_REC) == 0) { - echo->ts_flag |= TS_FLAG_REC; - - if (echo->ts_flag == TS_FLAG_OK) { - int seq_delay; - - seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / - (int)echo->samples_per_frame; - pjmedia_frame_queue_init(echo->frame_queue, seq_delay, - echo->prefetch); - } - } - - /* Cancel echo */ - if (echo->ts_flag == TS_FLAG_OK) { - void *play_buf; - unsigned size = 0; - - if (pjmedia_frame_queue_empty(echo->frame_queue)) { - int seq_delay; - - seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / - (int)echo->samples_per_frame; - pjmedia_frame_queue_init(echo->frame_queue, seq_delay, - echo->prefetch); - status = -1; - - } else { - status = pjmedia_frame_queue_get(echo->frame_queue, echo->rec_ts, - &play_buf, &size); - if (size != 0) { - speex_aec_cancel_echo(echo, rec_frm, (pj_int16_t*)play_buf, - options, NULL); - } - } - - if (status != PJ_SUCCESS) - speex_echo_state_reset(echo->state); - } - - pj_lock_release(echo->lock); - return PJ_SUCCESS; + speex_echo_state_reset(echo->state); } diff --git a/pjmedia/src/pjmedia/echo_suppress.c b/pjmedia/src/pjmedia/echo_suppress.c index 8ef071ba..a86a058d 100644 --- a/pjmedia/src/pjmedia/echo_suppress.c +++ b/pjmedia/src/pjmedia/echo_suppress.c @@ -35,9 +35,7 @@ */ typedef struct echo_supp { - pj_bool_t suppressing; pjmedia_silence_det *sd; - pj_time_val last_signal; unsigned samples_per_frame; unsigned tail_ms; } echo_supp; @@ -49,9 +47,9 @@ typedef struct echo_supp */ PJ_DEF(pj_status_t) echo_supp_create( pj_pool_t *pool, unsigned clock_rate, + unsigned channel_count, unsigned samples_per_frame, unsigned tail_ms, - unsigned latency_ms, unsigned options, void **p_state ) { @@ -59,8 +57,8 @@ PJ_DEF(pj_status_t) echo_supp_create( pj_pool_t *pool, pj_status_t status; PJ_UNUSED_ARG(clock_rate); + PJ_UNUSED_ARG(channel_count); PJ_UNUSED_ARG(options); - PJ_UNUSED_ARG(latency_ms); ec = PJ_POOL_ZALLOC_T(pool, struct echo_supp); ec->samples_per_frame = samples_per_frame; @@ -91,68 +89,14 @@ PJ_DEF(pj_status_t) echo_supp_destroy(void *state) /* - * Let the AEC knows that a frame has been played to the speaker. + * Reset */ -PJ_DEF(pj_status_t) echo_supp_playback( void *state, - pj_int16_t *play_frm ) +PJ_DEF(void) echo_supp_reset(void *state) { - echo_supp *ec = (echo_supp*) state; - pj_bool_t silence; - pj_bool_t last_suppressing = ec->suppressing; - - silence = pjmedia_silence_det_detect(ec->sd, play_frm, - ec->samples_per_frame, NULL); - - ec->suppressing = !silence; - - if (ec->suppressing) { - pj_gettimeofday(&ec->last_signal); - } - - if (ec->suppressing!=0 && last_suppressing==0) { - PJ_LOG(5,(THIS_FILE, "Start suppressing..")); - } else if (ec->suppressing==0 && last_suppressing!=0) { - PJ_LOG(5,(THIS_FILE, "Stop suppressing..")); - } - - return PJ_SUCCESS; -} - - -/* - * Let the AEC knows that a frame has been captured from the microphone. - */ -PJ_DEF(pj_status_t) echo_supp_capture( void *state, - pj_int16_t *rec_frm, - unsigned options ) -{ - echo_supp *ec = (echo_supp*) state; - pj_time_val now; - unsigned delay_ms; - - PJ_UNUSED_ARG(options); - - pj_gettimeofday(&now); - - PJ_TIME_VAL_SUB(now, ec->last_signal); - delay_ms = PJ_TIME_VAL_MSEC(now); - - if (delay_ms < ec->tail_ms) { -#if defined(PJMEDIA_ECHO_SUPPRESS_FACTOR) && PJMEDIA_ECHO_SUPPRESS_FACTOR!=0 - unsigned i; - for (i=0; i<ec->samples_per_frame; ++i) { - rec_frm[i] = (pj_int16_t)(rec_frm[i] >> - PJMEDIA_ECHO_SUPPRESS_FACTOR); - } -#else - pjmedia_zero_samples(rec_frm, ec->samples_per_frame); -#endif - } - - return PJ_SUCCESS; + PJ_UNUSED_ARG(state); + return; } - /* * Perform echo cancellation. */ diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index e180ed2b..50f35d8e 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -544,12 +544,16 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, if (status != PJ_SUCCESS) si.rec_latency = si.play_latency = 0; - delay_ms = (si.rec_latency + si.play_latency) * 1000 / - snd_port->clock_rate; - status = pjmedia_echo_create(pool, snd_port->clock_rate, - snd_port->samples_per_frame, - tail_ms, delay_ms, - options, &snd_port->ec_state); + //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 (status != PJ_SUCCESS) snd_port->ec_state = NULL; else |