Index: /pjproject/trunk/pjmedia/build/pjmedia.dsp
===================================================================
--- /pjproject/trunk/pjmedia/build/pjmedia.dsp	(revision 645)
+++ /pjproject/trunk/pjmedia/build/pjmedia.dsp	(revision 646)
@@ -92,5 +92,13 @@
 # Begin Source File
 
+SOURCE=..\src\pjmedia\aec_speex.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\pjmedia\alaw_ulaw.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\src\pjmedia\bidirectional.c
 # End Source File
 # Begin Source File
@@ -232,5 +240,13 @@
 # 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
Index: /pjproject/trunk/pjmedia/include/pjmedia.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia.h	(revision 645)
+++ /pjproject/trunk/pjmedia/include/pjmedia.h	(revision 646)
@@ -26,5 +26,7 @@
 
 #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>
Index: /pjproject/trunk/pjmedia/include/pjmedia/aec.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/aec.h	(revision 646)
+++ /pjproject/trunk/pjmedia/include/pjmedia/aec.h	(revision 646)
@@ -0,0 +1,153 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJMEDIA_AEC_H__
+#define __PJMEDIA_AEC_H__
+
+
+/**
+ * @file aec.h
+ * @brief AEC (Accoustic Echo Cancellation) API.
+ */
+#include <pjmedia/types.h>
+
+
+
+/**
+ * @defgroup PJMEDIA_AEC AEC AEC (Accoustic Echo Cancellation)
+ * @ingroup PJMEDIA_PORT
+ * @brief AEC (Accoustic Echo Cancellation) API.
+ * @{
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Opaque type for PJMEDIA AEC.
+ */
+typedef struct pjmedia_aec pjmedia_aec;
+
+
+/**
+ * Create the AEC. 
+ *
+ * @param pool		    Pool to allocate memory.
+ * @param clock_rate	    Media clock rate/sampling rate.
+ * @param samples_per_frame Number of samples per frame.
+ * @param tail_size	    Tail length, in number of samples.
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_create( pj_pool_t *pool,
+					 unsigned clock_rate,
+					 unsigned samples_per_frame,
+					 unsigned tail_size,
+					 unsigned options,
+					 pjmedia_aec **p_aec );
+
+
+/**
+ * Destroy the AEC. 
+ *
+ * @param aec		The AEC.
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_destroy(pjmedia_aec *aec );
+
+
+/**
+ * Let the AEC knows that a frame has been played to the speaker.
+ * The AEC will keep the frame in its internal buffer, to be used
+ * when cancelling the echo with #pjmedia_aec_capture().
+ *
+ * @param aec		The AEC.
+ * @param ts		Optional timestamp to let the AEC knows the
+ *			position of the frame relative to capture
+ *			position. If NULL, the AEC assumes that
+ *			application will supply the AEC with continuously
+ *			increasing timestamp.
+ * @param play_frm	Sample buffer containing frame to be played
+ *			(or has been played) to the playback device.
+ *			The frame must contain exactly samples_per_frame 
+ *			number of samples.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_playback( pjmedia_aec *aec,
+					   pj_int16_t *play_frm );
+
+
+/**
+ * Let the AEC knows that a frame has been captured from the microphone.
+ * The AEC will cancel the echo from the captured signal, using the
+ * internal buffer (supplied by #pjmedia_aec_playback()) as the
+ * FES (Far End Speech) reference.
+ *
+ * @param aec		The AEC.
+ * @param rec_frm	On input, it contains the input signal (captured 
+ *			from microphone) which echo is to be removed.
+ *			Upon returning this function, this buffer contain
+ *			the processed signal with the echo removed.
+ *			The frame must contain exactly samples_per_frame 
+ *			number of samples.
+ * @param options	Echo cancellation options, reserved for future use.
+ *			Put zero for now.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_capture( pjmedia_aec *aec,
+					  pj_int16_t *rec_frm,
+					  unsigned options );
+
+
+/**
+ * Perform echo cancellation.
+ *
+ * @param aec		The AEC.
+ * @param rec_frm	On input, it contains the input signal (captured 
+ *			from microphone) which echo is to be removed.
+ *			Upon returning this function, this buffer contain
+ *			the processed signal with the echo removed.
+ * @param play_frm	Sample buffer containing frame to be played
+ *			(or has been played) to the playback device.
+ *			The frame must contain exactly samples_per_frame 
+ *			number of samples.
+ * @param options	Echo cancellation options, reserved for future use.
+ *			Put zero for now.
+ * @param reserved	Reserved for future use, put NULL for now.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_aec_cancel_echo( pjmedia_aec *aec,
+					      pj_int16_t *rec_frm,
+					      const pj_int16_t *play_frm,
+					      unsigned options,
+					      void *reserved );
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif	/* __PJMEDIA_AEC_H__ */
+
Index: /pjproject/trunk/pjmedia/include/pjmedia/bidirectional.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/bidirectional.h	(revision 646)
+++ /pjproject/trunk/pjmedia/include/pjmedia/bidirectional.h	(revision 646)
@@ -0,0 +1,65 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJMEDIA_BIDIRECTIONAL_H__
+#define __PJMEDIA_BIDIRECTIONAL_H__
+
+/**
+ * @file bidirectional.h
+ * @brief Create bidirectional port from two unidirectional ports.
+ */
+#include <pjmedia/port.h>
+
+
+/**
+ * @defgroup PJMEDIA_BIDIRECTIONAL_PORT Bidirectional Port
+ * @ingroup PJMEDIA_PORT
+ * @brief Create bidirectional port from two unidirectional ports.
+ * @{
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Create bidirectional port from two unidirectional ports
+ *
+ * @param pool		Pool to allocate memory.
+ * @param get_port	Port where get_frame() will be directed to.
+ * @param put_port	Port where put_frame() will be directed to.
+ * @param p_port	Pointer to receive the port instance.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_bidirectional_port_create(pj_pool_t *pool,
+						       pjmedia_port *get_port,
+						       pjmedia_port *put_port,
+						       pjmedia_port **p_port );
+
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif	/* __PJMEDIA_BIDIRECTIONAL_H__ */
+
Index: /pjproject/trunk/pjmedia/src/pjmedia/aec_port.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/aec_port.c	(revision 645)
+++ /pjproject/trunk/pjmedia/src/pjmedia/aec_port.c	(revision 646)
@@ -18,5 +18,5 @@
  */
 #include <pjmedia/aec_port.h>
