diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-08-06 12:07:13 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-08-06 12:07:13 +0000 |
commit | 7d4e5f795015cc061a65f812c0642cfd2891681e (patch) | |
tree | 2530a16f89b39b79cc46df59913f261c44f54249 /pjmedia | |
parent | e597fd48b9586700656f2a5760153ef65790a44b (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')
-rw-r--r-- | pjmedia/build/Makefile | 5 | ||||
-rw-r--r-- | pjmedia/build/pjmedia.dsp | 40 | ||||
-rw-r--r-- | pjmedia/include/pjmedia.h | 6 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/aec.h | 153 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/config.h | 9 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/echo.h | 186 | ||||
-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.h | 29 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/aec_speex.c | 275 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/echo_common.c | 215 | ||||
-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.c | 321 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/echo_suppress.c | 186 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 49 |
14 files changed, 1036 insertions, 529 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; } |