Index: pjproject/trunk/pjmedia/include/pjmedia/config.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/config.h	(revision 2849)
+++ pjproject/trunk/pjmedia/include/pjmedia/config.h	(revision 2850)
@@ -150,4 +150,62 @@
 #ifndef PJMEDIA_WSOLA_IMP
 #   define PJMEDIA_WSOLA_IMP		    PJMEDIA_WSOLA_IMP_WSOLA
+#endif
+
+
+/**
+ * Specify the default maximum duration of synthetic audio that is generated
+ * by WSOLA. This value should be long enough to cover burst of packet losses. 
+ * but not too long, because as the duration increases the quality would 
+ * degrade considerably.
+ *
+ * Note that this limit is only applied when fading is enabled in the WSOLA
+ * session.
+ *
+ * Default: 80
+ */
+#ifndef PJMEDIA_WSOLA_MAX_EXPAND_MSEC
+#   define PJMEDIA_WSOLA_MAX_EXPAND_MSEC    80
+#endif
+
+
+/**
+ * Specify WSOLA template length, in milliseconds. The longer the template,
+ * the smoother signal to be generated at the expense of more computation
+ * needed, since the algorithm will have to compare more samples to find
+ * the most similar pitch.
+ *
+ * Default: 5
+ */
+#ifndef PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC
+#   define PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC	5
+#endif
+
+
+/**
+ * Specify WSOLA algorithm delay, in milliseconds. The algorithm delay is
+ * used to merge synthetic samples with real samples in the transition
+ * between real to synthetic and vice versa. The longer the delay, the 
+ * smoother signal to be generated, at the expense of longer latency and
+ * a slighty more computation.
+ *
+ * Default: 5
+ */
+#ifndef PJMEDIA_WSOLA_DELAY_MSEC
+#   define PJMEDIA_WSOLA_DELAY_MSEC	    5
+#endif
+
+
+/**
+ * Set this to non-zero to disable fade-out/in effect in the PLC when it
+ * instructs WSOLA to generate synthetic frames. The use of fading may
+ * or may not improve the quality of audio, depending on the nature of
+ * packet loss and the type of audio input (e.g. speech vs music).
+ * Disabling fading also implicitly remove the maximum limit of synthetic
+ * audio samples generated by WSOLA (see PJMEDIA_WSOLA_MAX_EXPAND_MSEC).
+ *
+ * Default: 0
+ */
+#ifndef PJMEDIA_WSOLA_PLC_NO_FADING
+#   define PJMEDIA_WSOLA_PLC_NO_FADING	    0
 #endif
 
Index: pjproject/trunk/pjmedia/include/pjmedia/wsola.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/wsola.h	(revision 2849)
+++ pjproject/trunk/pjmedia/include/pjmedia/wsola.h	(revision 2850)
@@ -70,5 +70,15 @@
      * non-contiguous buffer.
      */
-    PJMEDIA_WSOLA_NO_DISCARD = 4
+    PJMEDIA_WSOLA_NO_DISCARD = 4,
+
+    /**
+     * Disable fade-in and fade-out feature in the transition between
+     * actual and synthetic frames in WSOLA. With fade feature enabled, 
+     * WSOLA will only generate a limited number of synthetic frames 
+     * (configurable with #pjmedia_wsola_set_max_expand()), fading out 
+     * the volume on every more samples it generates, and when it reaches
+     * the limit it will only generate silence.
+     */
+    PJMEDIA_WSOLA_NO_FADING = 8
 };
 
@@ -94,4 +104,21 @@
 					  unsigned options,
 					  pjmedia_wsola **p_wsola);
+
+
+/**
+ * Specify maximum number of continuous synthetic frames that can be
+ * generated by WSOLA, in milliseconds. This option will only take
+ * effect if fading is not disabled via the option when the WSOLA
+ * session was created. Default value is PJMEDIA_WSOLA_MAX_EXPAND_MSEC
+ * (see also the documentation of PJMEDIA_WSOLA_MAX_EXPAND_MSEC for
+ * more information).
+ *
+ * @param wsola	    The WSOLA session
+ * @param msec	    The duration.
+ *
+ * @return	    PJ_SUCCESS normally.
+ */
+PJ_DECL(pj_status_t) pjmedia_wsola_set_max_expand(pjmedia_wsola *wsola,
+						  unsigned msec);
 
 
Index: pjproject/trunk/pjmedia/src/pjmedia/delaybuf.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/delaybuf.c	(revision 2849)
+++ pjproject/trunk/pjmedia/src/pjmedia/delaybuf.c	(revision 2850)
@@ -129,5 +129,5 @@
     /* Create WSOLA */
     status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1,