-#include "../pjmedia-codec/speex/speex_echo.h"
+#include <pjmedia/aec.h>
 #include <pjmedia/errno.h>
 #include <pj/assert.h>
@@ -27,14 +27,11 @@
 #define THIS_FILE   "aec_port.c"
 #define SIGNATURE   PJMEDIA_PORT_SIGNATURE('A', 'E', 'C', ' ')
+#define BUF_COUNT   32
 
-
-struct aec_port
+struct aec
 {
     pjmedia_port     base;
     pjmedia_port    *dn_port;
-    SpeexEchoState  *state;
-    pj_int16_t	    *tmp_frame;
-    pj_bool_t	     has_frame;
-    pj_int16_t	    *last_frame;
+    pjmedia_aec	    *aec;
 };
 
@@ -53,6 +50,6 @@
 {
     const pj_str_t AEC = { "AEC", 3 };
-    struct aec_port *aec_port;
-    int sampling_rate;
+    struct aec *aec;
+    pj_status_t status;
 
     PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL);
@@ -60,8 +57,8 @@
 		     PJ_EINVAL);
 
-    /* Create and initialize the port */
-    aec_port = pj_pool_zalloc(pool, sizeof(struct aec_port));
+    /* Create the port and the AEC itself */
+    aec = pj_pool_zalloc(pool, sizeof(struct aec));
     
