Index: pjproject/trunk/pjmedia/include/pjmedia/echo.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/echo.h	(revision 821)
+++ pjproject/trunk/pjmedia/include/pjmedia/echo.h	(revision 822)
@@ -83,4 +83,7 @@
  * @param samples_per_frame Number of samples per frame.
  * @param tail_ms	    Tail length, miliseconds.
+ * @param latency_ms	    Total lacency introduced by playback and 
+ *			    recording device. Set to zero if the latency
+ *			    is not known.
  * @param options	    Options. If PJMEDIA_ECHO_SIMPLE is specified,
  *			    then a simple echo suppressor implementation 
@@ -96,4 +99,5 @@
 					 unsigned samples_per_frame,
 					 unsigned tail_ms,
+					 unsigned latency_ms,
 					 unsigned options,
 					 pjmedia_echo_state **p_echo );
Index: pjproject/trunk/pjmedia/include/pjmedia/echo_port.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/echo_port.h	(revision 821)
+++ pjproject/trunk/pjmedia/include/pjmedia/echo_port.h	(revision 822)
@@ -47,4 +47,7 @@
  * @param dn_port	Downstream port.
  * @param tail_ms	Tail length in miliseconds.
+ * @param latency_ms	Total lacency introduced by playback and 
+ *			recording device. Set to zero if the latency
+ *			is not known.
  * @param options	Options, as in #pjmedia_echo_create().
  * @param p_port	Pointer to receive the port instance.
@@ -55,4 +58,5 @@
 					      pjmedia_port *dn_port,
 					      unsigned tail_ms,
+					      unsigned latency_ms,
 					      unsigned options,
 					      pjmedia_port **p_port );
Index: pjproject/trunk/pjmedia/src/pjmedia/echo_common.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/echo_common.c	(revision 821)
+++ pjproject/trunk/pjmedia/src/pjmedia/echo_common.c	(revision 822)
@@ -38,4 +38,5 @@
 			    unsigned samples_per_frame,
 			    unsigned tail_ms,
+			    unsigned latency_ms,
 			    unsigned options,
 			    void **p_state );
@@ -62,4 +63,5 @@
 				      unsigned samples_per_frame,
 				      unsigned tail_ms,
+				      unsigned latency_ms,
 				      unsigned options,
 				      void **p_state );
@@ -95,4 +97,5 @@
 				      unsigned samples_per_frame,
 				      unsigned tail_ms,
+				      unsigned latency_ms,
 				      unsigned options,
 				      void **p_state );
@@ -131,4 +134,5 @@
 					 unsigned samples_per_frame,
 					 unsigned tail_ms,
+					 unsigned latency_ms,
 					 unsigned options,
 					 pjmedia_echo_state **p_echo )
@@ -146,7 +150,6 @@
     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,
+	status = (*echo_supp_op.ec_create)(pool, clock_rate, samples_per_frame,
+					   tail_ms, latency_ms, options,
 					   &ec->state);
     } else {
@@ -154,5 +157,5 @@
 	status = (*aec_op.ec_create)(pool, clock_rate, 
 				     samples_per_frame,
-				     tail_ms, options,
+				     tail_ms, latency_ms, options,
 				     &ec->state);
     }
Index: pjproject/trunk/pjmedia/src/pjmedia/echo_port.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/echo_port.c	(revision 821)
+++ pjproject/trunk/pjmedia/src/pjmedia/echo_port.c	(revision 822)
@@ -47,4 +47,5 @@
 					     pjmedia_port *dn_port,
 					     unsigned tail_ms,
+					     unsigned latency_ms,
 					     unsigned options,
 					     pjmedia_port **p_port )
@@ -69,5 +70,5 @@
     status = pjmedia_echo_create(pool, dn_port->info.clock_rate, 
 				 dn_port->info.samples_per_frame,
-				 tail_ms, options, &ec->ec);
+				 tail_ms, latency_ms, options, &ec->ec);
     if (status != PJ_SUCCESS)
 	return status;
