summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pjmedia/build/Makefile5
-rw-r--r--pjmedia/build/pjmedia.dsp40
-rw-r--r--pjmedia/include/pjmedia.h6
-rw-r--r--pjmedia/include/pjmedia/aec.h153
-rw-r--r--pjmedia/include/pjmedia/config.h9
-rw-r--r--pjmedia/include/pjmedia/echo.h186
-rw-r--r--pjmedia/include/pjmedia/echo_port.h (renamed from pjmedia/include/pjmedia/aec_port.h)12
-rw-r--r--pjmedia/include/pjmedia/sound_port.h29
-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
-rw-r--r--pjsip-apps/build/Samples.mak1
-rw-r--r--pjsip/docs/PJSUA-TESTING.txt1
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h8
-rw-r--r--pjsip/src/pjsua-lib/pjsua_media.c12
18 files changed, 1048 insertions, 539 deletions
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index f176463f..e3167916 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -65,8 +65,9 @@ export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \
#
export PJMEDIA_SRCDIR = ../src/pjmedia
export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
- aec_port.o alaw_ulaw.o \
- clock_thread.o codec.o conference.o endpoint.o errno.o \
+ alaw_ulaw.o clock_thread.o codec.o conference.o \
+ echo_common.o echo_port.o echo_speex.o \
+ echo_suppress.o endpoint.o errno.o \
g711.o jbuf.o master_port.o mem_capture.o mem_player.o \
null_port.o plc_common.o plc_g711.o \
port.o splitcomb.o resample.o \
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp
index 6a1a3d0b..1f19648f 100644
--- a/pjmedia/build/pjmedia.dsp
+++ b/pjmedia/build/pjmedia.dsp
@@ -87,14 +87,6 @@ LIB32=link.exe -lib
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
-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
@@ -119,6 +111,22 @@ SOURCE=..\src\pjmedia\dsound.c
# End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\echo_common.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\src\pjmedia\echo_port.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\src\pjmedia\echo_speex.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\src\pjmedia\echo_suppress.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\endpoint.c
# End Source File
# Begin Source File
@@ -239,14 +247,6 @@ 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
@@ -271,6 +271,14 @@ SOURCE=..\include\pjmedia\doxygen.h
# End Source File
# Begin Source File
+SOURCE=..\include\pjmedia\echo.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\include\pjmedia\echo_port.h
+# End Source File
+# Begin Source File
+
SOURCE=..\include\pjmedia\endpoint.h
# End Source File
# Begin Source File
diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h
index 049e36c8..233dc889 100644
--- a/pjmedia/include/pjmedia.h
+++ b/pjmedia/include/pjmedia.h
@@ -25,14 +25,14 @@
*/
#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>
-#include <pjmedia/endpoint.h>
+#include <pjmedia/echo.h>
+#include <pjmedia/echo_port.h>
#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
#include <pjmedia/g711.h>
#include <pjmedia/jbuf.h>
#include <pjmedia/master_port.h>
diff --git a/pjmedia/include/pjmedia/aec.h b/pjmedia/include/pjmedia/aec.h
deleted file mode 100644
index 84ed7e0f..00000000
--- a/pjmedia/include/pjmedia/aec.h
+++ /dev/null
@@ -1,153 +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
- */
-#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_ms Tail length, miliseconds.
- *
- * @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_ms,
- 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/config.h b/pjmedia/include/pjmedia/config.h
index 9e53f562..871177ae 100644
--- a/pjmedia/include/pjmedia/config.h
+++ b/pjmedia/include/pjmedia/config.h
@@ -170,6 +170,15 @@
/**
+ * Speex Accoustic Echo Cancellation (AEC).
+ * By default is enabled.
+ */
+#ifndef PJMEDIA_HAS_SPEEX_AEC
+# define PJMEDIA_HAS_SPEEX_AEC 1
+#endif
+
+
+/**
* Support for sending and decoding RTCP port in SDP (RFC 3605).
* Default is yes.
*/
diff --git a/pjmedia/include/pjmedia/echo.h b/pjmedia/include/pjmedia/echo.h
new file mode 100644
index 00000000..205ef4ff
--- /dev/null
+++ b/pjmedia/include/pjmedia/echo.h
@@ -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
+ */
+#ifndef __PJMEDIA_ECHO_H__
+#define __PJMEDIA_ECHO_H__
+
+
+/**
+ * @file echo.h
+ * @brief Echo Cancellation API.
+ */
+#include <pjmedia/types.h>
+
+
+
+/**
+ * @defgroup PJMEDIA_Echo_Cancel Canceller Echo Cancellation
+ * @ingroup PJMEDIA_PORT
+ * @brief Echo Cancellation API.
+ * @{
+ *
+ * This section describes API to perform echo cancellation to audio signal.
+ * There may be multiple echo canceller implementation in PJMEDIA, ranging
+ * from simple echo suppressor to a full Accoustic Echo Canceller/AEC. By
+ * using this API, application should be able to use which EC backend to
+ * use base on the requirement and capability of the platform.
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Opaque type for PJMEDIA Echo Canceller state.
+ */
+typedef struct pjmedia_echo_state pjmedia_echo_state;
+
+
+/**
+ * Echo cancellation options.
+ */
+typedef enum pjmedia_echo_flag
+{
+ /**
+ * 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.
+ */
+ PJMEDIA_ECHO_SIMPLE = 1,
+
+ /**
+ * 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_flag;
+
+
+
+
+/**
+ * Create the echo canceller.
+ *
+ * @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_ms Tail length, miliseconds.
+ * @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_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ pjmedia_echo_state **p_echo );
+
+
+/**
+ * Destroy the Echo Canceller.
+ *
+ * @param echo The Echo Canceller.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+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.
+ * The Echo Canceller will keep the frame in its internal buffer, to be used
+ * when cancelling the echo with #pjmedia_echo_capture().
+ *
+ * @param echo The Echo Canceller.
+ * @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_echo_playback(pjmedia_echo_state *echo,
+ pj_int16_t *play_frm );
+
+
+/**
+ * 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())
+ * as the FES (Far End Speech) reference.
+ *
+ * @param echo The Echo Canceller.
+ * @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_echo_capture(pjmedia_echo_state *echo,
+ pj_int16_t *rec_frm,
+ unsigned options );
+
+
+/**
+ * Perform echo cancellation.
+ *
+ * @param echo The Echo Canceller.
+ * @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_echo_cancel( pjmedia_echo_state *echo,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif /* __PJMEDIA_ECHO_H__ */
+
diff --git a/pjmedia/include/pjmedia/aec_port.h b/pjmedia/include/pjmedia/echo_port.h
index 2968c915..97abdc1f 100644
--- a/pjmedia/include/pjmedia/aec_port.h
+++ b/pjmedia/include/pjmedia/echo_port.h
@@ -28,9 +28,11 @@
/**
- * @defgroup PJMEDIA_AEC_PORT AEC Port
+ * @defgroup PJMEDIA_ECHO_PORT Echo Cancellation Port
* @ingroup PJMEDIA_PORT
- * @brief AEC (Accoustic Echo Cancellation) media port.
+ * @brief Echo Cancellation Port
+ *
+ * Echo canceller media port, using @ref PJMEDIA_Echo_Cancel backend.
* @{
*/
@@ -39,18 +41,20 @@ PJ_BEGIN_DECL
/**
- * Create AEC port.
+ * Create echo canceller port.
*
* @param pool Pool to allocate memory.
* @param dn_port Downstream port.
* @param tail_ms Tail length in miliseconds.
+ * @param options Options, as in #pjmedia_echo_create().
* @param p_port Pointer to receive the port instance.
*
* @return PJ_SUCCESS on success.
*/
-PJ_DECL(pj_status_t) pjmedia_aec_port_create( pj_pool_t *pool,
+PJ_DECL(pj_status_t) pjmedia_echo_port_create(pj_pool_t *pool,
pjmedia_port *dn_port,
unsigned tail_ms,
+ unsigned options,
pjmedia_port **p_port );
diff --git a/pjmedia/include/pjmedia/sound_port.h b/pjmedia/include/pjmedia/sound_port.h
index c144275d..7653c35c 100644
--- a/pjmedia/include/pjmedia/sound_port.h
+++ b/pjmedia/include/pjmedia/sound_port.h
@@ -183,36 +183,39 @@ PJ_DECL(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream(
/**
- * Enable accoustic echo cancellation (AEC) to the specified sound.
- * The AEC can only be enabled for sound streams with full-duplex direction.
+ * Configure the echo cancellation tail length. By default, echo canceller
+ * is enabled in the sound device with the default tail length. After the
+ * sound port is created, application can query the current echo canceller
+ * tail length by calling #pjmedia_snd_port_get_ec_tail.
*
- * And note, you should only change the AEC settings when the sound port
- * is not connected to any downstream ports.
+ * Note that you should only change the EC settings when the sound port
+ * is not connected to any downstream ports, otherwise race condition may
+ * occur.
*
* @param snd_port The sound device port.
- * @param pool Pool to re-create the AEC if necessary.
+ * @param pool Pool to re-create the echo canceller if necessary.
* @param tail_ms Maximum echo tail length to be supported, in
- * miliseconds. If zero is specified, the AEC would
+ * miliseconds. If zero is specified, the EC would
* be disabled.
*
* @return PJ_SUCCESS on success.
*/
-PJ_DECL(pj_status_t) pjmedia_snd_port_set_aec(pjmedia_snd_port *snd_port,
- pj_pool_t *pool,
- unsigned tail_ms);
+PJ_DECL(pj_status_t) pjmedia_snd_port_set_ec_tail(pjmedia_snd_port *snd_port,
+ pj_pool_t *pool,
+ unsigned tail_ms);
/**
- * Get current AEC tail length, in miliseconds. The tail length will be zero
- * if AEC is not enabled.
+ * Get current echo canceller tail length, in miliseconds. The tail length
+ * will be zero if EC is not enabled.
*
* @param snd_port The sound device port.
* @param p_length Pointer to receive the tail length.
*
* @return PJ_SUCCESS on success.
*/
-PJ_DECL(pj_status_t) pjmedia_snd_port_get_aec_tail(pjmedia_snd_port *snd_port,
- unsigned *p_length);
+PJ_DECL(pj_status_t) pjmedia_snd_port_get_ec_tail(pjmedia_snd_port *snd_port,
+ unsigned *p_length);
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;
}
diff --git a/pjsip-apps/build/Samples.mak b/pjsip-apps/build/Samples.mak
index 35767918..e4d92eb8 100644
--- a/pjsip-apps/build/Samples.mak
+++ b/pjsip-apps/build/Samples.mak
@@ -26,6 +26,7 @@ export LIBS := $(subst /,$(HOST_PSEP),$(PJSUA_LIB_LIB)) \
$(subst /,$(HOST_PSEP),$(PJSIP_LIB)) \
$(subst /,$(HOST_PSEP),$(PJMEDIA_CODEC_LIB)) \
$(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \
+ $(subst /,$(HOST_PSEP),$(PJMEDIA_CODEC_LIB)) \
$(subst /,$(HOST_PSEP),$(PJLIB_UTIL_LIB)) \
$(subst /,$(HOST_PSEP),$(PJLIB_LIB))
diff --git a/pjsip/docs/PJSUA-TESTING.txt b/pjsip/docs/PJSUA-TESTING.txt
index c1fdd645..72fb797e 100644
--- a/pjsip/docs/PJSUA-TESTING.txt
+++ b/pjsip/docs/PJSUA-TESTING.txt
@@ -29,6 +29,7 @@ COMPATIBILITY WITH FWD
MULTIPLE ACCOUNTS (combo.cfg)
+DIGEST with qop=auth (sipcenter?)
AUDIO QUALITY
- call to another pjsua that loops incoming call
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 4ee7aba6..da921c44 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -2502,25 +2502,25 @@ PJ_DECL(pjmedia_port*) pjsua_set_no_snd_dev(void);
/**
- * Configure the AEC settings of the sound port.
+ * Configure the echo canceller tail length of the sound port.
*
* @param tail_ms The tail length, in miliseconds. Set to zero to
* disable AEC.
*
* @return PJ_SUCCESS on success.
*/
-PJ_DECL(pj_status_t) pjsua_set_aec(unsigned tail_ms);
+PJ_DECL(pj_status_t) pjsua_set_ec_tail(unsigned tail_ms);
/**
- * Get current AEC tail length.
+ * Get current echo canceller tail length.
*
* @param p_tail_ms Pointer to receive the tail length, in miliseconds.
* If AEC is disabled, the value will be zero.
*
* @return PJ_SUCCESS on success.
*/
-PJ_DECL(pj_status_t) pjsua_get_aec(unsigned *p_tail_ms);
+PJ_DECL(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms);
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index de21e788..d3c3eb2e 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -969,8 +969,8 @@ PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
pj_assert(conf_port != NULL);
/* Set AEC */
- pjmedia_snd_port_set_aec(pjsua_var.snd_port, pjsua_var.pool,
- pjsua_var.media_cfg.ec_tail_len);
+ pjmedia_snd_port_set_ec_tail(pjsua_var.snd_port, pjsua_var.pool,
+ pjsua_var.media_cfg.ec_tail_len);
/* Connect sound port to the bridge */
status = pjmedia_snd_port_connect(pjsua_var.snd_port,
@@ -1044,13 +1044,13 @@ PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
/*
* Configure the AEC settings of the sound port.
*/
-PJ_DEF(pj_status_t) pjsua_set_aec(unsigned tail_ms)
+PJ_DEF(pj_status_t) pjsua_set_ec_tail(unsigned tail_ms)
{
pjsua_var.media_cfg.ec_tail_len = tail_ms;
if (pjsua_var.snd_port)
- return pjmedia_snd_port_set_aec(pjsua_var.snd_port, pjsua_var.pool,
- tail_ms);
+ return pjmedia_snd_port_set_ec_tail(pjsua_var.snd_port, pjsua_var.pool,
+ tail_ms);
return PJ_SUCCESS;
}
@@ -1059,7 +1059,7 @@ PJ_DEF(pj_status_t) pjsua_set_aec(unsigned tail_ms)
/*
* Get current AEC tail length.
*/
-PJ_DEF(pj_status_t) pjsua_get_aec(unsigned *p_tail_ms)
+PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
{
*p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
return PJ_SUCCESS;