Change AEC into generic echo canceller framework with either AEC or simple echo suppressor backend can be selected during runtime.



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@653 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index f176463..e316791 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -65,8 +65,9 @@
 #
 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 6a1a3d0..1f19648 100644
--- a/pjmedia/build/pjmedia.dsp
+++ b/pjmedia/build/pjmedia.dsp
@@ -87,14 +87,6 @@
 # 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 @@
 # 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 @@
 # 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 @@
 # 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 049e36c..233dc88 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 84ed7e0..0000000
--- 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 9e53f56..871177a 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 0000000..205ef4f
--- /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
similarity index 80%
rename from pjmedia/include/pjmedia/aec_port.h
rename to pjmedia/include/pjmedia/echo_port.h
index 2968c91..97abdc1 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 @@
 
 
 /**
- * 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 c144275..7653c35 100644
--- a/pjmedia/include/pjmedia/sound_port.h
+++ b/pjmedia/include/pjmedia/sound_port.h
@@ -183,36 +183,39 @@
 
 
 /**
- * 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_port.c b/pjmedia/src/pjmedia/aec_port.c
deleted file mode 100644
index 70d3a21..0000000
--- a/pjmedia/src/pjmedia/aec_port.c
+++ /dev/null
@@ -1,137 +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_port.h>
-#include <pjmedia/aec.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 BUF_COUNT   32
-
-struct aec
-{
-    pjmedia_port     base;
-    pjmedia_port    *dn_port;
-    pjmedia_aec	    *aec;
-};
-
-
-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);
-
-
-PJ_DEF(pj_status_t) pjmedia_aec_port_create( pj_pool_t *pool,
-					     pjmedia_port *dn_port,
-					     unsigned tail_ms,
-					     pjmedia_port **p_port )
-{
-    const pj_str_t AEC = { "AEC", 3 };
-    struct aec *aec;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL);
-    PJ_ASSERT_RETURN(dn_port->info.bits_per_sample==16 && tail_ms, 
-		     PJ_EINVAL);
-
-    /* Create the port and the AEC itself */
-    aec = pj_pool_zalloc(pool, sizeof(struct aec));
-    
-    pjmedia_port_info_init(&aec->base.info, &AEC, SIGNATURE,
-			   dn_port->info.clock_rate, 
-			   dn_port->info.channel_count, 
-			   dn_port->info.bits_per_sample,
-			   dn_port->info.samples_per_frame);
-
-    status = pjmedia_aec_create(pool, dn_port->info.clock_rate, 
-				dn_port->info.samples_per_frame,
-				tail_ms, 0, &aec->aec);
-    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;
-
-    /* Done */
-    *p_port = &aec->base;
-
-    return PJ_SUCCESS;
-}
-
-
-static pj_status_t aec_put_frame(pjmedia_port *this_port, 
-				 const pjmedia_frame *frame)
-{
-    struct aec *aec = (struct aec*)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);
-    }
-
-    PJ_ASSERT_RETURN(frame->size == this_port->info.samples_per_frame * 2,
-		     PJ_EINVAL);
-
-    pjmedia_aec_capture(aec->aec, frame->buf, 0);
-
-    return pjmedia_port_put_frame(aec->dn_port, frame);
-}
-
-
-static pj_status_t aec_get_frame( pjmedia_port *this_port, 
-				  pjmedia_frame *frame)
-{
-    struct aec *aec = (struct aec*)this_port;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
-
-    status = pjmedia_port_get_frame(aec->dn_port, frame);
-    if (status!=PJ_SUCCESS || frame->type!=PJMEDIA_FRAME_TYPE_AUDIO) {
-	pjmedia_zero_samples(frame->buf, this_port->info.samples_per_frame);
-    }
-
-    pjmedia_aec_playback(aec->aec, frame->buf);
-
-    return status;
-}
-
-
-static pj_status_t aec_on_destroy(pjmedia_port *this_port)
-{
-    struct aec *aec = (struct aec*)this_port;
-
-    PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
-
-    pjmedia_aec_destroy(aec->aec);
-
-    return PJ_SUCCESS;
-}
-
-
diff --git a/pjmedia/src/pjmedia/aec_speex.c b/pjmedia/src/pjmedia/aec_speex.c
deleted file mode 100644
index 95cc15b..0000000
--- 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 0000000..b84fa95
--- /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/echo_port.c b/pjmedia/src/pjmedia/echo_port.c
new file mode 100644
index 0000000..82fbca1
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_port.c
@@ -0,0 +1,138 @@
+/* $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_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   "ec_port.c"
+#define SIGNATURE   PJMEDIA_PORT_SIGNATURE('E', 'C', 'H', 'O')
+#define BUF_COUNT   32
+
+struct ec
+{
+    pjmedia_port	 base;
+    pjmedia_port	*dn_port;
+    pjmedia_echo_state	*ec;
+};
+
+
+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_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 = { "EC", 2 };
+    struct ec *ec;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL);
+    PJ_ASSERT_RETURN(dn_port->info.bits_per_sample==16 && tail_ms, 
+		     PJ_EINVAL);
+
+    /* Create the port and the AEC itself */
+    ec = pj_pool_zalloc(pool, sizeof(struct ec));
+    
+    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_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 */
+    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 = &ec->base;
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_status_t ec_put_frame( pjmedia_port *this_port, 
+				 const pjmedia_frame *frame)
+{
+    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(ec->dn_port, frame);
+    }
+
+    PJ_ASSERT_RETURN(frame->size == this_port->info.samples_per_frame * 2,
+		     PJ_EINVAL);
+
+    pjmedia_echo_capture(ec->ec, frame->buf, 0);
+
+    return pjmedia_port_put_frame(ec->dn_port, frame);
+}
+
+
+static pj_status_t ec_get_frame( pjmedia_port *this_port, 
+				 pjmedia_frame *frame)
+{
+    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(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_echo_playback(ec->ec, frame->buf);
+
+    return status;
+}
+
+
+static pj_status_t ec_on_destroy(pjmedia_port *this_port)
+{
+    struct ec *ec = (struct ec*)this_port;
+
+    PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
+
+    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 0000000..bf8f3f7
--- /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 0000000..8cc6ab7
--- /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 5255eeb..dd08a33 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 @@
     pjmedia_port	*port;
     unsigned		 options;
 
-    pjmedia_aec		*aec;
+    pjmedia_echo_state	*ec_state;
     unsigned		 aec_tail_len;
     pjmedia_plc		*plc;
 
@@ -115,8 +115,8 @@
     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 @@
 	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 @@
 
     /* 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 @@
     }
 
     /* 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 @@
 /*
  * 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_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 @@
 
 
 /* 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;
 }