blob: 1cae14a7ca1088bc3cf3094406137fdba2f60eac [file] [log] [blame]
/* $Id$ */
/*
* 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"
#define SAFE_SHRINKING_DIFF 1
#define MIN_SHRINK_GAP_MSEC 200
typedef struct jb_framelist_t
{
char *flist_buffer;
int *flist_frame_type;
pj_size_t *flist_content_len;
pj_uint32_t *flist_bit_info;
unsigned flist_frame_size;
unsigned flist_max_count;
unsigned flist_empty;
unsigned flist_head;
unsigned flist_tail;
unsigned flist_origin;
} jb_framelist_t;
struct pjmedia_jbuf
{
pj_str_t name; // jitter buffer name
jb_framelist_t jb_framelist;
pj_size_t jb_frame_size; // frame size
unsigned jb_frame_ptime; // frame duration.
pj_size_t jb_max_count; // max frames in the jitter framelist->flist_buffer
int jb_level; // delay between source & destination
// (calculated according of the number of 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 on the framelist->flist_buffer (put/get)
int jb_last_seq_no; // seq no. of the last frame inserted to the framelist->flist_buffer
int jb_prefetch; // no. of frame to insert before removing some
// (at the beginning of the framelist->flist_buffer operation)
int jb_prefetch_cnt; // prefetch counter
int jb_def_prefetch; // Default prefetch
int jb_min_prefetch; // Minimum allowable prefetch
int jb_max_prefetch; // Maximum allowable prefetch
int jb_status; // status is 'init' until the first 'put' operation
pj_math_stat jb_delay; // Delay statistics of jitter buffer (in frame unit)
unsigned jb_last_del_seq; // Seq # of last frame deleted
unsigned jb_min_shrink_gap; // How often can we shrink
};
#define JB_STATUS_INITIALIZING 0
#define JB_STATUS_PROCESSING 1
#define JB_STATUS_PREFETCHING 2
/* 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_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->flist_frame_size = frame_size;
framelist->flist_max_count = max_count;
framelist->flist_buffer = (char*)
pj_pool_zalloc(pool,
framelist->flist_frame_size *
framelist->flist_max_count);
framelist->flist_frame_type = (int*)
pj_pool_zalloc(pool, sizeof(framelist->flist_frame_type[0]) *
framelist->flist_max_count);
framelist->flist_content_len = (pj_size_t*)
pj_pool_zalloc(pool, sizeof(framelist->flist_content_len[0]) *
framelist->flist_max_count);
framelist->flist_bit_info = (pj_uint32_t*)
pj_pool_zalloc(pool, sizeof(framelist->flist_bit_info[0]) *
framelist->flist_max_count);
framelist->flist_empty = 1;
return PJ_SUCCESS;
}
static pj_status_t jb_framelist_destroy(jb_framelist_t *framelist)
{
PJ_UNUSED_ARG(framelist);
return PJ_SUCCESS;
}
static unsigned jb_framelist_size(jb_framelist_t *framelist)
{
if (framelist->flist_tail == framelist->flist_head) {
return framelist->flist_empty ? 0 : framelist->flist_max_count;
} else {
return (framelist->flist_tail - framelist->flist_head +
framelist->flist_max_count) % framelist->flist_max_count;
}
}
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)
{
if (!framelist->flist_empty) {
pj_memcpy(frame,
framelist->flist_buffer +
framelist->flist_head * framelist->flist_frame_size,
framelist->flist_frame_size);
*p_type = (pjmedia_jb_frame_type)
framelist->flist_frame_type[framelist->flist_head];
if (size)
*size = framelist->flist_content_len[framelist->flist_head];
if (bit_info)
*bit_info = framelist->flist_bit_info[framelist->flist_head];
pj_bzero(framelist->flist_buffer +
framelist->flist_head * framelist->flist_frame_size,
framelist->flist_frame_size);
framelist->flist_frame_type[framelist->flist_head] =
PJMEDIA_JB_MISSING_FRAME;
framelist->flist_content_len[framelist->flist_head] = 0;
framelist->flist_origin++;
framelist->flist_head = (framelist->flist_head + 1 ) %
framelist->flist_max_count;
if (framelist->flist_head == framelist->flist_tail)
framelist->flist_empty = PJ_TRUE;
return PJ_TRUE;
} else {
pj_bzero(frame, framelist->flist_frame_size);
return PJ_FALSE;
}
}
static void jb_framelist_remove_head( jb_framelist_t *framelist,
unsigned count)
{
unsigned cur_size;
cur_size = jb_framelist_size(framelist);
if (count > cur_size)
count = cur_size;
if (count) {
// may be done in two steps if overlapping
unsigned step1,step2;
unsigned tmp = framelist->flist_head+count;
if (tmp > framelist->flist_max_count) {
step1 = framelist->flist_max_count - framelist->flist_head;
step2 = count-step1;
} else {
step1 = count;
step2 = 0;
}
pj_bzero(framelist->flist_buffer +
framelist->flist_head * framelist->flist_frame_size,
step1*framelist->flist_frame_size);
pj_memset(framelist->flist_frame_type+framelist->flist_head,
PJMEDIA_JB_MISSING_FRAME,
step1*sizeof(framelist->flist_frame_type[0]));
pj_bzero(framelist->flist_content_len+framelist->flist_head,
step1*sizeof(framelist->flist_content_len[0]));
if (step2) {
pj_bzero( framelist->flist_buffer,
step2*framelist->flist_frame_size);
pj_memset(framelist->flist_frame_type,
PJMEDIA_JB_MISSING_FRAME,
step2*sizeof(framelist->flist_frame_type[0]));
pj_bzero (framelist->flist_content_len,
step2*sizeof(framelist->flist_content_len[0]));
}
// update pointers
framelist->flist_origin += count;
framelist->flist_head = (framelist->flist_head + count) %
framelist->flist_max_count;
if (framelist->flist_head == framelist->flist_tail)
framelist->flist_empty = PJ_TRUE;
}
}
static pj_bool_t jb_framelist_put_at(jb_framelist_t *framelist,
unsigned index,
const void *frame,
unsigned frame_size,
pj_uint32_t bit_info)
{
unsigned where;
assert(frame_size <= framelist->flist_frame_size);
if (!framelist->flist_empty) {
unsigned max_index;
unsigned cur_size;
// too late
if (index < framelist->flist_origin)
return PJ_FALSE;
// too soon
max_index = framelist->flist_origin + framelist->flist_max_count - 1;
if (index > max_index)
return PJ_FALSE;
where = (index - framelist->flist_origin + framelist->flist_head) %
framelist->flist_max_count;
// update framelist->flist_tail pointer
cur_size = jb_framelist_size(framelist);
if (index >= framelist->flist_origin + cur_size) {
unsigned diff = (index - (framelist->flist_origin + cur_size));
framelist->flist_tail = (framelist->flist_tail + diff + 1) %
framelist->flist_max_count;
}
} else {
where = framelist->flist_tail;
framelist->flist_origin = index;
framelist->flist_tail = (framelist->flist_tail + 1) %
framelist->flist_max_count;
framelist->flist_empty = PJ_FALSE;
}
pj_memcpy(framelist->flist_buffer + where * framelist->flist_frame_size,
frame, frame_size);
framelist->flist_frame_type[where] = PJMEDIA_JB_NORMAL_FRAME;
framelist->flist_content_len[where] = frame_size;
framelist->flist_bit_info[where] = bit_info;
return PJ_TRUE;
}
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->name, name);
jb->jb_frame_size = frame_size;
jb->jb_frame_ptime = ptime;
jb->jb_last_seq_no = -1;
jb->jb_level = 0;
jb->jb_last_op = JB_OP_INIT;
jb->jb_prefetch = PJ_MIN(PJMEDIA_JB_DEFAULT_INIT_DELAY,max_count*4/5);
jb->jb_prefetch_cnt = 0;
jb->jb_min_prefetch = 0;
jb->jb_max_prefetch = max_count*4/5;
jb->jb_stable_hist = 0;
jb->jb_status = JB_STATUS_INITIALIZING;
jb->jb_max_hist_level = 0;
jb->jb_max_count = max_count;
jb->jb_min_shrink_gap= MIN_SHRINK_GAP_MSEC / ptime;
pj_math_stat_init(&jb->jb_delay);
*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_def_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_def_prefetch = prefetch;
jb->jb_min_prefetch = min_prefetch;
jb->jb_max_prefetch = max_prefetch;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb)
{
jb->jb_last_seq_no = -1;
jb->jb_level = 0;
jb->jb_last_op = JB_OP_INIT;
jb->jb_prefetch_cnt = 0;
jb->jb_stable_hist = 0;
jb->jb_status = JB_STATUS_INITIALIZING;
jb->jb_max_hist_level = 0;
jb_framelist_remove_head(&jb->jb_framelist,
jb_framelist_size(&jb->jb_framelist));
pj_math_stat_init(&jb->jb_delay);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_jbuf_destroy(pjmedia_jbuf *jb)
{
return jb_framelist_destroy(&jb->jb_framelist);
}
static void jbuf_calculate_jitter(pjmedia_jbuf *jb)
{
int diff, cur_size;
cur_size = jb_framelist_size(&jb->jb_framelist);
/* Only apply burst-level calculation on PUT operation since if VAD is
* active the burst-level may not be accurate.
*/
if (jb->jb_last_op == JB_OP_PUT) {
jb->jb_max_hist_level = PJ_MAX(jb->jb_max_hist_level,jb->jb_level);
/* Level is decreasing */
if (jb->jb_level < jb->jb_prefetch) {
enum { STABLE_HISTORY_LIMIT = 100 };
jb->jb_stable_hist++;
/* Only update the prefetch if 'stable' condition is reached
* (not just short time impulse)
*/
if (jb->jb_stable_hist > STABLE_HISTORY_LIMIT) {
diff = (jb->jb_prefetch - jb->jb_max_hist_level) / 3;
if (diff < 1)
diff = 1;
/* Update max_hist_level. */
jb->jb_max_hist_level = jb->jb_prefetch;
jb->jb_prefetch -= diff;
if (jb->jb_prefetch < jb->jb_min_prefetch)
jb->jb_prefetch = jb->jb_min_prefetch;
jb->jb_stable_hist = 0;
TRACE__((jb->name.ptr,"jb updated(1), prefetch=%d, size=%d",
jb->jb_prefetch, cur_size));
}
}
/* Level is increasing */
else if (jb->jb_level > jb->jb_prefetch) {
/* Instaneous set prefetch */
jb->jb_prefetch = PJ_MIN(jb->jb_max_hist_level,
(int)(jb->jb_max_count*4/5));
if (jb->jb_prefetch > jb->jb_max_prefetch)
jb->jb_prefetch = jb->jb_max_prefetch;
jb->jb_stable_hist = 0;
// Keep max_hist_level.
//jb->jb_max_hist_level = 0;
TRACE__((jb->name.ptr,"jb updated(2), prefetch=%d, size=%d",
jb->jb_prefetch, cur_size));
}
/* Level is unchanged */
else {
jb->jb_stable_hist = 0;
}
}
/* These code is used for shortening the delay in the jitter buffer. */
// Shrinking based on max_hist_level (recent max level).
//diff = cur_size - jb->jb_prefetch;
diff = cur_size - jb->jb_max_hist_level;
if (diff > SAFE_SHRINKING_DIFF &&
jb->jb_framelist.flist_origin-jb->jb_last_del_seq > jb->jb_min_shrink_gap)
{
/* Shrink slowly */
diff = 1;
/* Drop frame(s)! */
jb_framelist_remove_head(&jb->jb_framelist, diff);
jb->jb_last_del_seq = jb->jb_framelist.flist_origin;
pj_math_stat_update(&jb->jb_delay, cur_size - diff);
TRACE__((jb->name.ptr,
"JB shrinking %d frame(s), size=%d", diff,
jb_framelist_size(&jb->jb_framelist)));
} else {
pj_math_stat_update(&jb->jb_delay, cur_size);
}
jb->jb_level = 0;
}
PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper)
{
if(jb->jb_last_op != oper) {
jbuf_calculate_jitter(jb);
jb->jb_last_op = oper;
}
}
PJ_DEF(void) pjmedia_jbuf_put_frame( pjmedia_jbuf *jb,
const void *frame,
pj_size_t frame_size,
int frame_seq)
{
pjmedia_jbuf_put_frame2(jb, frame, frame_size, 0, frame_seq, 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)
{
pj_size_t min_frame_size;
int seq_diff;
if (jb->jb_last_seq_no == -1) {
jb->jb_last_seq_no = frame_seq - 1;
}
seq_diff = frame_seq - jb->jb_last_seq_no;
jb->jb_last_seq_no = PJ_MAX(jb->jb_last_seq_no, frame_seq);
if (seq_diff > 0) jb->jb_level += seq_diff;
if(jb->jb_status == JB_STATUS_INITIALIZING) {
jb->jb_status = JB_STATUS_PROCESSING;
jb->jb_level = 0;
} else {
jbuf_update(jb, JB_OP_PUT);
}
min_frame_size = PJ_MIN(frame_size, jb->jb_frame_size);
if (seq_diff > 0) {
while (jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
min_frame_size, bit_info) == PJ_FALSE)
{
jb_framelist_remove_head(&jb->jb_framelist,
PJ_MAX(jb->jb_max_count/4,1) );
}
if (jb->jb_prefetch_cnt < jb->jb_prefetch) {
jb->jb_prefetch_cnt += seq_diff;
TRACE__((jb->name.ptr, "PUT prefetch_cnt=%d/%d",
jb->jb_prefetch_cnt, jb->jb_prefetch));
if (jb->jb_status == JB_STATUS_PREFETCHING &&
jb->jb_prefetch_cnt >= jb->jb_prefetch)
{
jb->jb_status = JB_STATUS_PROCESSING;
}
}
if (discarded)
*discarded = PJ_FALSE;
}
else
{
pj_bool_t res;
res = jb_framelist_put_at(&jb->jb_framelist,frame_seq,frame,
min_frame_size, bit_info);
if (discarded)
*discarded = !res;
}
}
/*
* Get frame from jitter buffer.
*/
PJ_DEF(void) pjmedia_jbuf_get_frame( pjmedia_jbuf *jb,
void *frame,
char *p_frame_type)
{
pjmedia_jbuf_get_frame2(jb, frame, NULL, p_frame_type, 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_jb_frame_type ftype;
jb->jb_level++;
jbuf_update(jb, JB_OP_GET);
if (jb_framelist_size(&jb->jb_framelist) == 0) {
jb->jb_prefetch_cnt = 0;
if (jb->jb_def_prefetch)
jb->jb_status = JB_STATUS_PREFETCHING;
}
if (jb->jb_status == JB_STATUS_PREFETCHING &&
jb->jb_prefetch_cnt < jb->jb_prefetch)
{
/* Can't return frame because jitter buffer is filling up
* minimum prefetch.
*/
pj_bzero(frame, jb->jb_frame_size);
if (jb_framelist_size(&jb->jb_framelist) == 0)
*p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
else
*p_frame_type = PJMEDIA_JB_ZERO_PREFETCH_FRAME;
if (size)
*size = 0;
TRACE__((jb->name.ptr, "GET prefetch_cnt=%d/%d",
jb->jb_prefetch_cnt, jb->jb_prefetch));
return;
}
/* Retrieve a frame from frame list */
if (jb_framelist_get(&jb->jb_framelist,frame,size,&ftype,bit_info) ==
PJ_FALSE)
{
/* Can't return frame because jitter buffer is empty! */
pj_bzero(frame, jb->jb_frame_size);
*p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
if (size)
*size = 0;
return;
}
/* 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;
}
/*
* Get jitter buffer state.
*/
PJ_DEF(pj_status_t) pjmedia_jbuf_get_state( pjmedia_jbuf *jb,
pjmedia_jb_state *state )
{
PJ_ASSERT_RETURN(jb && state, PJ_EINVAL);
state->frame_size = jb->jb_frame_size;
state->prefetch = jb->jb_prefetch;
state->min_prefetch = jb->jb_min_prefetch;
state->max_prefetch = jb->jb_max_prefetch;
state->size = jb_framelist_size(&jb->jb_framelist);
state->avg_delay = jb->jb_delay.mean * jb->jb_frame_ptime;
state->min_delay = jb->jb_delay.min * jb->jb_frame_ptime;
state->max_delay = jb->jb_delay.max * jb->jb_frame_ptime;
state->dev_delay = pj_math_stat_get_stddev(&jb->jb_delay) *
jb->jb_frame_ptime;
return PJ_SUCCESS;
}