Modify WSOLA discard to support erasing frame from non-contiguous buffer

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1827 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/include/pjmedia/types.h b/pjmedia/include/pjmedia/types.h
index 49a4c25..fe3b234 100644
--- a/pjmedia/include/pjmedia/types.h
+++ b/pjmedia/include/pjmedia/types.h
@@ -190,7 +190,7 @@
 PJ_INLINE(void) pjmedia_zero_samples(pj_int16_t *samples, unsigned count)
 {
 #if 1
-    pj_bzero(samples, count*sizeof(pj_int16_t));
+    pj_bzero(samples, (count<<1));
 #elif 0
     unsigned i;
     for (i=0; i<count; ++i) samples[i] = 0;
@@ -212,7 +212,7 @@
 				     unsigned count)
 {
 #if 1
-    pj_memcpy(dst, src, count*sizeof(pj_int16_t));
+    pj_memcpy(dst, src, (count<<1));
 #elif 0
     unsigned i;
     for (i=0; i<count; ++i) dst[i] = src[i];
@@ -226,6 +226,28 @@
 
 
 /**
+ * This is a general purpose function to copy samples from/to buffers with
+ * equal size. Since this function is needed by many parts of the library, 
+ * by putting this functionality in one place, it enables some.
+ * clever people to optimize this function.
+ */
+PJ_INLINE(void) pjmedia_move_samples(pj_int16_t *dst, const pj_int16_t *src,
+				     unsigned count)
+{
+#if 1
+    pj_memmove(dst, src, (count<<1));
+#elif 0
+    unsigned i;
+    for (i=0; i<count; ++i) dst[i] = src[i];
+#else
+    unsigned i;
+    count >>= 1;
+    for (i=0; i<count; ++i) 
+	((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
+#endif
+}
+
+/**
  * @}
  */
 
diff --git a/pjmedia/include/pjmedia/wsola.h b/pjmedia/include/pjmedia/wsola.h
index 86daf36..9592db3 100644
--- a/pjmedia/include/pjmedia/wsola.h
+++ b/pjmedia/include/pjmedia/wsola.h
@@ -58,7 +58,13 @@
     /**
      * Specify that the WSOLA will not be used for PLC.
      */
-    PJMEDIA_WSOLA_NO_PLC = 2
+    PJMEDIA_WSOLA_NO_PLC = 2,
+
+    /**
+     * Specify that the WSOLA will not be used to discard frames in
+     * non-contiguous buffer.
+     */
+    PJMEDIA_WSOLA_NO_DISCARD = 4
 };
 
 
@@ -129,13 +135,17 @@
 
 /**
  * Compress or compact the specified buffer by removing some audio samples
- * from the buffer, without altering the pitch.
+ * from the buffer, without altering the pitch. For this function to work, 
+ * total length of the buffer must be more than twice \a erase_cnt.
  * 
  * @param wsola	    WSOLA session.
- * @param buf	    Pointer to buffer. For this function to work, the buffer
- *		    must contain more than twice \a erase_cnt number of 
- *		    samples.
- * @param buf_cnt   Number of samples in the buffer.
+ * @param buf1	    Pointer to buffer. 
+ * @param buf1_cnt  Number of samples in the buffer.
+ * @param buf2	    Pointer to second buffer, if the buffer is not
+ *		    contiguous. Otherwise this parameter must be NULL.
+ * @param buf2_cnt  Number of samples in the second buffer, if the buffer
+ *		    is not contiguous. Otherwise this parameter should be
+ *		    zero.
  * @param erase_cnt On input, specify the number of samples to be erased.
  *		    This function may erase more or less than the requested 
  *		    number, and the actual number of samples erased will be 
@@ -146,8 +156,10 @@
  *		    of the parameters are not valid.
  */
 PJ_DECL(pj_status_t) pjmedia_wsola_discard(pjmedia_wsola *wsola, 
-					   short buf[],
-					   unsigned buf_cnt, 
+					   short buf1[],
+					   unsigned buf1_cnt, 
+					   short buf2[],
+					   unsigned buf2_cnt,
 					   unsigned *erase_cnt);
 
 
diff --git a/pjmedia/src/pjmedia/wsola.c b/pjmedia/src/pjmedia/wsola.c
index 6d3e596..4a35137 100644
--- a/pjmedia/src/pjmedia/wsola.c
+++ b/pjmedia/src/pjmedia/wsola.c
@@ -38,6 +38,9 @@
 /* Generate extra samples, in msec */
 #define GEN_EXTRA_PTIME	(0.0)
 
+/* Number of frames in erase buffer */
+#define ERASE_CNT	((unsigned)3)
+
 
 #ifndef M_PI
 #   define  M_PI	3.14159265358979323846
@@ -66,6 +69,7 @@
     short    *buf;		/* The buffer.				*/
     short    *frm;	        /* Pointer to next frame to play in buf */
     short    *mergebuf;	        /* Temporary merge buffer.		*/
+    short    *erasebuf;		/* Temporary erase buffer.		*/
 #if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
     float    *hanning;		/* Hanning window.			*/
 #else
@@ -329,6 +333,12 @@
 	create_win(pool, &wsola->hanning, wsola->samples_per_frame);
     }
 
+    if ((options & PJMEDIA_WSOLA_NO_DISCARD) == 0) {
+	wsola->erasebuf = (short*)pj_pool_calloc(pool, samples_per_frame *
+						       ERASE_CNT,
+						 sizeof(short));
+    }
+
     *p_wsola = wsola;
     return PJ_SUCCESS;
 
@@ -365,10 +375,10 @@
 	}
 
 	dist = wsola->frm - start;