Index: pjproject/trunk/pjmedia/src/pjmedia/echo_speex.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/echo_speex.c	(revision 821)
+++ pjproject/trunk/pjmedia/src/pjmedia/echo_speex.c	(revision 822)
@@ -30,6 +30,199 @@
 
 
-#define THIS_FILE   "echo_speex.c"
-#define BUF_COUNT   8
+#define THIS_FILE	"echo_speex.c"
+#define BUF_COUNT	20
+#define MIN_PREFETCH	4
+#define MAX_PREFETCH	12
+
+
+
+#if 0
+# define TRACE_(expr)  PJ_LOG(5,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+
+typedef struct pjmedia_frame_queue pjmedia_frame_queue;
+
+struct fq_frame
+{
+    PJ_DECL_LIST_MEMBER(struct fq_frame);
+    void	*buf;
+    unsigned	 size;
+    pj_uint32_t	 seq;
+};
+
+struct pjmedia_frame_queue
+{
+    char	     obj_name[PJ_MAX_OBJ_NAME];
+    unsigned	     frame_size;
+    int		     samples_per_frame;
+    unsigned	     count;
+    unsigned	     max_count;
+    struct fq_frame  frame_list;
+    struct fq_frame  free_list;
+
+    int		     seq_delay;
+    int		     prefetch_count;
+};
+
+PJ_DEF(pj_status_t) pjmedia_frame_queue_create( pj_pool_t *pool,
+					        const char *name,
+					        unsigned frame_size,
+					        unsigned samples_per_frame,
+					        unsigned max_count,
+					        pjmedia_frame_queue **p_fq)
+{
+    pjmedia_frame_queue *fq;
+    unsigned i;
+
+    fq = pj_pool_zalloc(pool, sizeof(pjmedia_frame_queue));
+
+    pj_ansi_snprintf(fq->obj_name, sizeof(fq->obj_name), name, fq);
+    fq->obj_name[sizeof(fq->obj_name)-1] = '\0';
+
+    fq->max_count = max_count;
+    fq->frame_size = frame_size;
+    fq->samples_per_frame = samples_per_frame;
+    fq->count = 0;
+
+    pj_list_init(&fq->frame_list);
+    pj_list_init(&fq->free_list);
+
+    for (i=0; i<max_count; ++i) {
+	struct fq_frame *f;
+
+	f = pj_pool_zalloc(pool, sizeof(struct fq_frame));
+	f->buf = pj_pool_alloc(pool, frame_size);
+
+	pj_list_push_back(&fq->free_list, f);
+	
+    }
+
+    *p_fq = fq;
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_frame_queue_init( pjmedia_frame_queue *fq,
+					      int seq_delay,
+					      int prefetch_count)
+{
+    if (prefetch_count > MAX_PREFETCH)
+	prefetch_count = MAX_PREFETCH;
+
+    fq->seq_delay = seq_delay;
+    fq->prefetch_count = prefetch_count;
+    fq->count = 0;
+    pj_list_merge_first(&fq->free_list, &fq->frame_list);
+
+    PJ_LOG(5,(fq->obj_name, "AEC reset, delay=%d, prefetch=%d", 
+	      fq->seq_delay, fq->prefetch_count));
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_bool_t) pjmedia_frame_queue_empty( pjmedia_frame_queue *fq )
+{
+    return pj_list_empty(&fq->frame_list);
+}
+
+PJ_DEF(int) pjmedia_frame_queue_get_prefetch( pjmedia_frame_queue *fq )
+{
+    return fq->prefetch_count;
+}
+
+PJ_DEF(pj_status_t) pjmedia_frame_queue_put( pjmedia_frame_queue *fq,
+					     const void *framebuf,
+					     unsigned size,
+					     pj_uint32_t timestamp )
+{
+    struct fq_frame *f;
+
+    TRACE_((fq->obj_name, "PUT seq=%d, count=%d", 
+	    timestamp / fq->samples_per_frame, fq->count));
+
+    if (pj_list_empty(&fq->free_list)) {
+	PJ_LOG(5,(fq->obj_name, 
+		  " AEC info: queue is full, frame discarded "
+		  "[count=%d, seq=%d]",
+		  fq->max_count, timestamp / fq->samples_per_frame));
+	return PJ_ETOOMANY;
+    }
+
+    PJ_ASSERT_RETURN(size <= fq->frame_size, PJ_ETOOBIG);
+
+    f = fq->free_list.next;
+    pj_list_erase(f);
+
+    pj_memcpy(f->buf, framebuf, size);
+    f->size = size;
+    f->seq = timestamp / fq->samples_per_frame;
+
+    pj_list_push_back(&fq->frame_list, f);
+    ++fq->count;
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_frame_queue_get( pjmedia_frame_queue *fq,
+					     pj_uint32_t get_timestamp,
+					     void **framebuf,
+					     unsigned *size )
+{
+    pj_uint32_t frame_seq;
+    struct fq_frame *f;
+
+    frame_seq = get_timestamp/fq->samples_per_frame + fq->seq_delay -
+		fq->prefetch_count;
+
+    TRACE_((fq->obj_name, "GET seq=%d for seq=%d delay=%d, prefetch=%d", 
+	    get_timestamp/fq->samples_per_frame, frame_seq, fq->seq_delay, 
+	    fq->prefetch_count));
+
+    *size = 0;
+
+    /* Remove old frames */
+    for (;!pj_list_empty(&fq->frame_list);) {
+	f = fq->frame_list.next;
+	if (f->seq >= frame_seq)
+	    break;
+
+	PJ_LOG(5,(fq->obj_name, 
+		  " AEC Info: old frame removed (seq=%d, want=%d, count=%d)",
+		  f->seq, frame_seq, fq->count));
+	pj_list_erase(f);
+	--fq->count;
+	pj_list_push_back(&fq->free_list, f);
+    }
+
+    if (pj_list_empty(&fq->frame_list)) {
+	PJ_LOG(5,(fq->obj_name, 
+		  " AEC Info: empty queue for seq=%d!",
+		  frame_seq));
+	return PJ_ENOTFOUND;
+    }
+
+    f = fq->frame_list.next;
+
+    if (f->seq > frame_seq) {
+	PJ_LOG(5,(fq->obj_name, 
+		  " AEC Info: prefetching (first seq=%d)",
+		  f->seq));
+	return -1;
+    }
+
+    pj_list_erase(f);
+    --fq->count;
+
+    *framebuf = (void*)f->buf;
+    *size = f->size;
+
+    TRACE_((fq->obj_name, " returning frame with seq=%d, count=%d", 
+	    f->seq, fq->count));
+
+    pj_list_push_front(&fq->free_list, f);
+    return PJ_SUCCESS;
+}
 
 /*
@@ -40,4 +233,5 @@
 				      unsigned samples_per_frame,
 				      unsigned tail_ms,
+				      unsigned latency_ms,
 				      unsigned options,
 				      void **p_state );
@@ -55,7 +249,9 @@
 
 
-struct frame
-{
-    pj_int16_t	   *buf;
+enum
+{
+    TS_FLAG_PLAY = 1,
+    TS_FLAG_REC	 = 2,
+    TS_FLAG_OK	 = 3,
 };
 
@@ -65,14 +261,16 @@
     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.	    */
+    unsigned		  samples_per_frame;
+    unsigned		  prefetch;
+    unsigned		  options;
+    pj_int16_t		 *tmp_frame;
+    spx_int32_t		 *residue;
+
+    pj_uint32_t		  play_ts,
+			  rec_ts,
+			  ts_flag;
+
+    pjmedia_frame_queue	 *frame_queue;
+    pj_lock_t		 *lock;		/* To protect buffers, if required  */
 } speex_ec;
 
@@ -86,4 +284,5 @@
 				     unsigned samples_per_frame,
 				     unsigned tail_ms,
+				     unsigned latency_ms,
 				     unsigned options,
 				     void **p_echo )