-    pjmedia_port_info_init(&aec_port->base.info, &AEC, SIGNATURE,
+    pjmedia_port_info_init(&aec->base.info, &AEC, SIGNATURE,
 			   dn_port->info.clock_rate, 
 			   dn_port->info.channel_count, 
@@ -69,36 +66,19 @@
 			   dn_port->info.samples_per_frame);
 
-    aec_port->state = speex_echo_state_init(dn_port->info.samples_per_frame,
-					    tail_length);
-
-    /* Set sampling rate */
-    sampling_rate = 0;
-    speex_echo_ctl(aec_port->state, SPEEX_ECHO_GET_SAMPLING_RATE, 
-		   &sampling_rate);
-    sampling_rate = dn_port->info.clock_rate;
-    speex_echo_ctl(aec_port->state, SPEEX_ECHO_SET_SAMPLING_RATE, 
-		   &sampling_rate);
+    status = pjmedia_aec_create(pool, dn_port->info.clock_rate, 
+				dn_port->info.samples_per_frame,
+				tail_length, 0, &aec->aec);
+    if (status != PJ_SUCCESS)
+	return status;
 
     /* More init */
-    aec_port->dn_port = dn_port;
-    aec_port->base.get_frame = &aec_get_frame;
-    aec_port->base.put_frame = &aec_put_frame;
-    aec_port->base.on_destroy = &aec_on_destroy;
-
-    aec_port->last_frame = pj_pool_zalloc(pool, sizeof(pj_int16_t) *
-					    dn_port->info.samples_per_frame);
-    aec_port->tmp_frame = pj_pool_zalloc(pool, sizeof(pj_int16_t) *
-					    dn_port->info.samples_per_frame);
+    aec->dn_port = dn_port;
+    aec->base.get_frame = &aec_get_frame;
+    aec->base.put_frame = &aec_put_frame;
+    aec->base.on_destroy = &aec_on_destroy;
 
     /* Done */
-    *p_port = &aec_port->base;
+    *p_port = &aec->base;
 
-    PJ_LOG(4,(THIS_FILE, "AEC created for port %.*s, clock_rate=%d, "
-			 "samples per frame=%d, tail length=%d ms", 
-			 (int)dn_port->info.name.slen,
-			 dn_port->info.name.ptr,
-			 dn_port->info.clock_rate,
-			 dn_port->info.samples_per_frame,
-			 tail_length * 1000 / dn_port->info.clock_rate));
     return PJ_SUCCESS;
 }
@@ -108,10 +88,10 @@
 				 const pjmedia_frame *frame)
 {
-    struct aec_port *aec_port = (struct aec_port*)this_port;
+    struct aec *aec = (struct aec*)this_port;
 
     PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
 
-    if (frame->type == PJMEDIA_FRAME_TYPE_NONE || !aec_port->has_frame) {
-	return pjmedia_port_put_frame(aec_port->dn_port, frame);
+    if (frame->type == PJMEDIA_FRAME_TYPE_NONE ) {
+	return pjmedia_port_put_frame(aec->dn_port, frame);
     }
 
@@ -119,14 +99,7 @@
 		     PJ_EINVAL);
 
-    speex_echo_cancel(aec_port->state, 
-		      (const spx_int16_t*)frame->buf, 
-		      (const spx_int16_t*)aec_port->last_frame,
-		      (spx_int16_t*)aec_port->tmp_frame, 
-		      NULL);
+    pjmedia_aec_capture(aec->aec, frame->buf, 0);
 
-    pjmedia_copy_samples(frame->buf, aec_port->tmp_frame,
-			 this_port->info.samples_per_frame);
-
-    return pjmedia_port_put_frame(aec_port->dn_port, frame);
+    return pjmedia_port_put_frame(aec->dn_port, frame);
 }
 