-	memmove(wsola->frm + frmsz, start + frmsz, 
-		(wsola->buf+wsola->cur_cnt - (start+frmsz)) << 1);
+	pjmedia_move_samples(wsola->frm + frmsz, start + frmsz, 
+			     wsola->buf+wsola->cur_cnt - (start+frmsz));
 
-	memcpy(wsola->frm, wsola->mergebuf, frmsz << 1);
+	pjmedia_copy_samples(wsola->frm, wsola->mergebuf, frmsz);
 
 	wsola->cur_cnt = (pj_uint16_t)(wsola->cur_cnt + dist);
 	generated += dist;
@@ -384,7 +394,7 @@
 
 
 static unsigned compress(pjmedia_wsola *wsola, short *buf, unsigned count,
-			 unsigned erase_cnt)
+			 unsigned del_cnt)
 {
     unsigned samples_del = 0, rep;
 
@@ -393,7 +403,7 @@
 	unsigned frmsz = wsola->samples_per_frame;
 	unsigned dist;
 
-	if (count <= (erase_cnt << 1)) {
+	if (count <= (del_cnt << 1)) {
 	    TRACE_((THIS_FILE, "Not enough samples to compress!"));
 	    return samples_del;
 	}
@@ -414,16 +424,16 @@
 			 wsola->hanning);
 	}
 
-	memmove(buf + frmsz, buf + frmsz + dist,
-		(count - frmsz - dist) * 2);
+	pjmedia_move_samples(buf + frmsz, buf + frmsz + dist,
+			     count - frmsz - dist);
 
 	count -= dist;
 	samples_del += dist;
 
-	if (samples_del >= erase_cnt) {
+	if (samples_del >= del_cnt) {
 	    TRACE_((THIS_FILE, 
 		    "Erased %d of %d requested after %d iteration(s)",
-		    samples_del, erase_cnt, rep));
+		    samples_del, del_cnt, rep));
 	    break;
 	}
     }
@@ -452,11 +462,11 @@
 	    dst[i] = frm[i];
 
 	
-	memcpy(frm, wsola->frm, wsola->samples_per_frame << 1);
+	pjmedia_copy_samples(frm, wsola->frm, wsola->samples_per_frame);
 	wsola->cur_cnt = (pj_uint16_t)(wsola->hist_cnt + 
 				       wsola->samples_per_frame);
-	memmove(wsola->buf, wsola->buf+wsola->samples_per_frame, 
-		wsola->cur_cnt << 1);
+	pjmedia_move_samples(wsola->buf, wsola->buf+wsola->samples_per_frame,
+			      wsola->cur_cnt);
 
     } else {
 	/* Just append to end of buffer */
@@ -465,12 +475,12 @@
 		    "Appending new frame without interpolation"));
 	}
 
-	memcpy(wsola->buf + wsola->cur_cnt, frm, 
-	       wsola->samples_per_frame << 1);
-	memcpy(frm, wsola->frm, 
-	       wsola->samples_per_frame << 1);
-	memmove(wsola->buf, wsola->buf+wsola->samples_per_frame, 
-		wsola->cur_cnt << 1);
+	pjmedia_copy_samples(wsola->buf + wsola->cur_cnt, frm, 
+			     wsola->samples_per_frame);
+	pjmedia_copy_samples(frm, wsola->frm, 
+			     wsola->samples_per_frame);
+	pjmedia_move_samples(wsola->buf, wsola->buf+wsola->samples_per_frame,
+			     wsola->cur_cnt);
     }
     
     wsola->expand_cnt = 0;
@@ -492,9 +502,9 @@
 	/* We have one extra frame in the buffer, just return this frame
 	 * rather than generating a new one.
 	 */
-	memcpy(frm, wsola->frm, wsola->samples_per_frame << 1);
-	memmove(wsola->buf, wsola->buf+wsola->samples_per_frame, 
-	        (wsola->cur_cnt - wsola->samples_per_frame) << 1);
+	pjmedia_copy_samples(frm, wsola->frm, wsola->samples_per_frame);
+	pjmedia_move_samples(wsola->buf, wsola->buf+wsola->samples_per_frame,
+			     wsola->cur_cnt - wsola->samples_per_frame);
 
 	pj_assert(wsola->cur_cnt >= 
 		    wsola->hist_cnt + (wsola->samples_per_frame << 1));