@@ -91,6 +290,5 @@
     speex_ec *echo;
     int sampling_rate;
-    unsigned i;
-    int disabled;
+    int disabled, enabled;
     pj_status_t status;
 
@@ -111,4 +309,9 @@
 
     echo->samples_per_frame = samples_per_frame;
+    echo->prefetch = (latency_ms * clock_rate / 1000) / samples_per_frame;
+    if (echo->prefetch < MIN_PREFETCH)
+	echo->prefetch = MIN_PREFETCH;
+    if (echo->prefetch > MAX_PREFETCH)
+	echo->prefetch = MAX_PREFETCH;
     echo->options = options;
 
@@ -130,6 +333,7 @@
     /* Disable all preprocessing, we only want echo cancellation */
     disabled = 0;
+    enabled = 1;
     speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_DENOISE, 
-			 &disabled);
+			 &enabled);
     speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_AGC, 
 			 &disabled);
@@ -150,13 +354,17 @@
     /* Create temporary frame to receive residue */
     echo->residue = pj_pool_zalloc(pool, sizeof(spx_int32_t) * 
-					    samples_per_frame);
+					    (samples_per_frame+1));
     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);
-    }
-
+    /* Create frame queue */
+    status = pjmedia_frame_queue_create(pool, "aec%p", samples_per_frame*2,
+					samples_per_frame, BUF_COUNT, 
+					&echo->frame_queue);
+    if (status != PJ_SUCCESS) {
+	speex_preprocess_state_destroy(echo->preprocess);
+	speex_echo_state_destroy(echo->state);
+	pj_lock_destroy(echo->lock);
+	return status;
+    }
 
     /* Done */