@@ -135,17 +108,15 @@
 				  pjmedia_frame *frame)
 {
-    struct aec_port *aec_port = (struct aec_port*)this_port;
+    struct aec *aec = (struct aec*)this_port;
     pj_status_t status;
 
     PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
 
-    status = pjmedia_port_get_frame(aec_port->dn_port, frame);
-    if (status==PJ_SUCCESS && frame->type==PJMEDIA_FRAME_TYPE_AUDIO) {
-	aec_port->has_frame = PJ_TRUE;
-	pjmedia_copy_samples(aec_port->tmp_frame, frame->buf,
-			     this_port->info.samples_per_frame);
-    } else {
-	aec_port->has_frame = PJ_FALSE;	
+    status = pjmedia_port_get_frame(aec->dn_port, frame);
+    if (status!=PJ_SUCCESS || frame->type!=PJMEDIA_FRAME_TYPE_AUDIO) {
+	pjmedia_zero_samples(frame->buf, this_port->info.samples_per_frame);
     }
+
+    pjmedia_aec_playback(aec->aec, frame->buf);
 
     return status;
@@ -155,9 +126,9 @@
 static pj_status_t aec_on_destroy(pjmedia_port *this_port)
 {
-    struct aec_port *aec_port = (struct aec_port*)this_port;
+    struct aec *aec = (struct aec*)this_port;
 
     PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
 
-    speex_echo_state_destroy(aec_port->state);
+    pjmedia_aec_destroy(aec->aec);
 
     return PJ_SUCCESS;
Index: /pjproject/trunk/pjmedia/src/pjmedia/aec_speex.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/aec_speex.c	(revision 646)
+++ /pjproject/trunk/pjmedia/src/pjmedia/aec_speex.c	(revision 646)
@@ -0,0 +1,257 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+
+#include <pjmedia/aec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <speex/speex_echo.h>
+
+
+#define THIS_FILE   "aec_speex.c"
+#define BUF_COUNT   16
+
+
+struct frame
+{
+    pj_int16_t	   *buf;
+};
+
+struct pjmedia_aec
+{
+    SpeexEchoState  *state;
+    unsigned	     samples_per_frame;
+    unsigned	     options;
+    pj_int16_t	    *tmp_frame;
+
+    pj_lock_t	    *lock;		/* To protect buffers, if required  */
+
+    unsigned	     rpos;		/* Index to get oldest frame.	    */
+    unsigned	     wpos;		/* Index to put newest frame.	    */
+    struct frame     frames[BUF_COUNT];	/* Playback frame buffers.	    */
+};
+
+
+
+/*
+ * Create the AEC. 
+ */
+PJ_DEF(pj_status_t) pjmedia_aec_create( pj_pool_t *pool,
+					unsigned clock_rate,
+					unsigned samples_per_frame,
+					unsigned tail_size,
+					unsigned options,
+					pjmedia_aec **p_aec )
+{
+    pjmedia_aec *aec;
+    int sampling_rate;
+    unsigned i;
+    pj_status_t status;
+
+    aec = pj_pool_zalloc(pool, sizeof(pjmedia_aec));
+    PJ_ASSERT_RETURN(aec != NULL, PJ_ENOMEM);
+
+    status = pj_lock_create_simple_mutex(pool, "aec%p", &aec->lock);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    aec->samples_per_frame = samples_per_frame;
+    aec->options = options;
+
+    aec->state = speex_echo_state_init(samples_per_frame,tail_size);
+    if (aec->state == NULL) {
+	pj_lock_destroy(aec->lock);
+	return PJ_ENOMEM;
+    }
+
+    /* Set sampling rate */
+    sampling_rate = clock_rate;
+    speex_echo_ctl(aec->state, SPEEX_ECHO_SET_SAMPLING_RATE, 
+		   &sampling_rate);
+
+    /* Create temporary frame for echo cancellation */
+    aec->tmp_frame = pj_pool_zalloc(pool, sizeof(pj_int16_t) *
+					    samples_per_frame);
+    PJ_ASSERT_RETURN(aec->tmp_frame != NULL, PJ_ENOMEM);
+
+    /* Create internal playback buffers */
+    for (i=0; i<BUF_COUNT; ++i) {
+	aec->frames[i].buf = pj_pool_zalloc(pool, samples_per_frame * 2);
+	PJ_ASSERT_RETURN(aec->frames[i].buf != NULL, PJ_ENOMEM);
+    }
+
+
+    /* Done */
+    *p_aec = aec;
+
+    PJ_LOG(4,(THIS_FILE, "Echo canceller/AEC created, clock_rate=%d, "
+			 "samples per frame=%d, tail length=%d ms", 
+			 clock_rate,
+			 samples_per_frame,
+			 tail_size * 1000 / clock_rate));
+    return PJ_SUCCESS;
+
+}
+
+
+/*
+ * Destroy AEC
+ */
+PJ_DEF(pj_status_t) pjmedia_aec_destroy(pjmedia_aec *aec )
+{
+    PJ_ASSERT_RETURN(aec && aec->state, PJ_EINVAL);
+
+    if (aec->state) {
+	speex_echo_state_destroy(aec->state);
+	aec->state = NULL;
+    }
+
+    if (aec->lock) {
+	pj_lock_destroy(aec->lock);
+	aec->lock = NULL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Let the AEC knows that a frame has been played to the speaker.
+ */
+PJ_DEF(pj_status_t) pjmedia_aec_playback(pjmedia_aec *aec,
+					 pj_int16_t *play_frm )
+{
+    /* Sanity checks */
+    PJ_ASSERT_RETURN(aec && play_frm, PJ_EINVAL);
+
+    /* The AEC must be configured to support internal playback buffer */
+    PJ_ASSERT_RETURN(aec->frames[0].buf != NULL, PJ_EINVALIDOP);
+
+    pj_lock_acquire(aec->lock);
+
+    /* Check for overflows */
+    if (aec->wpos == aec->rpos) {
+	PJ_LOG(5,(THIS_FILE, "AEC overflow (playback runs faster, "
+			     "wpos=%d, rpos=%d)",
+			     aec->wpos, aec->rpos));
+	aec->rpos = (aec->wpos - BUF_COUNT/2) % BUF_COUNT;
+	speex_echo_state_reset(aec->state);
+    }
+
+    /* Save fhe frame */
+    pjmedia_copy_samples(aec->frames[aec->wpos].buf,
+			 play_frm, aec->samples_per_frame);
+    aec->wpos = (aec->wpos+1) % BUF_COUNT;
+
+    pj_lock_release(aec->lock);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Let the AEC knows that a frame has been captured from the microphone.
+ */
+PJ_DEF(pj_status_t) pjmedia_aec_capture( pjmedia_aec *aec,
+					 pj_int16_t *rec_frm,
+					 unsigned options )
+{
+    pj_status_t status;
+
+    /* Sanity checks */
+    PJ_ASSERT_RETURN(aec && rec_frm, PJ_EINVAL);
+
+    /* The AEC must be configured to support internal playback buffer */
+    PJ_ASSERT_RETURN(aec->frames[0].buf != NULL, PJ_EINVALIDOP);
+
+    /* Lock mutex */
+    pj_lock_acquire(aec->lock);
+
+
+    /* Check for underflow */
+    if (aec->rpos == aec->wpos) {
+	/* Return frame as it is */
+	pj_lock_release(aec->lock);
+
+	PJ_LOG(5,(THIS_FILE, "AEC underflow (capture runs faster than "
+			     "playback, wpos=%d, rpos=%d)", 
+			     aec->wpos, aec->rpos));
+	aec->rpos = (aec->wpos - BUF_COUNT/2) % BUF_COUNT;
+	speex_echo_state_reset(aec->state);
+
+	return PJ_SUCCESS;
+    }
+    
+
+    /* Cancel echo */
+    status = pjmedia_aec_cancel_echo(aec, rec_frm, 
+				     aec->frames[aec->rpos].buf, options,
+				     NULL);
+
+    aec->rpos = (aec->rpos + 1) % BUF_COUNT;
+
+    pj_lock_release(aec->lock);
+    return status;
+}
+
+
+/*
+ * Perform echo cancellation.
+ */
+PJ_DEF(pj_status_t) pjmedia_aec_cancel_echo( pjmedia_aec *aec,
+					     pj_int16_t *rec_frm,
+					     const pj_int16_t *play_frm,
+					     unsigned options,
+					     void *reserved )
+{
+    unsigned level0, level1;
+
+    /* Sanity checks */
+    PJ_ASSERT_RETURN(aec && rec_frm && play_frm && options==0 &&
+		     reserved==NULL, PJ_EINVAL);
+
+    /* Cancel echo, put output in temporary buffer */
+    speex_echo_cancel(aec->state, (const spx_int16_t*)rec_frm, 
+		      (const spx_int16_t*)play_frm, 
+		      (spx_int16_t*)aec->tmp_frame, NULL);
+
+#if 0
+    level0 = pjmedia_calc_avg_signal(rec_frm, aec->samples_per_frame);
+    level1 = pjmedia_calc_avg_signal(aec->tmp_frame, aec->samples_per_frame);
+
+    if (level1 < level0) {
+	PJ_LOG(5,(THIS_FILE, "Input signal reduced from %d to %d",
+		  level0, level1));
+    }
+#else
+    PJ_UNUSED_ARG(level0);
+    PJ_UNUSED_ARG(level1);
+#endif
+
+    /* Copy temporary buffer back to original rec_frm */
+    pjmedia_copy_samples(rec_frm, aec->tmp_frame, aec->samples_per_frame);
+
+    return PJ_SUCCESS;
+
+}
+
Index: /pjproject/trunk/pjmedia/src/pjmedia/bidirectional.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/bidirectional.c	(revision 646)
+++ /pjproject/trunk/pjmedia/src/pjmedia/bidirectional.c	(revision 646)
@@ -0,0 +1,75 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjmedia/bidirectional.h>
+#include <pj/pool.h>
+
+
+#define THIS_FILE   "bidirectional.c"
+#define SIGNATURE   PJMEDIA_PORT_SIGNATURE('B', 'D', 'I', 'R')
+
+struct bidir_port
+{
+    pjmedia_port     base;
+    pjmedia_port    *get_port;
+    pjmedia_port    *put_port;
+};
+
+
+static pj_status_t put_frame(pjmedia_port *this_port, 
+			     const pjmedia_frame *frame)
+{
+    struct bidir_port *p = (struct bidir_port*)this_port;
+    return pjmedia_port_put_frame(p->put_port, frame);
+}
+
+
+static pj_status_t get_frame(pjmedia_port *this_port, 
+			     pjmedia_frame *frame)
+{
+    struct bidir_port *p = (struct bidir_port*)this_port;
+    return pjmedia_port_get_frame(p->get_port, frame);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_bidirectional_port_create( pj_pool_t *pool,
+						       pjmedia_port *get_port,
+						       pjmedia_port *put_port,
+						       pjmedia_port **p_port )
+{
+    struct bidir_port *port;
+
+    port = pj_pool_zalloc(pool, sizeof(struct bidir_port));
+
+    pjmedia_port_info_init(&port->base.info, &get_port->info.name, SIGNATURE,
+			   get_port->info.clock_rate,
+			   get_port->info.channel_count,
+			   get_port->info.bits_per_sample,
+			   get_port->info.samples_per_frame);
+
+    port->get_port = get_port;
+    port->put_port = put_port;
+
+    port->base.get_frame = &get_frame;
+    port->base.put_frame = &put_frame;
+
+    *p_port = &port->base;
+
+    return PJ_SUCCESS;
+}
+
Index: /pjproject/trunk/pjmedia/src/pjmedia/sound_port.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/sound_port.c	(revision 645)
+++ /pjproject/trunk/pjmedia/src/pjmedia/sound_port.c	(revision 646)
@@ -18,4 +18,5 @@
  */
 #include <pjmedia/sound_port.h>
+#include <pjmedia/aec.h>
 #include <pjmedia/errno.h>
 #include <pjmedia/plc.h>
@@ -25,7 +26,14 @@
 #include <pj/string.h>	    /* pj_memset() */
 
+#ifndef PJMEDIA_SOUND_HAS_AEC
+#   define PJMEDIA_SOUND_HAS_AEC	1
+#endif
+
+#if defined(PJMEDIA_SOUND_HAS_AEC) && PJMEDIA_SOUND_HAS_AEC!=0
+#   include <speex/speex_echo.h>
+#endif
 
 //#define SIMULATE_LOST_PCT   20
-
+#define AEC_TAIL    500	    /* in ms */
 
 #define THIS_FILE	    "sound_port.c"
@@ -49,4 +57,5 @@
     unsigned		 options;
 
+    pjmedia_aec		*aec;
     pjmedia_plc		*plc;
 
@@ -105,4 +114,9 @@
     if (snd_port->plc)
 	pjmedia_plc_save(snd_port->plc, output);
+
+    if (snd_port->aec) {
+	pjmedia_aec_playback(snd_port->aec, output);
+    }
+
 
     return PJ_SUCCESS;
@@ -122,4 +136,8 @@
 
 
+    if (snd_port->aec) {
+	pjmedia_aec_playback(snd_port->aec, output);
+    }
+
     return PJ_SUCCESS;
 }
@@ -132,5 +150,5 @@
 static pj_status_t rec_cb(/* in */   void *user_data,
 			  /* in */   pj_uint32_t timestamp,
-			  /* in */   const void *input,
+			  /* in */   void *input,
 			  /* in*/    unsigned size)
 {
@@ -138,4 +156,9 @@
     pjmedia_port *port;
     pjmedia_frame frame;
+
+    /* Cancel echo */
+    if (snd_port->aec) {
+	pjmedia_aec_capture(snd_port->aec, input, 0);
+    }
 
     /* We're risking accessing the port without holding any mutex.
@@ -228,4 +251,14 @@
     }
 
+    /* Create AEC only when direction is full duplex */
+    if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+	status = pjmedia_aec_create(pool, snd_port->clock_rate, 
+				    snd_port->samples_per_frame, 
+				    snd_port->clock_rate * AEC_TAIL / 1000,
+				    0, &snd_port->aec);
+	if (status != PJ_SUCCESS)
+	    snd_port->aec = NULL;
+    }
+
     /* Start sound stream. */
     status = pjmedia_snd_stream_start(snd_port->snd_stream);
@@ -251,4 +284,10 @@
 	pjmedia_snd_stream_close(snd_port->snd_stream);
 	snd_port->snd_stream = NULL;
+    }
+
+    /* Destroy AEC */
+    if (snd_port->aec) {
+	pjmedia_aec_destroy(snd_port->aec);
+	snd_port->aec = NULL;
     }
 
Index: /pjproject/trunk/pjsip-apps/build/samples.dsp
===================================================================
--- /pjproject/trunk/pjsip-apps/build/samples.dsp	(revision 645)
+++ /pjproject/trunk/pjsip-apps/build/samples.dsp	(revision 646)
@@ -87,4 +87,8 @@
 # Begin Source File
 
+SOURCE=..\src\samples\aectest.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\samples\confbench.c
 # End Source File
Index: /pjproject/trunk/pjsip-apps/src/samples/aectest.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/samples/aectest.c	(revision 646)
+++ /pjproject/trunk/pjsip-apps/src/samples/aectest.c	(revision 646)
@@ -0,0 +1,221 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+
+
+/**
+ * \page page_pjmedia_samples_aectest_c Samples: AEC Test (aectest.c)
+ *
+ * Play a file to speaker, run AEC, and record the microphone input
+ * to see if echo is coming.
+ *
+ * This file is pjsip-apps/src/samples/aectest.c
+ *
+ * \includelineno aectest.c
+ */
+#include <pjmedia.h>
+#include <pjlib-util.h>	/* pj_getopt */
+#include <pjlib.h>
+
+/* For logging purpose. */
+#define THIS_FILE   "playfile.c"
+#define PTIME	    20
+
+static const char *desc = 
+" FILE		    						    \n"
+"		    						    \n"
+"  aectest.c	    						    \n"
+"		    						    \n"
+" PURPOSE	    						    \n"
+"		    						    \n"
+"  Test the AEC effectiveness.					    \n"
+"		    						    \n"
+" USAGE		    						    \n"
+"		    						    \n"
+"  aectest INPUT.WAV OUTPUT.WAV					    \n"
+"		    						    \n"
+"  INPUT.WAV is the file to be played to the speaker.		    \n"
+"  OUTPUT.WAV is the output file containing recorded signal from the\n"
+"  microphone.";
+
+
+static void app_perror(const char *sender, const char *title, pj_status_t st)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(st, errmsg, sizeof(errmsg));
+    PJ_LOG(3,(sender, "%s: %s", title, errmsg));
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+    pj_caching_pool cp;
+    pjmedia_endpt *med_endpt;
+    pj_pool_t	  *pool;
+    pjmedia_port  *play_port;
+    pjmedia_port  *rec_port;
+    pjmedia_port  *bidir_port;
+    pjmedia_snd_port *snd;
+    char tmp[10];
+    pj_status_t status;
+
+
+    if (argc != 3) {
+    	puts("Error: arguments required");
+	puts(desc);
+	return 1;
+    }
+
+
+    /* Must init PJLIB first: */
+    status = pj_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Must create a pool factory before we can allocate any memory. */
+    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+    /* 
+     * Initialize media endpoint.
+     * This will implicitly initialize PJMEDIA too.
+     */
+    status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Create memory pool for our file player */
+    pool = pj_pool_create( &cp.factory,	    /* pool factory	    */
+			   "wav",	    /* pool name.	    */
+			   4000,	    /* init size	    */
+			   4000,	    /* increment size	    */
+			   NULL		    /* callback on error    */
+			   );
+
+    /* Create file media port from the WAV file */
+    status = pjmedia_wav_player_port_create(  pool,	/* memory pool	    */
+					      argv[1],	/* file to play	    */
+					      PTIME,	/* ptime.	    */
+					      0,	/* flags	    */
+					      0,	/* default buffer   */
+					      &play_port);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to open input WAV file", status);
+	return 1;
+    }
+
+    if (play_port->info.channel_count != 1) {
+	puts("Error: input WAV must have 1 channel audio");
+	return 1;
+    }
+    if (play_port->info.bits_per_sample != 16) {
+	puts("Error: input WAV must be encoded as 16bit PCM");
+	return 1;
+    }
+
+#ifdef PJ_DARWINOS
+    /* Need to force clock rate on MacOS */
+    if (play_port->info.clock_rate != 44100) {
+	pjmedia_port *resample_port;
+
+	status = pjmedia_resample_port_create(pool, play_port, 44100, 0,
+					      &resample_port);
+	if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Unable to create resampling port", status);
+	    return 1;
+	}
+
+	data.play_port = resample_port;
+    }
+#endif
+
+    /* Create WAV output file port */
+    status = pjmedia_wav_writer_port_create(pool, argv[2], 
+					    play_port->info.clock_rate,
+					    play_port->info.channel_count,
+					    play_port->info.samples_per_frame,
+					    play_port->info.bits_per_sample,
+					    0, 0, &rec_port);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to open output file", status);
+	return 1;
+    }
+
+    /* Create bidirectional port from the WAV ports */
+    pjmedia_bidirectional_port_create(pool, play_port, rec_port, &bidir_port);
+
+    /* Create AEC port */
+    if (0) {
+	pjmedia_aec_port_create(pool, bidir_port, 
+				bidir_port->info.clock_rate * 200 / 1000,
+				&bidir_port);
+    }
+
+    /* Create sound device. */
+    status = pjmedia_snd_port_create(pool, -1, -1, 
+				     play_port->info.clock_rate,
+				     play_port->info.channel_count,
+				     play_port->info.samples_per_frame,
+				     play_port->info.bits_per_sample,
+				     0, &snd);
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to open sound device", status);
+	return 1;
+    }
+
+
+    /* Connect sound to the port */
+    pjmedia_snd_port_connect(snd, bidir_port);
+
+
+    puts("");
+    printf("Playing %s and recording to %s\n", argv[1], argv[2]);
+    puts("Press <ENTER> to quit");
+
+    fgets(tmp, sizeof(tmp), stdin);
+
+    
+    /* Start deinitialization: */
+
+    /* Destroy sound device */
+    status = pjmedia_snd_port_destroy( snd );
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+    /* Destroy file port(s) */
+    status = pjmedia_port_destroy( play_port );
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+    status = pjmedia_port_destroy( rec_port );
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+    /* Release application pool */
+    pj_pool_release( pool );
+
+    /* Destroy media endpoint. */
+    pjmedia_endpt_destroy( med_endpt );
+
+    /* Destroy pool factory */
+    pj_caching_pool_destroy( &cp );
+
+
+    /* Done. */
+    return 0;
+}
+
Index: /pjproject/trunk/pjsip-apps/src/samples/debug.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 645)
+++ /pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 646)
@@ -28,4 +28,4 @@
  *  #include "playfile.c"
  */
-#include "pjsip-perf.c"
+#include "aectest.c"
 
