summaryrefslogtreecommitdiff
path: root/pjmedia/src
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-08-06 12:07:13 +0000
committerBenny Prijono <bennylp@teluu.com>2006-08-06 12:07:13 +0000
commit7d4e5f795015cc061a65f812c0642cfd2891681e (patch)
tree2530a16f89b39b79cc46df59913f261c44f54249 /pjmedia/src
parente597fd48b9586700656f2a5760153ef65790a44b (diff)
Change AEC into generic echo canceller framework with either AEC or simple echo suppressor backend can be selected during runtime.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@653 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src')
-rw-r--r--pjmedia/src/pjmedia/aec_speex.c275
-rw-r--r--pjmedia/src/pjmedia/echo_common.c215
-rw-r--r--pjmedia/src/pjmedia/echo_port.c (renamed from pjmedia/src/pjmedia/aec_port.c)79
-rw-r--r--pjmedia/src/pjmedia/echo_speex.c321
-rw-r--r--pjmedia/src/pjmedia/echo_suppress.c186
-rw-r--r--pjmedia/src/pjmedia/sound_port.c49
6 files changed, 787 insertions, 338 deletions
diff --git a/pjmedia/src/pjmedia/aec_speex.c b/pjmedia/src/pjmedia/aec_speex.c
deleted file mode 100644
index 95cc15b4..00000000
--- a/pjmedia/src/pjmedia/aec_speex.c
+++ /dev/null
@@ -1,275 +0,0 @@
-/* $Id$ */
-/*
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include <pjmedia/aec.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>
-
-
-#define THIS_FILE "aec_speex.c"
-#define BUF_COUNT 8
-
-
-struct frame
-{
- pj_int16_t *buf;
-};
-
-struct pjmedia_aec
-{
- SpeexEchoState *state;
- SpeexPreprocessState *preprocess;
-
- unsigned samples_per_frame;
- unsigned options;
- pj_int16_t *tmp_frame;
- spx_int32_t *residue;
-
- pj_lock_t *lock; /* To protect buffers, if required */
-
- unsigned rpos; /* Index to get oldest frame. */
- unsigned wpos; /* Index to put newest frame. */
- struct frame frames[BUF_COUNT]; /* Playback frame buffers. */
-};
-
-
-
-/*
- * Create the AEC.
- */
-PJ_DEF(pj_status_t) pjmedia_aec_create( pj_pool_t *pool,
- unsigned clock_rate,
- unsigned samples_per_frame,
- unsigned tail_ms,
- unsigned options,
- pjmedia_aec **p_aec )
-{
- pjmedia_aec *aec;
- int sampling_rate;
- unsigned i;
- pj_status_t status;
-
- *p_aec = NULL;
-
- aec = pj_pool_zalloc(pool, sizeof(pjmedia_aec));
- PJ_ASSERT_RETURN(aec != NULL, PJ_ENOMEM);
-
- status = pj_lock_create_simple_mutex(pool, "aec%p", &aec->lock);
- if (status != PJ_SUCCESS)
- return status;
-
- aec->samples_per_frame = samples_per_frame;
- aec->options = options;
-
- aec->state = speex_echo_state_init(samples_per_frame,
- clock_rate * tail_ms / 1000);
- if (aec->state == NULL) {
- pj_lock_destroy(aec->lock);
- return PJ_ENOMEM;
- }
-
- aec->preprocess = speex_preprocess_state_init(samples_per_frame,
- clock_rate);
- if (aec->preprocess == NULL) {
- speex_echo_state_destroy(aec->state);
- pj_lock_destroy(aec->lock);
- return PJ_ENOMEM;
- }
-
- /* Set sampling rate */
- sampling_rate = clock_rate;
- speex_echo_ctl(aec->state, SPEEX_ECHO_SET_SAMPLING_RATE,
- &sampling_rate);
-
- /* Create temporary frame for echo cancellation */
- aec->tmp_frame = pj_pool_zalloc(pool, 2 * samples_per_frame);
- PJ_ASSERT_RETURN(aec->tmp_frame != NULL, PJ_ENOMEM);
-
- /* Create temporary frame to receive residue */
- aec->residue = pj_pool_zalloc(pool, sizeof(spx_int32_t) *
- samples_per_frame);
- PJ_ASSERT_RETURN(aec->residue != NULL, PJ_ENOMEM);
-
- /* Create internal playback buffers */
- for (i=0; i<BUF_COUNT; ++i) {
- aec->frames[i].buf = pj_pool_zalloc(pool, samples_per_frame * 2);
- PJ_ASSERT_RETURN(aec->frames[i].buf != NULL, PJ_ENOMEM);
- }
-
-
- /* Done */
- *p_aec = aec;
-
- PJ_LOG(4,(THIS_FILE, "Echo canceller/AEC created, clock_rate=%d, "
- "samples per frame=%d, tail length=%d ms",
- clock_rate,
- samples_per_frame,
- tail_ms));
- return PJ_SUCCESS;
-
-}
-
-
-/*
- * Destroy AEC
- */
-PJ_DEF(pj_status_t) pjmedia_aec_destroy(pjmedia_aec *aec )
-{
- PJ_ASSERT_RETURN(aec && aec->state, PJ_EINVAL);
-
- if (aec->lock)
- pj_lock_acquire(aec->lock);
-
- if (aec->state) {
- speex_echo_state_destroy(aec->state);
- aec->state = NULL;
- }
-
- if (aec->preprocess) {
- speex_preprocess_state_destroy(aec->preprocess);
- aec->preprocess = NULL;
- }
-
- if (aec->lock) {
- pj_lock_destroy(aec->lock);
- aec->lock = NULL;
- }
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Let the AEC knows that a frame has been played to the speaker.
- */
-PJ_DEF(pj_status_t) pjmedia_aec_playback(pjmedia_aec *aec,
- pj_int16_t *play_frm )
-{
- /* Sanity checks */
- PJ_ASSERT_RETURN(aec && play_frm, PJ_EINVAL);
-
- /* The AEC must be configured to support internal playback buffer */
- PJ_ASSERT_RETURN(aec->frames[0].buf != NULL, PJ_EINVALIDOP);
-
- pj_lock_acquire(aec->lock);
-
- /* Check for overflows */
- if (aec->wpos == aec->rpos) {
- PJ_LOG(5,(THIS_FILE, "AEC overflow (playback runs faster, "
- "wpos=%d, rpos=%d)",
- aec->wpos, aec->rpos));
- aec->rpos = (aec->wpos - BUF_COUNT/2) % BUF_COUNT;
- speex_echo_state_reset(aec->state);
- }
-
- /* Save fhe frame */
- pjmedia_copy_samples(aec->frames[aec->wpos].buf,
- play_frm, aec->samples_per_frame);
- aec->wpos = (aec->wpos+1) % BUF_COUNT;
-
- pj_lock_release(aec->lock);
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Let the AEC knows that a frame has been captured from the microphone.
- */
-PJ_DEF(pj_status_t) pjmedia_aec_capture( pjmedia_aec *aec,
- pj_int16_t *rec_frm,
- unsigned options )
-{
- pj_status_t status;
-
- /* Sanity checks */
- PJ_ASSERT_RETURN(aec && rec_frm, PJ_EINVAL);
-
- /* The AEC must be configured to support internal playback buffer */
- PJ_ASSERT_RETURN(aec->frames[0].buf != NULL, PJ_EINVALIDOP);
-
- /* Lock mutex */
- pj_lock_acquire(aec->lock);
-
-
- /* Check for underflow */
- if (aec->rpos == aec->wpos) {
- /* Return frame as it is */
- pj_lock_release(aec->lock);
-
- PJ_LOG(5,(THIS_FILE, "AEC underflow (capture runs faster than "
- "playback, wpos=%d, rpos=%d)",
- aec->wpos, aec->rpos));
- aec->rpos = (aec->wpos - BUF_COUNT/2) % BUF_COUNT;
- speex_echo_state_reset(aec->state);
-
- return PJ_SUCCESS;
- }
-
-
- /* Cancel echo */
- status = pjmedia_aec_cancel_echo(aec, rec_frm,
- aec->frames[aec->rpos].buf, options,
- NULL);
-
- aec->rpos = (aec->rpos + 1) % BUF_COUNT;
-
- pj_lock_release(aec->lock);
- return status;
-}
-
-
-/*
- * Perform echo cancellation.
- */
-PJ_DEF(pj_status_t) pjmedia_aec_cancel_echo( pjmedia_aec *aec,
- pj_int16_t *rec_frm,
- const pj_int16_t *play_frm,
- unsigned options,
- void *reserved )
-{
- /* Sanity checks */
- PJ_ASSERT_RETURN(aec && rec_frm && play_frm && options==0 &&
- reserved==NULL, PJ_EINVAL);
-
- /* Cancel echo, put output in temporary buffer */
- speex_echo_cancel(aec->state, (const spx_int16_t*)rec_frm,
- (const spx_int16_t*)play_frm,
- (spx_int16_t*)aec->tmp_frame,
- aec->residue);
-
-
- /* Preprocess output */
- speex_preprocess(aec->preprocess, (spx_int16_t*)aec->tmp_frame,
- aec->residue);
-
- /* Copy temporary buffer back to original rec_frm */
- pjmedia_copy_samples(rec_frm, aec->tmp_frame, aec->samples_per_frame);
-
- return PJ_SUCCESS;
-
-}
-
diff --git a/pjmedia/src/pjmedia/echo_common.c b/pjmedia/src/pjmedia/echo_common.c
new file mode 100644
index 00000000..b84fa953
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_common.c
@@ -0,0 +1,215 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjmedia/echo.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+
+
+typedef struct ec_operations ec_operations;
+
+struct pjmedia_echo_state
+{
+ void *state;
+ ec_operations *op;
+};
+
+
+struct ec_operations
+{
+ pj_status_t (*ec_create)(pj_pool_t *pool,
+ unsigned clock_rate,
+ 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 );
+ pj_status_t (*ec_cancel)(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+};
+
+
+
+/*
+ * Simple echo suppressor
+ */
+PJ_DECL(pj_status_t) echo_supp_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_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(pj_status_t) echo_supp_cancel_echo(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+
+static struct ec_operations echo_supp_op =
+{
+ &echo_supp_create,
+ &echo_supp_destroy,
+ &echo_supp_playback,
+ &echo_supp_capture,
+ &echo_supp_cancel_echo
+};
+
+
+
+/*
+ * Speex AEC prototypes
+ */
+#if defined(PJMEDIA_HAS_SPEEX_AEC) && PJMEDIA_HAS_SPEEX_AEC!=0
+PJ_DECL(pj_status_t) speex_aec_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_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(pj_status_t) speex_aec_cancel_echo(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+
+static struct ec_operations aec_op =
+{
+ &speex_aec_create,
+ &speex_aec_destroy,
+ &speex_aec_playback,
+ &speex_aec_capture,
+ &speex_aec_cancel_echo
+};
+
+#else
+static struct ec_operations aec_op = echo_supp_op;
+#endif
+
+
+
+/*
+ * Create the echo canceller.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ pjmedia_echo_state **p_echo )
+{
+ 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;
+#endif
+
+ ec = pj_pool_zalloc(pool, sizeof(struct pjmedia_echo_state));
+
+ if (options & PJMEDIA_ECHO_SIMPLE) {
+ ec->op = &echo_supp_op;
+ status = (*echo_supp_op.ec_create)(pool, clock_rate,
+ samples_per_frame,
+ tail_ms, options,
+ &ec->state);
+ } else {
+ ec->op = &aec_op;
+ status = (*aec_op.ec_create)(pool, clock_rate,
+ samples_per_frame,
+ tail_ms, options,
+ &ec->state);
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_assert(ec->state != NULL);
+
+ *p_echo = ec;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy the Echo Canceller.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_destroy(pjmedia_echo_state *echo )
+{
+ return (*echo->op->ec_destroy)(echo->state);
+}
+
+
+
+/*
+ * Let the Echo Canceller knows 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);
+}
+
+
+/*
+ * Let the Echo Canceller knows that a frame has been captured from
+ * the microphone.
+ */
+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);
+}
+
+
+/*
+ * Perform echo cancellation.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_cancel( pjmedia_echo_state *echo,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved )
+{
+ return (*echo->op->ec_cancel)( echo->state, rec_frm, play_frm, options,
+ reserved);
+}
+
diff --git a/pjmedia/src/pjmedia/aec_port.c b/pjmedia/src/pjmedia/echo_port.c
index 70d3a217..82fbca11 100644
--- a/pjmedia/src/pjmedia/aec_port.c
+++ b/pjmedia/src/pjmedia/echo_port.c
@@ -16,40 +16,41 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#include <pjmedia/aec_port.h>
-#include <pjmedia/aec.h>
+#include <pjmedia/echo_port.h>
+#include <pjmedia/echo.h>
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>
-#define THIS_FILE "aec_port.c"
-#define SIGNATURE PJMEDIA_PORT_SIGNATURE('A', 'E', 'C', ' ')
+#define THIS_FILE "ec_port.c"
+#define SIGNATURE PJMEDIA_PORT_SIGNATURE('E', 'C', 'H', 'O')
#define BUF_COUNT 32
-struct aec
+struct ec
{
- pjmedia_port base;
- pjmedia_port *dn_port;
- pjmedia_aec *aec;
+ pjmedia_port base;
+ pjmedia_port *dn_port;
+ pjmedia_echo_state *ec;
};
-static pj_status_t aec_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
-static pj_status_t aec_get_frame(pjmedia_port *this_port,
- pjmedia_frame *frame);
-static pj_status_t aec_on_destroy(pjmedia_port *this_port);
+static pj_status_t ec_put_frame(pjmedia_port *this_port,
+ const pjmedia_frame *frame);
+static pj_status_t ec_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t ec_on_destroy(pjmedia_port *this_port);
-PJ_DEF(pj_status_t) pjmedia_aec_port_create( pj_pool_t *pool,
+PJ_DEF(pj_status_t) pjmedia_echo_port_create(pj_pool_t *pool,
pjmedia_port *dn_port,
unsigned tail_ms,
+ unsigned options,
pjmedia_port **p_port )
{
- const pj_str_t AEC = { "AEC", 3 };
- struct aec *aec;
+ const pj_str_t AEC = { "EC", 2 };
+ struct ec *ec;
pj_status_t status;
PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL);
@@ -57,79 +58,79 @@ PJ_DEF(pj_status_t) pjmedia_aec_port_create( pj_pool_t *pool,
PJ_EINVAL);
/* Create the port and the AEC itself */
- aec = pj_pool_zalloc(pool, sizeof(struct aec));
+ ec = pj_pool_zalloc(pool, sizeof(struct ec));
- pjmedia_port_info_init(&aec->base.info, &AEC, SIGNATURE,
+ pjmedia_port_info_init(&ec->base.info, &AEC, SIGNATURE,
dn_port->info.clock_rate,
dn_port->info.channel_count,
dn_port->info.bits_per_sample,
dn_port->info.samples_per_frame);
- status = pjmedia_aec_create(pool, dn_port->info.clock_rate,
- dn_port->info.samples_per_frame,
- tail_ms, 0, &aec->aec);
+ status = pjmedia_echo_create(pool, dn_port->info.clock_rate,
+ dn_port->info.samples_per_frame,
+ tail_ms, options, &ec->ec);
if (status != PJ_SUCCESS)
return status;
/* More init */
- aec->dn_port = dn_port;
- aec->base.get_frame = &aec_get_frame;
- aec->base.put_frame = &aec_put_frame;
- aec->base.on_destroy = &aec_on_destroy;
+ ec->dn_port = dn_port;
+ ec->base.get_frame = &ec_get_frame;
+ ec->base.put_frame = &ec_put_frame;
+ ec->base.on_destroy = &ec_on_destroy;
/* Done */
- *p_port = &aec->base;
+ *p_port = &ec->base;
return PJ_SUCCESS;
}
-static pj_status_t aec_put_frame(pjmedia_port *this_port,
+static pj_status_t ec_put_frame( pjmedia_port *this_port,
const pjmedia_frame *frame)
{
- struct aec *aec = (struct aec*)this_port;
+ struct ec *ec = (struct ec*)this_port;
PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
if (frame->type == PJMEDIA_FRAME_TYPE_NONE ) {
- return pjmedia_port_put_frame(aec->dn_port, frame);
+ return pjmedia_port_put_frame(ec->dn_port, frame);
}
PJ_ASSERT_RETURN(frame->size == this_port->info.samples_per_frame * 2,
PJ_EINVAL);
- pjmedia_aec_capture(aec->aec, frame->buf, 0);
+ pjmedia_echo_capture(ec->ec, frame->buf, 0);
- return pjmedia_port_put_frame(aec->dn_port, frame);
+ return pjmedia_port_put_frame(ec->dn_port, frame);
}
-static pj_status_t aec_get_frame( pjmedia_port *this_port,
- pjmedia_frame *frame)
+static pj_status_t ec_get_frame( pjmedia_port *this_port,
+ pjmedia_frame *frame)
{
- struct aec *aec = (struct aec*)this_port;
+ struct ec *ec = (struct ec*)this_port;
pj_status_t status;
PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
- status = pjmedia_port_get_frame(aec->dn_port, frame);
+ status = pjmedia_port_get_frame(ec->dn_port, frame);
if (status!=PJ_SUCCESS || frame->type!=PJMEDIA_FRAME_TYPE_AUDIO) {
pjmedia_zero_samples(frame->buf, this_port->info.samples_per_frame);
}
- pjmedia_aec_playback(aec->aec, frame->buf);
+ pjmedia_echo_playback(ec->ec, frame->buf);
return status;
}
-static pj_status_t aec_on_destroy(pjmedia_port *this_port)
+static pj_status_t ec_on_destroy(pjmedia_port *this_port)
{
- struct aec *aec = (struct aec*)this_port;
+ struct ec *ec = (struct ec*)this_port;
PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
- pjmedia_aec_destroy(aec->aec);
+ pjmedia_echo_destroy(ec->ec);
return PJ_SUCCESS;
}
diff --git a/pjmedia/src/pjmedia/echo_speex.c b/pjmedia/src/pjmedia/echo_speex.c
new file mode 100644
index 00000000..bf8f3f72
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_speex.c
@@ -0,0 +1,321 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <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>
+
+
+#define THIS_FILE "echo_speex.c"
+#define BUF_COUNT 8
+
+/*
+ * Prototypes
+ */
+PJ_DECL(pj_status_t) speex_aec_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_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(pj_status_t) speex_aec_cancel_echo(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+
+
+struct frame
+{
+ pj_int16_t *buf;
+};
+
+typedef struct speex_ec
+{
+ SpeexEchoState *state;
+ SpeexPreprocessState *preprocess;
+
+ unsigned samples_per_frame;
+ unsigned options;
+ pj_int16_t *tmp_frame;
+ spx_int32_t *residue;
+
+ pj_lock_t *lock; /* To protect buffers, if required */
+
+ unsigned rpos; /* Index to get oldest frame. */
+ unsigned wpos; /* Index to put newest frame. */
+ struct frame frames[BUF_COUNT]; /* Playback frame buffers. */
+} speex_ec;
+
+
+
+/*
+ * Create the AEC.
+ */
+PJ_DEF(pj_status_t) speex_aec_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ void **p_echo )
+{
+ speex_ec *echo;
+ int sampling_rate;
+ unsigned i;
+ int disabled;
+ pj_status_t status;
+
+ *p_echo = NULL;
+
+ echo = pj_pool_zalloc(pool, sizeof(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->options = options;
+
+ echo->state = speex_echo_state_init(samples_per_frame,
+ clock_rate * tail_ms / 1000);
+ if (echo->state == NULL) {
+ pj_lock_destroy(echo->lock);
+ return PJ_ENOMEM;
+ }
+
+ echo->preprocess = speex_preprocess_state_init(samples_per_frame,
+ clock_rate);
+ if (echo->preprocess == NULL) {
+ speex_echo_state_destroy(echo->state);
+ pj_lock_destroy(echo->lock);
+ return PJ_ENOMEM;
+ }
+
+ /* Disable all preprocessing, we only want echo cancellation */
+ disabled = 0;
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_DENOISE,
+ &disabled);
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_AGC,
+ &disabled);
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_VAD,
+ &disabled);
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_DEREVERB,
+ &disabled);
+
+ /* Set sampling rate */
+ sampling_rate = clock_rate;
+ speex_echo_ctl(echo->state, SPEEX_ECHO_SET_SAMPLING_RATE,
+ &sampling_rate);
+
+ /* Create temporary frame for echo cancellation */
+ echo->tmp_frame = 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 = pj_pool_zalloc(pool, sizeof(spx_int32_t) *
+ samples_per_frame);
+ PJ_ASSERT_RETURN(echo->residue != NULL, PJ_ENOMEM);
+
+ /* Create internal playback buffers */
+ for (i=0; i<BUF_COUNT; ++i) {
+ echo->frames[i].buf = pj_pool_zalloc(pool, samples_per_frame * 2);
+ PJ_ASSERT_RETURN(echo->frames[i].buf != NULL, PJ_ENOMEM);
+ }
+
+
+ /* 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",
+ clock_rate,
+ samples_per_frame,
+ tail_ms));
+ return PJ_SUCCESS;
+
+}
+
+
+/*
+ * Destroy AEC
+ */
+PJ_DEF(pj_status_t) speex_aec_destroy(void *state )
+{
+ speex_ec *echo = 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;
+ }
+
+ if (echo->preprocess) {
+ speex_preprocess_state_destroy(echo->preprocess);
+ 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.
+ */
+PJ_DEF(pj_status_t) speex_aec_playback(void *state,
+ pj_int16_t *play_frm )
+{
+ speex_ec *echo = 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->frames[0].buf != NULL, PJ_EINVALIDOP);
+
+ pj_lock_acquire(echo->lock);
+
+ /* Check for overflows */
+ if (echo->wpos == echo->rpos) {
+ PJ_LOG(5,(THIS_FILE, "Speex AEC overflow (playback runs faster, "
+ "wpos=%d, rpos=%d)",
+ echo->wpos, echo->rpos));
+ echo->rpos = (echo->wpos - BUF_COUNT/2) % BUF_COUNT;
+ speex_echo_state_reset(echo->state);
+ }
+
+ /* Save fhe frame */
+ pjmedia_copy_samples(echo->frames[echo->wpos].buf,
+ play_frm, echo->samples_per_frame);
+ echo->wpos = (echo->wpos+1) % BUF_COUNT;
+
+ 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 = state;
+ pj_status_t status;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(echo && rec_frm, PJ_EINVAL);
+
+ /* The AEC must be configured to support internal playback buffer */
+ PJ_ASSERT_RETURN(echo->frames[0].buf != NULL, PJ_EINVALIDOP);
+
+ /* Lock mutex */
+ pj_lock_acquire(echo->lock);
+
+
+ /* Check for underflow */
+ if (echo->rpos == echo->wpos) {
+ /* Return frame as it is */
+ pj_lock_release(echo->lock);
+
+ PJ_LOG(5,(THIS_FILE, "Speex AEC underflow (capture runs faster than "
+ "playback, wpos=%d, rpos=%d)",
+ echo->wpos, echo->rpos));
+ echo->rpos = (echo->wpos - BUF_COUNT/2) % BUF_COUNT;
+ speex_echo_state_reset(echo->state);
+
+ return PJ_SUCCESS;
+ }
+
+
+ /* Cancel echo */
+ status = speex_aec_cancel_echo(echo, rec_frm,
+ echo->frames[echo->rpos].buf, options,
+ NULL);
+
+ echo->rpos = (echo->rpos + 1) % BUF_COUNT;
+
+ pj_lock_release(echo->lock);
+ return status;
+}
+
+
+/*
+ * Perform echo cancellation.
+ */
+PJ_DEF(pj_status_t) speex_aec_cancel_echo( void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved )
+{
+ speex_ec *echo = state;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(echo && rec_frm && play_frm && options==0 &&
+ reserved==NULL, PJ_EINVAL);
+
+ /* Cancel echo, put output in temporary buffer */
+ speex_echo_cancel(echo->state, (const spx_int16_t*)rec_frm,
+ (const spx_int16_t*)play_frm,
+ (spx_int16_t*)echo->tmp_frame,
+ echo->residue);
+
+
+ /* Preprocess output */
+ speex_preprocess(echo->preprocess, (spx_int16_t*)echo->tmp_frame,
+ echo->residue);
+
+ /* Copy temporary buffer back to original rec_frm */
+ pjmedia_copy_samples(rec_frm, echo->tmp_frame, echo->samples_per_frame);
+
+ return PJ_SUCCESS;
+
+}
+
diff --git a/pjmedia/src/pjmedia/echo_suppress.c b/pjmedia/src/pjmedia/echo_suppress.c
new file mode 100644
index 00000000..8cc6ab7b
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_suppress.c
@@ -0,0 +1,186 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia/types.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>
+
+
+#define THIS_FILE "echo_suppress.c"
+#define PJMEDIA_ECHO_SUPPRESS_THRESHOLD 20
+
+
+/*
+ * Simple echo suppresor
+ */
+typedef struct echo_supp
+{
+ unsigned threshold;
+ pj_bool_t suppressing;
+ pj_time_val last_signal;
+ unsigned samples_per_frame;
+ unsigned tail_ms;
+} echo_supp;
+
+
+
+/*
+ * Prototypes.
+ */
+PJ_DECL(pj_status_t) echo_supp_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_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(pj_status_t) echo_supp_cancel_echo(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+
+
+
+/*
+ * Create.
+ */
+PJ_DEF(pj_status_t) echo_supp_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ void **p_state )
+{
+ echo_supp *ec;
+
+ PJ_UNUSED_ARG(clock_rate);
+ PJ_UNUSED_ARG(options);
+
+ ec = pj_pool_zalloc(pool, sizeof(struct echo_supp));
+ ec->threshold = PJMEDIA_ECHO_SUPPRESS_THRESHOLD;
+ ec->samples_per_frame = samples_per_frame;
+ ec->tail_ms = tail_ms;
+
+ *p_state = ec;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy.
+ */
+PJ_DEF(pj_status_t) echo_supp_destroy(void *state)
+{
+ PJ_UNUSED_ARG(state);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Let the AEC knows that a frame has been played to the speaker.
+ */
+PJ_DEF(pj_status_t) echo_supp_playback( void *state,
+ pj_int16_t *play_frm )
+{
+ echo_supp *ec = state;
+ pj_bool_t last_suppressing = ec->suppressing;
+ unsigned level;
+
+ level = pjmedia_calc_avg_signal(play_frm, ec->samples_per_frame);
+ level = linear2ulaw(level) ^ 0xff;
+
+ if (level >= ec->threshold) {
+ pj_gettimeofday(&ec->last_signal);
+ ec->suppressing = 1;
+ } else {
+ ec->suppressing = 0;
+ }
+
+ 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 = 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) {
+ pjmedia_zero_samples(rec_frm, ec->samples_per_frame);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Perform echo cancellation.
+ */
+PJ_DEF(pj_status_t) echo_supp_cancel_echo( void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved )
+{
+ echo_supp *ec = state;
+ unsigned level;
+
+ PJ_UNUSED_ARG(options);
+ PJ_UNUSED_ARG(reserved);
+
+ level = pjmedia_calc_avg_signal(play_frm, ec->samples_per_frame);
+ level = linear2ulaw(level) ^ 0xff;
+
+ if (level >= ec->threshold) {
+ pjmedia_zero_samples(rec_frm, ec->samples_per_frame);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c
index 5255eeba..dd08a338 100644
--- a/pjmedia/src/pjmedia/sound_port.c
+++ b/pjmedia/src/pjmedia/sound_port.c
@@ -17,7 +17,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia/sound_port.h>
-#include <pjmedia/aec.h>
+#include <pjmedia/echo.h>
#include <pjmedia/errno.h>
#include <pjmedia/plc.h>
#include <pj/assert.h>
@@ -56,7 +56,7 @@ struct pjmedia_snd_port
pjmedia_port *port;
unsigned options;
- pjmedia_aec *aec;
+ pjmedia_echo_state *ec_state;
unsigned aec_tail_len;
pjmedia_plc *plc;
@@ -115,8 +115,8 @@ static pj_status_t play_cb(/* in */ void *user_data,
if (snd_port->plc)
pjmedia_plc_save(snd_port->plc, output);
- if (snd_port->aec) {
- pjmedia_aec_playback(snd_port->aec, output);
+ if (snd_port->ec_state) {
+ pjmedia_echo_playback(snd_port->ec_state, output);
}
@@ -164,8 +164,8 @@ static pj_status_t rec_cb(/* in */ void *user_data,
return PJ_SUCCESS;
/* Cancel echo */
- if (snd_port->aec) {
- pjmedia_aec_capture(snd_port->aec, input, 0);
+ if (snd_port->ec_state) {
+ pjmedia_echo_capture(snd_port->ec_state, input, 0);
}
frame.buf = (void*)input;
@@ -251,10 +251,10 @@ static pj_status_t start_sound_device( pj_pool_t *pool,
/* Create AEC only when direction is full duplex */
if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
- status = pjmedia_snd_port_set_aec(snd_port, pool, AEC_TAIL);
+ status = pjmedia_snd_port_set_ec_tail(snd_port, pool, AEC_TAIL);
if (status != PJ_SUCCESS) {
PJ_LOG(4,(THIS_FILE, "Unable to create AEC"));
- snd_port->aec = NULL;
+ snd_port->ec_state = NULL;
}
}
@@ -284,9 +284,9 @@ static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port )
}
/* Destroy AEC */
- if (snd_port->aec) {
- pjmedia_aec_destroy(snd_port->aec);
- snd_port->aec = NULL;
+ if (snd_port->ec_state) {
+ pjmedia_echo_destroy(snd_port->ec_state);
+ snd_port->ec_state = NULL;
}
return PJ_SUCCESS;
@@ -432,9 +432,9 @@ PJ_DEF(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
/*
* Enable AEC
*/
-PJ_DEF(pj_status_t) pjmedia_snd_port_set_aec( pjmedia_snd_port *snd_port,
- pj_pool_t *pool,
- unsigned tail_ms)
+PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec_tail(pjmedia_snd_port *snd_port,
+ pj_pool_t *pool,
+ unsigned tail_ms)
{
pj_status_t status;
@@ -444,21 +444,22 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_set_aec( pjmedia_snd_port *snd_port,
PJ_EINVALIDOP);
/* Destroy AEC */
- if (snd_port->aec) {
- pjmedia_aec_destroy(snd_port->aec);
- snd_port->aec = NULL;
+ if (snd_port->ec_state) {
+ pjmedia_echo_destroy(snd_port->ec_state);
+ snd_port->ec_state = NULL;
}
snd_port->aec_tail_len = tail_ms;
if (tail_ms != 0) {
- status = pjmedia_aec_create(pool, snd_port->clock_rate,
+ status = pjmedia_echo_create(pool, snd_port->clock_rate,
snd_port->samples_per_frame,
- tail_ms, 0, &snd_port->aec);
+ tail_ms, 0, &snd_port->ec_state);
if (status != PJ_SUCCESS)
- snd_port->aec = NULL;
+ snd_port->ec_state = NULL;
} else {
- PJ_LOG(4,(THIS_FILE, "AEC disabled in the sound port"));
+ PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the "
+ "sound port"));
status = PJ_SUCCESS;
}
@@ -467,11 +468,11 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_set_aec( pjmedia_snd_port *snd_port,
/* Get AEC tail length */
-PJ_DEF(pj_status_t) pjmedia_snd_port_get_aec_tail( pjmedia_snd_port *snd_port,
- unsigned *p_length)
+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->aec ? snd_port->aec_tail_len : 0;
+ *p_length = snd_port->ec_state ? snd_port->aec_tail_len : 0;
return PJ_SUCCESS;
}