@@ -164,8 +372,7 @@
 
     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));
+			 "samples per frame=%d, tail length=%d ms, "
+			 "latency=%d ms", 
+			 clock_rate, samples_per_frame, tail_ms, latency_ms));
     return PJ_SUCCESS;
 
@@ -216,21 +423,27 @@
 
     /* The AEC must be configured to support internal playback buffer */
-    PJ_ASSERT_RETURN(echo->frames[0].buf != NULL, PJ_EINVALIDOP);
+    PJ_ASSERT_RETURN(echo->frame_queue!= 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;
+    /* Inc timestamp */
+    echo->play_ts += echo->samples_per_frame;
+
+    /* Initialize frame delay. */
+    if ((echo->ts_flag & TS_FLAG_PLAY) == 0) {
+	echo->ts_flag |= TS_FLAG_PLAY;
+
+	if (echo->ts_flag == TS_FLAG_OK) {
+	    int seq_delay;
+
+	    seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / 
+			    (int)echo->samples_per_frame;
+	    pjmedia_frame_queue_init(echo->frame_queue, seq_delay, 
+				     echo->prefetch);
+	}
+    }
+
+    pjmedia_frame_queue_put(echo->frame_queue, play_frm, 
+			    echo->samples_per_frame*2, echo->play_ts);
 
     pj_lock_release(echo->lock);
@@ -248,5 +461,5 @@
 {
     speex_ec *echo = state;
-    pj_status_t status;
+    pj_status_t status = PJ_SUCCESS;
 
     /* Sanity checks */
@@ -254,34 +467,56 @@
 
     /* The AEC must be configured to support internal playback buffer */
-    PJ_ASSERT_RETURN(echo->frames[0].buf != NULL, PJ_EINVALIDOP);
+    PJ_ASSERT_RETURN(echo->frame_queue!= 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;
-    }
-    
+    /* Inc timestamp */
+    echo->rec_ts += echo->samples_per_frame;
+
+    /* Init frame delay. */
+    if ((echo->ts_flag & TS_FLAG_REC) == 0) {
+	echo->ts_flag |= TS_FLAG_REC;
+
+	if (echo->ts_flag == TS_FLAG_OK) {
+	    int seq_delay;
+
+	    seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / 
+			    (int)echo->samples_per_frame;
+	    pjmedia_frame_queue_init(echo->frame_queue, seq_delay, 
+				     echo->prefetch);
+	}
+    }
 
     /* Cancel echo */
-    status = speex_aec_cancel_echo(echo, rec_frm, 
-				   echo->frames[echo->rpos].buf, options,
-				   NULL);
-
-    echo->rpos = (echo->rpos + 1) % BUF_COUNT;
+    if (echo->ts_flag == TS_FLAG_OK) {
+	void *play_buf;
+	unsigned size = 0;
+	
+	if (pjmedia_frame_queue_empty(echo->frame_queue)) {
+	    int seq_delay, prefetch;
+
+	    seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / 
+			    (int)echo->samples_per_frame;
+	    prefetch = pjmedia_frame_queue_get_prefetch(echo->frame_queue);
+	    //++prefetch;
+	    pjmedia_frame_queue_init(echo->frame_queue, seq_delay, prefetch);
+	    status = -1;
+
+	} else {
+	    status = pjmedia_frame_queue_get(echo->frame_queue, echo->rec_ts,
+					     &play_buf, &size);
+	    if (size != 0) {
+		speex_aec_cancel_echo(echo, rec_frm, (pj_int16_t*)play_buf,
+				      options, NULL);
+	    }	
+	}
+
+	if (status != PJ_SUCCESS)
+	    speex_echo_state_reset(echo->state);
+    }
 
     pj_lock_release(echo->lock);
-    return status;
+    return PJ_SUCCESS;
 }
 
@@ -320,2 +555,3 @@
 }
 
