提交 9c475bb6 authored 作者: Michael Jerris's avatar Michael Jerris

backport change from the iaxclient sf repository rev 648, 649, 650, 656.

git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@5158 d0543943-73ff-0310-b7d9-9358b9ac24b2
上级 343d77a4
...@@ -226,6 +226,10 @@ extern unsigned int iax_session_get_capability(struct iax_session *s); ...@@ -226,6 +226,10 @@ extern unsigned int iax_session_get_capability(struct iax_session *s);
extern char iax_pref_codec_add(struct iax_session *session, unsigned int format); extern char iax_pref_codec_add(struct iax_session *session, unsigned int format);
extern void iax_pref_codec_del(struct iax_session *session, unsigned int format); extern void iax_pref_codec_del(struct iax_session *session, unsigned int format);
extern int iax_pref_codec_get(struct iax_session *session, unsigned int *array, int len); extern int iax_pref_codec_get(struct iax_session *session, unsigned int *array, int len);
/* Fine tune jitterbuffer */
extern void iax_set_jb_target_extra( long value );
extern char *iax_get_peer_ip(struct iax_session *session); extern char *iax_get_peer_ip(struct iax_session *session);
extern char *iax_event_get_apparent_ip(struct iax_event *event); extern char *iax_event_get_apparent_ip(struct iax_event *event);
extern void iax_set_samplerate(struct iax_session *session, unsigned short samplemask); extern void iax_set_samplerate(struct iax_session *session, unsigned short samplemask);
......
...@@ -140,6 +140,9 @@ static int netfd = -1; ...@@ -140,6 +140,9 @@ static int netfd = -1;
/* Max timeouts */ /* Max timeouts */
static int maxretries = 10; static int maxretries = 10;
/* configurable jitterbuffer options */
static long jb_target_extra = -1;
static int do_shutdown = 0; static int do_shutdown = 0;
/* external global networking replacements */ /* external global networking replacements */
...@@ -521,6 +524,7 @@ struct iax_session *iax_session_new(void) ...@@ -521,6 +524,7 @@ struct iax_session *iax_session_new(void)
jbconf.max_jitterbuf = 0; jbconf.max_jitterbuf = 0;
jbconf.resync_threshold = 1000; jbconf.resync_threshold = 1000;
jbconf.max_contig_interp = 0; jbconf.max_contig_interp = 0;
jbconf.target_extra = jb_target_extra;
jb_setconf(s->jb, &jbconf); jb_setconf(s->jb, &jbconf);
} }
#endif #endif
...@@ -944,6 +948,12 @@ int iax_shutdown(void) ...@@ -944,6 +948,12 @@ int iax_shutdown(void)
return do_shutdown++; return do_shutdown++;
} }
void iax_set_jb_target_extra( long value )
{
/* store in jb_target_extra, a static global */
jb_target_extra = value ;
}
int iax_init(char *ip, int preferredportno) int iax_init(char *ip, int preferredportno)
{ {
int portno = preferredportno; int portno = preferredportno;
......
/* /*
* jitterbuf: an application-independent jitterbuffer * jitterbuf: an application-independent jitterbuffer
* *
* Copyrights: * Copyrights:
* Copyright (C) 2004-2005, Horizon Wimba, Inc. * Copyright (C) 2004-2005, Horizon Wimba, Inc.
* *
* Contributors: * Contributors:
* Steve Kann <stevek@stevek.com> * Steve Kann <stevek@stevek.com>
* *
* This program is free software, distributed under the terms of * This program is free software, distributed under the terms of
* the GNU Lesser (Library) General Public License * the GNU Lesser (Library) General Public License
* *
* Copyright on this file is disclaimed to Digium for inclusion in Asterisk * Copyright on this file is disclaimed to Digium for inclusion in Asterisk
*/ */
#include "iax2.h" #include "iax2.h"
#include "jitterbuf.h" #include <stdio.h>
#include <stdio.h> #include <stdlib.h>
#include <stdlib.h> #include <string.h>
#include <string.h> #include <sys/types.h>
/* define these here, just for ancient compiler systems */ #include "jitterbuf.h"
#define JB_LONGMAX 2147483647L
#define JB_LONGMIN (-JB_LONGMAX - 1L) /* define these here, just for ancient compiler systems */
#define JB_LONGMAX 2147483647L
/* MS VC can't do __VA_ARGS__ */ #define JB_LONGMIN (-JB_LONGMAX - 1L)
#if defined(WIN32) && defined(_MSC_VER)
#define jb_warn if (warnf) warnf /* MS VC can't do __VA_ARGS__ */
#define jb_err if (errf) errf #if (defined(WIN32) || defined(_WIN32_WCE)) && defined(_MSC_VER)
#define jb_dbg if (dbgf) dbgf #define jb_warn if (warnf) warnf
#define jb_err if (errf) errf
#ifdef DEEP_DEBUG #define jb_dbg if (dbgf) dbgf
#define jb_dbg2 if (dbgf) dbgf
#else #ifdef DEEP_DEBUG
#define jb_dbg2 if (0) dbgf #define jb_dbg2 if (dbgf) dbgf
#endif #else
#define jb_dbg2 if (0) dbgf
#else #endif
#define jb_warn(...) (warnf ? warnf(__VA_ARGS__) : (void)0) #else
#define jb_err(...) (errf ? errf(__VA_ARGS__) : (void)0)
#define jb_dbg(...) (dbgf ? dbgf(__VA_ARGS__) : (void)0) #define jb_warn(...) (warnf ? warnf(__VA_ARGS__) : (void)0)
#define jb_err(...) (errf ? errf(__VA_ARGS__) : (void)0)
#ifdef DEEP_DEBUG #define jb_dbg(...) (dbgf ? dbgf(__VA_ARGS__) : (void)0)
#define jb_dbg2(...) (dbgf ? dbgf(__VA_ARGS__) : (void)0)
#else #ifdef DEEP_DEBUG
#define jb_dbg2(...) ((void)0) #define jb_dbg2(...) (dbgf ? dbgf(__VA_ARGS__) : (void)0)
#endif #else
#define jb_dbg2(...) ((void)0)
#endif #endif
static jb_output_function_t warnf, errf, dbgf; #endif
void jb_setoutput(jb_output_function_t err, jb_output_function_t warn, jb_output_function_t dbg) static jb_output_function_t warnf, errf, dbgf;
{
errf = err; void jb_setoutput(jb_output_function_t err, jb_output_function_t warn, jb_output_function_t dbg)
warnf = warn; {
dbgf = dbg; errf = err;
} warnf = warn;
dbgf = dbg;
static void increment_losspct(jitterbuf *jb) }
{
jb->info.losspct = (100000 + 499 * jb->info.losspct)/500; static void increment_losspct(jitterbuf *jb)
} {
jb->info.losspct = (100000 + 499 * jb->info.losspct)/500;
static void decrement_losspct(jitterbuf *jb) }
{
jb->info.losspct = (499 * jb->info.losspct)/500; static void decrement_losspct(jitterbuf *jb)
} {
jb->info.losspct = (499 * jb->info.losspct)/500;
void jb_reset(jitterbuf *jb) }
{
/* only save settings */ void jb_reset(jitterbuf *jb)
jb_conf s = jb->info.conf; {
memset(jb,0,sizeof(jitterbuf)); /* only save settings */
jb->info.conf = s; jb_conf s = jb->info.conf;
memset(jb, 0, sizeof(*jb));
/* initialize length */ jb->info.conf = s;
jb->info.current = jb->info.target = JB_TARGET_EXTRA;
jb->info.silence_begin_ts = -1; /* initialize length, using the configured value */
} jb->info.current = jb->info.target = jb->info.conf.target_extra;
jb->info.silence_begin_ts = -1;
jitterbuf * jb_new() }
{
jitterbuf *jb; jitterbuf * jb_new()
{
jitterbuf *jb;
jb = malloc(sizeof(jitterbuf));
if(!jb) return NULL; if (!(jb = (jitterbuf *)malloc(sizeof(*jb))))
return NULL;
jb_reset(jb);
jb->info.conf.target_extra = JB_TARGET_EXTRA;
jb_dbg2("jb_new() = %x\n", jb);
return jb; jb_reset(jb);
}
jb_dbg2("jb_new() = %x\n", jb);
void jb_destroy(jitterbuf *jb) return jb;
{ }
jb_frame *frame;
jb_dbg2("jb_destroy(%x)\n", jb); void jb_destroy(jitterbuf *jb)
{
/* free all the frames on the "free list" */ jb_frame *frame;
frame = jb->free; jb_dbg2("jb_destroy(%x)\n", jb);
while(frame != NULL) {
jb_frame *next = frame->next; /* free all the frames on the "free list" */
free(frame); frame = jb->free;
frame = next; while (frame != NULL) {
} jb_frame *next = frame->next;
free(frame);
/* free ourselves! */ frame = next;
free(jb); }
}
/* free ourselves! */
free(jb);
}
/* simple history manipulation */
/* maybe later we can make the history buckets variable size, or something? */
/* drop parameter determines whether we will drop outliers to minimize
* delay */ #if 0
#if 0 static int longcmp(const void *a, const void *b)
static int longcmp(const void *a, const void *b) {
{ return *(long *)a - *(long *)b;
return *(long *)a - *(long *)b; }
} #endif
#endif
/* simple history manipulation */
static int history_put(jitterbuf *jb, time_in_ms_t ts, time_in_ms_t now, long ms) /* maybe later we can make the history buckets variable size, or something? */
{ /* drop parameter determines whether we will drop outliers to minimize
time_in_ms_t delay = now - (ts - jb->info.resync_offset); * delay */
time_in_ms_t threshold = 2 * jb->info.jitter + jb->info.conf.resync_threshold; static int history_put(jitterbuf *jb, time_in_ms_t ts, time_in_ms_t now, long ms)
time_in_ms_t kicked; {
time_in_ms_t delay = now - (ts - jb->info.resync_offset);
/* don't add special/negative times to history */ time_in_ms_t threshold = 2 * jb->info.jitter + jb->info.conf.resync_threshold;
if (ts <= 0) time_in_ms_t kicked;
return 0;
/* don't add special/negative times to history */
/* check for drastic change in delay */ if (ts <= 0)
if (jb->info.conf.resync_threshold != -1) { return 0;
if (iax_abs(delay - jb->info.last_delay) > threshold) {
jb->info.cnt_delay_discont++; /* check for drastic change in delay */
if (jb->info.cnt_delay_discont > 3) { if (jb->info.conf.resync_threshold != -1) {
/* resync the jitterbuffer */ if (iax_abs(delay - jb->info.last_delay) > threshold) {
jb->info.cnt_delay_discont = 0; jb->info.cnt_delay_discont++;
jb->hist_ptr = 0; if (jb->info.cnt_delay_discont > 3) {
jb->hist_maxbuf_valid = 0; /* resync the jitterbuffer */
jb->info.cnt_delay_discont = 0;
jb_warn("Resyncing the jb. last_delay %ld, this delay %ld, threshold %ld, new offset %ld\n", jb->info.last_delay, delay, threshold, ts - now); jb->hist_ptr = 0;
jb->info.resync_offset = ts - now; jb->hist_maxbuf_valid = 0;
jb->info.last_delay = delay = 0; /* after resync, frame is right on time */
} else { jb_warn("Resyncing the jb. last_delay %ld, this delay %ld, threshold %ld, new offset %ld\n", jb->info.last_delay, delay, threshold, ts - now);
return -1; jb->info.resync_offset = ts - now;
} jb->info.last_delay = delay = 0; /* after resync, frame is right on time */
} else { } else {
jb->info.last_delay = delay; return -1;
jb->info.cnt_delay_discont = 0; }
} } else {
} jb->info.last_delay = delay;
jb->info.cnt_delay_discont = 0;
kicked = jb->history[jb->hist_ptr & JB_HISTORY_SZ]; }
}
jb->history[(jb->hist_ptr++) % JB_HISTORY_SZ] = delay;
kicked = jb->history[jb->hist_ptr % JB_HISTORY_SZ];
/* optimization; the max/min buffers don't need to be recalculated, if this packet's
* entry doesn't change them. This happens if this packet is not involved, _and_ any packet jb->history[(jb->hist_ptr++) % JB_HISTORY_SZ] = delay;
* that got kicked out of the history is also not involved
* We do a number of comparisons, but it's probably still worthwhile, because it will usually /* optimization; the max/min buffers don't need to be recalculated,
* succeed, and should be a lot faster than going through all 500 packets in history */ * if this packet's entry doesn't change them. This happens if this
if(!jb->hist_maxbuf_valid) * packet is not involved, _and_ any packet that got kicked out of
return 0; * the history is also not involved. We do a number of comparisons,
* but it's probably still worthwhile, because it will usually
/* don't do this until we've filled history * succeed, and should be a lot faster than going through all 500
* (reduces some edge cases below) */ * packets in history */
if(jb->hist_ptr < JB_HISTORY_SZ) if (!jb->hist_maxbuf_valid)
goto invalidate; return 0;
/* if the new delay would go into min */ /* don't do this until we've filled history
if(delay < jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) * (reduces some edge cases below) */
goto invalidate; if (jb->hist_ptr < JB_HISTORY_SZ)
goto invalidate;
/* or max.. */
if(delay > jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) /* if the new delay would go into min */
goto invalidate; if (delay < jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1])
goto invalidate;
/* or the kicked delay would be in min */
if(kicked <= jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) /* or max.. */
goto invalidate; if (delay > jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1])
goto invalidate;
if(kicked >= jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1])
goto invalidate; /* or the kicked delay would be in min */
if (kicked <= jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1])
/* if we got here, we don't need to invalidate, 'cause this delay didn't goto invalidate;
* affect things */
return 0; if (kicked >= jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1])
/* end optimization */ goto invalidate;
/* if we got here, we don't need to invalidate, 'cause this delay didn't
invalidate: * affect things */
jb->hist_maxbuf_valid = 0; return 0;
return 0; /* end optimization */
}
static void history_calc_maxbuf(jitterbuf *jb) invalidate:
{ jb->hist_maxbuf_valid = 0;
int i,j; return 0;
}
if(jb->hist_ptr == 0) return;
static void history_calc_maxbuf(jitterbuf *jb)
{
/* initialize maxbuf/minbuf to the latest value */ int i,j;
for(i=0;i<JB_HISTORY_MAXBUF_SZ;i++) {
/* if (jb->hist_ptr == 0)
* jb->hist_maxbuf[i] = jb->history[(jb->hist_ptr-1) % JB_HISTORY_SZ]; return;
* jb->hist_minbuf[i] = jb->history[(jb->hist_ptr-1) % JB_HISTORY_SZ];
*/
jb->hist_maxbuf[i] = JB_LONGMIN; /* initialize maxbuf/minbuf to the latest value */
jb->hist_minbuf[i] = JB_LONGMAX; for (i=0;i<JB_HISTORY_MAXBUF_SZ;i++) {
} /*
* jb->hist_maxbuf[i] = jb->history[(jb->hist_ptr-1) % JB_HISTORY_SZ];
/* use insertion sort to populate maxbuf */ * jb->hist_minbuf[i] = jb->history[(jb->hist_ptr-1) % JB_HISTORY_SZ];
/* we want it to be the top "n" values, in order */ */
jb->hist_maxbuf[i] = JB_LONGMIN;
/* start at the beginning, or JB_HISTORY_SZ frames ago */ jb->hist_minbuf[i] = JB_LONGMAX;
i = (jb->hist_ptr > JB_HISTORY_SZ) ? (jb->hist_ptr - JB_HISTORY_SZ) : 0; }
for(;i<jb->hist_ptr;i++) { /* use insertion sort to populate maxbuf */
time_in_ms_t toins = jb->history[i % JB_HISTORY_SZ]; /* we want it to be the top "n" values, in order */
/* if the maxbuf should get this */ /* start at the beginning, or JB_HISTORY_SZ frames ago */
if(toins > jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) { i = (jb->hist_ptr > JB_HISTORY_SZ) ? (jb->hist_ptr - JB_HISTORY_SZ) : 0;
/* insertion-sort it into the maxbuf */ for (;i<jb->hist_ptr;i++) {
for(j=0;j<JB_HISTORY_MAXBUF_SZ;j++) { time_in_ms_t toins = jb->history[i % JB_HISTORY_SZ];
/* found where it fits */
if(toins > jb->hist_maxbuf[j]) { /* if the maxbuf should get this */
/* move over */ if (toins > jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) {
memmove(jb->hist_maxbuf+j+1,jb->hist_maxbuf+j, (JB_HISTORY_MAXBUF_SZ-(j+1)) * sizeof(long));
/* insert */ /* insertion-sort it into the maxbuf */
jb->hist_maxbuf[j] = toins; for (j=0;j<JB_HISTORY_MAXBUF_SZ;j++) {
/* found where it fits */
break; if (toins > jb->hist_maxbuf[j]) {
} /* move over */
} memmove(jb->hist_maxbuf + j + 1, jb->hist_maxbuf + j, (JB_HISTORY_MAXBUF_SZ - (j + 1)) * sizeof(jb->hist_maxbuf[0]));
} /* insert */
jb->hist_maxbuf[j] = toins;
/* if the minbuf should get this */
if(toins < jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) { break;
}
/* insertion-sort it into the maxbuf */ }
for(j=0;j<JB_HISTORY_MAXBUF_SZ;j++) { }
/* found where it fits */
if(toins < jb->hist_minbuf[j]) { /* if the minbuf should get this */
/* move over */ if (toins < jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) {
memmove(jb->hist_minbuf+j+1,jb->hist_minbuf+j, (JB_HISTORY_MAXBUF_SZ-(j+1)) * sizeof(long));
/* insert */ /* insertion-sort it into the maxbuf */
jb->hist_minbuf[j] = toins; for (j=0;j<JB_HISTORY_MAXBUF_SZ;j++) {
/* found where it fits */
break; if (toins < jb->hist_minbuf[j]) {
} /* move over */
} memmove(jb->hist_minbuf + j + 1, jb->hist_minbuf + j, (JB_HISTORY_MAXBUF_SZ - (j + 1)) * sizeof(jb->hist_minbuf[0]));
} /* insert */
jb->hist_minbuf[j] = toins;
if(0) {
int k; break;
fprintf(stderr, "toins = %ld\n", toins); }
fprintf(stderr, "maxbuf ="); }
for(k=0;k<JB_HISTORY_MAXBUF_SZ;k++) }
fprintf(stderr, "%ld ", jb->hist_maxbuf[k]);
fprintf(stderr, "\nminbuf ="); if (0) {
for(k=0;k<JB_HISTORY_MAXBUF_SZ;k++) int k;
fprintf(stderr, "%ld ", jb->hist_minbuf[k]); fprintf(stderr, "toins = %ld\n", toins);
fprintf(stderr, "\n"); fprintf(stderr, "maxbuf =");
} for (k=0;k<JB_HISTORY_MAXBUF_SZ;k++)
} fprintf(stderr, "%ld ", jb->hist_maxbuf[k]);
fprintf(stderr, "\nminbuf =");
jb->hist_maxbuf_valid = 1; for (k=0;k<JB_HISTORY_MAXBUF_SZ;k++)
} fprintf(stderr, "%ld ", jb->hist_minbuf[k]);
fprintf(stderr, "\n");
static void history_get(jitterbuf *jb) }
{ }
time_in_ms_t max, min, jitter;
int index; jb->hist_maxbuf_valid = 1;
int count; }
if(!jb->hist_maxbuf_valid) static void history_get(jitterbuf *jb)
history_calc_maxbuf(jb); {
time_in_ms_t max, min, jitter;
/* count is how many items in history we're examining */ int index;
count = (jb->hist_ptr < JB_HISTORY_SZ) ? jb->hist_ptr : JB_HISTORY_SZ; int count;
/* index is the "n"ths highest/lowest that we'll look for */ if (!jb->hist_maxbuf_valid)
index = count * JB_HISTORY_DROPPCT / 100; history_calc_maxbuf(jb);
/* sanity checks for index */ /* count is how many items in history we're examining */
if(index > (JB_HISTORY_MAXBUF_SZ - 1)) index = JB_HISTORY_MAXBUF_SZ - 1; count = (jb->hist_ptr < JB_HISTORY_SZ) ? jb->hist_ptr : JB_HISTORY_SZ;
/* index is the "n"ths highest/lowest that we'll look for */
if(index < 0) { index = count * JB_HISTORY_DROPPCT / 100;
jb->info.min = 0;
jb->info.jitter = 0; /* sanity checks for index */
return; if (index > (JB_HISTORY_MAXBUF_SZ - 1))
} index = JB_HISTORY_MAXBUF_SZ - 1;
max = jb->hist_maxbuf[index];
min = jb->hist_minbuf[index]; if (index < 0) {
jb->info.min = 0;
jitter = max - min; jb->info.jitter = 0;
return;
/* these debug stmts compare the difference between looking at the absolute jitter, and the }
* values we get by throwing away the outliers */
/* max = jb->hist_maxbuf[index];
fprintf(stderr, "[%d] min=%d, max=%d, jitter=%d\n", index, min, max, jitter); min = jb->hist_minbuf[index];
fprintf(stderr, "[%d] min=%d, max=%d, jitter=%d\n", 0, jb->hist_minbuf[0], jb->hist_maxbuf[0], jb->hist_maxbuf[0]-jb->hist_minbuf[0]);
*/ jitter = max - min;
jb->info.min = min; /* these debug stmts compare the difference between looking at the absolute jitter, and the
jb->info.jitter = jitter; * values we get by throwing away the outliers */
} /*
fprintf(stderr, "[%d] min=%d, max=%d, jitter=%d\n", index, min, max, jitter);
/* returns 1 if frame was inserted into head of queue, 0 otherwise */ fprintf(stderr, "[%d] min=%d, max=%d, jitter=%d\n", 0, jb->hist_minbuf[0], jb->hist_maxbuf[0], jb->hist_maxbuf[0]-jb->hist_minbuf[0]);
static int queue_put(jitterbuf *jb, void *data, int type, long ms, time_in_ms_t ts) */
{
jb_frame *frame; jb->info.min = min;
jb_frame *p; jb->info.jitter = jitter;
int head = 0; }
time_in_ms_t resync_ts = ts - jb->info.resync_offset;
/* returns 1 if frame was inserted into head of queue, 0 otherwise */
frame = jb->free; static int queue_put(jitterbuf *jb, void *data, const enum jb_frame_type type, long ms, time_in_ms_t ts)
if(frame) { {
jb->free = frame->next; jb_frame *frame;
} else { jb_frame *p;
frame = malloc(sizeof(jb_frame)); int head = 0;
} time_in_ms_t resync_ts = ts - jb->info.resync_offset;
if(!frame) { if ((frame = jb->free)) {
jb_err("cannot allocate frame\n"); jb->free = frame->next;
return 0; } else if (!(frame = (jb_frame *)malloc(sizeof(*frame)))) {
} jb_err("cannot allocate frame\n");
return 0;
jb->info.frames_cur++; }
frame->data = data; jb->info.frames_cur++;
frame->ts = resync_ts;
frame->ms = ms; frame->data = data;
frame->type = type; frame->ts = resync_ts;
frame->ms = ms;
/* frame->type = type;
* frames are a circular list, jb-frames points to to the lowest ts,
* jb->frames->prev points to the highest ts /*
*/ * frames are a circular list, jb-frames points to to the lowest ts,
* jb->frames->prev points to the highest ts
if(!jb->frames) { /* queue is empty */ */
jb->frames = frame;
frame->next = frame; if (!jb->frames) { /* queue is empty */
frame->prev = frame; jb->frames = frame;
head = 1; frame->next = frame;
} else if (resync_ts < jb->frames->ts) { frame->prev = frame;
frame->next = jb->frames; head = 1;
frame->prev = jb->frames->prev; } else if (resync_ts < jb->frames->ts) {
frame->next = jb->frames;
frame->next->prev = frame; frame->prev = jb->frames->prev;
frame->prev->next = frame;
frame->next->prev = frame;
/* frame is out of order */ frame->prev->next = frame;
jb->info.frames_ooo++;
/* frame is out of order */
jb->frames = frame; jb->info.frames_ooo++;
head = 1;
} else { jb->frames = frame;
p = jb->frames; head = 1;
} else {
/* frame is out of order */ p = jb->frames;
if(ts < p->prev->ts) jb->info.frames_ooo++;
/* frame is out of order */
while (resync_ts < p->prev->ts && p->prev != jb->frames) if (resync_ts < p->prev->ts) jb->info.frames_ooo++;
p = p->prev;
while (resync_ts < p->prev->ts && p->prev != jb->frames)
frame->next = p; p = p->prev;
frame->prev = p->prev;
frame->next = p;
frame->next->prev = frame; frame->prev = p->prev;
frame->prev->next = frame;
} frame->next->prev = frame;
return head; frame->prev->next = frame;
} }
return head;
static time_in_ms_t queue_next(jitterbuf *jb) }
{
if(jb->frames) return jb->frames->ts; static time_in_ms_t queue_next(jitterbuf *jb)
else return -1; {
} if (jb->frames)
return jb->frames->ts;
static time_in_ms_t queue_last(jitterbuf *jb) else
{ return -1;
if(jb->frames) return jb->frames->prev->ts; }
else return -1;
} static time_in_ms_t queue_last(jitterbuf *jb)
{
static jb_frame *_queue_get(jitterbuf *jb, time_in_ms_t ts, int all) if (jb->frames)
{ return jb->frames->prev->ts;
jb_frame *frame; else
frame = jb->frames; return -1;
}
if(!frame)
return NULL; static jb_frame *_queue_get(jitterbuf *jb, time_in_ms_t ts, int all)
{
/*jb_warn("queue_get: ASK %ld FIRST %ld\n", ts, frame->ts); */ jb_frame *frame;
frame = jb->frames;
if(all || ts >= frame->ts) {
/* remove this frame */ if (!frame)
frame->prev->next = frame->next; return NULL;
frame->next->prev = frame->prev;
/*jb_warn("queue_get: ASK %ld FIRST %ld\n", ts, frame->ts); */
if(frame->next == frame)
jb->frames = NULL; if (all || ts >= frame->ts) {
else /* remove this frame */
jb->frames = frame->next; frame->prev->next = frame->next;
frame->next->prev = frame->prev;
/* insert onto "free" single-linked list */ if (frame->next == frame)
frame->next = jb->free; jb->frames = NULL;
jb->free = frame; else
jb->frames = frame->next;
jb->info.frames_cur--;
/* we return the frame pointer, even though it's on free list, /* insert onto "free" single-linked list */
* but caller must copy data */ frame->next = jb->free;
return frame; jb->free = frame;
}
jb->info.frames_cur--;
return NULL;
} /* we return the frame pointer, even though it's on free list,
* but caller must copy data */
static jb_frame *queue_get(jitterbuf *jb, time_in_ms_t ts) return frame;
{ }
return _queue_get(jb,ts,0);
} return NULL;
}
static jb_frame *queue_getall(jitterbuf *jb)
{ static jb_frame *queue_get(jitterbuf *jb, time_in_ms_t ts)
return _queue_get(jb,0,1); {
} return _queue_get(jb,ts,0);
}
#if 0
/* some diagnostics */ static jb_frame *queue_getall(jitterbuf *jb)
static void jb_dbginfo(jitterbuf *jb) {
{ return _queue_get(jb,0,1);
if(dbgf == NULL) return; }
jb_dbg("\njb info: fin=%ld fout=%ld flate=%ld flost=%ld fdrop=%ld fcur=%ld\n", #if 0
jb->info.frames_in, jb->info.frames_out, jb->info.frames_late, jb->info.frames_lost, jb->info.frames_dropped, jb->info.frames_cur); /* some diagnostics */
static void jb_dbginfo(jitterbuf *jb)
jb_dbg(" jitter=%ld current=%ld target=%ld min=%ld sil=%d len=%d len/fcur=%ld\n", {
jb->info.jitter, jb->info.current, jb->info.target, jb->info.min, jb->info.silence_begin_ts, jb->info.current - jb->info.min, if (dbgf == NULL)
jb->info.frames_cur ? (jb->info.current - jb->info.min)/jb->info.frames_cur : -8); return;
if(jb->info.frames_in > 0)
jb_dbg("jb info: Loss PCT = %ld%%, Late PCT = %ld%%\n", jb_dbg("\njb info: fin=%ld fout=%ld flate=%ld flost=%ld fdrop=%ld fcur=%ld\n",
jb->info.frames_lost * 100/(jb->info.frames_in + jb->info.frames_lost), jb->info.frames_in, jb->info.frames_out, jb->info.frames_late, jb->info.frames_lost, jb->info.frames_dropped, jb->info.frames_cur);
jb->info.frames_late * 100/jb->info.frames_in);
jb_dbg("jb info: queue %d -> %d. last_ts %d (queue len: %d) last_ms %d\n", jb_dbg("jitter=%ld current=%ld target=%ld min=%ld sil=%d len=%d len/fcur=%ld\n",
queue_next(jb), jb->info.jitter, jb->info.current, jb->info.target, jb->info.min, jb->info.silence_begin_ts, jb->info.current - jb->info.min,
queue_last(jb), jb->info.frames_cur ? (jb->info.current - jb->info.min)/jb->info.frames_cur : -8);
jb->info.next_voice_ts, if (jb->info.frames_in > 0)
queue_last(jb) - queue_next(jb), jb_dbg("jb info: Loss PCT = %ld%%, Late PCT = %ld%%\n",
jb->info.last_voice_ms); jb->info.frames_lost * 100/(jb->info.frames_in + jb->info.frames_lost),
} jb->info.frames_late * 100/jb->info.frames_in);
#endif jb_dbg("jb info: queue %d -> %d. last_ts %d (queue len: %d) last_ms %d\n",
queue_next(jb),
#ifdef DEEP_DEBUG queue_last(jb),
static void jb_chkqueue(jitterbuf *jb) jb->info.next_voice_ts,
{ queue_last(jb) - queue_next(jb),
int i=0; jb->info.last_voice_ms);
jb_frame *p = jb->frames; }
#endif
if(!p) {
return; #ifdef DEEP_DEBUG
} static void jb_chkqueue(jitterbuf *jb)
{
do { int i=0;
if(p->next == NULL) { jb_frame *p = jb->frames;
jb_err("Queue is BROKEN at item [%d]", i);
} if (!p) {
i++; return;
p=p->next; }
} while (p->next != jb->frames);
} do {
if (p->next == NULL) {
static void jb_dbgqueue(jitterbuf *jb) jb_err("Queue is BROKEN at item [%d]", i);
{ }
int i=0; i++;
jb_frame *p = jb->frames; p=p->next;
} while (p->next != jb->frames);
jb_dbg("queue: "); }
if(!p) { static void jb_dbgqueue(jitterbuf *jb)
jb_dbg("EMPTY\n"); {
return; int i=0;
} jb_frame *p = jb->frames;
do { jb_dbg("queue: ");
jb_dbg("[%d]=%ld ", i++, p->ts);
p=p->next; if (!p) {
} while (p->next != jb->frames); jb_dbg("EMPTY\n");
return;
jb_dbg("\n"); }
}
#endif do {
jb_dbg("[%d]=%ld ", i++, p->ts);
int jb_put(jitterbuf *jb, void *data, int type, long ms, time_in_ms_t ts, time_in_ms_t now) p=p->next;
{ } while (p->next != jb->frames);
jb_dbg2("jb_put(%x,%x,%ld,%ld,%ld)\n", jb, data, ms, ts, now);
jb_dbg("\n");
jb->info.frames_in++; }
#endif
if(type == JB_TYPE_VOICE) {
/* presently, I'm only adding VOICE frames to history and drift calculations; mostly because with the enum jb_return_code jb_put(jitterbuf *jb, void *data, const enum jb_frame_type type, long ms, time_in_ms_t ts, time_in_ms_t now)
* IAX integrations, I'm sending retransmitted control frames with their awkward timestamps through */ {
if (history_put(jb,ts,now,ms)) jb_dbg2("jb_put(%x,%x,%ld,%ld,%ld)\n", jb, data, ms, ts, now);
return JB_DROP;
} jb->info.frames_in++;
/* if put into head of queue, caller needs to reschedule */ if (type == JB_TYPE_VOICE) {
if (queue_put(jb,data,type,ms,ts)) { /* presently, I'm only adding VOICE frames to history and drift
return JB_SCHED; * calculations; mostly because with the IAX integrations, I'm
} * sending retransmitted control frames with their awkward
* timestamps through
return JB_OK; */
} if (history_put(jb,ts,now,ms))
return JB_DROP;
}
static int _jb_get(jitterbuf *jb, jb_frame *frameout, time_in_ms_t now, long interpl)
{ /* if put into head of queue, caller needs to reschedule */
jb_frame *frame; if (queue_put(jb,data,type,ms,ts)) {
time_in_ms_t diff; return JB_SCHED;
}
/*if((now - jb_next(jb)) > 2 * jb->info.last_voice_ms) jb_warn("SCHED: %ld", (now - jb_next(jb))); */
/* get jitter info */ return JB_OK;
history_get(jb); }
/* target */ static enum jb_return_code _jb_get(jitterbuf *jb, jb_frame *frameout, time_in_ms_t now, long interpl)
jb->info.target = jb->info.jitter + jb->info.min + JB_TARGET_EXTRA; {
jb_frame *frame;
/* if a hard clamp was requested, use it */ time_in_ms_t diff;
if((jb->info.conf.max_jitterbuf) && ((jb->info.target - jb->info.min) > jb->info.conf.max_jitterbuf)) {
jb_dbg("clamping target from %d to %d\n", (jb->info.target - jb->info.min), jb->info.conf.max_jitterbuf); /*if ((now - jb_next(jb)) > 2 * jb->info.last_voice_ms) jb_warn("SCHED: %ld", (now - jb_next(jb))); */
jb->info.target = jb->info.min + jb->info.conf.max_jitterbuf; /* get jitter info */
} history_get(jb);
diff = jb->info.target - jb->info.current;
/* target */
/* jb_warn("diff = %d lms=%d last = %d now = %d\n", diff, */ jb->info.target = jb->info.jitter + jb->info.min + jb->info.conf.target_extra;
/* jb->info.last_voice_ms, jb->info.last_adjustment, now); */
/* if a hard clamp was requested, use it */
/* let's work on non-silent case first */ if ((jb->info.conf.max_jitterbuf) && ((jb->info.target - jb->info.min) > jb->info.conf.max_jitterbuf)) {
if(!jb->info.silence_begin_ts) { jb_dbg("clamping target from %d to %d\n", (jb->info.target - jb->info.min), jb->info.conf.max_jitterbuf);
/* we want to grow */ jb->info.target = jb->info.min + jb->info.conf.max_jitterbuf;
if( (diff > 0) && }
/* we haven't grown in the delay length */
(((jb->info.last_adjustment + JB_ADJUST_DELAY) < now) || diff = jb->info.target - jb->info.current;
/* we need to grow more than the "length" we have left */
(diff > queue_last(jb) - queue_next(jb)) ) ) { /* jb_warn("diff = %d lms=%d last = %d now = %d\n", diff, */
/* grow by interp frame len */ /* jb->info.last_voice_ms, jb->info.last_adjustment, now); */
jb->info.current += interpl;
jb->info.next_voice_ts += interpl; /* let's work on non-silent case first */
jb->info.last_voice_ms = interpl; if (!jb->info.silence_begin_ts) {
jb->info.last_adjustment = now; /* we want to grow */
jb->info.cnt_contig_interp++; if ((diff > 0) &&
jb_dbg("G"); /* we haven't grown in the delay length */
/* assume silence instead of continuing to interpolate */ (((jb->info.last_adjustment + JB_ADJUST_DELAY) < now) ||
if (jb->info.conf.max_contig_interp && jb->info.cnt_contig_interp >= jb->info.conf.max_contig_interp) /* we need to grow more than the "length" we have left */
jb->info.silence_begin_ts = jb->info.next_voice_ts - jb->info.current; (diff > queue_last(jb) - queue_next(jb)) ) ) {
return JB_INTERP; /* grow by interp frame length */
} jb->info.current += interpl;
jb->info.next_voice_ts += interpl;
frame = queue_get(jb, jb->info.next_voice_ts - jb->info.current); jb->info.last_voice_ms = interpl;
jb->info.last_adjustment = now;
/* not a voice frame; just return it. */ jb->info.cnt_contig_interp++;
if(frame && frame->type != JB_TYPE_VOICE) { /* assume silence instead of continuing to interpolate */
/* track start of silence */ if (jb->info.conf.max_contig_interp && jb->info.cnt_contig_interp >= jb->info.conf.max_contig_interp) {
if(frame->type == JB_TYPE_SILENCE) { jb->info.silence_begin_ts = jb->info.next_voice_ts - jb->info.current;
jb->info.silence_begin_ts = frame->ts; }
jb->info.cnt_contig_interp = 0; jb_dbg("G");
} return JB_INTERP;
}
*frameout = *frame;
jb->info.frames_out++; frame = queue_get(jb, jb->info.next_voice_ts - jb->info.current);
jb_dbg("o");
return JB_OK; /* not a voice frame; just return it. */
} if (frame && frame->type != JB_TYPE_VOICE) {
/* track start of silence */
/* voice frame is later than expected */ if (frame->type == JB_TYPE_SILENCE) {
if(frame && frame->ts + jb->info.current < jb->info.next_voice_ts) { jb->info.silence_begin_ts = frame->ts;
if (frame->ts + jb->info.current > jb->info.next_voice_ts - jb->info.last_voice_ms) { jb->info.cnt_contig_interp = 0;
/* either we interpolated past this frame in the last jb_get */ }
/* or the frame is still in order, but came a little too quick */
*frameout = *frame; *frameout = *frame;
/* reset expectation for next frame */ jb->info.frames_out++;
jb->info.next_voice_ts = frame->ts + jb->info.current + frame->ms; jb_dbg("o");
jb->info.frames_out++; return JB_OK;
decrement_losspct(jb); }
jb->info.cnt_contig_interp = 0;
jb_dbg("v"); /* voice frame is later than expected */
return JB_OK; if (frame && frame->ts + jb->info.current < jb->info.next_voice_ts) {
} else { if (frame->ts + jb->info.current > jb->info.next_voice_ts - jb->info.last_voice_ms) {
/* voice frame is late */ /* either we interpolated past this frame in the last jb_get */
*frameout = *frame; /* or the frame is still in order, but came a little too quick */
jb->info.frames_out++; *frameout = *frame;
decrement_losspct(jb); /* reset expectation for next frame */
jb->info.frames_late++; jb->info.next_voice_ts = frame->ts + jb->info.current + frame->ms;
jb->info.frames_lost--; jb->info.frames_out++;
jb_dbg("l"); decrement_losspct(jb);
/*jb_warn("\nlate: wanted=%ld, this=%ld, next=%ld\n", jb->info.next_voice_ts - jb->info.current, frame->ts, queue_next(jb)); jb->info.cnt_contig_interp = 0;
jb_warninfo(jb); */ jb_dbg("v");
return JB_DROP; return JB_OK;
} } else {
} /* voice frame is late */
*frameout = *frame;
/* keep track of frame sizes, to allow for variable sized-frames */ jb->info.frames_out++;
if(frame && frame->ms > 0) { decrement_losspct(jb);
jb->info.last_voice_ms = frame->ms; jb->info.frames_late++;
} jb->info.frames_lost--;
jb_dbg("l");
/* we want to shrink; shrink at 1 frame / 500ms */ /*jb_warn("\nlate: wanted=%ld, this=%ld, next=%ld\n", jb->info.next_voice_ts - jb->info.current, frame->ts, queue_next(jb));
/* unless we don't have a frame, then shrink 1 frame */ jb_warninfo(jb); */
/* every 80ms (though perhaps we can shrink even faster */ return JB_DROP;
/* in this case) */ }
if(diff < -JB_TARGET_EXTRA && }
((!frame && jb->info.last_adjustment + 80 < now) ||
(jb->info.last_adjustment + 500 < now))) { /* keep track of frame sizes, to allow for variable sized-frames */
jb->info.last_adjustment = now; if (frame && frame->ms > 0) {
jb->info.cnt_contig_interp = 0; jb->info.last_voice_ms = frame->ms;
}
if(frame) {
*frameout = *frame; /* we want to shrink; shrink at 1 frame / 500ms */
/* shrink by frame size we're throwing out */ /* unless we don't have a frame, then shrink 1 frame */
jb->info.current -= frame->ms; /* every 80ms (though perhaps we can shrink even faster */
jb->info.frames_out++; /* in this case) */
decrement_losspct(jb); if (diff < -jb->info.conf.target_extra &&
jb->info.frames_dropped++; ((!frame && jb->info.last_adjustment + 80 < now) ||
jb_dbg("s"); (jb->info.last_adjustment + 500 < now))) {
return JB_DROP;
} else { jb->info.last_adjustment = now;
/* shrink by last_voice_ms */ jb->info.cnt_contig_interp = 0;
jb->info.current -= jb->info.last_voice_ms;
jb->info.frames_lost++; if (frame) {
increment_losspct(jb); *frameout = *frame;
jb_dbg("S"); /* shrink by frame size we're throwing out */
return JB_NOFRAME; jb->info.current -= frame->ms;
} jb->info.frames_out++;
} decrement_losspct(jb);
jb->info.frames_dropped++;
/* lost frame */ jb_dbg("s");
if(!frame) { return JB_DROP;
/* this is a bit of a hack for now, but if we're close to } else {
* target, and we find a missing frame, it makes sense to /* shrink by last_voice_ms */
* grow, because the frame might just be a bit late; jb->info.current -= jb->info.last_voice_ms;
* otherwise, we presently get into a pattern where we return jb->info.frames_lost++;
* INTERP for the lost frame, then it shows up next, and we increment_losspct(jb);
* throw it away because it's late */ jb_dbg("S");
/* I've recently only been able to replicate this using return JB_NOFRAME;
* iaxclient talking to app_echo on asterisk. In this case, }
* my outgoing packets go through asterisk's (old) }
* jitterbuffer, and then might get an unusual increasing delay
* there if it decides to grow?? */ /* lost frame */
/* Update: that might have been a different bug, that has been fixed.. if (!frame) {
* But, this still seemed like a good idea, except that it ended up making a single actual /* this is a bit of a hack for now, but if we're close to
* lost frame get interpolated two or more times, when there was "room" to grow, so it might * target, and we find a missing frame, it makes sense to
* be a bit of a bad idea overall */ * grow, because the frame might just be a bit late;
/*if(diff > -1 * jb->info.last_voice_ms) { * otherwise, we presently get into a pattern where we return
jb->info.current += jb->info.last_voice_ms; * INTERP for the lost frame, then it shows up next, and we
jb->info.last_adjustment = now; * throw it away because it's late */
jb_warn("g"); /* I've recently only been able to replicate this using
return JB_INTERP; * iaxclient talking to app_echo on asterisk. In this case,
} */ * my outgoing packets go through asterisk's (old)
jb->info.frames_lost++; * jitterbuffer, and then might get an unusual increasing delay
increment_losspct(jb); * there if it decides to grow?? */
jb->info.next_voice_ts += interpl; /* Update: that might have been a different bug, that has been fixed..
jb->info.last_voice_ms = interpl; * But, this still seemed like a good idea, except that it ended up making a single actual
jb->info.cnt_contig_interp++; * lost frame get interpolated two or more times, when there was "room" to grow, so it might
/* assume silence instead of continuing to interpolate */ * be a bit of a bad idea overall */
if (jb->info.conf.max_contig_interp && jb->info.cnt_contig_interp >= jb->info.conf.max_contig_interp) /*if (diff > -1 * jb->info.last_voice_ms) {
jb->info.silence_begin_ts = jb->info.next_voice_ts - jb->info.current; jb->info.current += jb->info.last_voice_ms;
jb_dbg("L"); jb->info.last_adjustment = now;
return JB_INTERP; jb_warn("g");
} return JB_INTERP;
} */
/* normal case; return the frame, increment stuff */ jb->info.frames_lost++;
*frameout = *frame; increment_losspct(jb);
jb->info.next_voice_ts += frame->ms; jb->info.next_voice_ts += interpl;
jb->info.frames_out++; jb->info.last_voice_ms = interpl;
decrement_losspct(jb); jb->info.cnt_contig_interp++;
jb->info.cnt_contig_interp = 0; /* assume silence instead of continuing to interpolate */
jb_dbg("v"); if (jb->info.conf.max_contig_interp && jb->info.cnt_contig_interp >= jb->info.conf.max_contig_interp) {
return JB_OK; jb->info.silence_begin_ts = jb->info.next_voice_ts - jb->info.current;
} else { }
/* TODO: after we get the non-silent case down, we'll make the jb_dbg("L");
* silent case -- basically, we'll just grow and shrink faster return JB_INTERP;
* here, plus handle next_voice_ts a bit differently */ }
/* to disable silent special case altogether, just uncomment this: */ /* normal case; return the frame, increment stuff */
/* jb->info.silence_begin_ts = 0; */ *frameout = *frame;
jb->info.next_voice_ts += frame->ms;
/* shrink interpl len every 10ms during silence */ jb->info.frames_out++;
if (diff < -JB_TARGET_EXTRA && jb->info.cnt_contig_interp = 0;
jb->info.last_adjustment + 10 <= now) { decrement_losspct(jb);
jb->info.current -= interpl; jb_dbg("v");
jb->info.last_adjustment = now; return JB_OK;
} } else {
/* TODO: after we get the non-silent case down, we'll make the
frame = queue_get(jb, now - jb->info.current); * silent case -- basically, we'll just grow and shrink faster
if(!frame) { * here, plus handle next_voice_ts a bit differently */
return JB_NOFRAME;
} else if (frame->type != JB_TYPE_VOICE) { /* to disable silent special case altogether, just uncomment this: */
/* normal case; in silent mode, got a non-voice frame */ /* jb->info.silence_begin_ts = 0; */
*frameout = *frame;
jb->info.frames_out++; /* shrink interpl len every 10ms during silence */
return JB_OK; if (diff < -jb->info.conf.target_extra &&
} jb->info.last_adjustment + 10 <= now) {
if (frame->ts < jb->info.silence_begin_ts) { jb->info.current -= interpl;
/* voice frame is late */ jb->info.last_adjustment = now;
*frameout = *frame; }
jb->info.frames_out++;
decrement_losspct(jb); frame = queue_get(jb, now - jb->info.current);
jb->info.frames_late++; if (!frame) {
jb->info.frames_lost--; return JB_NOFRAME;
jb_dbg("l"); } else if (frame->type != JB_TYPE_VOICE) {
/*jb_warn("\nlate: wanted=%ld, this=%ld, next=%ld\n", jb->info.next_voice_ts - jb->info.current, frame->ts, queue_next(jb)); /* normal case; in silent mode, got a non-voice frame */
jb_warninfo(jb); */ *frameout = *frame;
return JB_DROP; jb->info.frames_out++;
} else { return JB_OK;
/* voice frame */ }
/* try setting current to target right away here */ if (frame->ts < jb->info.silence_begin_ts) {
jb->info.current = jb->info.target; /* voice frame is late */
jb->info.silence_begin_ts = 0; *frameout = *frame;
jb->info.next_voice_ts = frame->ts + jb->info.current + frame->ms; jb->info.frames_out++;
jb->info.last_voice_ms = frame->ms; decrement_losspct(jb);
jb->info.frames_out++; jb->info.frames_late++;
decrement_losspct(jb); jb->info.frames_lost--;
*frameout = *frame; jb_dbg("l");
jb_dbg("V"); /*jb_warn("\nlate: wanted=%ld, this=%ld, next=%ld\n", jb->info.next_voice_ts - jb->info.current, frame->ts, queue_next(jb));
return JB_OK; jb_warninfo(jb); */
} return JB_DROP;
} } else {
} /* voice frame */
/* try setting current to target right away here */
time_in_ms_t jb_next(jitterbuf *jb) jb->info.current = jb->info.target;
{ jb->info.silence_begin_ts = 0;
if(jb->info.silence_begin_ts) { jb->info.next_voice_ts = frame->ts + jb->info.current + frame->ms;
time_in_ms_t next = queue_next(jb); jb->info.last_voice_ms = frame->ms;
if(next > 0) { jb->info.frames_out++;
/* shrink during silence */ decrement_losspct(jb);
if (jb->info.target - jb->info.current < -JB_TARGET_EXTRA) *frameout = *frame;
return jb->info.last_adjustment + 10; jb_dbg("V");
return next + jb->info.target; return JB_OK;
} }
else return JB_LONGMAX; }
} else { }
return jb->info.next_voice_ts;
} time_in_ms_t jb_next(jitterbuf *jb)
} {
if (jb->info.silence_begin_ts) {
int jb_get(jitterbuf *jb, jb_frame *frameout, time_in_ms_t now, long interpl) if (jb->frames) {
{ time_in_ms_t next = queue_next(jb);
int ret = _jb_get(jb,frameout,now,interpl); history_get(jb);
#if 0 /* shrink during silence */
static int lastts=0; if (jb->info.target - jb->info.current < -jb->info.conf.target_extra)
int thists = ((ret == JB_OK) || (ret == JB_DROP)) ? frameout->ts : 0; return jb->info.last_adjustment + 10;
jb_warn("jb_get(%x,%x,%ld) = %d (%d)\n", jb, frameout, now, ret, thists); return next + jb->info.target;
if(thists && thists < lastts) jb_warn("XXXX timestamp roll-back!!!\n"); }
lastts = thists; else
#endif return JB_LONGMAX;
return ret; } else {
} return jb->info.next_voice_ts;
}
int jb_getall(jitterbuf *jb, jb_frame *frameout) }
{
jb_frame *frame; enum jb_return_code jb_get(jitterbuf *jb, jb_frame *frameout, time_in_ms_t now, long interpl)
frame = queue_getall(jb); {
enum jb_return_code ret = _jb_get(jb, frameout, now, interpl);
if(!frame) { #if 0
return JB_NOFRAME; static int lastts=0;
} int thists = ((ret == JB_OK) || (ret == JB_DROP)) ? frameout->ts : 0;
jb_warn("jb_get(%x,%x,%ld) = %d (%d)\n", jb, frameout, now, ret, thists);
*frameout = *frame; if (thists && thists < lastts) jb_warn("XXXX timestamp roll-back!!!\n");
return JB_OK; lastts = thists;
} #endif
return ret;
}
int jb_getinfo(jitterbuf *jb, jb_info *stats)
{ enum jb_return_code jb_getall(jitterbuf *jb, jb_frame *frameout)
{
history_get(jb); jb_frame *frame;
frame = queue_getall(jb);
*stats = jb->info;
if (!frame) {
return JB_OK; return JB_NOFRAME;
} }
int jb_setconf(jitterbuf *jb, jb_conf *conf) *frameout = *frame;
{ return JB_OK;
/* take selected settings from the struct */ }
jb->info.conf.max_jitterbuf = conf->max_jitterbuf;
jb->info.conf.resync_threshold = conf->resync_threshold; enum jb_return_code jb_getinfo(jitterbuf *jb, jb_info *stats)
jb->info.conf.max_contig_interp = conf->max_contig_interp; {
history_get(jb);
return JB_OK;
} *stats = jb->info;
return JB_OK;
}
enum jb_return_code jb_setconf(jitterbuf *jb, jb_conf *conf)
{
/* take selected settings from the struct */
jb->info.conf.max_jitterbuf = conf->max_jitterbuf;
jb->info.conf.resync_threshold = conf->resync_threshold;
jb->info.conf.max_contig_interp = conf->max_contig_interp;
/* -1 indicates use of the default JB_TARGET_EXTRA value */
jb->info.conf.target_extra = ( conf->target_extra == -1 )
? JB_TARGET_EXTRA
: conf->target_extra
;
/* update these to match new target_extra setting */
jb->info.current = jb->info.conf.target_extra;
jb->info.target = jb->info.conf.target_extra;
return JB_OK;
}
/* /*
* jitterbuf: an application-independent jitterbuffer * jitterbuf: an application-independent jitterbuffer
* *
* Copyrights: * Copyrights:
* Copyright (C) 2004-2005, Horizon Wimba, Inc. * Copyright (C) 2004-2005, Horizon Wimba, Inc.
* *
* Contributors: * Contributors:
* Steve Kann <stevek@stevek.com> * Steve Kann <stevek@stevek.com>
* *
* This program is free software, distributed under the terms of * This program is free software, distributed under the terms of
* the GNU Lesser (Library) General Public License * the GNU Lesser (Library) General Public License
* *
* Copyright on this file is disclaimed to Digium for inclusion in Asterisk * Copyright on this file is disclaimed to Digium for inclusion in Asterisk
*/ */
#ifndef _JITTERBUF_H_ #ifndef _JITTERBUF_H_
#define _JITTERBUF_H_ #define _JITTERBUF_H_
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/* configuration constants */ /* configuration constants */
/* Number of historical timestamps to use in calculating jitter and drift */ /* Number of historical timestamps to use in calculating jitter and drift */
#define JB_HISTORY_SZ 500 #define JB_HISTORY_SZ 500
/* what percentage of timestamps should we drop from the history when we examine it; /* what percentage of timestamps should we drop from the history when we examine it;
* this might eventually be something made configurable */ * this might eventually be something made configurable */
#define JB_HISTORY_DROPPCT 3 #define JB_HISTORY_DROPPCT 3
/* the maximum droppct we can handle (say it was configurable). */ /* the maximum droppct we can handle (say it was configurable). */
#define JB_HISTORY_DROPPCT_MAX 4 #define JB_HISTORY_DROPPCT_MAX 4
/* the size of the buffer we use to keep the top and botton timestamps for dropping */ /* the size of the buffer we use to keep the top and botton timestamps for dropping */
#define JB_HISTORY_MAXBUF_SZ JB_HISTORY_SZ * JB_HISTORY_DROPPCT_MAX / 100 #define JB_HISTORY_MAXBUF_SZ JB_HISTORY_SZ * JB_HISTORY_DROPPCT_MAX / 100
/* amount of additional jitterbuffer adjustment */ /* amount of additional jitterbuffer adjustment */
#define JB_TARGET_EXTRA 40 #define JB_TARGET_EXTRA 40
/* ms between growing and shrinking; may not be honored if jitterbuffer runs out of space */ /* ms between growing and shrinking; may not be honored if jitterbuffer runs out of space */
#define JB_ADJUST_DELAY 40 #define JB_ADJUST_DELAY 40
enum jb_return_code {
/* return codes */ /* return codes */
#define JB_OK 0 JB_OK, /* 0 */
#define JB_EMPTY 1 JB_EMPTY, /* 1 */
#define JB_NOFRAME 2 JB_NOFRAME, /* 2 */
#define JB_INTERP 3 JB_INTERP, /* 3 */
#define JB_DROP 4 JB_DROP, /* 4 */
#define JB_SCHED 5 JB_SCHED /* 5 */
};
/* frame types */
#define JB_TYPE_CONTROL 0 enum jb_frame_type {
#define JB_TYPE_VOICE 1 /* frame types */
#define JB_TYPE_VIDEO 2 /* reserved */ JB_TYPE_CONTROL, /* 0 */
#define JB_TYPE_SILENCE 3 JB_TYPE_VOICE, /* 1 */
JB_TYPE_VIDEO, /* 2 - reserved */
JB_TYPE_SILENCE /* 3 */
typedef struct jb_conf { };
/* settings */
long max_jitterbuf; /* defines a hard clamp to use in setting the jitter buffer delay */ typedef struct jb_conf {
long resync_threshold; /* the jb will resync when delay increases to (2 * jitter) + this param */ /* settings */
long max_contig_interp; /* the max interp frames to return in a row */ long max_jitterbuf; /* defines a hard clamp to use in setting the jitter buffer delay */
} jb_conf; long resync_threshold; /* the jb will resync when delay increases to (2 * jitter) + this param */
long max_contig_interp; /* the max interp frames to return in a row */
typedef struct jb_info { long target_extra; /* amount of additional jitterbuffer adjustment, overrides JB_TARGET_EXTRA */
jb_conf conf; } jb_conf;
/* statistics */ typedef struct jb_info {
long frames_in; /* number of frames input to the jitterbuffer.*/ jb_conf conf;
long frames_out; /* number of frames output from the jitterbuffer.*/
long frames_late; /* number of frames which were too late, and dropped.*/ /* statistics */
long frames_lost; /* number of missing frames.*/ long frames_in; /* number of frames input to the jitterbuffer.*/
long frames_dropped; /* number of frames dropped (shrinkage) */ long frames_out; /* number of frames output from the jitterbuffer.*/
long frames_ooo; /* number of frames received out-of-order */ long frames_late; /* number of frames which were too late, and dropped.*/
long frames_cur; /* number of frames presently in jb, awaiting delivery.*/ long frames_lost; /* number of missing frames.*/
time_in_ms_t jitter; /* jitter measured within current history interval*/ long frames_dropped; /* number of frames dropped (shrinkage) */
time_in_ms_t min; /* minimum lateness within current history interval */ long frames_ooo; /* number of frames received out-of-order */
time_in_ms_t current; /* the present jitterbuffer adjustment */ long frames_cur; /* number of frames presently in jb, awaiting delivery.*/
time_in_ms_t target; /* the target jitterbuffer adjustment */ time_in_ms_t jitter; /* jitter measured within current history interval*/
long losspct; /* recent lost frame percentage (* 1000) */ time_in_ms_t min; /* minimum lateness within current history interval */
time_in_ms_t next_voice_ts; /* the ts of the next frame to be read from the jb - in receiver's time */ time_in_ms_t current; /* the present jitterbuffer adjustment */
long last_voice_ms; /* the duration of the last voice frame */ time_in_ms_t target; /* the target jitterbuffer adjustment */
time_in_ms_t silence_begin_ts; /* the time of the last CNG frame, when in silence */ long losspct; /* recent lost frame percentage (* 1000) */
time_in_ms_t last_adjustment; /* the time of the last adjustment */ time_in_ms_t next_voice_ts; /* the ts of the next frame to be read from the jb - in receiver's time */
time_in_ms_t last_delay; /* the last now added to history */ long last_voice_ms; /* the duration of the last voice frame */
long cnt_delay_discont; /* the count of discontinuous delays */ time_in_ms_t silence_begin_ts; /* the time of the last CNG frame, when in silence */
time_in_ms_t resync_offset; /* the amount to offset ts to support resyncs */ time_in_ms_t last_adjustment; /* the time of the last adjustment */
long cnt_contig_interp; /* the number of contiguous interp frames returned */ time_in_ms_t last_delay; /* the last now added to history */
} jb_info; long cnt_delay_discont; /* the count of discontinuous delays */
time_in_ms_t resync_offset; /* the amount to offset ts to support resyncs */
typedef struct jb_frame { long cnt_contig_interp; /* the number of contiguous interp frames returned */
void *data; /* the frame data */ } jb_info;
time_in_ms_t ts; /* the relative delivery time expected */
long ms; /* the time covered by this frame, in sec/8000 */ typedef struct jb_frame {
int type; /* the type of frame */ void *data; /* the frame data */
struct jb_frame *next, *prev; time_in_ms_t ts; /* the relative delivery time expected */
} jb_frame; long ms; /* the time covered by this frame, in sec/8000 */
enum jb_frame_type type; /* the type of frame */
typedef struct jitterbuf { struct jb_frame *next, *prev;
jb_info info; } jb_frame;
/* history */ typedef struct jitterbuf {
time_in_ms_t history[JB_HISTORY_SZ]; /* history */ jb_info info;
int hist_ptr; /* points to index in history for next entry */
time_in_ms_t hist_maxbuf[JB_HISTORY_MAXBUF_SZ]; /* a sorted buffer of the max delays (highest first) */ /* history */
time_in_ms_t hist_minbuf[JB_HISTORY_MAXBUF_SZ]; /* a sorted buffer of the min delays (lowest first) */ time_in_ms_t history[JB_HISTORY_SZ]; /* history */
int hist_maxbuf_valid; /* are the "maxbuf"/minbuf valid? */ int hist_ptr; /* points to index in history for next entry */
time_in_ms_t hist_maxbuf[JB_HISTORY_MAXBUF_SZ]; /* a sorted buffer of the max delays (highest first) */
time_in_ms_t hist_minbuf[JB_HISTORY_MAXBUF_SZ]; /* a sorted buffer of the min delays (lowest first) */
jb_frame *frames; /* queued frames */ int hist_maxbuf_valid; /* are the "maxbuf"/minbuf valid? */
jb_frame *free; /* free frames (avoid malloc?) */
} jitterbuf; jb_frame *frames; /* queued frames */
jb_frame *free; /* free frames (avoid malloc?) */
} jitterbuf;
/* new jitterbuf */
extern jitterbuf * jb_new(void);
/* new jitterbuf */
/* destroy jitterbuf */ jitterbuf * jb_new(void);
extern void jb_destroy(jitterbuf *jb);
/* destroy jitterbuf */
/* reset jitterbuf */ void jb_destroy(jitterbuf *jb);
/* NOTE: The jitterbuffer should be empty before you call this, otherwise
* you will leak queued frames, and some internal structures */ /* reset jitterbuf */
extern void jb_reset(jitterbuf *jb); /* NOTE: The jitterbuffer should be empty before you call this, otherwise
* you will leak queued frames, and some internal structures */
/* queue a frame data=frame data, timings (in ms): ms=length of frame (for voice), ts=ts (sender's time) void jb_reset(jitterbuf *jb);
* now=now (in receiver's time) return value is one of
* JB_OK: Frame added. Last call to jb_next() still valid /* queue a frame data=frame data, timings (in ms): ms=length of frame (for voice), ts=ts (sender's time)
* JB_DROP: Drop this frame immediately * now=now (in receiver's time) return value is one of
* JB_SCHED: Frame added. Call jb_next() to get a new time for the next frame * JB_OK: Frame added. Last call to jb_next() still valid
*/ * JB_DROP: Drop this frame immediately
extern int jb_put(jitterbuf *jb, void *data, int type, long ms, time_in_ms_t ts, time_in_ms_t now); * JB_SCHED: Frame added. Call jb_next() to get a new time for the next frame
*/
/* get a frame for time now (receiver's time) return value is one of enum jb_return_code jb_put(jitterbuf *jb, void *data, const enum jb_frame_type type, long ms, time_in_ms_t ts, time_in_ms_t now);
* JB_OK: You've got frame!
* JB_DROP: Here's an audio frame you should just drop. Ask me again for this time.. /* get a frame for time now (receiver's time) return value is one of
* JB_NOFRAME: There's no frame scheduled for this time. * JB_OK: You've got frame!
* JB_INTERP: Please interpolate an interpl-length frame for this time (either we need to grow, or there was a lost frame) * JB_DROP: Here's an audio frame you should just drop. Ask me again for this time..
* JB_EMPTY: The jb is empty. * JB_NOFRAME: There's no frame scheduled for this time.
*/ * JB_INTERP: Please interpolate an interpl-length frame for this time (either we need to grow, or there was a lost frame)
extern int jb_get(jitterbuf *jb, jb_frame *frame, time_in_ms_t now, long interpl); * JB_EMPTY: The jb is empty.
*/
/* unconditionally get frames from jitterbuf until empty */ enum jb_return_code jb_get(jitterbuf *jb, jb_frame *frame, time_in_ms_t now, long interpl);
extern int jb_getall(jitterbuf *jb, jb_frame *frameout);
/* unconditionally get frames from jitterbuf until empty */
/* when is the next frame due out, in receiver's time (0=EMPTY) enum jb_return_code jb_getall(jitterbuf *jb, jb_frame *frameout);
* This value may change as frames are added (esp non-audio frames) */
extern time_in_ms_t jb_next(jitterbuf *jb); /* when is the next frame due out, in receiver's time (0=EMPTY)
* This value may change as frames are added (esp non-audio frames) */
/* get jitterbuf info: only "statistics" may be valid */ time_in_ms_t jb_next(jitterbuf *jb);
extern int jb_getinfo(jitterbuf *jb, jb_info *stats);
/* get jitterbuf info: only "statistics" may be valid */
/* set jitterbuf conf */ enum jb_return_code jb_getinfo(jitterbuf *jb, jb_info *stats);
extern int jb_setconf(jitterbuf *jb, jb_conf *conf);
/* set jitterbuf conf */
typedef void (*jb_output_function_t)(const char *fmt, ...); enum jb_return_code jb_setconf(jitterbuf *jb, jb_conf *conf);
extern void jb_setoutput(jb_output_function_t err, jb_output_function_t warn, jb_output_function_t dbg);
typedef void (*jb_output_function_t)(const char *fmt, ...);
#ifdef __cplusplus extern void jb_setoutput(jb_output_function_t err, jb_output_function_t warn, jb_output_function_t dbg);
}
#endif #ifdef __cplusplus
}
#endif
#endif
#endif
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论