Index: pjproject/branches/projects/jbuf2/pjmedia/build/pjmedia.vcproj
===================================================================
--- pjproject/branches/projects/jbuf2/pjmedia/build/pjmedia.vcproj	(revision 1769)
+++ pjproject/branches/projects/jbuf2/pjmedia/build/pjmedia.vcproj	(revision 1770)
@@ -511,4 +511,8 @@
 			</File>
 			<File
+				RelativePath="..\src\pjmedia\jbuf2.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\pjmedia\master_port.c"
 				>
@@ -1142,4 +1146,8 @@
 			</File>
 			<File
+				RelativePath="..\include\pjmedia\jbuf2.h"
+				>
+			</File>
+			<File
 				RelativePath="..\include\pjmedia\master_port.h"
 				>
Index: pjproject/branches/projects/jbuf2/pjmedia/include/pjmedia.h
===================================================================
--- pjproject/branches/projects/jbuf2/pjmedia/include/pjmedia.h	(revision 1769)
+++ pjproject/branches/projects/jbuf2/pjmedia/include/pjmedia.h	(revision 1770)
@@ -37,4 +37,5 @@
 #include <pjmedia/g711.h>
 #include <pjmedia/jbuf.h>
+#include <pjmedia/jbuf2.h>
 #include <pjmedia/master_port.h>
 #include <pjmedia/mem_port.h>