+ 
Index: pjproject/trunk/pjmedia/src/pjmedia/sound_port.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/sound_port.c	(revision 821)
+++ pjproject/trunk/pjmedia/src/pjmedia/sound_port.c	(revision 822)
@@ -493,5 +493,6 @@
 	status = pjmedia_echo_create(pool, snd_port->clock_rate, 
 				    snd_port->samples_per_frame, 
-				    tail_ms, options, &snd_port->ec_state);
+				    tail_ms, delay_ms,
+				    options, &snd_port->ec_state);
 	if (status != PJ_SUCCESS)
 	    snd_port->ec_state = NULL;
Index: pjproject/trunk/pjsip-apps/src/samples/aectest.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/aectest.c	(revision 821)
+++ pjproject/trunk/pjsip-apps/src/samples/aectest.c	(revision 822)
@@ -36,4 +36,5 @@
 #define THIS_FILE   "playfile.c"
 #define PTIME	    20
+#define TAIL_LENGTH 800
 
 static const char *desc = 
@@ -175,5 +176,5 @@
 
     /* Customize AEC */
-    pjmedia_snd_port_set_aec(snd, pool, 800);
+    pjmedia_snd_port_set_ec(snd, pool, TAIL_LENGTH, 0);
 
     /* Connect sound to the port */
Index: pjproject/trunk/pjsip-apps/src/samples/debug.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 821)
+++ pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 822)
@@ -28,3 +28,3 @@
  *  #include "playfile.c"
  */
-#include "tonegen.c"
+#include "aectest.c"
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 821)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 822)
@@ -2216,11 +2216,23 @@
  */
 #ifndef PJSUA_MAX_CONF_PORTS
-#   define PJSUA_MAX_CONF_PORTS	    254
+#   define PJSUA_MAX_CONF_PORTS		254
 #endif
 
-#define PJSUA_DEFAULT_CLOCK_RATE    16000
-#define PJSUA_DEFAULT_CODEC_QUALITY 5
-#define PJSUA_DEFAULT_ILBC_MODE	    20
-#define PJSUA_DEFAULT_EC_TAIL_LEN   0
+#ifndef PJSUA_DEFAULT_CLOCK_RATE
+#   define PJSUA_DEFAULT_CLOCK_RATE	16000
+#endif
+
+#ifndef PJSUA_DEFAULT_CODEC_QUALITY
+#   define PJSUA_DEFAULT_CODEC_QUALITY	5
+#endif
+
+#ifndef PJSUA_DEFAULT_ILBC_MODE
+#   define PJSUA_DEFAULT_ILBC_MODE	20
+#endif
+
+
+#ifndef PJSUA_DEFAULT_EC_TAIL_LEN
+#   define PJSUA_DEFAULT_EC_TAIL_LEN	600
+#endif
 
 