-				  0, &b->wsola);
+				  PJMEDIA_WSOLA_NO_FADING, &b->wsola);
     if (status != PJ_SUCCESS)
 	return status;
Index: pjproject/trunk/pjmedia/src/pjmedia/plc_common.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/plc_common.c	(revision 2849)
+++ pjproject/trunk/pjmedia/src/pjmedia/plc_common.c	(revision 2850)
@@ -126,4 +126,5 @@
 {
     struct wsola_plc *o;
+    unsigned flag;
     pj_status_t status;
 
@@ -133,6 +134,10 @@
     o->prev_lost = PJ_FALSE;
 
+    flag = PJMEDIA_WSOLA_NO_DISCARD;
+    if (PJMEDIA_WSOLA_PLC_NO_FADING)
+	flag |= PJMEDIA_WSOLA_NO_FADING;
+
     status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1,
-				  PJMEDIA_WSOLA_NO_DISCARD, &o->wsola);
+				  flag, &o->wsola);
     if (status != PJ_SUCCESS)
 	return NULL;
Index: pjproject/trunk/pjmedia/src/pjmedia/stream.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/stream.c	(revision 2849)
+++ pjproject/trunk/pjmedia/src/pjmedia/stream.c	(revision 2850)
@@ -44,4 +44,11 @@
 #define BYTES_PER_SAMPLE		2
 
+/* Limit the number of synthetic audio samples that are generated by PLC.
+ * Normally PLC should have it's own means to limit the number of
+ * synthetic frames, so we need to set this to a reasonably large value
+ * just as precaution
+ */
+#define MAX_PLC_MSEC			240
+
 /**
  * Media channel.
@@ -99,4 +106,7 @@
     unsigned		     enc_buf_count; /**< Number of samples in the
 						 encoding buffer.	    */
+
+    unsigned		     plc_cnt;	    /**< # of consecutive PLC frames*/
+    unsigned		     max_plc_cnt;   /**< Max # of PLC frames	    */
 
     unsigned		     vad_enabled;   /**< VAD enabled in param.	    */
@@ -198,11 +208,11 @@
 }
 
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0
 /*
- * Send keep-alive packet.
+ * Send keep-alive packet using non-codec frame.
  */
 static void send_keep_alive_packet(pjmedia_stream *stream)
 {
-#if defined(PJMEDIA_STREAM_ENABLE_KA) && \
-    PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_EMPTY_RTP
+#if PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_EMPTY_RTP
 
     /* Keep-alive packet is empty RTP */
@@ -225,6 +235,5 @@
     TRC_((stream->port.info.name.ptr, "Keep-alive sent (empty RTP)"));
 
-#elif defined(PJMEDIA_STREAM_ENABLE_KA) && \
-      PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_USER
+#elif PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_USER
 
     /* Keep-alive packet is defined in PJMEDIA_STREAM_KA_USER_PKT */
@@ -244,4 +253,5 @@
 #endif
 }
+#endif	/* defined(PJMEDIA_STREAM_ENABLE_KA) */
 
 /*
@@ -295,5 +305,6 @@
 	    /* Activate PLC */
 	    if (stream->codec->op->recover && 
-		stream->codec_param.setting.plc) 
+		stream->codec_param.setting.plc &&
+		stream->plc_cnt < stream->max_plc_cnt) 
 	    {
 		pjmedia_frame frame_out;
@@ -305,4 +316,6 @@
 						       &frame_out);
 
+		++stream->plc_cnt;
+
 	    } else {
 		status = -1;
@@ -319,5 +332,5 @@
 			  "Lost frame recovered"));
 	    }
