summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-08-04 11:08:00 +0000
committerBenny Prijono <bennylp@teluu.com>2006-08-04 11:08:00 +0000
commit4a01731c11f29c2042459f37516ec313c4e8a495 (patch)
treed5049d0a6bad525526f93efed107b06097cec7ff
parent60fb5677579381a10db65cf32333fbabdd4a715e (diff)
More experimentation with AEC: (1) added media port to create bidirectional port from two unidirectional ports, (2) split AEC functionality into AEC algorithm (aec.h) and AEC media port (aec_port.h), (3) Added the AEC functionality in the sound_port.c.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@646 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--pjmedia/build/pjmedia.dsp16
-rw-r--r--pjmedia/include/pjmedia.h2
-rw-r--r--pjmedia/include/pjmedia/aec.h153
-rw-r--r--pjmedia/include/pjmedia/bidirectional.h65
-rw-r--r--pjmedia/src/pjmedia/aec_port.c95
-rw-r--r--pjmedia/src/pjmedia/aec_speex.c257
-rw-r--r--pjmedia/src/pjmedia/bidirectional.c75
-rw-r--r--pjmedia/src/pjmedia/sound_port.c43
-rw-r--r--pjsip-apps/build/samples.dsp4
-rw-r--r--pjsip-apps/src/samples/aectest.c221
-rw-r--r--pjsip-apps/src/samples/debug.c2
11 files changed, 868 insertions, 65 deletions
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp
index 62a544d3..6a1a3d0b 100644
--- a/pjmedia/build/pjmedia.dsp
+++ b/pjmedia/build/pjmedia.dsp
@@ -91,10 +91,18 @@ SOURCE=..\src\pjmedia\aec_port.c
# End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\aec_speex.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\alaw_ulaw.c
# End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\bidirectional.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\clock_thread.c
# End Source File
# Begin Source File
@@ -231,10 +239,18 @@ SOURCE=..\src\pjmedia\wave.c
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
+SOURCE=..\include\pjmedia\aec.h
+# End Source File
+# Begin Source File
+
SOURCE=..\include\pjmedia\aec_port.h
# End Source File
# Begin Source File
+SOURCE=..\include\pjmedia\bidirectional.h
+# End Source File
+# Begin Source File
+
SOURCE=..\include\pjmedia\clock.h
# End Source File
# Begin Source File
diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h
index fc79c463..049e36c8 100644
--- a/pjmedia/include/pjmedia.h
+++ b/pjmedia/include/pjmedia.h
@@ -25,7 +25,9 @@
*/
#include <pjmedia/types.h>
+#include <pjmedia/aec.h>
#include <pjmedia/aec_port.h>
+#include <pjmedia/bidirectional.h>
#include <pjmedia/clock.h>
#include <pjmedia/codec.h>
#include <pjmedia/conference.h>
diff --git a/pjmedia/include/pjmedia/aec.h b/pjmedia/include/pjmedia/aec.h
new file mode 100644
index 00000000..a2354ef3
--- /dev/null
+++ b/pjmedia/include/pjmedia/aec.h
@@ -0,0 +1,153 @@
+/* $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
+ */
+#ifndef __PJMEDIA_AEC_H__
+#define __PJMEDIA_AEC_H__
+
+
+/**
+ * @file aec.h
+ * @brief AEC (Accoustic Echo Cancellation) API.
+ */
+#include <pjmedia/types.h>
+
+
+
+/**
+ * @defgroup PJMEDIA_AEC AEC AEC (Accoustic Echo Cancellation)
+ * @ingroup PJMEDIA_PORT
+ * @brief AEC (Accoustic Echo Cancellation) API.
+ * @{
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Opaque type for PJMEDIA AEC.
+ */
+typedef struct pjmedia_aec pjmedia_aec;
+
+
+/**
+ * Create the AEC.
+ *
+ * @param pool Pool to allocate memory.
+ * @param clock_rate Media clock rate/sampling rate.
+ * @param samples_per_frame Number of samples per frame.
+ * @param tail_size Tail length, in number of samples.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_size,
+ unsigned options,
+ pjmedia_aec **p_aec );
+
+
+/**
+ * Destroy the AEC.
+ *
+ * @param aec The AEC.
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_destroy(pjmedia_aec *aec );
+
+
+/**
+ * Let the AEC knows that a frame has been played to the speaker.
+ * The AEC will keep the frame in its internal buffer, to be used
+ * when cancelling the echo with #pjmedia_aec_capture().
+ *
+ * @param aec The AEC.
+ * @param ts Optional timestamp to let the AEC knows the
+ * position of the frame relative to capture
+ * position. If NULL, the AEC assumes that
+ * application will supply the AEC with continuously
+ * increasing timestamp.
+ * @param play_frm Sample buffer containing frame to be played
+ * (or has been played) to the playback device.
+ * The frame must contain exactly samples_per_frame
+ * number of samples.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_playback( pjmedia_aec *aec,
+ pj_int16_t *play_frm );
+
+
+/**
+ * Let the AEC knows that a frame has been captured from the microphone.
+ * The AEC will cancel the echo from the captured signal, using the
+ * internal buffer (supplied by #pjmedia_aec_playback()) as the
+ * FES (Far End Speech) reference.
+ *
+ * @param aec The AEC.
+ * @param rec_frm On input, it contains the input signal (captured
+ * from microphone) which echo is to be removed.
+ * Upon returning this function, this buffer contain
+ * the processed signal with the echo removed.
+ * The frame must contain exactly samples_per_frame
+ * number of samples.
+ * @param options Echo cancellation options, reserved for future use.
+ * Put zero for now.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_capture( pjmedia_aec *aec,
+ pj_int16_t *rec_frm,
+ unsigned options );
+
+
+/**
+ * Perform echo cancellation.
+ *
+ * @param aec The AEC.
+ * @param rec_frm On input, it contains the input signal (captured
+ * from microphone) which echo is to be removed.
+ * Upon returning this function, this buffer contain
+ * the processed signal with the echo removed.
+ * @param play_frm Sample buffer containing frame to be played
+ * (or has been played) to the playback device.
+ * The frame must contain exactly samples_per_frame
+ * number of samples.
+ * @param options Echo cancellation options, reserved for future use.
+ * Put zero for now.
+ * @param reserved Reserved for future use, put NULL for now.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(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 );
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif /* __PJMEDIA_AEC_H__ */
+
diff --git a/pjmedia/include/pjmedia/bidirectional.h b/pjmedia/include/pjmedia/bidirectional.h
new file mode 100644
index 00000000..5b16d4c6
--- /dev/null
+++ b/pjmedia/include/pjmedia/bidirectional.h
@@ -0,0 +1,65 @@
+/* $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
+ */
+#ifndef __PJMEDIA_BIDIRECTIONAL_H__
+#define __PJMEDIA_BIDIRECTIONAL_H__
+
+/**
+ * @file bidirectional.h
+ * @brief Create bidirectional port from two unidirectional ports.
+ */
+#include <pjmedia/port.h>
+
+
+/**
+ * @defgroup PJMEDIA_BIDIRECTIONAL_PORT Bidirectional Port
+ * @ingroup PJMEDIA_PORT
+ * @brief Create bidirectional port from two unidirectional ports.
+ * @{
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Create bidirectional port from two unidirectional ports
+ *
+ * @param pool Pool to allocate memory.
+ * @param get_port Port where get_frame() will be directed to.
+ * @param put_port Port where put_frame() will be directed to.
+ * @param p_port Pointer to receive the port instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_bidirectional_port_create(pj_pool_t *pool,
+ pjmedia_port *get_port,
+ pjmedia_port *put_port,
+ pjmedia_port **p_port );
+
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif /* __PJMEDIA_BIDIRECTIONAL_H__ */
+
diff --git a/pjmedia/src/pjmedia/aec_port.c b/pjmedia/src/pjmedia/aec_port.c
index f772d859..aa56b175 100644
--- a/pjmedia/src/pjmedia/aec_port.c
+++ b/pjmedia/src/pjmedia/aec_port.c
@@ -17,7 +17,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia/aec_port.h>
-#include "../pjmedia-codec/speex/speex_echo.h"
+#include <pjmedia/aec.h>
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/log.h>
@@ -26,16 +26,13 @@
#define THIS_FILE "aec_port.c"
#define SIGNATURE PJMEDIA_PORT_SIGNATURE('A', 'E', 'C', ' ')
+#define BUF_COUNT 32
-
-struct aec_port
+struct aec
{
pjmedia_port base;
pjmedia_port *dn_port;
- SpeexEchoState *state;
- pj_int16_t *tmp_frame;
- pj_bool_t has_frame;
- pj_int16_t *last_frame;
+ pjmedia_aec *aec;
};
@@ -52,54 +49,37 @@ PJ_DEF(pj_status_t) pjmedia_aec_port_create( pj_pool_t *pool,
pjmedia_port **p_port )
{
const pj_str_t AEC = { "AEC", 3 };
- struct aec_port *aec_port;
- int sampling_rate;
+ struct aec *aec;
+ pj_status_t status;
PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL);
PJ_ASSERT_RETURN(dn_port->info.bits_per_sample==16 && tail_length,
PJ_EINVAL);
- /* Create and initialize the port */
- aec_port = pj_pool_zalloc(pool, sizeof(struct aec_port));
+ /* Create the port and the AEC itself */
+ aec = pj_pool_zalloc(pool, sizeof(struct aec));
- pjmedia_port_info_init(&aec_port->base.info, &AEC, SIGNATURE,
+ pjmedia_port_info_init(&aec->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);
- aec_port->state = speex_echo_state_init(dn_port->info.samples_per_frame,
- tail_length);
-
- /* Set sampling rate */
- sampling_rate = 0;
- speex_echo_ctl(aec_port->state, SPEEX_ECHO_GET_SAMPLING_RATE,
- &sampling_rate);
- sampling_rate = dn_port->info.clock_rate;
- speex_echo_ctl(aec_port->state, SPEEX_ECHO_SET_SAMPLING_RATE,
- &sampling_rate);
+ status = pjmedia_aec_create(pool, dn_port->info.clock_rate,
+ dn_port->info.samples_per_frame,
+ tail_length, 0, &aec->aec);
+ if (status != PJ_SUCCESS)
+ return status;
/* More init */
- aec_port->dn_port = dn_port;
- aec_port->base.get_frame = &aec_get_frame;
- aec_port->base.put_frame = &aec_put_frame;
- aec_port->base.on_destroy = &aec_on_destroy;
-
- aec_port->last_frame = pj_pool_zalloc(pool, sizeof(pj_int16_t) *
- dn_port->info.samples_per_frame);
- aec_port->tmp_frame = pj_pool_zalloc(pool, sizeof(pj_int16_t) *
- dn_port->info.samples_per_frame);
+ 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;
/* Done */
- *p_port = &aec_port->base;
-
- PJ_LOG(4,(THIS_FILE, "AEC created for port %.*s, clock_rate=%d, "
- "samples per frame=%d, tail length=%d ms",
- (int)dn_port->info.name.slen,
- dn_port->info.name.ptr,
- dn_port->info.clock_rate,
- dn_port->info.samples_per_frame,
- tail_length * 1000 / dn_port->info.clock_rate));
+ *p_port = &aec->base;
+
return PJ_SUCCESS;
}
@@ -107,58 +87,49 @@ PJ_DEF(pj_status_t) pjmedia_aec_port_create( pj_pool_t *pool,
static pj_status_t aec_put_frame(pjmedia_port *this_port,
const pjmedia_frame *frame)
{
- struct aec_port *aec_port = (struct aec_port*)this_port;
+ struct aec *aec = (struct aec*)this_port;
PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
- if (frame->type == PJMEDIA_FRAME_TYPE_NONE || !aec_port->has_frame) {
- return pjmedia_port_put_frame(aec_port->dn_port, frame);
+ if (frame->type == PJMEDIA_FRAME_TYPE_NONE ) {
+ return pjmedia_port_put_frame(aec->dn_port, frame);
}
PJ_ASSERT_RETURN(frame->size == this_port->info.samples_per_frame * 2,
PJ_EINVAL);
- speex_echo_cancel(aec_port->state,
- (const spx_int16_t*)frame->buf,
- (const spx_int16_t*)aec_port->last_frame,
- (spx_int16_t*)aec_port->tmp_frame,
- NULL);
-
- pjmedia_copy_samples(frame->buf, aec_port->tmp_frame,
- this_port->info.samples_per_frame);
+ pjmedia_aec_capture(aec->aec, frame->buf, 0);
- return pjmedia_port_put_frame(aec_port->dn_port, frame);
+ return pjmedia_port_put_frame(aec->dn_port, frame);
}
static pj_status_t aec_get_frame( pjmedia_port *this_port,
pjmedia_frame *frame)
{
- struct aec_port *aec_port = (struct aec_port*)this_port;
+ struct aec *aec = (struct aec*)this_port;
pj_status_t status;
PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
- status = pjmedia_port_get_frame(aec_port->dn_port, frame);
- if (status==PJ_SUCCESS && frame->type==PJMEDIA_FRAME_TYPE_AUDIO) {
- aec_port->has_frame = PJ_TRUE;
- pjmedia_copy_samples(aec_port->tmp_frame, frame->buf,
- this_port->info.samples_per_frame);
- } else {
- aec_port->has_frame = PJ_FALSE;
+ status = pjmedia_port_get_frame(aec->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);
+
return status;
}
static pj_status_t aec_on_destroy(pjmedia_port *this_port)
{
- struct aec_port *aec_port = (struct aec_port*)this_port;
+ struct aec *aec = (struct aec*)this_port;
PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
- speex_echo_state_destroy(aec_port->state);
+ pjmedia_aec_destroy(aec->aec);
return PJ_SUCCESS;
}
diff --git a/pjmedia/src/pjmedia/aec_speex.c b/pjmedia/src/pjmedia/aec_speex.c
new file mode 100644
index 00000000..76a8dd44
--- /dev/null
+++ b/pjmedia/src/pjmedia/aec_speex.c
@@ -0,0 +1,257 @@
+/* $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>
+
+
+#define THIS_FILE "aec_speex.c"
+#define BUF_COUNT 16
+
+
+struct frame
+{
+ pj_int16_t *buf;
+};
+
+struct pjmedia_aec
+{
+ SpeexEchoState *state;
+ unsigned samples_per_frame;
+ unsigned options;
+ pj_int16_t *tmp_frame;
+
+ 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_size,
+ unsigned options,
+ pjmedia_aec **p_aec )
+{
+ pjmedia_aec *aec;
+ int sampling_rate;
+ unsigned i;
+ pj_status_t status;
+
+ 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,tail_size);
+ if (aec->state == NULL) {
+ 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, sizeof(pj_int16_t) *
+ samples_per_frame);
+ PJ_ASSERT_RETURN(aec->tmp_frame != 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_size * 1000 / clock_rate));
+ 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->state) {
+ speex_echo_state_destroy(aec->state);
+ aec->state = 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 )
+{
+ unsigned level0, level1;
+
+ /* 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, NULL);
+
+#if 0
+ level0 = pjmedia_calc_avg_signal(rec_frm, aec->samples_per_frame);
+ level1 = pjmedia_calc_avg_signal(aec->tmp_frame, aec->samples_per_frame);
+
+ if (level1 < level0) {
+ PJ_LOG(5,(THIS_FILE, "Input signal reduced from %d to %d",
+ level0, level1));
+ }
+#else
+ PJ_UNUSED_ARG(level0);
+ PJ_UNUSED_ARG(level1);
+#endif
+
+ /* 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/bidirectional.c b/pjmedia/src/pjmedia/bidirectional.c
new file mode 100644
index 00000000..7f2cb0ba
--- /dev/null
+++ b/pjmedia/src/pjmedia/bidirectional.c
@@ -0,0 +1,75 @@
+/* $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/bidirectional.h>
+#include <pj/pool.h>
+
+
+#define THIS_FILE "bidirectional.c"
+#define SIGNATURE PJMEDIA_PORT_SIGNATURE('B', 'D', 'I', 'R')
+
+struct bidir_port
+{
+ pjmedia_port base;
+ pjmedia_port *get_port;
+ pjmedia_port *put_port;
+};
+
+
+static pj_status_t put_frame(pjmedia_port *this_port,
+ const pjmedia_frame *frame)
+{
+ struct bidir_port *p = (struct bidir_port*)this_port;
+ return pjmedia_port_put_frame(p->put_port, frame);
+}
+
+
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct bidir_port *p = (struct bidir_port*)this_port;
+ return pjmedia_port_get_frame(p->get_port, frame);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_bidirectional_port_create( pj_pool_t *pool,
+ pjmedia_port *get_port,
+ pjmedia_port *put_port,
+ pjmedia_port **p_port )
+{
+ struct bidir_port *port;
+
+ port = pj_pool_zalloc(pool, sizeof(struct bidir_port));
+
+ pjmedia_port_info_init(&port->base.info, &get_port->info.name, SIGNATURE,
+ get_port->info.clock_rate,
+ get_port->info.channel_count,
+ get_port->info.bits_per_sample,
+ get_port->info.samples_per_frame);
+
+ port->get_port = get_port;
+ port->put_port = put_port;
+
+ port->base.get_frame = &get_frame;
+ port->base.put_frame = &put_frame;
+
+ *p_port = &port->base;
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c
index 08f2232a..ee3e61c9 100644
--- a/pjmedia/src/pjmedia/sound_port.c
+++ b/pjmedia/src/pjmedia/sound_port.c
@@ -17,6 +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/errno.h>
#include <pjmedia/plc.h>
#include <pj/assert.h>
@@ -24,9 +25,16 @@
#include <pj/rand.h>
#include <pj/string.h> /* pj_memset() */
+#ifndef PJMEDIA_SOUND_HAS_AEC
+# define PJMEDIA_SOUND_HAS_AEC 1
+#endif
-//#define SIMULATE_LOST_PCT 20
+#if defined(PJMEDIA_SOUND_HAS_AEC) && PJMEDIA_SOUND_HAS_AEC!=0
+# include <speex/speex_echo.h>
+#endif
+//#define SIMULATE_LOST_PCT 20
+#define AEC_TAIL 500 /* in ms */
#define THIS_FILE "sound_port.c"
@@ -48,6 +56,7 @@ struct pjmedia_snd_port
pjmedia_port *port;
unsigned options;
+ pjmedia_aec *aec;
pjmedia_plc *plc;
unsigned clock_rate;
@@ -105,6 +114,11 @@ 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);
+ }
+
+
return PJ_SUCCESS;
no_frame:
@@ -121,6 +135,10 @@ no_frame:
}
+ if (snd_port->aec) {
+ pjmedia_aec_playback(snd_port->aec, output);
+ }
+
return PJ_SUCCESS;
}
@@ -131,13 +149,18 @@ no_frame:
*/
static pj_status_t rec_cb(/* in */ void *user_data,
/* in */ pj_uint32_t timestamp,
- /* in */ const void *input,
+ /* in */ void *input,
/* in*/ unsigned size)
{
pjmedia_snd_port *snd_port = user_data;
pjmedia_port *port;
pjmedia_frame frame;
+ /* Cancel echo */
+ if (snd_port->aec) {
+ pjmedia_aec_capture(snd_port->aec, input, 0);
+ }
+
/* 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.
@@ -227,6 +250,16 @@ static pj_status_t start_sound_device( pj_pool_t *pool,
snd_port->plc = NULL;
}
+ /* Create AEC only when direction is full duplex */
+ if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ status = pjmedia_aec_create(pool, snd_port->clock_rate,
+ snd_port->samples_per_frame,
+ snd_port->clock_rate * AEC_TAIL / 1000,
+ 0, &snd_port->aec);
+ if (status != PJ_SUCCESS)
+ snd_port->aec = NULL;
+ }
+
/* Start sound stream. */
status = pjmedia_snd_stream_start(snd_port->snd_stream);
if (status != PJ_SUCCESS) {
@@ -252,6 +285,12 @@ static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port )
snd_port->snd_stream = NULL;
}
+ /* Destroy AEC */
+ if (snd_port->aec) {
+ pjmedia_aec_destroy(snd_port->aec);
+ snd_port->aec = NULL;
+ }
+
return PJ_SUCCESS;
}
diff --git a/pjsip-apps/build/samples.dsp b/pjsip-apps/build/samples.dsp
index f479c7bd..1b53137b 100644
--- a/pjsip-apps/build/samples.dsp
+++ b/pjsip-apps/build/samples.dsp
@@ -86,6 +86,10 @@ CFG=samples - Win32 Debug
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
+SOURCE=..\src\samples\aectest.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\samples\confbench.c
# End Source File
# Begin Source File
diff --git a/pjsip-apps/src/samples/aectest.c b/pjsip-apps/src/samples/aectest.c
new file mode 100644
index 00000000..a2770728
--- /dev/null
+++ b/pjsip-apps/src/samples/aectest.c
@@ -0,0 +1,221 @@
+/* $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
+ */
+
+
+/**
+ * \page page_pjmedia_samples_aectest_c Samples: AEC Test (aectest.c)
+ *
+ * Play a file to speaker, run AEC, and record the microphone input
+ * to see if echo is coming.
+ *
+ * This file is pjsip-apps/src/samples/aectest.c
+ *
+ * \includelineno aectest.c
+ */
+#include <pjmedia.h>
+#include <pjlib-util.h> /* pj_getopt */
+#include <pjlib.h>
+
+/* For logging purpose. */
+#define THIS_FILE "playfile.c"
+#define PTIME 20
+
+static const char *desc =
+" FILE \n"
+" \n"
+" aectest.c \n"
+" \n"
+" PURPOSE \n"
+" \n"
+" Test the AEC effectiveness. \n"
+" \n"
+" USAGE \n"
+" \n"
+" aectest INPUT.WAV OUTPUT.WAV \n"
+" \n"
+" INPUT.WAV is the file to be played to the speaker. \n"
+" OUTPUT.WAV is the output file containing recorded signal from the\n"
+" microphone.";
+
+
+static void app_perror(const char *sender, const char *title, pj_status_t st)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(st, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(sender, "%s: %s", title, errmsg));
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *play_port;
+ pjmedia_port *rec_port;
+ pjmedia_port *bidir_port;
+ pjmedia_snd_port *snd;
+ char tmp[10];
+ pj_status_t status;
+
+
+ if (argc != 3) {
+ puts("Error: arguments required");
+ puts(desc);
+ return 1;
+ }
+
+
+ /* Must init PJLIB first: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "wav", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ /* Create file media port from the WAV file */
+ status = pjmedia_wav_player_port_create( pool, /* memory pool */
+ argv[1], /* file to play */
+ PTIME, /* ptime. */
+ 0, /* flags */
+ 0, /* default buffer */
+ &play_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open input WAV file", status);
+ return 1;
+ }
+
+ if (play_port->info.channel_count != 1) {
+ puts("Error: input WAV must have 1 channel audio");
+ return 1;
+ }
+ if (play_port->info.bits_per_sample != 16) {
+ puts("Error: input WAV must be encoded as 16bit PCM");
+ return 1;
+ }
+
+#ifdef PJ_DARWINOS
+ /* Need to force clock rate on MacOS */
+ if (play_port->info.clock_rate != 44100) {
+ pjmedia_port *resample_port;
+
+ status = pjmedia_resample_port_create(pool, play_port, 44100, 0,
+ &resample_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create resampling port", status);
+ return 1;
+ }
+
+ data.play_port = resample_port;
+ }
+#endif
+
+ /* Create WAV output file port */
+ status = pjmedia_wav_writer_port_create(pool, argv[2],
+ play_port->info.clock_rate,
+ play_port->info.channel_count,
+ play_port->info.samples_per_frame,
+ play_port->info.bits_per_sample,
+ 0, 0, &rec_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open output file", status);
+ return 1;
+ }
+
+ /* Create bidirectional port from the WAV ports */
+ pjmedia_bidirectional_port_create(pool, play_port, rec_port, &bidir_port);
+
+ /* Create AEC port */
+ if (0) {
+ pjmedia_aec_port_create(pool, bidir_port,
+ bidir_port->info.clock_rate * 200 / 1000,
+ &bidir_port);
+ }
+
+ /* Create sound device. */
+ status = pjmedia_snd_port_create(pool, -1, -1,
+ play_port->info.clock_rate,
+ play_port->info.channel_count,
+ play_port->info.samples_per_frame,
+ play_port->info.bits_per_sample,
+ 0, &snd);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open sound device", status);
+ return 1;
+ }
+
+
+ /* Connect sound to the port */
+ pjmedia_snd_port_connect(snd, bidir_port);
+
+
+ puts("");
+ printf("Playing %s and recording to %s\n", argv[1], argv[2]);
+ puts("Press <ENTER> to quit");
+
+ fgets(tmp, sizeof(tmp), stdin);
+
+
+ /* Start deinitialization: */
+
+ /* Destroy sound device */
+ status = pjmedia_snd_port_destroy( snd );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Destroy file port(s) */
+ status = pjmedia_port_destroy( play_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ status = pjmedia_port_destroy( rec_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+
+ /* Done. */
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/debug.c b/pjsip-apps/src/samples/debug.c
index b7cc8213..ea31c9a7 100644
--- a/pjsip-apps/src/samples/debug.c
+++ b/pjsip-apps/src/samples/debug.c
@@ -27,5 +27,5 @@
* E.g.:
* #include "playfile.c"
*/
-#include "pjsip-perf.c"
+#include "aectest.c"