@@ -511,9 +521,9 @@
 	/* Expand buffer */
 	expand(wsola, new_samples);
 
-	memcpy(frm, wsola->frm, wsola->samples_per_frame << 1);
-	memmove(wsola->buf, wsola->buf+wsola->samples_per_frame, 
-		(wsola->cur_cnt - wsola->samples_per_frame) << 1);
+	pjmedia_copy_samples(frm, wsola->frm, wsola->samples_per_frame);
+	pjmedia_move_samples(wsola->buf, wsola->buf+wsola->samples_per_frame,
+			     wsola->cur_cnt - wsola->samples_per_frame);
 
 	pj_assert(wsola->cur_cnt >= 
 		   wsola->hist_cnt + (wsola->samples_per_frame << 1));
@@ -530,15 +540,80 @@
 
 
 PJ_DEF(pj_status_t) pjmedia_wsola_discard( pjmedia_wsola *wsola, 
-					   short buf[],
-					   unsigned buf_cnt, 
-					   unsigned *erase_cnt)
+					   short buf1[],
+					   unsigned buf1_cnt, 
+					   short buf2[],
+					   unsigned buf2_cnt,
+					   unsigned *del_cnt)
 {
-    PJ_ASSERT_RETURN(wsola && buf && buf_cnt && erase_cnt, PJ_EINVAL);
-    PJ_ASSERT_RETURN(*erase_cnt, PJ_EINVAL);
+    PJ_ASSERT_RETURN(wsola && buf1 && buf1_cnt && del_cnt, PJ_EINVAL);
+    PJ_ASSERT_RETURN(*del_cnt, PJ_EINVAL);
 
-    *erase_cnt = compress(wsola, buf, buf_cnt, *erase_cnt);
-    return (*erase_cnt) > 0 ? PJ_SUCCESS : PJ_ETOOSMALL;
+    if (buf2_cnt == 0) {
+	/* Buffer is contiguous space, no need to use temporary
+	 * buffer.
+	 */
+	*del_cnt = compress(wsola, buf1, buf1_cnt, *del_cnt);
+
+    } else {
+	PJ_ASSERT_RETURN(buf2, PJ_EINVAL);
+
+	if (buf1_cnt < ERASE_CNT * wsola->samples_per_frame &&
+	    buf2_cnt < ERASE_CNT * wsola->samples_per_frame &&
+	    wsola->erasebuf == NULL)
+	{
+	    /* We need erasebuf but WSOLA was created with 
+	     * PJMEDIA_WSOLA_NO_DISCARD flag.
+	     */
+	    pj_assert(!"WSOLA need erase buffer!");
+	    return PJ_EINVALIDOP;
+	}
+
+	if (buf2_cnt >= ERASE_CNT * wsola->samples_per_frame) {
+	    *del_cnt = compress(wsola, buf2, buf2_cnt, *del_cnt);
+	} else if (buf1_cnt >= ERASE_CNT * wsola->samples_per_frame) {
+	    unsigned max;
+
+	    *del_cnt = compress(wsola, buf1, buf1_cnt, *del_cnt);
+
+	    max = *del_cnt;
+	    if (max > buf2_cnt)
+		max = buf2_cnt;
+
+	    pjmedia_move_samples(buf1+buf1_cnt-(*del_cnt), buf2, 
+				 max);
+	    if (max < buf2_cnt) {
+		pjmedia_move_samples(buf2, buf2+(*del_cnt), 
+				     buf2_cnt-max);
+	    }
+
+	} else {
+	    unsigned buf_cnt = buf1_cnt + buf2_cnt;
+	    unsigned max;
+
+	    if (buf_cnt > ERASE_CNT * wsola->samples_per_frame)
+		buf_cnt = ERASE_CNT * wsola->samples_per_frame;
+
+	    pjmedia_copy_samples(wsola->erasebuf, buf1, buf1_cnt);
+	    pjmedia_copy_samples(wsola->erasebuf+buf1_cnt, buf2, 
+				 buf_cnt-buf1_cnt);
+
+	    *del_cnt = compress(wsola, wsola->erasebuf, buf_cnt, *del_cnt);
+
+	    buf_cnt -= (*del_cnt);
+
+	    max = buf_cnt;
+	    if (max > buf1_cnt)
+		max = buf1_cnt;
+	    pjmedia_copy_samples(buf1, wsola->erasebuf, max);
+
+	    if (max < buf_cnt) {
+		pjmedia_copy_samples(buf2, wsola->erasebuf+max, buf_cnt-max);
+	    }
+	}
+    }
+
+    return (*del_cnt) > 0 ? PJ_SUCCESS : PJ_ETOOSMALL;
 }