-	    
+
 	} else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) {
 
@@ -326,5 +339,8 @@
 	     * the frame. 
 	     */
-	    if (frame_type != stream->jb_last_frm) {
+	    //Using this "if" will only invoke PLC for the first packet
+	    //lost and not the subsequent ones.
+	    //if (frame_type != stream->jb_last_frm) {
+	    if (1) {
 		pjmedia_jb_state jb_state;
 		const char *with_plc = "";
@@ -332,5 +348,6 @@
 		/* Activate PLC to smoothen the missing frame */
 		if (stream->codec->op->recover && 
-		    stream->codec_param.setting.plc) 
+		    stream->codec_param.setting.plc &&
+		    stream->plc_cnt < stream->max_plc_cnt) 
 		{
 		    pjmedia_frame frame_out;
@@ -344,7 +361,10 @@
 			if (status != PJ_SUCCESS)
 			    break;
+
 			samples_count += samples_per_frame;
-
-		    } while (samples_count < samples_required);
+			++stream->plc_cnt;
+
+		    } while (samples_count < samples_required &&
+			     stream->plc_cnt < stream->max_plc_cnt);
 
 		    with_plc = ", plc invoked";
@@ -380,5 +400,6 @@
 	    /* Always activate PLC when it's available.. */
 	    if (stream->codec->op->recover && 
-		stream->codec_param.setting.plc) 
+		stream->codec_param.setting.plc &&
+		stream->plc_cnt < stream->max_plc_cnt) 
 	    {
 		pjmedia_frame frame_out;
@@ -394,7 +415,11 @@
 		    samples_count += samples_per_frame;
 
-		} while (samples_count < samples_required);
-
-		if (stream->jb_last_frm != frame_type) {
+		    ++stream->plc_cnt;
+
+		} while (samples_count < samples_required &&
+			 stream->plc_cnt < stream->max_plc_cnt);
+
+		//if (stream->jb_last_frm != frame_type) {
+		if (1) {
 		    PJ_LOG(5,(stream->port.info.name.ptr, 
 			      "Jitter buffer is bufferring with plc (prefetch=%d)",
@@ -419,4 +444,6 @@
 	    /* Got "NORMAL" frame from jitter buffer */
 	    pjmedia_frame frame_in, frame_out;
+
+	    stream->plc_cnt = 0;
 
 	    /* Decode */
@@ -1822,4 +1849,8 @@
 	++stream->frame_size;
     }
+
+    /* How many consecutive PLC frames can be generated */
+    stream->max_plc_cnt = (MAX_PLC_MSEC+stream->codec_param.info.frm_ptime-1)/
+			    stream->codec_param.info.frm_ptime;
 
 #if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
Index: pjproject/trunk/pjmedia/src/pjmedia/wsola.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/wsola.c	(revision 2849)
+++ pjproject/trunk/pjmedia/src/pjmedia/wsola.c	(revision 2850)
@@ -71,8 +71,8 @@
 
 /* Template size, in msec */
-#define TEMPLATE_PTIME	5
+#define TEMPLATE_PTIME	PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC
 
 /* Hanning window size, in msec */
-#define HANNING_PTIME	5
+#define HANNING_PTIME	PJMEDIA_WSOLA_DELAY_MSEC
 
 /* Number of frames in erase buffer */
@@ -84,4 +84,9 @@
 /* Maximum distance from template for find_pitch() of expansion, in frames */
 #define EXP_MAX_DIST	HIST_CNT
+
+/* Duration of a continuous synthetic frames after which the volume 
+ * of the synthetic frame will be set to zero with fading-out effect.
+ */
+#define MAX_EXPAND_MSEC	PJMEDIA_WSOLA_MAX_EXPAND_MSEC
 
 
@@ -132,5 +137,6 @@
 
     pj_uint16_t		 min_extra;	    /* Minimum extra (const)	    */
-    pj_uint16_t		 expand_cnt;	    /* Consecutive expansion count  */
+    unsigned		 max_expand_cnt;    /* Max # of synthetic samples   */
+    unsigned		 fade_out_pos;	    /* Last fade-out position	    */
     pj_uint16_t		 expand_sr_min_dist;/* Minimum distance from template 
 					       for find_pitch() on expansion
@@ -147,4 +153,5 @@
 
     pj_timestamp	 ts;		    /* Running timestamp.	    */
+
 };
 
@@ -433,4 +440,62 @@
 #endif	/* PJ_HAS_FLOATING_POINT */
 
+/* Apply fade-in to the buffer.
+ *  - fade_cnt is the number of samples on which the volume
+ *       will go from zero to 100%
+ *  - fade_pos is current sample position within fade_cnt range.
+ *       It is zero for the first sample, so the first sample will
+ *	 have zero volume. This value is increasing.
+ */
+static void fade_in(pj_int16_t buf[], int count,
+		    int fade_in_pos, int fade_cnt)
+{
+#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+    float fade_pos = (float)fade_in_pos;
+#else
+    int fade_pos = fade_in_pos;
+#endif
+
+    if (fade_cnt - fade_pos < count) {
+	for (; fade_pos < fade_cnt; ++fade_pos, ++buf) {
+	    *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt);
+	}
+	/* Leave the remaining samples as is */
+    } else {
+	pj_int16_t *end = buf + count;
+	for (; buf != end; ++fade_pos, ++buf) {
+	    *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt);
+	}
+    }
+}
+
+/* Apply fade-out to the buffer. */
+static void wsola_fade_out(pjmedia_wsola *wsola, 
+			   pj_int16_t buf[], int count)
+{
+    pj_int16_t *end = buf + count;
+    int fade_cnt = wsola->max_expand_cnt;
+#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+    float fade_pos = (float)wsola->fade_out_pos;
+#else
+    int fade_pos = wsola->fade_out_pos;
+#endif
+
+    if (wsola->fade_out_pos == 0) {
+	pjmedia_zero_samples(buf, count);
+    } else if (fade_pos < count) {
+	for (; fade_pos; --fade_pos, ++buf) {
+	    *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt);
+	}
+	if (buf != end)
+	    pjmedia_zero_samples(buf, end - buf);
+	wsola->fade_out_pos = 0;
+    } else {
+	for (; buf != end; --fade_pos, ++buf) {
+	    *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt);
+	}
+	wsola->fade_out_pos -= count;
+    }
+}
+
 
 PJ_DEF(pj_status_t) pjmedia_wsola_create( pj_pool_t *pool, 
@@ -456,4 +521,6 @@
     wsola->channel_count = (pj_uint16_t) channel_count;
     wsola->options = (pj_uint16_t) options;
+    wsola->max_expand_cnt = clock_rate * MAX_EXPAND_MSEC / 1000;
+    wsola->fade_out_pos = wsola->max_expand_cnt;
 
     /* Create circular buffer */
@@ -524,4 +591,11 @@
 }
 
