summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia/jbuf.c
diff options
context:
space:
mode:
authorDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
committerDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
commitf3ab456a17af1c89a6e3be4d20c5944853df1cb0 (patch)
treed00e1a332cd038a6d906a1ea0ac91e1a4458e617 /pjmedia/src/pjmedia/jbuf.c
Import pjproject-2.0.1
Diffstat (limited to 'pjmedia/src/pjmedia/jbuf.c')
-rw-r--r--pjmedia/src/pjmedia/jbuf.c1190
1 files changed, 1190 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/jbuf.c b/pjmedia/src/pjmedia/jbuf.c
new file mode 100644
index 0000000..74f6469
--- /dev/null
+++ b/pjmedia/src/pjmedia/jbuf.c
@@ -0,0 +1,1190 @@
+/* $Id: jbuf.c 4100 2012-04-26 16:57:47Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * 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
+ */
+/*
+ * Based on implementation kindly contributed by Switchlab, Ltd.
+ */
+#include <pjmedia/jbuf.h>
+#include <pjmedia/errno.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "jbuf.c"
+
+
+/* Invalid sequence number, used as the initial value. */
+#define INVALID_OFFSET -9999
+
+/* Maximum burst length, whenever an operation is bursting longer than
+ * this value, JB will assume that the opposite operation was idle.
+ */
+#define MAX_BURST_MSEC 1000
+
+/* Number of OP switches to be performed in JB_STATUS_INITIALIZING, before
+ * JB can switch its states to JB_STATUS_PROCESSING.
+ */
+#define INIT_CYCLE 10
+
+
+/* Minimal difference between JB size and 2*burst-level to perform
+ * JB shrinking in static discard algorithm.
+ */
+#define STA_DISC_SAFE_SHRINKING_DIFF 1
+
+
+/* Struct of JB internal buffer, represented in a circular buffer containing
+ * frame content, frame type, frame length, and frame bit info.
+ */
+typedef struct jb_framelist_t
+{
+ /* Settings */
+ unsigned frame_size; /**< maximum size of frame */
+ unsigned max_count; /**< maximum number of frames */
+
+ /* Buffers */
+ char *content; /**< frame content array */
+ int *frame_type; /**< frame type array */
+ pj_size_t *content_len; /**< frame length array */
+ pj_uint32_t *bit_info; /**< frame bit info array */
+ pj_uint32_t *ts; /**< timestamp array */
+
+ /* States */
+ unsigned head; /**< index of head, pointed frame
+ will be returned by next GET */
+ unsigned size; /**< current size of framelist,
+ including discarded frames. */
+ unsigned discarded_num; /**< current number of discarded
+ frames. */
+ int origin; /**< original index of flist_head */
+
+} jb_framelist_t;
+
+
+typedef void (*discard_algo)(pjmedia_jbuf *jb);
+static void jbuf_discard_static(pjmedia_jbuf *jb);
+static void jbuf_discard_progressive(pjmedia_jbuf *jb);
+
+
+struct pjmedia_jbuf
+{
+ /* Settings (consts) */
+ pj_str_t jb_name; /**< jitter buffer name */
+ pj_size_t jb_frame_size; /**< frame size */
+ unsigned jb_frame_ptime; /**< frame duration. */
+ pj_size_t jb_max_count; /**< capacity of jitter buffer,
+ in frames */
+ int jb_init_prefetch; /**< Initial prefetch */
+ int jb_min_prefetch; /**< Minimum allowable prefetch */
+ int jb_max_prefetch; /**< Maximum allowable prefetch */
+ int jb_max_burst; /**< maximum possible burst, whenever
+ burst exceeds this value, it
+ won't be included in level
+ calculation */
+ int jb_min_shrink_gap; /**< How often can we shrink */
+ discard_algo jb_discard_algo; /**< Discard algorithm */
+
+ /* Buffer */
+ jb_framelist_t jb_framelist; /**< the buffer */
+
+ /* States */
+ int jb_level; /**< delay between source &
+ destination (calculated according
+ of the number of burst get/put
+ operations) */
+ int jb_max_hist_level; /**< max level during the last level
+ calculations */
+ int jb_stable_hist; /**< num of times the delay has been
+ lower then the prefetch num */
+ int jb_last_op; /**< last operation executed
+ (put/get) */
+ int jb_eff_level; /**< effective burst level */
+ int jb_prefetch; /**< no. of frame to insert before
+ removing some (at the beginning
+ of the framelist->content
+ operation), the value may be
+ continuously updated based on
+ current frame burst level. */
+ pj_bool_t jb_prefetching; /**< flag if jbuf is prefetching. */
+ int jb_status; /**< status is 'init' until the first
+ 'put' operation */
+ int jb_init_cycle_cnt; /**< status is 'init' until the first
+ 'put' operation */
+
+ int jb_discard_ref; /**< Seq # of last frame deleted or
+ discarded */
+ unsigned jb_discard_dist; /**< Distance from jb_discard_ref
+ to perform discard (in frm) */
+
+ /* Statistics */
+ pj_math_stat jb_delay; /**< Delay statistics of jitter buffer
+ (in ms) */
+ pj_math_stat jb_burst; /**< Burst statistics (in frames) */
+ unsigned jb_lost; /**< Number of lost frames. */
+ unsigned jb_discard; /**< Number of discarded frames. */
+ unsigned jb_empty; /**< Number of empty/prefetching frame
+ returned by GET. */
+};
+
+
+#define JB_STATUS_INITIALIZING 0
+#define JB_STATUS_PROCESSING 1
+
+
+
+/* Progressive discard algorithm introduced to reduce JB latency
+ * by discarding incoming frames with adaptive aggressiveness based on
+ * actual burst level.
+ */
+#define PROGRESSIVE_DISCARD 1
+
+/* Internal JB frame flag, discarded frame will not be returned by JB to
+ * application, it's just simply discarded.
+ */
+#define PJMEDIA_JB_DISCARDED_FRAME 1024
+
+
+
+/* Enabling this would log the jitter buffer state about once per
+ * second.
+ */
+#if 1
+# define TRACE__(args) PJ_LOG(5,args)
+#else
+# define TRACE__(args)
+#endif
+
+static pj_status_t jb_framelist_reset(jb_framelist_t *framelist);
+static unsigned jb_framelist_remove_head(jb_framelist_t *framelist,
+ unsigned count);
+
+static pj_status_t jb_framelist_init( pj_pool_t *pool,
+ jb_framelist_t *framelist,
+ unsigned frame_size,
+ unsigned max_count)
+{
+ PJ_ASSERT_RETURN(pool && framelist, PJ_EINVAL);
+
+ pj_bzero(framelist, sizeof(jb_framelist_t));
+
+ framelist->frame_size = frame_size;
+ framelist->max_count = max_count;
+ framelist->content = (char*)
+ pj_pool_alloc(pool,
+ framelist->frame_size*
+ framelist->max_count);
+ framelist->frame_type = (int*)
+ pj_pool_alloc(pool,
+ sizeof(framelist->frame_type[0])*
+ framelist->max_count);
+ framelist->content_len = (pj_size_t*)
+ pj_pool_alloc(pool,
+ sizeof(framelist->content_len[0])*
+ framelist->max_count);
+ framelist->bit_info = (pj_uint32_t*)
+ pj_pool_alloc(pool,
+ sizeof(framelist->bit_info[0])*
+ framelist->max_count);
+ framelist->ts = (pj_uint32_t*)
+ pj_pool_alloc(pool,
+ sizeof(framelist->ts[0])*
+ framelist->max_count);
+
+ return jb_framelist_reset(framelist);
+
+}
+
+static pj_status_t jb_framelist_destroy(jb_framelist_t *framelist)
+{
+ PJ_UNUSED_ARG(framelist);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t jb_framelist_reset(jb_framelist_t *framelist)
+{
+ framelist->head = 0;
+ framelist->origin = INVALID_OFFSET;
+ framelist->size = 0;
+ framelist->discarded_num = 0;
+
+
+ //pj_bzero(framelist->content,
+ // framelist->frame_size *
+ // framelist->max_count);
+
+ pj_memset(framelist->frame_type,
+ PJMEDIA_JB_MISSING_FRAME,
+ sizeof(framelist->frame_type[0]) *
+ framelist->max_count);
+
+ pj_bzero(framelist->content_len,
+ sizeof(framelist->content_len[0]) *
+ framelist->max_count);
+
+ //pj_bzero(framelist->bit_info,
+ // sizeof(framelist->bit_info[0]) *
+ // framelist->max_count);
+
+ return PJ_SUCCESS;
+}
+
+
+static unsigned jb_framelist_size(const jb_framelist_t *framelist)
+{
+ return framelist->size;
+}
+
+
+static unsigned jb_framelist_eff_size(const jb_framelist_t *framelist)
+{
+ return (framelist->size - framelist->discarded_num);
+}
+
+static int jb_framelist_origin(const jb_framelist_t *framelist)
+{
+ return framelist->origin;
+}
+
+
+static pj_bool_t jb_framelist_get(jb_framelist_t *framelist,
+ void *frame, pj_size_t *size,
+ pjmedia_jb_frame_type *p_type,
+ pj_uint32_t *bit_info,
+ pj_uint32_t *ts,
+ int *seq)
+{
+ if (framelist->size) {
+ pj_bool_t prev_discarded = PJ_FALSE;
+
+ /* Skip discarded frames */
+ while (framelist->frame_type[framelist->head] ==
+ PJMEDIA_JB_DISCARDED_FRAME)
+ {
+ jb_framelist_remove_head(framelist, 1);
+ prev_discarded = PJ_TRUE;
+ }
+
+ /* Return the head frame if any */
+ if (framelist->size) {
+ if (prev_discarded) {
+ /* Ticket #1188: when previous frame(s) was discarded, return
+ * 'missing' frame to trigger PLC to get smoother signal.
+ */
+ *p_type = PJMEDIA_JB_MISSING_FRAME;
+ if (size)
+ *size = 0;
+ if (bit_info)
+ *bit_info = 0;
+ } else {
+ pj_memcpy(frame,
+ framelist->content +
+ framelist->head * framelist->frame_size,
+ framelist->frame_size);
+ *p_type = (pjmedia_jb_frame_type)
+ framelist->frame_type[framelist->head];
+ if (size)
+ *size = framelist->content_len[framelist->head];
+ if (bit_info)
+ *bit_info = framelist->bit_info[framelist->head];
+ }
+ if (ts)
+ *ts = framelist->ts[framelist->head];
+ if (seq)
+ *seq = framelist->origin;
+
+ //pj_bzero(framelist->content +
+ // framelist->head * framelist->frame_size,
+ // framelist->frame_size);
+ framelist->frame_type[framelist->head] = PJMEDIA_JB_MISSING_FRAME;
+ framelist->content_len[framelist->head] = 0;
+ framelist->bit_info[framelist->head] = 0;
+ framelist->ts[framelist->head] = 0;
+
+ framelist->origin++;
+ framelist->head = (framelist->head + 1) % framelist->max_count;
+ framelist->size--;
+
+ return PJ_TRUE;
+ }
+ }
+
+ /* No frame available */
+ pj_bzero(frame, framelist->frame_size);
+
+ return PJ_FALSE;
+}
+
+
+static pj_bool_t jb_framelist_peek(jb_framelist_t *framelist,
+ unsigned offset,
+ const void **frame,
+ pj_size_t *size,
+ pjmedia_jb_frame_type *type,
+ pj_uint32_t *bit_info,
+ pj_uint32_t *ts,
+ int *seq)
+{
+ unsigned pos, idx;
+
+ if (offset >= jb_framelist_eff_size(framelist))
+ return PJ_FALSE;
+
+ pos = framelist->head;
+ idx = offset;
+
+ /* Find actual peek position, note there may be discarded frames */
+ while (1) {
+ if (framelist->frame_type[pos] != PJMEDIA_JB_DISCARDED_FRAME) {
+ if (idx == 0)
+ break;
+ else
+ --idx;
+ }
+ pos = (pos + 1) % framelist->max_count;
+ }
+
+ /* Return the frame pointer */
+ if (frame)
+ *frame = framelist->content + pos*framelist->frame_size;
+ if (type)
+ *type = (pjmedia_jb_frame_type)
+ framelist->frame_type[pos];
+ if (size)
+ *size = framelist->content_len[pos];
+ if (bit_info)
+ *bit_info = framelist->bit_info[pos];
+ if (ts)
+ *ts = framelist->ts[pos];
+ if (seq)
+ *seq = framelist->origin + offset;
+
+ return PJ_TRUE;
+}
+
+
+/* Remove oldest frames as many as param 'count' */
+static unsigned jb_framelist_remove_head(jb_framelist_t *framelist,
+ unsigned count)
+{
+ if (count > framelist->size)
+ count = framelist->size;
+
+ if (count) {
+ /* may be done in two steps if overlapping */
+ unsigned step1,step2;
+ unsigned tmp = framelist->head+count;
+ unsigned i;
+
+ if (tmp > framelist->max_count) {
+ step1 = framelist->max_count - framelist->head;
+ step2 = count-step1;
+ } else {
+ step1 = count;
+ step2 = 0;
+ }
+
+ for (i = framelist->head; i < (framelist->head + step1); ++i) {
+ if (framelist->frame_type[i] == PJMEDIA_JB_DISCARDED_FRAME) {
+ pj_assert(framelist->discarded_num > 0);
+ framelist->discarded_num--;
+ }
+ }
+
+ //pj_bzero(framelist->content +
+ // framelist->head * framelist->frame_size,
+ // step1*framelist->frame_size);
+ pj_memset(framelist->frame_type+framelist->head,
+ PJMEDIA_JB_MISSING_FRAME,
+ step1*sizeof(framelist->frame_type[0]));
+ pj_bzero(framelist->content_len+framelist->head,
+ step1*sizeof(framelist->content_len[0]));
+
+ if (step2) {
+ for (i = 0; i < step2; ++i) {
+ if (framelist->frame_type[i] == PJMEDIA_JB_DISCARDED_FRAME) {
+ pj_assert(framelist->discarded_num > 0);
+ framelist->discarded_num--;
+ }
+ }
+ //pj_bzero( framelist->content,
+ // step2*framelist->frame_size);
+ pj_memset(framelist->frame_type,
+ PJMEDIA_JB_MISSING_FRAME,
+ step2*sizeof(framelist->frame_type[0]));
+ pj_bzero (framelist->content_len,
+ step2*sizeof(framelist->content_len[0]));
+ }
+
+ /* update states */
+ framelist->origin += count;
+ framelist->head = (framelist->head + count) % framelist->max_count;
+ framelist->size -= count;
+ }
+
+ return count;
+}
+
+
+static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist,
+ int index,
+ const void *frame,
+ unsigned frame_size,
+ pj_uint32_t bit_info,
+ pj_uint32_t ts,
+ unsigned frame_type)
+{
+ int distance;
+ unsigned pos;
+ enum { MAX_MISORDER = 100 };
+ enum { MAX_DROPOUT = 3000 };
+
+ PJ_ASSERT_RETURN(frame_size <= framelist->frame_size, PJ_EINVAL);
+
+ /* too late or sequence restart */
+ if (index < framelist->origin) {
+ if (framelist->origin - index < MAX_MISORDER) {
+ /* too late */
+ return PJ_ETOOSMALL;
+ } else {
+ /* sequence restart */
+ framelist->origin = index - framelist->size;
+ }
+ }
+
+ /* if jbuf is empty, just reset the origin */
+ if (framelist->size == 0) {
+ pj_assert(framelist->discarded_num == 0);
+ framelist->origin = index;
+ }
+
+ /* get distance of this frame to the first frame in the buffer */
+ distance = index - framelist->origin;
+
+ /* far jump, the distance is greater than buffer capacity */
+ if (distance >= (int)framelist->max_count) {
+ if (distance > MAX_DROPOUT) {
+ /* jump too far, reset the buffer */
+ jb_framelist_reset(framelist);
+ framelist->origin = index;
+ distance = 0;
+ } else {
+ /* otherwise, reject the frame */
+ return PJ_ETOOMANY;
+ }
+ }
+
+ /* get the slot position */
+ pos = (framelist->head + distance) % framelist->max_count;
+
+ /* if the slot is occupied, it must be duplicated frame, ignore it. */
+ if (framelist->frame_type[pos] != PJMEDIA_JB_MISSING_FRAME)
+ return PJ_EEXISTS;
+
+ /* put the frame into the slot */
+ framelist->frame_type[pos] = frame_type;
+ framelist->content_len[pos] = frame_size;
+ framelist->bit_info[pos] = bit_info;
+ framelist->ts[pos] = ts;
+
+ /* update framelist size */
+ if (framelist->origin + (int)framelist->size <= index)
+ framelist->size = distance + 1;
+
+ if(PJMEDIA_JB_NORMAL_FRAME == frame_type) {
+ /* copy frame content */
+ pj_memcpy(framelist->content + pos * framelist->frame_size,
+ frame, frame_size);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t jb_framelist_discard(jb_framelist_t *framelist,
+ int index)
+{
+ unsigned pos;
+
+ PJ_ASSERT_RETURN(index >= framelist->origin &&
+ index < framelist->origin + (int)framelist->size,
+ PJ_EINVAL);
+
+ /* Get the slot position */
+ pos = (framelist->head + (index - framelist->origin)) %
+ framelist->max_count;
+
+ /* Discard the frame */
+ framelist->frame_type[pos] = PJMEDIA_JB_DISCARDED_FRAME;
+ framelist->discarded_num++;
+
+ return PJ_SUCCESS;
+}
+
+
+enum pjmedia_jb_op
+{
+ JB_OP_INIT = -1,
+ JB_OP_PUT = 1,
+ JB_OP_GET = 2
+};
+
+
+PJ_DEF(pj_status_t) pjmedia_jbuf_create(pj_pool_t *pool,
+ const pj_str_t *name,
+ unsigned frame_size,
+ unsigned ptime,
+ unsigned max_count,
+ pjmedia_jbuf **p_jb)
+{
+ pjmedia_jbuf *jb;
+ pj_status_t status;
+
+ jb = PJ_POOL_ZALLOC_T(pool, pjmedia_jbuf);
+
+ status = jb_framelist_init(pool, &jb->jb_framelist, frame_size, max_count);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_strdup_with_null(pool, &jb->jb_name, name);
+ jb->jb_frame_size = frame_size;
+ jb->jb_frame_ptime = ptime;
+ jb->jb_prefetch = PJ_MIN(PJMEDIA_JB_DEFAULT_INIT_DELAY,max_count*4/5);
+ jb->jb_min_prefetch = 0;
+ jb->jb_max_prefetch = max_count*4/5;
+ jb->jb_max_count = max_count;
+ jb->jb_min_shrink_gap= PJMEDIA_JBUF_DISC_MIN_GAP / ptime;
+ jb->jb_max_burst = PJ_MAX(MAX_BURST_MSEC / ptime, max_count*3/4);
+
+ pj_math_stat_init(&jb->jb_delay);
+ pj_math_stat_init(&jb->jb_burst);
+
+ pjmedia_jbuf_set_discard(jb, PJMEDIA_JB_DISCARD_PROGRESSIVE);
+ pjmedia_jbuf_reset(jb);
+
+ *p_jb = jb;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set the jitter buffer to fixed delay mode. The default behavior
+ * is to adapt the delay with actual packet delay.
+ *
+ */
+PJ_DEF(pj_status_t) pjmedia_jbuf_set_fixed( pjmedia_jbuf *jb,
+ unsigned prefetch)
+{
+ PJ_ASSERT_RETURN(jb, PJ_EINVAL);
+ PJ_ASSERT_RETURN(prefetch <= jb->jb_max_count, PJ_EINVAL);
+
+ jb->jb_min_prefetch = jb->jb_max_prefetch =
+ jb->jb_prefetch = jb->jb_init_prefetch = prefetch;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set the jitter buffer to adaptive mode.
+ */
+PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb,
+ unsigned prefetch,
+ unsigned min_prefetch,
+ unsigned max_prefetch)
+{
+ PJ_ASSERT_RETURN(jb, PJ_EINVAL);
+ PJ_ASSERT_RETURN(min_prefetch <= max_prefetch &&
+ prefetch <= max_prefetch &&
+ max_prefetch <= jb->jb_max_count,
+ PJ_EINVAL);
+
+ jb->jb_prefetch = jb->jb_init_prefetch = prefetch;
+ jb->jb_min_prefetch = min_prefetch;
+ jb->jb_max_prefetch = max_prefetch;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_jbuf_set_discard( pjmedia_jbuf *jb,
+ pjmedia_jb_discard_algo algo)
+{
+ PJ_ASSERT_RETURN(jb, PJ_EINVAL);
+ PJ_ASSERT_RETURN(algo >= PJMEDIA_JB_DISCARD_NONE &&
+ algo <= PJMEDIA_JB_DISCARD_PROGRESSIVE,
+ PJ_EINVAL);
+
+ switch(algo) {
+ case PJMEDIA_JB_DISCARD_PROGRESSIVE:
+ jb->jb_discard_algo = &jbuf_discard_progressive;
+ break;
+ case PJMEDIA_JB_DISCARD_STATIC:
+ jb->jb_discard_algo = &jbuf_discard_static;
+ break;
+ default:
+ jb->jb_discard_algo = NULL;
+ break;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb)
+{
+ jb->jb_level = 0;
+ jb->jb_last_op = JB_OP_INIT;
+ jb->jb_stable_hist = 0;
+ jb->jb_status = JB_STATUS_INITIALIZING;
+ jb->jb_init_cycle_cnt= 0;
+ jb->jb_max_hist_level= 0;
+ jb->jb_prefetching = (jb->jb_prefetch != 0);
+ jb->jb_discard_dist = 0;
+
+ jb_framelist_reset(&jb->jb_framelist);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_jbuf_destroy(pjmedia_jbuf *jb)
+{
+ PJ_LOG(5, (jb->jb_name.ptr, ""
+ "JB summary:\n"
+ " size=%d/eff=%d prefetch=%d level=%d\n"
+ " delay (min/max/avg/dev)=%d/%d/%d/%d ms\n"
+ " burst (min/max/avg/dev)=%d/%d/%d/%d frames\n"
+ " lost=%d discard=%d empty=%d",
+ jb_framelist_size(&jb->jb_framelist),
+ jb_framelist_eff_size(&jb->jb_framelist),
+ jb->jb_prefetch, jb->jb_eff_level,
+ jb->jb_delay.min, jb->jb_delay.max, jb->jb_delay.mean,
+ pj_math_stat_get_stddev(&jb->jb_delay),
+ jb->jb_burst.min, jb->jb_burst.max, jb->jb_burst.mean,
+ pj_math_stat_get_stddev(&jb->jb_burst),
+ jb->jb_lost, jb->jb_discard, jb->jb_empty));
+
+ return jb_framelist_destroy(&jb->jb_framelist);
+}
+
+PJ_DEF(pj_bool_t) pjmedia_jbuf_is_full(const pjmedia_jbuf *jb)
+{
+ return jb->jb_framelist.size == jb->jb_framelist.max_count;
+}
+
+static void jbuf_calculate_jitter(pjmedia_jbuf *jb)
+{
+ int diff, cur_size;
+
+ cur_size = jb_framelist_eff_size(&jb->jb_framelist);
+ pj_math_stat_update(&jb->jb_burst, jb->jb_level);
+ jb->jb_max_hist_level = PJ_MAX(jb->jb_max_hist_level, jb->jb_level);
+
+ /* Burst level is decreasing */
+ if (jb->jb_level < jb->jb_eff_level) {
+
+ enum { STABLE_HISTORY_LIMIT = 20 };
+
+ jb->jb_stable_hist++;
+
+ /* Only update the effective level (and prefetch) if 'stable'
+ * condition is reached (not just short time impulse)
+ */
+ if (jb->jb_stable_hist > STABLE_HISTORY_LIMIT) {
+
+ diff = (jb->jb_eff_level - jb->jb_max_hist_level) / 3;
+
+ if (diff < 1)
+ diff = 1;
+
+ /* Update effective burst level */
+ jb->jb_eff_level -= diff;
+
+ /* Update prefetch based on level */
+ if (jb->jb_init_prefetch) {
+ jb->jb_prefetch = jb->jb_eff_level;
+ if (jb->jb_prefetch < jb->jb_min_prefetch)
+ jb->jb_prefetch = jb->jb_min_prefetch;
+ }
+
+ /* Reset history */
+ jb->jb_max_hist_level = 0;
+ jb->jb_stable_hist = 0;
+
+ TRACE__((jb->jb_name.ptr,"jb updated(1), lvl=%d pre=%d, size=%d",
+ jb->jb_eff_level, jb->jb_prefetch, cur_size));
+ }
+ }
+
+ /* Burst level is increasing */
+ else if (jb->jb_level > jb->jb_eff_level) {
+
+ /* Instaneous set effective burst level to recent maximum level */
+ jb->jb_eff_level = PJ_MIN(jb->jb_max_hist_level,
+ (int)(jb->jb_max_count*4/5));
+
+ /* Update prefetch based on level */
+ if (jb->jb_init_prefetch) {
+ jb->jb_prefetch = jb->jb_eff_level;
+ if (jb->jb_prefetch > jb->jb_max_prefetch)
+ jb->jb_prefetch = jb->jb_max_prefetch;
+ }
+
+ jb->jb_stable_hist = 0;
+ /* Do not reset max_hist_level. */
+ //jb->jb_max_hist_level = 0;
+
+ TRACE__((jb->jb_name.ptr,"jb updated(2), lvl=%d pre=%d, size=%d",
+ jb->jb_eff_level, jb->jb_prefetch, cur_size));
+ }
+
+ /* Level is unchanged */
+ else {
+ jb->jb_stable_hist = 0;
+ }
+}
+
+
+static void jbuf_discard_static(pjmedia_jbuf *jb)
+{
+ /* These code is used for shortening the delay in the jitter buffer.
+ * It needs shrink only when there is possibility of drift. Drift
+ * detection is performed by inspecting the jitter buffer size, if
+ * its size is twice of current burst level, there can be drift.
+ *
+ * Moreover, normally drift level is quite low, so JB shouldn't need
+ * to shrink aggresively, it will shrink maximum one frame per
+ * PJMEDIA_JBUF_DISC_MIN_GAP ms. Theoritically, JB may handle drift level
+ * as much as = FRAME_PTIME/PJMEDIA_JBUF_DISC_MIN_GAP * 100%
+ *
+ * Whenever there is drift, where PUT > GET, this method will keep
+ * the latency (JB size) as much as twice of burst level.
+ */
+
+ /* Shrinking due of drift will be implicitly done by progressive discard,
+ * so just disable it when progressive discard is active.
+ */
+ int diff, burst_level;
+
+ burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
+ diff = jb_framelist_eff_size(&jb->jb_framelist) - burst_level*2;
+
+ if (diff >= STA_DISC_SAFE_SHRINKING_DIFF) {
+ int seq_origin;
+
+ /* Check and adjust jb_discard_ref, in case there was
+ * seq restart
+ */
+ seq_origin = jb_framelist_origin(&jb->jb_framelist);
+ if (seq_origin < jb->jb_discard_ref)
+ jb->jb_discard_ref = seq_origin;
+
+ if (seq_origin - jb->jb_discard_ref >= jb->jb_min_shrink_gap)
+ {
+ /* Shrink slowly, one frame per cycle */
+ diff = 1;
+
+ /* Drop frame(s)! */
+ diff = jb_framelist_remove_head(&jb->jb_framelist, diff);
+ jb->jb_discard_ref = jb_framelist_origin(&jb->jb_framelist);
+ jb->jb_discard += diff;
+
+ TRACE__((jb->jb_name.ptr,
+ "JB shrinking %d frame(s), cur size=%d", diff,
+ jb_framelist_eff_size(&jb->jb_framelist)));
+ }
+ }
+}
+
+
+static void jbuf_discard_progressive(pjmedia_jbuf *jb)
+{
+ unsigned cur_size, burst_level, overflow, T, discard_dist;
+ int last_seq;
+
+ /* Should be done in PUT operation */
+ if (jb->jb_last_op != JB_OP_PUT)
+ return;
+
+ /* Check if latency is longer than burst */
+ cur_size = jb_framelist_eff_size(&jb->jb_framelist);
+ burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
+ if (cur_size <= burst_level) {
+ /* Reset any scheduled discard */
+ jb->jb_discard_dist = 0;
+ return;
+ }
+
+ /* Estimate discard duration needed for adjusting latency */
+ if (burst_level <= PJMEDIA_JBUF_PRO_DISC_MIN_BURST)
+ T = PJMEDIA_JBUF_PRO_DISC_T1;
+ else if (burst_level >= PJMEDIA_JBUF_PRO_DISC_MAX_BURST)
+ T = PJMEDIA_JBUF_PRO_DISC_T2;
+ else
+ T = PJMEDIA_JBUF_PRO_DISC_T1 +
+ (PJMEDIA_JBUF_PRO_DISC_T2 - PJMEDIA_JBUF_PRO_DISC_T1) *
+ (burst_level - PJMEDIA_JBUF_PRO_DISC_MIN_BURST) /
+ (PJMEDIA_JBUF_PRO_DISC_MAX_BURST-PJMEDIA_JBUF_PRO_DISC_MIN_BURST);
+
+ /* Calculate current discard distance */
+ overflow = cur_size - burst_level;
+ discard_dist = T / overflow / jb->jb_frame_ptime;
+
+ /* Get last seq number in the JB */
+ last_seq = jb_framelist_origin(&jb->jb_framelist) +
+ jb_framelist_size(&jb->jb_framelist) - 1;
+
+ /* Setup new discard schedule if none, otherwise, update the existing
+ * discard schedule (can be delayed or accelerated).
+ */
+ if (jb->jb_discard_dist == 0) {
+ /* Setup new discard schedule */
+ jb->jb_discard_ref = last_seq;
+ } else if (last_seq < jb->jb_discard_ref) {
+ /* Seq restarted, update discard reference */
+ jb->jb_discard_ref = last_seq;
+ }
+ jb->jb_discard_dist = PJ_MAX(jb->jb_min_shrink_gap, (int)discard_dist);
+
+ /* Check if we need to discard now */
+ if (last_seq >= (jb->jb_discard_ref + (int)jb->jb_discard_dist)) {
+ int discard_seq;
+
+ discard_seq = jb->jb_discard_ref + jb->jb_discard_dist;
+ if (discard_seq < jb_framelist_origin(&jb->jb_framelist))
+ discard_seq = jb_framelist_origin(&jb->jb_framelist);
+
+ jb_framelist_discard(&jb->jb_framelist, discard_seq);
+
+ TRACE__((jb->jb_name.ptr,
+ "Discard #%d: ref=#%d dist=%d orig=%d size=%d/%d "
+ "burst=%d/%d",
+ discard_seq,
+ jb->jb_discard_ref,
+ jb->jb_discard_dist,
+ jb_framelist_origin(&jb->jb_framelist),
+ cur_size,
+ jb_framelist_size(&jb->jb_framelist),
+ jb->jb_eff_level,
+ burst_level));
+
+ /* Update discard reference */
+ jb->jb_discard_ref = discard_seq;
+ }
+}
+
+
+PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper)
+{
+ if(jb->jb_last_op != oper) {
+ jb->jb_last_op = oper;
+
+ if (jb->jb_status == JB_STATUS_INITIALIZING) {
+ /* Switch status 'initializing' -> 'processing' after some OP
+ * switch cycles and current OP is GET (burst level is calculated
+ * based on PUT burst), so burst calculation is guaranted to be
+ * performed right after the status switching.
+ */
+ if (++jb->jb_init_cycle_cnt >= INIT_CYCLE && oper == JB_OP_GET) {
+ jb->jb_status = JB_STATUS_PROCESSING;
+ /* To make sure the burst calculation will be done right after
+ * this, adjust burst level if it exceeds max burst level.
+ */
+ jb->jb_level = PJ_MIN(jb->jb_level, jb->jb_max_burst);
+ } else {
+ jb->jb_level = 0;
+ return;
+ }
+ }
+
+ /* Perform jitter calculation based on PUT burst-level only, since
+ * GET burst-level may not be accurate, e.g: when VAD is active.
+ * Note that when burst-level is too big, i.e: exceeds jb_max_burst,
+ * the GET op may be idle, in this case, we better skip the jitter
+ * calculation.
+ */
+ if (oper == JB_OP_GET && jb->jb_level <= jb->jb_max_burst)
+ jbuf_calculate_jitter(jb);
+
+ jb->jb_level = 0;
+ }
+
+ /* Call discard algorithm */
+ if (jb->jb_status == JB_STATUS_PROCESSING && jb->jb_discard_algo) {
+ (*jb->jb_discard_algo)(jb);
+ }
+}
+
+PJ_DEF(void) pjmedia_jbuf_put_frame( pjmedia_jbuf *jb,
+ const void *frame,
+ pj_size_t frame_size,
+ int frame_seq)
+{
+ pjmedia_jbuf_put_frame3(jb, frame, frame_size, 0, frame_seq, 0, NULL);
+}
+
+PJ_DEF(void) pjmedia_jbuf_put_frame2(pjmedia_jbuf *jb,
+ const void *frame,
+ pj_size_t frame_size,
+ pj_uint32_t bit_info,
+ int frame_seq,
+ pj_bool_t *discarded)
+{
+ pjmedia_jbuf_put_frame3(jb, frame, frame_size, bit_info, frame_seq, 0,
+ discarded);
+}
+
+PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb,
+ const void *frame,
+ pj_size_t frame_size,
+ pj_uint32_t bit_info,
+ int frame_seq,
+ pj_uint32_t ts,
+ pj_bool_t *discarded)
+{
+ pj_size_t min_frame_size;
+ int new_size, cur_size;
+ pj_status_t status;
+
+ cur_size = jb_framelist_eff_size(&jb->jb_framelist);
+
+ /* Attempt to store the frame */
+ min_frame_size = PJ_MIN(frame_size, jb->jb_frame_size);
+ status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
+ min_frame_size, bit_info, ts,
+ PJMEDIA_JB_NORMAL_FRAME);
+
+ /* Jitter buffer is full, remove some older frames */
+ while (status == PJ_ETOOMANY) {
+ int distance;
+ unsigned removed;
+
+ /* Remove as few as possible just to make this frame in. Note that
+ * the cases of seq-jump, out-of-order, and seq restart should have
+ * been handled/normalized by previous call of jb_framelist_put_at().
+ * So we're confident about 'distance' value here.
+ */
+ distance = (frame_seq - jb_framelist_origin(&jb->jb_framelist)) -
+ jb->jb_max_count + 1;
+ pj_assert(distance > 0);
+
+ removed = jb_framelist_remove_head(&jb->jb_framelist, distance);
+ status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
+ min_frame_size, bit_info, ts,
+ PJMEDIA_JB_NORMAL_FRAME);
+
+ jb->jb_discard += removed;
+ }
+
+ /* Get new JB size after PUT */
+ new_size = jb_framelist_eff_size(&jb->jb_framelist);
+
+ /* Return the flag if this frame is discarded */
+ if (discarded)
+ *discarded = (status != PJ_SUCCESS);
+
+ if (status == PJ_SUCCESS) {
+ if (jb->jb_prefetching) {
+ TRACE__((jb->jb_name.ptr, "PUT prefetch_cnt=%d/%d",
+ new_size, jb->jb_prefetch));
+ if (new_size >= jb->jb_prefetch)
+ jb->jb_prefetching = PJ_FALSE;
+ }
+ jb->jb_level += (new_size > cur_size ? new_size-cur_size : 1);
+ jbuf_update(jb, JB_OP_PUT);
+ } else
+ jb->jb_discard++;
+}
+
+/*
+ * Get frame from jitter buffer.
+ */
+PJ_DEF(void) pjmedia_jbuf_get_frame( pjmedia_jbuf *jb,
+ void *frame,
+ char *p_frame_type)
+{
+ pjmedia_jbuf_get_frame3(jb, frame, NULL, p_frame_type, NULL,
+ NULL, NULL);
+}
+
+/*
+ * Get frame from jitter buffer.
+ */
+PJ_DEF(void) pjmedia_jbuf_get_frame2(pjmedia_jbuf *jb,
+ void *frame,
+ pj_size_t *size,
+ char *p_frame_type,
+ pj_uint32_t *bit_info)
+{
+ pjmedia_jbuf_get_frame3(jb, frame, size, p_frame_type, bit_info,
+ NULL, NULL);
+}
+
+/*
+ * Get frame from jitter buffer.
+ */
+PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb,
+ void *frame,
+ pj_size_t *size,
+ char *p_frame_type,
+ pj_uint32_t *bit_info,
+ pj_uint32_t *ts,
+ int *seq)
+{
+ if (jb->jb_prefetching) {
+
+ /* Can't return frame because jitter buffer is filling up
+ * minimum prefetch.
+ */
+
+ //pj_bzero(frame, jb->jb_frame_size);
+ *p_frame_type = PJMEDIA_JB_ZERO_PREFETCH_FRAME;
+ if (size)
+ *size = 0;
+
+ TRACE__((jb->jb_name.ptr, "GET prefetch_cnt=%d/%d",
+ jb_framelist_eff_size(&jb->jb_framelist), jb->jb_prefetch));
+
+ jb->jb_empty++;
+
+ } else {
+
+ pjmedia_jb_frame_type ftype = PJMEDIA_JB_NORMAL_FRAME;
+ pj_bool_t res;
+
+ /* Try to retrieve a frame from frame list */
+ res = jb_framelist_get(&jb->jb_framelist, frame, size, &ftype,
+ bit_info, ts, seq);
+ if (res) {
+ /* We've successfully retrieved a frame from the frame list, but
+ * the frame could be a blank frame!
+ */
+ if (ftype == PJMEDIA_JB_NORMAL_FRAME) {
+ *p_frame_type = PJMEDIA_JB_NORMAL_FRAME;
+ } else {
+ *p_frame_type = PJMEDIA_JB_MISSING_FRAME;
+ jb->jb_lost++;
+ }
+
+ /* Store delay history at the first GET */
+ if (jb->jb_last_op == JB_OP_PUT) {
+ unsigned cur_size;
+
+ /* We've just retrieved one frame, so add one to cur_size */
+ cur_size = jb_framelist_eff_size(&jb->jb_framelist) + 1;
+ pj_math_stat_update(&jb->jb_delay,
+ cur_size*jb->jb_frame_ptime);
+ }
+ } else {
+ /* Jitter buffer is empty */
+ if (jb->jb_prefetch)
+ jb->jb_prefetching = PJ_TRUE;
+
+ //pj_bzero(frame, jb->jb_frame_size);
+ *p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
+ if (size)
+ *size = 0;
+
+ jb->jb_empty++;
+ }
+ }
+
+ jb->jb_level++;
+ jbuf_update(jb, JB_OP_GET);
+}
+
+/*
+ * Get jitter buffer state.
+ */
+PJ_DEF(pj_status_t) pjmedia_jbuf_get_state( const pjmedia_jbuf *jb,
+ pjmedia_jb_state *state )
+{
+ PJ_ASSERT_RETURN(jb && state, PJ_EINVAL);
+
+ state->frame_size = jb->jb_frame_size;
+ state->min_prefetch = jb->jb_min_prefetch;
+ state->max_prefetch = jb->jb_max_prefetch;
+
+ state->burst = jb->jb_eff_level;
+ state->prefetch = jb->jb_prefetch;
+ state->size = jb_framelist_eff_size(&jb->jb_framelist);
+
+ state->avg_delay = jb->jb_delay.mean;
+ state->min_delay = jb->jb_delay.min;
+ state->max_delay = jb->jb_delay.max;
+ state->dev_delay = pj_math_stat_get_stddev(&jb->jb_delay);
+
+ state->avg_burst = jb->jb_burst.mean;
+ state->empty = jb->jb_empty;
+ state->discard = jb->jb_discard;
+ state->lost = jb->jb_lost;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(void) pjmedia_jbuf_peek_frame( pjmedia_jbuf *jb,
+ unsigned offset,
+ const void **frame,
+ pj_size_t *size,
+ char *p_frm_type,
+ pj_uint32_t *bit_info,
+ pj_uint32_t *ts,
+ int *seq)
+{
+ pjmedia_jb_frame_type ftype;
+ pj_bool_t res;
+
+ res = jb_framelist_peek(&jb->jb_framelist, offset, frame, size, &ftype,
+ bit_info, ts, seq);
+ if (!res)
+ *p_frm_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
+ else if (ftype == PJMEDIA_JB_NORMAL_FRAME)
+ *p_frm_type = PJMEDIA_JB_NORMAL_FRAME;
+ else
+ *p_frm_type = PJMEDIA_JB_MISSING_FRAME;
+}
+
+
+PJ_DEF(unsigned) pjmedia_jbuf_remove_frame(pjmedia_jbuf *jb,
+ unsigned frame_cnt)
+{
+ unsigned count, last_discard_num;
+
+ last_discard_num = jb->jb_framelist.discarded_num;
+ count = jb_framelist_remove_head(&jb->jb_framelist, frame_cnt);
+
+ /* Remove some more when there were discarded frames included */
+ while (jb->jb_framelist.discarded_num < last_discard_num) {
+ /* Calculate frames count to be removed next */
+ frame_cnt = last_discard_num - jb->jb_framelist.discarded_num;
+
+ /* Normalize non-discarded frames count just been removed */
+ count -= frame_cnt;
+
+ /* Remove more frames */
+ last_discard_num = jb->jb_framelist.discarded_num;
+ count += jb_framelist_remove_head(&jb->jb_framelist, frame_cnt);
+ }
+
+ return count;
+}