Index: pjproject/branches/projects/jbuf2/pjmedia/include/pjmedia/jbuf2.h
===================================================================
--- pjproject/branches/projects/jbuf2/pjmedia/include/pjmedia/jbuf2.h	(revision 1770)
+++ pjproject/branches/projects/jbuf2/pjmedia/include/pjmedia/jbuf2.h	(revision 1770)
@@ -0,0 +1,285 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 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_JBUF2_H__
+#define __PJMEDIA_JBUF2_H__
+
+
+/**
+ * @file jbuf2.h
+ * @brief Adaptive jitter buffer implementation.
+ */
+#include <pjmedia/types.h>
+
+/**
+ * @defgroup PJMED_JBUF2 Adaptive jitter buffer
+ * @ingroup PJMEDIA_FRAME_OP
+ * @{
+ * This section describes PJMEDIA's implementation of de-jitter buffer.
+ * The de-jitter buffer may be set to operate in adaptive mode or fixed
+ * delay mode.
+ *
+ * The jitter buffer is also able to report the status of the current
+ * frame (#pjmedia_jb_frame_type). This status is used for examply by
+ * @ref PJMED_STRM to invoke the codec's @ref PJMED_PLC algorithm.
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Opaque declaration for jitter buffer.
+ */
+typedef struct pjmedia_jb2_t pjmedia_jb2_t;
+
+/**
+ * Types of frame returned by the jitter buffer.
+ */
+typedef enum pjmedia_jb2_frame_type 
+{
+    PJMEDIA_JB_FT_NULL_FRAME	   = 0, /**< Frame not available */
+    PJMEDIA_JB_FT_NORMAL_FRAME	   = 1, /**< Normal encoded frame */
+    PJMEDIA_JB_FT_NORMAL_RAW_FRAME = 2, /**< Normal PCM frame */
+    PJMEDIA_JB_FT_INTERP_RAW_FRAME = 3, /**< Interpolation frame */
+} pjmedia_jb2_frame_type ;
+
+/**
+ * Jitter buffer frame definition.
+ */
+typedef struct pjmedia_jb2_frame
+{
+    pjmedia_jb2_frame_type    type;
+    void		     *buffer;
+    pj_size_t		      size; /* in bytes */
+    pj_uint8_t		      pt;
+    pj_uint16_t		      seq;
+    pj_uint32_t		      ts;
+} pjmedia_jb2_frame;
+
+/*
+ * Jitter buffer state.
+ */
+typedef struct pjmedia_jb2_state
+{
+    /* frames */
+    pj_uint16_t	 level;
+    pj_uint32_t	 frame_cnt;
+    
+    /* samples */
+    pj_int32_t	 drift;
+    pj_uint32_t	 drift_span;
+    pj_uint32_t	 cur_size;
+    pj_uint32_t	 opt_size;
+
+} pjmedia_jb2_state;
+
+
+/*
+ * Jitter buffer statistic.
+ */
+typedef struct pjmedia_jb2_stat
+{
+    /* frames */
+    pj_uint32_t	 lost;
+    pj_uint32_t	 late;
+    pj_uint32_t	 ooo;
+    pj_uint32_t	 out;
+    pj_uint32_t	 in;
+
+    /* ticks */
+    pj_uint32_t	 full;
+    pj_uint32_t	 empty;
+
+    /* samples */
+    pj_uint32_t	 max_size;
+    pj_int32_t	 max_drift;
+    pj_int32_t	 max_drift_span;
+    pj_int32_t	 max_comp;
+
+    /* ticks */
+    pj_uint16_t	 max_level;
+} pjmedia_jb2_stat;
+
+/**
+ * @see pjmedia_jb_frame_type.
+ */
+typedef enum pjmedia_jb2_frame_type pjmedia_jb2_frame_type;
+
+
+/**
+ * This structure describes jitter buffer current status.
+ */
+typedef struct pjmedia_jb2_setting
+{
+    /**
+     * The maximum bytes number of each raw/PCM frame that will be kept in 
+     * the jitter buffer.
+     */
+    pj_size_t	frame_size;
+
+    /**
+     * The number of samples for each frame in GET or PUT operation.
+     * == timestamp interval between two consecutive frames.
+     */
+    unsigned	samples_per_frame;
+
+    /**
+     * Maximum number of frames that can be kept in the jitter buffer. This 
+     * effectively means the maximum delay that may be introduced by this jitter
+     * buffer. Set this to zero for adaptive jitter buffer and non-zero for 
+     * fixed jitter buffer.
+     */
+    unsigned    max_frames;
+
+} pjmedia_jb2_setting;
+
+typedef struct pjmedia_jb2_cb
+{
+    pj_status_t (*decode) (pjmedia_jb2_frame *frame);
+    pj_status_t (*plc)    (pjmedia_jb2_frame *frame);
+    pj_status_t (*cng)    (pjmedia_jb2_frame *frame);
+} pjmedia_jb2_cb;
+
+
+/**
+ * Create an adaptive jitter buffer according to the specification. If
+ * application wants to have a fixed jitter buffer, it may call
+ * #pjmedia_jb2_set_fixed() after the jitter buffer is created.
+ *
+ * This function may allocate large chunk of memory to keep the frames in 
+ * the buffer.
+ *
+ * @param pool		The pool to allocate memory.
+ * @param name		Name to identify the jitter buffer for logging
+ *			purpose.
+ * @param p_jb		Pointer to receive jitter buffer instance.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_jb2_create(pj_pool_t *pool,
+					  const pj_str_t *name,
+					  const pjmedia_jb2_setting *setting,
+					  const pjmedia_jb2_cb *cb,
+					  pjmedia_jb2_t **p_jb);
+
+
+/**
+ * Destroy jitter buffer instance.
+ *
+ * @param jb		The jitter buffer.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_jb2_destroy(pjmedia_jb2_t *jb);
+
+
+/**
+ * Restart jitter. This function flushes all packets in the buffer and
+ * reset the internal sequence number.
+ *
+ * @param jb		The jitter buffer.
+ * 
+ * @param setting	New setting to be applied, it can be NULL
+ *			if user want to use the current setting.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_jb2_reset(pjmedia_jb2_t *jb);
+
+
+/**
+ * Put a frame to the jitter buffer. If the frame can be accepted (based
+ * on the sequence number), the jitter buffer will copy the frame and put
+ * it in the appropriate position in the buffer.
+ *
+ * Application MUST manage it's own synchronization when multiple threads
+ * are accessing the jitter buffer at the same time.
+ *
+ * @param jb		The jitter buffer.
+ * @param frame		Pointer to frame buffer to be stored in the jitter
+ *			buffer.
+ * @param size		The frame size.
+ * @param frame_seq	The frame sequence number.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_jb2_put_frame(pjmedia_jb2_t *jb, 
+					   const pjmedia_jb2_frame *frm);
+
+/**
+ * Get a frame from the jitter buffer. The jitter buffer will return the
+ * oldest frame from it's buffer, when it is available.
+ *
+ * Application MUST manage it's own synchronization when multiple threads
+ * are accessing the jitter buffer at the same time.
+ *
+ * @param jb		The jitter buffer.
+ * @param frame		Buffer to receive the payload from the jitter buffer.
+ *			Application MUST make sure that the buffer has
+ *			appropriate size (i.e. not less than the frame size,
+ *			as specified when the jitter buffer was created).
+ *			The jitter buffer only copied a frame to this 
+ *			buffer when the frame type returned by this function
+ *			is PJMEDIA_JB_NORMAL_FRAME.
+ * @param p_frm_type	Pointer to receive frame type. If jitter buffer is
+ *			currently empty or bufferring, the frame type will
+ *			be set to PJMEDIA_JB_ZERO_FRAME, and no frame will
+ *			be copied. If the jitter buffer detects that frame is
+ *			missing with current sequence number, the frame type
+ *			will be set to PJMEDIA_JB_MISSING_FRAME, and no
+ *			frame will be copied. If there is a frame, the jitter
+ *			buffer will copy the frame to the buffer, and frame
+ *			type will be set to PJMEDIA_JB_NORMAL_FRAME.
+ */
+PJ_DECL(pj_status_t) pjmedia_jb2_get_frame(pjmedia_jb2_t *jb, 
+				           pjmedia_jb2_frame *frm);
+				      
+
+/**
+ * Get jitter buffer current state.
+ *
+ * @param jb		The jitter buffer.
+ * @param state		Buffer to receive jitter buffer state.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_jb2_get_state(pjmedia_jb2_t *jb,
+					   pjmedia_jb2_state *state);
+
+
+/**
+ * Get jitter buffer current statistic.
+ *
+ * @param jb		The jitter buffer.
+ * @param statistic	Buffer to receive jitter buffer statistic.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_jb2_get_stat(pjmedia_jb2_t *jb,
+					  pjmedia_jb2_stat *stat);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+#endif	/* __PJMEDIA_JBUF2_H__ */
Index: pjproject/branches/projects/jbuf2/pjmedia/src/pjmedia/jbuf2.c
===================================================================
--- pjproject/branches/projects/jbuf2/pjmedia/src/pjmedia/jbuf2.c	(revision 1770)
+++ pjproject/branches/projects/jbuf2/pjmedia/src/pjmedia/jbuf2.c	(revision 1770)
@@ -0,0 +1,1206 @@
+/* $Id: jbuf2.c 1266 2007-05-11 15:14:34Z bennylp $ */
+/* 
+ * Copyright (C) 2003-2008 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/jbuf2.h>
+#include <pjmedia/errno.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE   "jbuf2.c"
+
+#if 1
+#  define TRACE__(args)	    PJ_LOG(4,args)
+#else
+#  define TRACE__(args)
+#endif
+
+/* General macro */
+#define	PJ_ABS(x)	((x > 0) ? (x) : -(x))
+#define	PJ_MAX(x, y)	((x > y) ? (x) : (y))
+#define	PJ_MIN(x, y)	((x < y) ? (x) : (y))
+
+/* Max frames specifies maximum frames allowed to be allocated.
+ */
+#define MAX_FRAMES		100
+
+/* History constants specify entries number of history,
+ * and entry life time.
+ */
+#define HISTORY_COUNT		5	/* minimum = 2 */
+#define HISTORY_AGE		50	/* in ticks */
+
+/* Drift margin and span are used for verifying whether drift is acceptable.
+ * Whenever there is unacceptable drift, frames will be cleaned up.
+ * Default: max drift +/-1600 samples in every 8000 samples.
+ */
+#define MAX_DRIFT_MARGIN	2000	/* in samples */
+#define MAX_DRIFT_MARGIN_SPAN	8000	/* in samples */
+
+
+/* AGC & AGC_SET.
+ * Used for updating drift and jb max size.
+ */
+typedef struct AGC_SET {
+    int ATTACK_A;
+    int ATTACK_B;
+    int DECAY_A;
+    int DECAY_B;
+} AGC_SET;
+
+static const AGC_SET AGC_DRIFT_SET = {
+    1, 0,
+    1, 0
+};
+
+#define SIMPLE_AGC(target, current, SET) \
+    if (target > current) \
+	target = ((target)*SET.ATTACK_A + (current)*SET.ATTACK_B) /  \
+		 (SET.ATTACK_A + SET.ATTACK_B); \
+    else \
+	target = ((target)*SET.DECAY_A + (current)*SET.DECAY_B) /  \
+		 (SET.DECAY_A + SET.DECAY_B)
+
+/* Temporary, new error codes */
+#define PJMEDIA_JB2_ELATE 1
+#define PJMEDIA_JB2_EDROP 2
+#define PJMEDIA_JB2_EFAILDECODE 3
+
+/* Comfort noise payload type */
+#define PT_CN 13
+
+#define INC_OPT_SIZE_TO_DRIFT_CALC
+
+/* Terms:
+ * - "tick" is simply heartbeat/counter of JB operations (GET & PUT), 
+ *   used as JB timestamp or timer source.
+ * - "level"/"burst" refers to number of consecutive calls of single operation. 
+ *   JB uses positive level number for PUT and negative for GET.
+ * - "drift" refers to rate difference/skew between PUT and GET.
+ *   JB uses positive drift number if PUT rate is higher than GET rate,
+ *   negative otherwise, 0 if no drift.
+ * - "drift span" is just a way to represent how long drift occurs without 
+ *   floating point involvement. The number is in unit samples based on
+ *   number of samples processed by GET.
+ *   ex: drift/drift_span = -160/8000 means each time GET processed 8000 samples
+ *	 PUT processed less 160 samples.
+ * - "accumulated modulo (span)" in drift compensation is a mechanism to 
+ *   guarantee accuracy on calculating number of samples to be compensated, 
+ *   because all JB calculation is performed in integer.
+ *   ex: current drift = 160/8000, current samples available in the JB = 310,
+ *       when performing drift compensation, JB will calculate that it needs
+ *       to shrink the samples from 310 into 293.8. the '0.8' will be managed by
+ *       the accumulated modulo (span) to be applied later along with other 
+ *       modulo generated by next samples.
+ *
+ * Frames structure:
+ * - Frames are collected in a (double linked) list in incremental order of 
+ *   playing (frame to be played first is in the front).
+ *
+ * History structure:
+ * - JB holds array of history, each element represent condition of JB at 
+ *   specified time t (marked by tick), ordered as the newest is the first.
+ * - History has age, when it is expired, history will be shifted/discarded.
+ *
+ * Drift calculation:
+ * - Performed each time history expired:
+ *   just simply calculate the difference of GET & PUT counter, along with
+ *   drift span calculated from history span.
+ *
+ */
+
+typedef struct jb_frame_list
+{
+    PJ_DECL_LIST_MEMBER(struct jb_frame_list);
+    pjmedia_jb2_frame	frame;
+} jb_frame_list;
+
+typedef struct jb_history
+{
+    pj_uint32_t	 t;
+    pj_uint16_t	 level;
+    pj_uint32_t	 in;
+    pj_uint32_t	 out;
+} jb_history;
+
+typedef struct jb_drift_state
+{
+    pj_int32_t		     pending;		/* pending compensation, in samples */
+    pj_int32_t		     acc_mod;		/* accumulated modulo	    */
+    pj_uint32_t		     acc_mod_span;	/* accumulated modulo span  */
+    pj_uint32_t		     last_ts;		/* ts of last touched frame */
+} jb_drift_state;
+
+struct pjmedia_jb2_t
+{
+    /* general properties */
+    pj_str_t		     name;		/* jitter buffer name	    */
+    pjmedia_jb2_setting	     setting;
+
+    /* memory pool */
+    pj_pool_t		    *pool;
+
+    /* lists */
+    jb_frame_list	     frames_pool;
+    jb_frame_list	     frames;
+
+    /* statistic & state */
+    pjmedia_jb2_stat	     stat;
+    pjmedia_jb2_state	     state;
+
+    /* last get frame */
+    pjmedia_jb2_frame	     last_frame_out;
+
+    /* state update */
+    jb_history		     hist[HISTORY_COUNT];
+
+    /* internal state */
+    pj_int16_t		     cur_level;
+    pj_uint32_t		     tick;
+    pj_bool_t		     idle;
+    jb_drift_state	     drift_state;
+
+    /* callbacks */
+    pjmedia_jb2_cb	     cb;
+};
+
+static void init_jb(pjmedia_jb2_t *jb);
+static void update_state(pjmedia_jb2_t *jb, pj_bool_t on_get);
+static void compensate_drift(pjmedia_jb2_t *jb);
+static pj_status_t decode_frames(pjmedia_jb2_t *jb, jb_frame_list* start, 
+				 int max);
+
+/* Allocate frame,
+ * get from frames pool if available, otherwise allocate from memory.
+ * If max frame is exceeded, just return NULL.
+ */
+static jb_frame_list* alloc_frame(pjmedia_jb2_t *jb)
+{
+    jb_frame_list* f;
+
+    if (!pj_list_empty(&jb->frames_pool)) {
+	f = jb->frames_pool.next;
+	pj_list_erase(f);
+    } else if (jb->state.frame_cnt >= jb->setting.max_frames) {
+	TRACE__((THIS_FILE, "Frame numbers exceeds max(%d)!",
+	    jb->state.frame_cnt, jb->setting.max_frames));
+	return NULL;
+    } else {
+	f = PJ_POOL_ZALLOC_T(jb->pool, jb_frame_list);
+	if (NULL == f) {
+	    TRACE__((THIS_FILE, "Failed to allocate frame!"));
+	    return NULL;
+	}
+	f->frame.buffer = pj_pool_alloc(jb->pool, jb->setting.frame_size);
+	if (NULL == f->frame.buffer) return NULL;
+	++jb->state.frame_cnt;
+    }
+
+    return f;
+}
+
+/* Release frame from JB frames, to frames pool. */
+PJ_INLINE(void) release_frame(pjmedia_jb2_t *jb, jb_frame_list* f)
+{
+    pj_list_erase(f);
+    pj_list_push_back(&jb->frames_pool, f);
+}
+
+
+/* Virtual contigue buffer */
+typedef struct jb_vbuf
+{
+    /* private */
+    pjmedia_jb2_t *jb;
+    jb_frame_list *f[8];
+    int	cnt;
+
+    /* public */
+    int size;
+} jb_vbuf;
+
+static void jb_vbuf_create(pjmedia_jb2_t *jb, jb_vbuf *buf)
+{
+    jb_frame_list *f;
+    int i = 0;
+
+    pj_assert(jb && buf);
+
+    pj_bzero(buf, sizeof(*buf));
+
+    if (pj_list_empty(&jb->frames) || 
+	jb->frames.next->frame.type == PJMEDIA_JB_FT_NORMAL_FRAME)
+    {
+	return;
+    }
+
+    f = jb->frames.next;
+    do {
+	buf->cnt++;
+	buf->size += f->frame.size;
+	buf->f[i++] = f;
+	f = f->next;
+    } while (i < 2 &&
+	     f!=&jb->frames && 
+	     f->frame.type != PJMEDIA_JB_FT_NORMAL_FRAME && 
+	     (f->frame.seq - f->prev->frame.seq <= 1));
+
+    buf->jb = jb;
+}
+
+static pj_int16_t jb_vbuf_get_sample(jb_vbuf *buf, unsigned idx)
+{
+    int i;
+    unsigned idx_ = idx;
+    
+    pj_assert(buf && idx < (unsigned)buf->size/2);
+
+    for (i = 0; i < buf->cnt; ++i) {
+	if (idx_ < buf->f[i]->frame.size/2) {
+	    pj_int16_t *p = (pj_int16_t*) buf->f[i]->frame.buffer;
+	    return p[idx_];
+	}
+	idx_ -= (buf->f[i]->frame.size/2);
+    }
+
+    return 0;
+}
+
+static void jb_vbuf_set_sample(jb_vbuf *buf, unsigned idx, 
+				   pj_int16_t val)
+{
+    int i;
+    unsigned idx_ = idx;
+    
+    pj_assert(buf && idx < (unsigned)buf->size/2);
+
+    for (i = 0; i < buf->cnt; ++i) {
+	if (idx_ < buf->f[i]->frame.size/2) {
+	    pj_int16_t *p = (pj_int16_t*) buf->f[i]->frame.buffer;
+	    p[idx_] = val;
+	    return;
+	}
+	idx_ -= buf->f[i]->frame.size/2;
+    }
+}
+
+/* Can only shrink */
+static void jb_vbuf_shrink_to(jb_vbuf *buf, int size)
+{
+    int i;
+    
+    pj_assert(buf && size < buf->size);
+
+    for (i = buf->cnt-1; i>=0 && buf->size>size; --i) {
+	if ((buf->size - (int)buf->f[i]->frame.size) >= size) {
+	    buf->cnt--;
+	    buf->size -= buf->f[i]->frame.size;
+	    /* modify jb */
+	    buf->jb->state.cur_size -= buf->f[i]->frame.size/2;
+	    buf->f[i]->frame.size = 0;
+	    //release_frame(buf->jb, buf->f[i]);
+	} else {
+	    int diff = buf->size - size;
+	    buf->f[i]->frame.size -= diff;
+	    buf->size -= diff;
+	    /* modify jb */
+	    buf->jb->state.cur_size -= diff/2;
+	}
+    }
+}
+
+static void init_jb(pjmedia_jb2_t *jb)
+{
+    jb_frame_list *f, *next_f;
+    int i;
+
+    /* Reset frames */
+    f = jb->frames.next;
+    while (f != &jb->frames) {
+	next_f = f->next;
+	release_frame(jb, f);
+	f = next_f;
+    }
+    pj_list_init(&jb->frames);
+
+    /* Init last frame out */
+    pj_bzero(&jb->last_frame_out, sizeof(jb->last_frame_out));
+
+    /* Init history entries.
+     * History entries need to be set this way, since drift calculation is 
+     * based on the newest & the oldest history entries, while jitter level
+     * we dont bother as long as level is set to 0.
+     */
+    for (i = 0; i < HISTORY_COUNT; ++i) {
+	jb->hist[i].t = jb->tick;
+	jb->hist[i].level = 0;
+	jb->hist[i].in = jb->stat.in;
+	jb->hist[i].out = jb->stat.out;
+    }
+
+    /* Reset state */
+    i = jb->state.frame_cnt;
+    pj_bzero(&jb->state, sizeof(pjmedia_jb2_state));
+    jb->state.frame_cnt = i;
+    jb->state.level = 1;
+
+    /* Reset drift state */
+    pj_bzero(&jb->drift_state, sizeof(jb_drift_state));
+
+    /* Set not idle */
+    jb->idle = PJ_FALSE;
+
+    /* Reset current level */
+    jb->cur_level = 0;
+    jb->drift_state.acc_mod_span = 1;
+
+    /* Inform user about JB (re)init */
+}
+
+/* Decode all consecutive undecoded frames. */
+static pj_status_t decode_frames(pjmedia_jb2_t *jb, jb_frame_list* start,
+				 int max)
+{
+    jb_frame_list* tmp = start;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(jb && start && jb->cb.decode, PJ_EINVAL);
+
+    /* Decoded already */
+    if (start->frame.type != PJMEDIA_JB_FT_NORMAL_FRAME)
+	return PJ_SUCCESS;
+
+    do {
+	status = jb->cb.decode(&tmp->frame);
+        
+	pj_assert(tmp->frame.size == jb->setting.frame_size);
+
+	if (status != PJ_SUCCESS) {
+	    TRACE__((THIS_FILE,"Failed to decode frame!"));
+	    return PJMEDIA_JB2_EFAILDECODE;
+	}
+	tmp->frame.type = PJMEDIA_JB_FT_NORMAL_RAW_FRAME;
+	tmp = tmp->next;
+        
+	jb->state.cur_size += jb->setting.samples_per_frame;
+
+    } while (tmp != &jb->frames && 
+	     tmp->frame.type == PJMEDIA_JB_FT_NORMAL_FRAME &&
+	     (tmp->frame.ts - tmp->prev->frame.ts == 
+	     jb->setting.samples_per_frame) &&
+	     (!max || --max > 0));
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_jb2_create( pj_pool_t *pool,
+					  const pj_str_t *name,
+					  const pjmedia_jb2_setting *setting,
+					  const pjmedia_jb2_cb *cb,
+					  pjmedia_jb2_t **p_jb )
+{
+    pjmedia_jb2_t *jb;
+
+    PJ_ASSERT_RETURN(pool && setting && cb && p_jb, PJ_EINVAL);
+
+    if (setting->max_frames > MAX_FRAMES) {
+	PJ_LOG(4,(THIS_FILE, "Max frames requested is too big!"));
+	return PJ_EINVAL;
+    }
+
+    jb = PJ_POOL_ZALLOC_T(pool, pjmedia_jb2_t);
+    jb->pool = pool;
+
+    if (name)
+	pj_strdup_with_null(jb->pool, &jb->name, name);
+    else
+	pj_strdup2(pool, &jb->name, "jb2");
+
+    pj_list_init(&jb->frames_pool);
+    pj_list_init(&jb->frames);
+
+    jb->setting = *setting;
+    jb->cb = *cb;
+
+    pjmedia_jb2_reset(jb);
+
+    *p_jb = jb;
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_jb2_destroy(pjmedia_jb2_t *jb)
+{
+    PJ_UNUSED_ARG(jb);
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_jb2_reset(pjmedia_jb2_t *jb)
+{
+    PJ_ASSERT_RETURN(jb, PJ_EINVAL);
+
+    /* Reset tick */
+    jb->tick = 0;
+
+    /* Reset statistic */
+    pj_bzero(&jb->stat, sizeof(pjmedia_jb2_stat));
+
+    if (!jb->setting.max_frames)
+	jb->setting.max_frames = MAX_FRAMES;
+    
+    /* Init buffers */
+    init_jb(jb);
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_jb2_get_state(pjmedia_jb2_t *jb,
+					   pjmedia_jb2_state *state)
+{
+    PJ_ASSERT_RETURN(jb && state, PJ_EINVAL);
+
+    *state = jb->state;
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_jb2_get_stat(pjmedia_jb2_t *jb,
+					  pjmedia_jb2_stat *stat)
+{
+    PJ_ASSERT_RETURN(jb && stat, PJ_EINVAL);
+
+    *stat = jb->stat;
+
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_jb2_put_frame(pjmedia_jb2_t *jb, 
+					  const pjmedia_jb2_frame *f)
+{
+    jb_frame_list *tmp;
+    jb_frame_list *pos;
+    pj_status_t status = PJ_SUCCESS;
+
+    pj_assert(jb && f && f->size <= jb->setting.frame_size);
+
+    ++jb->stat.in;
+    update_state(jb, PJ_FALSE);
+
+    pos = jb->frames.prev;
+
+    if (jb->idle)
+	goto ON_RETURN;
+
+    /* Late? */
+    if (jb->last_frame_out.ts > f->ts) {
+	TRACE__((THIS_FILE,"Packet late!"));
+	++jb->stat.late;
+
+	/* previously, this frame must be assumed lost */
+	--jb->stat.lost;
+	/* then avoid increasing stat.in twice */
+	--jb->stat.in;
+
+	status = PJMEDIA_JB2_ELATE;
+	goto ON_RETURN;
+    }
+
+    /* Locate the appropriate pos when not empty */
+    if (!pj_list_empty(&jb->frames)) {
+	/* Locate node whose smaller ts
+	 * traverse frames reversally, because this frame is potentially sit
+	 * at the end of the list frames.
+	 */
+	while (pos != &jb->frames && pos->frame.ts > f->ts)
+	    pos = pos->prev;
+
+	/* Not at the end, out-of-order? */
+	if (pos != jb->frames.prev) {
+	    ++jb->stat.ooo;
+	    TRACE__((THIS_FILE,"Packet out of order!"));
+	}
+
+	/* JB full? */
+	if (jb->state.cur_size >= 
+	    jb->setting.max_frames * jb->setting.samples_per_frame) 
+	{
+	    ++jb->stat.full;
+	}
+    }
+
+    /* Got the position, insert the frame into JB */
+    tmp = alloc_frame(jb);
+    if (!tmp) return PJ_ENOMEM;
+
+    tmp->frame.pt = f->pt;
+    tmp->frame.seq = f->seq;
+    tmp->frame.size = (f->seq == 1? 160: f->size);
+    tmp->frame.ts = f->ts;
+    tmp->frame.type = f->type;
+    pj_memcpy(tmp->frame.buffer, f->buffer, f->size);
+
+    pj_list_insert_after(pos, tmp);
+
+    /* Decode frame(s) if needed */
+    if (tmp->frame.type == PJMEDIA_JB_FT_NORMAL_RAW_FRAME) {
+	if (f->seq == 1)
+	    jb->state.cur_size += 80;
+	else
+	    jb->state.cur_size += jb->setting.samples_per_frame;
+    }
+    else if (tmp->prev != &jb->frames && 
+	     (tmp->frame.ts - tmp->prev->frame.ts == 
+	     jb->setting.samples_per_frame) &&
+	     tmp->prev->frame.type != PJMEDIA_JB_FT_NORMAL_FRAME)
+    {
+	status = decode_frames(jb, tmp, 0);
+    }
+
+ON_RETURN:
+    return status;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_jb2_get_frame(pjmedia_jb2_t *jb, 
+				          pjmedia_jb2_frame *f)
+{
+    jb_frame_list *tmp, *next_frm;
+    pj_size_t i;
+    pj_status_t status = PJ_SUCCESS;
+
+    pj_assert(jb && f && f->size >= jb->setting.frame_size);
+
+    ++jb->stat.out;
+    update_state(jb, PJ_TRUE);
+
+    if (jb->idle)
+	goto ON_RETURN;
+
+    /* Check first frame */
+    if (!pj_list_empty(&jb->frames)) {
+	tmp = jb->frames.next;
+
+	/* Loss (jumping sequence)? */
+	if (jb->last_frame_out.seq && 
+	    (tmp->frame.seq - jb->last_frame_out.seq > 1))
+	{
+	    jb_frame_list *tmp_plc;
+
+	    /* Generate PLC frame*/
+	    tmp_plc = alloc_frame(jb);
+	    if (!tmp_plc) return PJ_ENOMEM;
+
+	    /* Fill buffer with PLC */
+	    if (jb->cb.plc) {
+		status = jb->cb.plc(&tmp_plc->frame);
+		if (status != PJ_SUCCESS)
+		    return status;
+	    }
+
+	    tmp_plc->frame.type = PJMEDIA_JB_FT_INTERP_RAW_FRAME;
+	    tmp_plc->frame.pt = jb->last_frame_out.pt;
+	    tmp_plc->frame.seq = jb->last_frame_out.seq + 1;
+	    tmp_plc->frame.size = jb->setting.frame_size;
+	    tmp_plc->frame.ts = jb->last_frame_out.ts + 
+				jb->setting.samples_per_frame;
+
+	    pj_list_push_front(&jb->frames, tmp_plc);
+	    ++jb->stat.in;
+	    ++jb->stat.lost;
+	    TRACE__((THIS_FILE,"Frame lost!"));
+
+	    jb->state.cur_size += jb->setting.samples_per_frame;
+	}
+
+	/* Or there is CN packet */
+	else if (tmp->frame.pt == PT_CN) {
+	    unsigned cn_samples;
+	    jb_frame_list *tmp_cn, *pos;
+
+	    /* Generate CN frames */
+	    if (jb->last_frame_out.ts)
+		cn_samples = tmp->frame.ts - jb->last_frame_out.ts;
+	    else
+		cn_samples = jb->setting.samples_per_frame;
+
+	    pos = &jb->frames;
+	    for (i = 0; i<cn_samples; i += jb->setting.samples_per_frame)
+	    {
+		tmp_cn = alloc_frame(jb);
+		if (!tmp_cn) return PJ_ENOMEM;
+
+		/* Fill buffer with CNG */
+		status = jb->cb.cng(&tmp_cn->frame);
+		if (status != PJ_SUCCESS)
+		    return status;
+
+		tmp_cn->frame.type = PJMEDIA_JB_FT_INTERP_RAW_FRAME;
+		tmp_cn->frame.pt = jb->last_frame_out.pt;
+		tmp_cn->frame.seq = tmp->frame.seq;
+		tmp_cn->frame.size = jb->setting.frame_size;
+		tmp_cn->frame.ts = jb->last_frame_out.seq + i +
+				   jb->setting.samples_per_frame;
+
+		pj_list_insert_after(pos, tmp_cn);
+		pos = tmp_cn;
+		jb->state.cur_size += jb->setting.samples_per_frame;
+	    }
+
+	    /* Discard this CN frame, replaced by real PCM frames */
+	    release_frame(jb, tmp);
+
+	    /* Update statistic */
+	    jb->stat.in += cn_samples - 1;
+	}
+
+	/* Or there is undecoded packet */
+	else if (tmp->frame.type == PJMEDIA_JB_FT_NORMAL_FRAME) {
+	    /* Just decode max 2 frames, bec. this might be in speaker thread.
+	     * One for being fetched, one for luring PUT op to decode the rest.
+	     */
+	    status = decode_frames(jb, tmp, 2);
+	    if (status != PJ_SUCCESS)
+		return status;
+	}
+    }
+
+    /* Compensate drift right before any frame fetched */
+    compensate_drift(jb);
+
+    /* Consistency checking, when cur_size!=0, there should be frame(s)
+     * in the JB frames list.
+     */
+    pj_assert((jb->state.cur_size==0) || 
+	      (!pj_list_empty(&jb->frames) && jb->state.cur_size));
+
+    /* Not enough samples in PCM buffer */
+    if (jb->state.cur_size < jb->setting.samples_per_frame) {
+	goto RETURN_EMPTY_FRAME;
+    }
+
+    /* Prepare the requested frame */
+    tmp = jb->frames.next;
+
+    f->type = PJMEDIA_JB_FT_NORMAL_RAW_FRAME;
+    f->pt = tmp->frame.pt;
+    f->seq = tmp->frame.seq;
+    f->size = 0;
+    f->ts = tmp->frame.ts;
+    
+    /* Fill frame buffer, make sure size == setting.frame_size */
+    while (f->size < jb->setting.frame_size) {
+	next_frm = tmp->next;
+
+	if (tmp->frame.size <= jb->setting.frame_size - f->size) {
+	    i = tmp->frame.size;
+	    pj_memcpy((pj_int8_t*)f->buffer + f->size, tmp->frame.buffer, i);
+
+	    /* Update last_frame_out */
+	    jb->last_frame_out = tmp->frame;
+
+	    release_frame(jb, tmp);
+	} else {
+	    i = jb->setting.frame_size - f->size;
+	    pj_memcpy((pj_int8_t*)f->buffer + f->size, tmp->frame.buffer, i);
+	    tmp->frame.size -= i;
+
+	    /* Shift buffer, don't shift the pointer! */
+	    pj_memmove(tmp->frame.buffer, (pj_int8_t*)tmp->frame.buffer + i, 
+		       tmp->frame.size);
+	}
+
+	f->size += i;
+	tmp = next_frm;
+    }
+
+    jb->state.cur_size -= jb->setting.samples_per_frame;
+
+    goto ON_RETURN;
+
+RETURN_EMPTY_FRAME:
+    TRACE__((THIS_FILE,"Return empty frame!"));
+
+    ++jb->stat.empty;
+
+    f->type = PJMEDIA_JB_FT_NULL_FRAME;
+    f->pt = jb->last_frame_out.pt;
+    f->seq = jb->last_frame_out.seq;
+    f->size = jb->setting.frame_size;
+    f->ts = jb->last_frame_out.ts;
+    pj_bzero(f->buffer, f->size);
+
+    /* Instead of return zero samples, it'd be better to return noise */
+
+ON_RETURN:
+    return status;
+}
+
+static void update_state(pjmedia_jb2_t *jb, pj_bool_t on_get)
+{
+    /* Heart beat */
+    ++jb->tick;
+
+    /* Switch operation? */
+    if ((on_get && jb->cur_level > 0) || (!on_get && jb->cur_level < 0)) {
+
+	/* JB idle? */
+	if (jb->idle) {
+	    TRACE__((THIS_FILE, "Idle ended, reinit jitter buffer."));
+	    jb->idle = PJ_FALSE;
+
+	    /* Reinit JB */
+	    init_jb(jb);
+	}
+
+	/* Update level? */
+	else if (PJ_ABS(jb->cur_level) > (int)jb->hist[0].level) {
+	    pj_uint16_t level_sum, level_factor;
+	    pj_uint16_t i;
+
+	    /* Debug purpose */
+	    char dbg_hist[128] = {0};
+	    int  idbg_hist = 0;
+
+	    /* Update level and max size */
+	    jb->hist[0].level = PJ_ABS(jb->cur_level);
+
+	    /* Calculate level */
+	    level_sum = level_factor = 0;
+	    for (i = 0; i < HISTORY_COUNT; ++i) {
+		if (jb->hist[i].level) {
+		    level_sum += jb->hist[i].level * (i+1);
+		    level_factor += i+1;
+		}
+		idbg_hist += sprintf(dbg_hist+idbg_hist, " %d", 
+				     jb->hist[i].level);
+	    }
+	    jb->state.level = level_sum / level_factor;
+	    if (level_sum % level_factor)
+		++jb->state.level;
+
+	    /* Calculate optimum size */
+	    jb->state.opt_size = jb->setting.samples_per_frame * level_sum /
+				 level_factor + jb->setting.samples_per_frame;
+	    if (jb->state.opt_size >
+		jb->setting.max_frames * jb->setting.samples_per_frame)
+	    {
+		TRACE__((THIS_FILE, "Warning: jitter level is too high"));
+		jb->state.opt_size = 
+		    jb->setting.max_frames * jb->setting.samples_per_frame;
+
+	    }
+#ifdef INC_OPT_SIZE_TO_DRIFT_CALC
+	    /* Include opt_size to drift calculation */
+	    jb->state.drift += ((int)jb->state.cur_size - 
+				(int)jb->state.opt_size) 
+			       / 2;
+#endif
+
+	    TRACE__((THIS_FILE, "History=%s Level=%d Opt.size=%d", dbg_hist, 
+				jb->state.level, jb->state.opt_size));
+	}
+	/* Reset level counter */
+	jb->cur_level = 0;
+    }
+
+    /* Increase level counter */
+    jb->cur_level += (on_get? -1 : 1);
+    
+    /* Expired history? */
+    if (jb->tick - jb->hist[0].t >= HISTORY_AGE)
+    {
+	pj_int32_t new_drift, new_drift_span;
+
+	/* Check idle */
+	if (jb->hist[0].in == jb->stat.in || jb->hist[0].out == jb->stat.out) {
+	    if (!jb->idle)
+		TRACE__((THIS_FILE, "Idle operation detected."));
+
+	    jb->idle = PJ_TRUE;
+
+	    goto SHIFT_HISTORY;
+	}
+
+	/* Calculate drift */
+	new_drift = ((jb->stat.in - jb->hist[HISTORY_COUNT-1].in) - 
+		     (jb->stat.out - jb->hist[HISTORY_COUNT-1].out)) *
+		    (int)jb->setting.samples_per_frame;
+	new_drift_span = (jb->stat.in - jb->hist[HISTORY_COUNT-1].in)
+			 * (int)jb->setting.samples_per_frame;
+
+	/* Update drift */
+	SIMPLE_AGC(new_drift, jb->state.drift, AGC_DRIFT_SET);
+	jb->state.drift = new_drift;
+	jb->state.drift_span = new_drift_span;
+
+	TRACE__((THIS_FILE, "Drift info update: %d/%d (in=%d out=%d)",
+	    jb->state.drift, jb->state.drift_span, 
+	    jb->stat.in - jb->hist[HISTORY_COUNT-1].in,
+	    jb->stat.out - jb->hist[HISTORY_COUNT-1].out));
+
+SHIFT_HISTORY:
+	/* Discard oldest entry & init the new entry */
+	pj_memmove(&jb->hist[1], &jb->hist[0], 
+		   sizeof(jb_history) * (HISTORY_COUNT-1));
+	jb->hist[0].t = jb->tick;
+	jb->hist[0].level = 0;
+	jb->hist[0].in = jb->stat.in;
+	jb->hist[0].out = jb->stat.out;
+    }
+
+    /* Finally, statistic updates */
+    if (jb->state.cur_size > jb->stat.max_size)
+	jb->stat.max_size = jb->state.cur_size;
+    if (PJ_ABS(jb->state.drift) > PJ_ABS(jb->stat.max_drift)) {
+	jb->stat.max_drift = jb->state.drift;
+	jb->stat.max_drift_span = jb->state.drift_span;
+    }
+    if (jb->state.level > jb->stat.max_level)
+	jb->stat.max_level = jb->state.level;
+}
+
+
+/* Find matching samples pattern */
+#define MATCH_WINDOW_LEN    8
+
+static pj_status_t find_matched_window(jb_vbuf *buf, pj_bool_t left_ref, 
+				       int pref_dist,
+				       unsigned *ref_, unsigned *match_)
+{
+    const int MATCH_THRESHOLD = MATCH_WINDOW_LEN*1200;
+    unsigned ref, ptr, end;
+    int i, similarity, s1, s2;
+
+    if (buf->size/2 < MATCH_WINDOW_LEN * 2) {
+	TRACE__((THIS_FILE, "Buf size too small (%d) to perform matching",
+		 buf->size));
+	return PJ_ENOTFOUND;
+    }
+
+    /* Check minimum distance */
+    pj_assert(pref_dist >= MATCH_WINDOW_LEN);
+
+    if (left_ref) {
+	ref = 0;
+	end = MATCH_WINDOW_LEN - 1;
+	if (buf->size/2 > pref_dist + MATCH_WINDOW_LEN)
+	    ptr = pref_dist;
+	else
+	    ptr = buf->size/2 - MATCH_WINDOW_LEN - 1;
+    } else {
+	ref = buf->size/2 - MATCH_WINDOW_LEN - 1;
+
+	/* do not +1, this is for insertion, inserting 0 sample is no use */
+	end = ref - MATCH_WINDOW_LEN;
+	if ((int)ref > pref_dist)
+	    ptr = ref - pref_dist;
+	else
+	    ptr = 0;
+    }
+
+    *ref_ = ref;
+
+    while (ptr != end) {
+	similarity = 0;
+	for (i = 0; i < MATCH_WINDOW_LEN; ++i) {
+	    s1 = jb_vbuf_get_sample(buf, ref+i);
+	    s2 = jb_vbuf_get_sample(buf, ptr+i);
+	    similarity += PJ_ABS(s1 - s2);
+	    if (similarity >= MATCH_THRESHOLD)
+		break;
+	}
+
+	if (similarity <= MATCH_THRESHOLD) {
+	    *match_ = ptr;
+	    return PJ_SUCCESS;
+	}
+	ptr += left_ref ? -1 : 1;
+    }
+
+    return PJ_ENOTFOUND;
+}
+
+/* Add n samples to buf, the additional samples will be put in dest,
+ * the buf may be modified but the buf size is not. Function will return
+ * the number of inserted samples.
+ */
+static int insert_samples(jb_vbuf *buf, int n, 
+			  void *dest, pj_size_t dest_size)
+{
+    unsigned ref, match;
+    pj_status_t status;
+    int i, distance;
+    pj_int16_t s1, s2;
+
+    if (n < MATCH_WINDOW_LEN)
+	n = MATCH_WINDOW_LEN;
+
+    if (n > (int)dest_size / 2)
+	n = dest_size / 2;
+
+    status = find_matched_window(buf, PJ_FALSE, n, &ref, &match);
+    if (status != PJ_SUCCESS) 
+	return 0;
+
+    distance = ref - match;
+    pj_assert(distance > 0);
+
+    // memcpy
+    for (i = 0; i < distance; ++i) {
+	s1 = jb_vbuf_get_sample(buf, match + MATCH_WINDOW_LEN + i);
+	*((pj_int16_t*)dest + i) = s1;
+    }
+
+    /* Blend ref & match */
+    for (i = 0; i < MATCH_WINDOW_LEN; ++i) {
+	s1 = jb_vbuf_get_sample(buf, ref + i);
+	s2 = jb_vbuf_get_sample(buf, match + i);
+	jb_vbuf_set_sample(buf, ref + i, (s1 + s2) / 2);
+    }
+
+    return distance;
+}
+
+/* Delete n samples from buf, Function will return the number of 
+ * deleted samples.
+ */
+static int delete_samples(jb_vbuf *buf, int n)
+{
+    unsigned match, ref;
+    pj_status_t status;
+    int i, distance;
+    pj_int16_t s1, s2;
+
+    if (n < MATCH_WINDOW_LEN)
+	n = MATCH_WINDOW_LEN;
+
+    if (n > (int) buf->size / 2)
+	n = buf->size / 2;
+
+    status = find_matched_window(buf, PJ_TRUE, n, &ref, &match);
+    if (status != PJ_SUCCESS) 
+	return 0;
+
+    distance = match - ref;
+    pj_assert(distance > 0);
+
+    /* Blend ref & match */
+    for (i = 0; i < MATCH_WINDOW_LEN; ++i) {
+	s1 = jb_vbuf_get_sample(buf, ref + i);
+	s2 = jb_vbuf_get_sample(buf, match + i);
+	jb_vbuf_set_sample(buf, ref + i, (s1 + s2) / 2);
+    }
+
+    // memmove
+    for (i = 0; i < buf->size/2 - distance - MATCH_WINDOW_LEN; ++i) {
+	s1 = jb_vbuf_get_sample(buf, match + MATCH_WINDOW_LEN + i);
+	jb_vbuf_set_sample(buf, ref + MATCH_WINDOW_LEN + i, s1);
+    }
+
+    jb_vbuf_shrink_to(buf, buf->size - distance*2);
+
+    return distance;
+}
+
+static void compensate_drift(pjmedia_jb2_t *jb)
+{
+    int culprit, orig_cur_size, orig_culprit;
+    jb_vbuf buf;
+    int sample_size, uncomp_cnt;
+    pj_int32_t drift, itmp;
+
+    /* No drift, just leave */
+    if (!jb->state.drift || !jb->state.drift_span) {
+	jb->drift_state.last_ts = 0;
+	return;
+    }
+
+    /* Validate drift by comparing current drift to drift margin constant.
+     * Note that the drift value need to be rescaled to the same span before
+     * comparing.
+     */
+   // drift_span = PJ_MIN(jb->stat.in - jb->hist[HISTORY_COUNT-1].in,
+			//jb->stat.out - jb->hist[HISTORY_COUNT-1].out)
+		 //* (int)jb->setting.samples_per_frame;
+   // drift = jb->state.drift * MAX_DRIFT_MARGIN_SPAN / drift_span;
+    drift = jb->state.drift * MAX_DRIFT_MARGIN_SPAN / 
+	    (int)jb->state.drift_span;
+    if (PJ_ABS(drift) > MAX_DRIFT_MARGIN) {
+	TRACE__((THIS_FILE, "Abnormal drift (%d/%d), reinit jitter buffer!",
+		 drift, MAX_DRIFT_MARGIN_SPAN));
+
+	/* Reinit JB */
+	init_jb(jb);
+
+	return;
+    }
+
+    /* Check target frame, first frame is always PCM */
+    if (pj_list_empty(&jb->frames) || 
+	(jb->drift_state.last_ts && 
+	(jb->frames.next->frame.ts <= jb->drift_state.last_ts)))
+	return;
+
+    /* Get sample size */
+    sample_size = jb->setting.frame_size / jb->setting.samples_per_frame;
+
+    /* Get uncompensated frame count */
+    if (jb->drift_state.last_ts)
+	uncomp_cnt = ((jb->frames.next->frame.ts - jb->drift_state.last_ts) / 
+		     jb->setting.samples_per_frame);
+    else
+	uncomp_cnt = 1;
+
+    /* Create virtual contigue buffer */
+    jb_vbuf_create(jb, &buf);
+    pj_assert(buf.size > 0);
+    jb->drift_state.last_ts = buf.f[buf.cnt-1]->frame.ts;
+
+    /* Calculate number of samples to be inserted/deleted, culprit. */
+    /* Make culprit positive number, somebody says compilers may behave 
+     * differently for negative number division.
+     */
+    itmp = PJ_ABS(jb->state.drift) * buf.cnt * jb->setting.samples_per_frame;
+    culprit = itmp / jb->state.drift_span;
+
+    /* Collect modulo for long term adjustment */
+    itmp = itmp % jb->state.drift_span;
+
+    /* All frames played should be compensated */
+    culprit *= uncomp_cnt;
+    itmp *= uncomp_cnt;
+
+    TRACE__((THIS_FILE, "Introducing new culprit:%d/%d mod:%d cur_size:%d", 
+	     culprit, buf.cnt, itmp, jb->state.cur_size));
+    pj_assert(buf.size/2 <= (int)jb->state.cur_size);
+
+    /* Check if modulo span changed */
+    if (jb->state.drift_span > jb->drift_state.acc_mod_span) {
+	jb->drift_state.acc_mod = jb->drift_state.acc_mod * 
+				  jb->state.drift_span /
+				  jb->drift_state.acc_mod_span;
+	jb->drift_state.acc_mod_span = jb->state.drift_span;
+    } else if (jb->state.drift_span < jb->drift_state.acc_mod_span) {
+	itmp = itmp * jb->drift_state.acc_mod_span / jb->state.drift_span;
+    }
+    jb->drift_state.acc_mod += (jb->state.drift>0 ? itmp : -itmp);
+
+    /* Apply accumulated modulo */
+    if ((unsigned)PJ_ABS(jb->drift_state.acc_mod) >= 
+	jb->drift_state.acc_mod_span)
+    {
+	itmp = jb->drift_state.acc_mod / (int)jb->drift_state.acc_mod_span;
+	itmp = PJ_ABS(itmp);
+
+	culprit += itmp;
+	jb->drift_state.acc_mod %= (int)jb->drift_state.acc_mod_span;
+    }
+
+    /* Apply pending compensation */
+    culprit += jb->drift_state.pending;
+
+    /* Culprit may be negative when pending is negative.
+     * pending the compensation if this happens.
+     */
+    if (culprit < 0) {
+	jb->drift_state.pending = culprit;
+	return;
+    }
+
+    /* Nothing to compensate */
+    if (!culprit)
+	return;
+
+    orig_cur_size = jb->state.cur_size;
+    orig_culprit = culprit;
+
+    /* Apply drift compensation to the PCM frame. */
+
+    /* TODO:
+     * There are couples of methods for shrinking/growing audio samples,
+     * which each of them has its own advantage/disadvantage trade off,
+     * so it may be better to provide modular mechanism to enable algorithm 
+     * switch easier.
+     */
+    
+    /* IN > OUT (need to shrink) */
+    /* Simple method: just drop samples */
+    if (jb->state.drift > 0) {
+
+	int deleted = 0;
+
+	//if (jb->state.cur_size >
+	//    (jb->setting.samples_per_frame + culprit))
+	{
+	    deleted = delete_samples(&buf, culprit);
+	}
+
+	jb->drift_state.pending = culprit - deleted;
+
+	culprit = deleted;
+    } 
+
+    /* IN < OUT ==> (need to grow) */
+    /* Another simple method: insert null samples */
+    else {
+
+	int inserted = 0;
+	jb_frame_list *tmp_f;
+	
+	tmp_f = alloc_frame(jb);
+	if (tmp_f) {
+	    inserted = insert_samples(&buf,
+				      culprit, tmp_f->frame.buffer, 
+				      jb->setting.frame_size);
+
+	    if (inserted > 0) {
+		tmp_f->frame.pt = buf.f[buf.cnt-1]->frame.pt;
+		tmp_f->frame.seq = buf.f[buf.cnt-1]->frame.seq;
+		tmp_f->frame.size = inserted * sample_size;
+		tmp_f->frame.ts = buf.f[buf.cnt-1]->frame.ts;
+		tmp_f->frame.type = buf.f[buf.cnt-1]->frame.type;
+    	    
+		jb->state.cur_size += inserted;
+		pj_list_insert_after(buf.f[buf.cnt-1], tmp_f);
+	    } else {
+		pj_list_push_back(&jb->frames_pool, tmp_f);
+	    }
+	}
+	jb->drift_state.pending = culprit - inserted;
+
+	culprit = inserted;
+    }
+
+    if (culprit > PJ_ABS(jb->stat.max_comp))
+	jb->stat.max_comp = jb->state.drift > 0 ? -culprit : culprit;
+
+    TRACE__((THIS_FILE, "COMPENSATE drift:%d/%d cur_size:%d->%d applied:%d/%d "
+			"mod:%d/%d",
+			jb->state.drift, jb->state.drift_span, 
+			orig_cur_size, jb->state.cur_size, 
+			culprit, orig_culprit,
+			jb->drift_state.acc_mod, jb->drift_state.acc_mod_span));
+}
+
+