+PJ_DEF(pj_status_t) pjmedia_wsola_set_max_expand(pjmedia_wsola *wsola,
+						  unsigned msec)
+{
+    PJ_ASSERT_RETURN(wsola, PJ_EINVAL);
+    wsola->max_expand_cnt = msec * wsola->clock_rate / 1000;
+    return PJ_SUCCESS;
+}
 
 PJ_DEF(pj_status_t) pjmedia_wsola_reset( pjmedia_wsola *wsola,
@@ -534,4 +608,5 @@
     pjmedia_circ_buf_set_len(wsola->buf, wsola->hist_size + wsola->min_extra);
     pjmedia_zero_samples(wsola->buf->start, wsola->buf->len); 
+    wsola->fade_out_pos = wsola->max_expand_cnt;
 
     return PJ_SUCCESS;
@@ -683,5 +758,4 @@
 
     /* Update vars */
-    wsola->expand_cnt = 0;
     wsola->ts.u64 += wsola->samples_per_frame;
 
@@ -692,4 +766,10 @@
 	pj_int16_t *ola_left;
 
+	/* Trim excessive len */
+	if ((int)buf_len > wsola->hist_size + (wsola->min_extra<<1)) {
+	    buf_len = wsola->hist_size + (wsola->min_extra<<1);
+	    pjmedia_circ_buf_set_len(wsola->buf, buf_len);
+	}
+
 	pjmedia_circ_buf_get_read_regions(wsola->buf, &reg1, &reg1_len, 
 					  &reg2, &reg2_len);
@@ -698,4 +778,20 @@
 	       (unsigned)(wsola->hist_size + (wsola->min_extra<<1)));
 
+	/* Continue applying fade out to the extra samples */
+	if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) {
+	    if (reg2_len == 0) {
+		wsola_fade_out(wsola, reg1 + reg1_len - (wsola->min_extra<<1),
+			       (wsola->min_extra<<1));
+	    } else if ((int)reg2_len >= (wsola->min_extra<<1)) {
+		wsola_fade_out(wsola, reg2 + reg2_len - (wsola->min_extra<<1),
+			       (wsola->min_extra<<1));
+	    } else {
+		unsigned tmp = (wsola->min_extra<<1) - reg2_len;
+		wsola_fade_out(wsola, reg1 + reg1_len - tmp, tmp);
+		wsola_fade_out(wsola, reg2, reg2_len);
+	    }
+	}
+
+	/* Get the region in buffer to be merged with the frame */
 	if (reg2_len == 0) {
 	    ola_left = reg1 + reg1_len - wsola->min_extra;
@@ -711,9 +807,71 @@
 	}
 
+	/* Apply fade-in to the frame before merging */
+	if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) {
+	    unsigned count = wsola->min_extra;
+	    int fade_in_pos;
+
+	    /* Scale fade_in position based on last fade-out */
+	    fade_in_pos = wsola->fade_out_pos * count /
+			  wsola->max_expand_cnt;
+
+	    /* Fade-in it */
+	    fade_in(frm, wsola->samples_per_frame,
+		    fade_in_pos, count);
+	}
+
+	/* Merge it */
 	overlapp_add_simple(frm, wsola->min_extra, ola_left, frm);
 
+	/* Trim len */
 	buf_len -= wsola->min_extra;
 	pjmedia_circ_buf_set_len(wsola->buf, buf_len);
-    }
+
+    } else if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0 &&
+	       wsola->fade_out_pos != wsola->max_expand_cnt) 
+    {
+	unsigned count = wsola->min_extra;
+	int fade_in_pos;
+
+	/* Fade out the remaining synthetic samples */
+	if (buf_len > wsola->hist_size) {
+	    pj_int16_t *reg1, *reg2;
+	    unsigned reg1_len, reg2_len;
+
+	    /* Number of samples to fade out */
+	    count = buf_len - wsola->hist_size;
+
+	    pjmedia_circ_buf_get_read_regions(wsola->buf, &reg1, &reg1_len, 
+					      &reg2, &reg2_len);
+
+	    CHECK_(pjmedia_circ_buf_get_len(wsola->buf) >= 
+		   (unsigned)(wsola->hist_size + (wsola->min_extra<<1)));
+
+	    /* Continue applying fade out to the extra samples */
+	    if (reg2_len == 0) {
+		wsola_fade_out(wsola, reg1 + reg1_len - count, count);
+	    } else if ((int)reg2_len >= count) {
+		wsola_fade_out(wsola, reg2 + reg2_len - count, count);
+	    } else {
+		unsigned tmp = count - reg2_len;
+		wsola_fade_out(wsola, reg1 + reg1_len - tmp, tmp);
+		wsola_fade_out(wsola, reg2, reg2_len);
+	    }
+	}
+
+	/* Apply fade-in to the frame */
+	count = wsola->min_extra;
+
+	/* Scale fade_in position based on last fade-out */
+	fade_in_pos = wsola->fade_out_pos * count /
+		      wsola->max_expand_cnt;
+
+	/* Fade it in */
+	fade_in(frm, wsola->samples_per_frame,
+		fade_in_pos, count);
+
+    }
+
+    wsola->fade_out_pos = wsola->max_expand_cnt;
 
     status = pjmedia_circ_buf_write(wsola->buf, frm, wsola->samples_per_frame);
@@ -738,6 +896,4 @@
 {
     unsigned samples_len, samples_req;
-
-
     pj_status_t status = PJ_SUCCESS;
 
@@ -758,5 +914,4 @@
 	TRACE_((THIS_FILE, "Buf size after expanded = %d", 
 		pjmedia_circ_buf_get_len(wsola->buf)));
-	wsola->expand_cnt++;
     }
 
@@ -769,4 +924,9 @@
 
     pjmedia_circ_buf_adv_read_ptr(wsola->buf, wsola->samples_per_frame);
+
+    /* Apply fade-out to the frame */
+    if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) {
+	wsola_fade_out(wsola, frm, wsola->samples_per_frame);
+    }
 
     return PJ_SUCCESS;
