1071 lines
30 KiB
C
Executable File
1071 lines
30 KiB
C
Executable File
/* File from: http://cms.speakup.nl/tech/opensource/jitterbuffer/verslag-20051209.pdf/ */
|
|
|
|
/*******************************************************
|
|
* jitterbuffer:
|
|
* an application-independent jitterbuffer, which tries
|
|
* to achieve the maximum user perception during a call.
|
|
* For more information look at:
|
|
* http://www.speakup.nl/opensource/jitterbuffer/
|
|
*
|
|
* Copyright on this file is held by:
|
|
* - Jesse Kaijen <jesse@speakup.nl>
|
|
* - SpeakUp <info@speakup.nl>
|
|
*
|
|
* Contributors:
|
|
* Jesse Kaijen <jesse@speakup.nl>
|
|
*
|
|
* This program is free software, distributed under the terms of:
|
|
* - the GNU Lesser (Library) General Public License
|
|
* - the Mozilla Public License
|
|
*
|
|
* if you are interested in an different licence type, please contact us.
|
|
*
|
|
* How to use the jitterbuffer, please look at the comments
|
|
* in the headerfile.
|
|
*
|
|
* Further details on specific implementations,
|
|
* please look at the comments in the code file.
|
|
*/
|
|
#include "tinydav/audio/tdav_jitterbuffer.h"
|
|
|
|
#if !(HAVE_SPEEX_DSP && HAVE_SPEEX_JB)
|
|
|
|
#include "tsk_memory.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
|
|
#define jb_warn(...) (warnf ? warnf(__VA_ARGS__) : (void)0)
|
|
#define jb_err(...) (errf ? errf(__VA_ARGS__) : (void)0)
|
|
#define jb_dbg(...) (dbgf ? dbgf(__VA_ARGS__) : (void)0)
|
|
|
|
//public functions
|
|
jitterbuffer *jb_new();
|
|
void jb_reset(jitterbuffer *jb);
|
|
void jb_reset_all(jitterbuffer *jb);
|
|
void jb_destroy(jitterbuffer *jb);
|
|
void jb_set_settings(jitterbuffer *jb, jb_settings *settings);
|
|
|
|
void jb_get_info(jitterbuffer *jb, jb_info *stats);
|
|
void jb_get_settings(jitterbuffer *jb, jb_settings *settings);
|
|
float jb_guess_mos(float p, long d, int codec);
|
|
int jb_has_frames(jitterbuffer *jb);
|
|
|
|
void jb_put(jitterbuffer *jb, void *data, int type, long ms, long ts, long now, int codec);
|
|
int jb_get(jitterbuffer *jb, void **data, long now, long interpl);
|
|
|
|
|
|
|
|
//private functions
|
|
static void set_default_settings(jitterbuffer *jb);
|
|
static void reset(jitterbuffer *jb);
|
|
static long find_pointer(long *array, long max_index, long value);
|
|
static void frame_free(jb_frame *frame);
|
|
|
|
static void put_control(jitterbuffer *jb, void *data, int type, long ts);
|
|
static void put_voice(jitterbuffer *jb, void *data, int type, long ms, long ts, int codec);
|
|
static void put_history(jitterbuffer *jb, long ts, long now, long ms, int codec);
|
|
static void calculate_info(jitterbuffer *jb, long ts, long now, int codec);
|
|
|
|
static int get_control(jitterbuffer *jb, void **data);
|
|
static int get_voice(jitterbuffer *jb, void **data, long now, long interpl);
|
|
static int get_voicecase(jitterbuffer *jb, void **data, long now, long interpl, long diff);
|
|
|
|
static int get_next_frametype(jitterbuffer *jb, long ts);
|
|
static long get_next_framets(jitterbuffer *jb);
|
|
static jb_frame *get_frame(jitterbuffer *jb, long ts);
|
|
static jb_frame *get_all_frames(jitterbuffer *jb);
|
|
|
|
//debug...
|
|
static jb_output_function_t warnf, errf, dbgf;
|
|
void jb_setoutput(jb_output_function_t warn, jb_output_function_t err, jb_output_function_t dbg)
|
|
{
|
|
warnf = warn;
|
|
errf = err;
|
|
dbgf = dbg;
|
|
}
|
|
|
|
|
|
/***********
|
|
* create a new jitterbuffer
|
|
* return NULL if malloc doesn't work
|
|
* else return jb with default_settings.
|
|
*/
|
|
jitterbuffer *jb_new()
|
|
{
|
|
jitterbuffer *jb;
|
|
|
|
jb_dbg("N");
|
|
jb = tsk_calloc(1, sizeof(jitterbuffer));
|
|
if (!jb) {
|
|
jb_err("cannot allocate jitterbuffer\n");
|
|
return NULL;
|
|
}
|
|
set_default_settings(jb);
|
|
reset(jb);
|
|
return jb;
|
|
}
|
|
|
|
|
|
/***********
|
|
* empty voice messages
|
|
* reset statistics
|
|
* keep the settings
|
|
*/
|
|
void jb_reset(jitterbuffer *jb)
|
|
{
|
|
jb_frame *frame;
|
|
|
|
jb_dbg("R");
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_reset()\n");
|
|
return;
|
|
}
|
|
|
|
//free voice
|
|
while(jb->voiceframes) {
|
|
frame = get_all_frames(jb);
|
|
frame_free(frame);
|
|
}
|
|
//reset stats
|
|
memset(&(jb->info),0,sizeof(jb_info) );
|
|
// set default settings
|
|
reset(jb);
|
|
}
|
|
|
|
|
|
/***********
|
|
* empty nonvoice messages
|
|
* empty voice messages
|
|
* reset statistics
|
|
* reset settings to default
|
|
*/
|
|
void jb_reset_all(jitterbuffer *jb)
|
|
{
|
|
jb_frame *frame;
|
|
|
|
jb_dbg("r");
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_reset_all()\n");
|
|
return;
|
|
}
|
|
|
|
// free nonvoice
|
|
while(jb->controlframes) {
|
|
frame = jb->controlframes;
|
|
jb->controlframes = frame->next;
|
|
frame_free(frame);
|
|
}
|
|
// free voice and reset statistics is done by jb_reset
|
|
jb_reset(jb);
|
|
set_default_settings(jb);
|
|
}
|
|
|
|
|
|
/***********
|
|
* destroy the jitterbuffer
|
|
* free all the [non]voice frames with reset_all
|
|
* free the jitterbuffer
|
|
*/
|
|
void jb_destroy(jitterbuffer *jb)
|
|
{
|
|
jb_dbg("D");
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_destroy()\n");
|
|
return;
|
|
}
|
|
|
|
jb_reset_all(jb);
|
|
free(jb);
|
|
}
|
|
|
|
|
|
/***********
|
|
* Set settings for the jitterbuffer.
|
|
* Only if a setting is defined it will be written
|
|
* in the jb->settings.
|
|
* This means that no setting can be set to zero
|
|
*/
|
|
void jb_set_settings(jitterbuffer *jb, jb_settings *settings)
|
|
{
|
|
jb_dbg("S");
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_set_settings()\n");
|
|
return;
|
|
}
|
|
|
|
if (settings->min_jb) {
|
|
jb->settings.min_jb = settings->min_jb;
|
|
}
|
|
if (settings->max_jb) {
|
|
jb->settings.max_jb = settings->max_jb;
|
|
}
|
|
if (settings->max_successive_interp) {
|
|
jb->settings.max_successive_interp = settings->max_successive_interp;
|
|
}
|
|
if (settings->extra_delay) {
|
|
jb->settings.extra_delay = settings->extra_delay;
|
|
}
|
|
if (settings->wait_grow) {
|
|
jb->settings.wait_grow = settings->wait_grow;
|
|
}
|
|
if (settings->wait_shrink) {
|
|
jb->settings.wait_shrink = settings->wait_shrink;
|
|
}
|
|
if (settings->max_diff) {
|
|
jb->settings.max_diff = settings->max_diff;
|
|
}
|
|
}
|
|
|
|
|
|
/***********
|
|
* validates the statistics
|
|
* the losspct due the jitterbuffer will be calculated.
|
|
* delay and delay_target will be calculated
|
|
* *stats = info
|
|
*/
|
|
void jb_get_info(jitterbuffer *jb, jb_info *stats)
|
|
{
|
|
long max_index, pointer;
|
|
|
|
jb_dbg("I");
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_get_info()\n");
|
|
return;
|
|
}
|
|
|
|
jb->info.delay = jb->current - jb->min;
|
|
jb->info.delay_target = jb->target - jb->min;
|
|
|
|
//calculate the losspct...
|
|
max_index = (jb->hist_pointer < JB_HISTORY_SIZE) ?
|
|
jb->hist_pointer : JB_HISTORY_SIZE-1;
|
|
if (max_index>1) {
|
|
pointer = find_pointer(&jb->hist_sorted_delay[0], max_index,
|
|
jb->current);
|
|
jb->info.losspct = ((max_index - pointer)*100/max_index);
|
|
if (jb->info.losspct < 0) {
|
|
jb->info.losspct = 0;
|
|
}
|
|
}
|
|
else {
|
|
jb->info.losspct = 0;
|
|
}
|
|
|
|
*stats = jb->info;
|
|
}
|
|
|
|
|
|
/***********
|
|
* gives the settings for this jitterbuffer
|
|
* *settings = settings
|
|
*/
|
|
void jb_get_settings(jitterbuffer *jb, jb_settings *settings)
|
|
{
|
|
jb_dbg("S");
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_get_settings()\n");
|
|
return;
|
|
}
|
|
|
|
*settings = jb->settings;
|
|
}
|
|
|
|
|
|
/***********
|
|
* returns an estimate on the MOS with given loss, delay and codec
|
|
* if the formula is not present the default will be used
|
|
* please use the JB_CODEC_OTHER if you want to define your own formula
|
|
*
|
|
*/
|
|
float jb_guess_mos(float p, long d, int codec)
|
|
{
|
|
float result;
|
|
|
|
switch (codec) {
|
|
case JB_CODEC_GSM_EFR:
|
|
result = (4.31f - 0.23f*p - 0.0071f*d);
|
|
break;
|
|
|
|
case JB_CODEC_G723_1:
|
|
result = (3.99f - 0.16f*p - 0.0071f*d);
|
|
break;
|
|
|
|
case JB_CODEC_G729:
|
|
case JB_CODEC_G729A:
|
|
result = (4.13f - 0.14f*p - 0.0071f*d);
|
|
break;
|
|
|
|
case JB_CODEC_G711x_PLC:
|
|
result = (4.42f - 0.087f*p - 0.0071f*d);
|
|
break;
|
|
|
|
case JB_CODEC_G711x:
|
|
result = (4.42f - 0.63f*p - 0.0071f*d);
|
|
break;
|
|
|
|
case JB_CODEC_OTHER:
|
|
default:
|
|
result = (4.42f - 0.63f*p - 0.0071f*d);
|
|
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/***********
|
|
* if there are any frames left in JB returns JB_OK, otherwise returns JB_EMPTY
|
|
*/
|
|
int jb_has_frames(jitterbuffer *jb)
|
|
{
|
|
jb_dbg("H");
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_has_frames()\n");
|
|
return JB_NOJB;
|
|
}
|
|
|
|
if(jb->controlframes || jb->voiceframes) {
|
|
return JB_OK;
|
|
}
|
|
else {
|
|
return JB_EMPTY;
|
|
}
|
|
}
|
|
|
|
|
|
/***********
|
|
* Put a packet into the jitterbuffers
|
|
* Only the timestamps of voicepackets are put in the history
|
|
* this because the jitterbuffer only works for voicepackets
|
|
* don't put packets twice in history and queue (e.g. transmitting every frame twice)
|
|
* keep track of statistics
|
|
*/
|
|
void jb_put(jitterbuffer *jb, void *data, int type, long ms, long ts, long now, int codec)
|
|
{
|
|
long pointer, max_index;
|
|
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_put()\n");
|
|
return;
|
|
}
|
|
|
|
jb->info.frames_received++;
|
|
|
|
if (type == JB_TYPE_CONTROL) {
|
|
//put the packet into the contol-queue of the jitterbuffer
|
|
jb_dbg("pC");
|
|
put_control(jb,data,type,ts);
|
|
|
|
}
|
|
else if (type == JB_TYPE_VOICE) {
|
|
// only add voice that aren't already in the buffer
|
|
max_index = (jb->hist_pointer < JB_HISTORY_SIZE) ? jb->hist_pointer : JB_HISTORY_SIZE-1;
|
|
pointer = find_pointer(&jb->hist_sorted_timestamp[0], max_index, ts);
|
|
if (jb->hist_sorted_timestamp[pointer]==ts) { //timestamp already in queue
|
|
jb_dbg("pT");
|
|
free(data);
|
|
jb->info.frames_dropped_twice++;
|
|
}
|
|
else { //add
|
|
jb_dbg("pV");
|
|
/* add voicepacket to history */
|
|
put_history(jb,ts,now,ms,codec);
|
|
/*calculate jitterbuffer size*/
|
|
calculate_info(jb, ts, now, codec);
|
|
/*put the packet into the queue of the jitterbuffer*/
|
|
put_voice(jb,data,type,ms,ts,codec);
|
|
}
|
|
|
|
}
|
|
else if (type == JB_TYPE_SILENCE) { //silence
|
|
jb_dbg("pS");
|
|
put_voice(jb,data,type,ms,ts,codec);
|
|
|
|
}
|
|
else { //should NEVER happen
|
|
jb_err("jb_put(): type not known\n");
|
|
free(data);
|
|
}
|
|
}
|
|
|
|
|
|
/***********
|
|
* control frames have a higher priority then voice frames
|
|
* returns JB_OK if a frame is available and *data points to the packet
|
|
* returns JB_NOFRAME if it's no time to play voice and no control available
|
|
* returns JB_INTERP if interpolating is required
|
|
* returns JB_EMPTY if no voice frame is in the jitterbuffer (only during silence)
|
|
*/
|
|
int jb_get(jitterbuffer *jb, void **data, long now, long interpl)
|
|
{
|
|
int result;
|
|
|
|
jb_dbg("A");
|
|
if (jb == NULL) {
|
|
jb_err("no jitterbuffer in jb_get()\n");
|
|
return JB_NOJB;
|
|
}
|
|
|
|
result = get_control(jb, data);
|
|
if (result != JB_OK ) { //no control message available maybe there is voice...
|
|
result = get_voice(jb, data, now, interpl);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/***********
|
|
* set all the settings to default
|
|
*/
|
|
static void set_default_settings(jitterbuffer *jb)
|
|
{
|
|
jb->settings.min_jb = JB_MIN_SIZE;
|
|
jb->settings.max_jb = JB_MAX_SIZE;
|
|
jb->settings.max_successive_interp = JB_MAX_SUCCESSIVE_INTERP;
|
|
jb->settings.extra_delay = JB_ALLOW_EXTRA_DELAY;
|
|
jb->settings.wait_grow = JB_WAIT_GROW;
|
|
jb->settings.wait_shrink = JB_WAIT_SHRINK;
|
|
jb->settings.max_diff = JB_MAX_DIFF;
|
|
}
|
|
|
|
|
|
/***********
|
|
* reset the jitterbuffer so we can start in silence and
|
|
* we start with a new history
|
|
*/
|
|
static void reset(jitterbuffer *jb)
|
|
{
|
|
jb->hist_pointer = 0; //start over
|
|
jb->silence_begin_ts = 0; //no begin_ts defined
|
|
jb->info.silence =1; //we always start in silence
|
|
}
|
|
|
|
|
|
/***********
|
|
* Search algorithm
|
|
* @REQUIRE max_index is within array
|
|
*
|
|
* Find the position of value in hist_sorted_delay
|
|
* if value doesn't exist return first pointer where array[low]>value
|
|
* int low; //the lowest index being examined
|
|
* int max_index; //the highest index being examined
|
|
* int mid; //the middle index between low and max_index.
|
|
* mid ==(low+max_index)/2
|
|
* at the end low is the position of value or where array[low]>value
|
|
*/
|
|
static long find_pointer(long *array, long max_index, long value)
|
|
{
|
|
register long low, mid, high;
|
|
low = 0;
|
|
high = max_index;
|
|
while (low<=high) {
|
|
mid= (low+high)/2;
|
|
if (array[mid] < value) {
|
|
low = mid+1;
|
|
}
|
|
else {
|
|
high = mid-1;
|
|
}
|
|
}
|
|
while(low < max_index && (array[low]==array[(low+1)]) ) {
|
|
low++;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
|
|
/***********
|
|
* free the given frame, afterwards the framepointer is undefined
|
|
*/
|
|
static void frame_free(jb_frame *frame)
|
|
{
|
|
if (frame->data) {
|
|
free(frame->data);
|
|
}
|
|
free(frame);
|
|
}
|
|
|
|
|
|
/***********
|
|
* put a nonvoice frame into the nonvoice queue
|
|
*/
|
|
static void put_control(jitterbuffer *jb, void *data, int type, long ts)
|
|
{
|
|
jb_frame *frame, *p;
|
|
|
|
frame = malloc(sizeof(jb_frame));
|
|
if(!frame) {
|
|
jb_err("cannot allocate frame\n");
|
|
return;
|
|
}
|
|
frame->data = data;
|
|
frame->ts = ts;
|
|
frame->type = type;
|
|
frame->next = NULL;
|
|
data = NULL;//to avoid stealing memory
|
|
|
|
p = jb->controlframes;
|
|
if (p) { //there are already control messages
|
|
if (ts < p->ts) {
|
|
jb->controlframes = frame;
|
|
frame->next = p;
|
|
}
|
|
else {
|
|
while (p->next && (ts >=p->next->ts)) {//sort on timestamps! so find place to put...
|
|
p = p->next;
|
|
}
|
|
if (p->next) {
|
|
frame->next = p->next;
|
|
}
|
|
p->next = frame;
|
|
}
|
|
}
|
|
else {
|
|
jb->controlframes = frame;
|
|
}
|
|
}
|
|
|
|
|
|
/***********
|
|
* put a voice or silence frame into the jitterbuffer
|
|
*/
|
|
static void put_voice(jitterbuffer *jb, void *data, int type, long ms, long ts, int codec)
|
|
{
|
|
jb_frame *frame, *p;
|
|
frame = malloc(sizeof(jb_frame));
|
|
if(!frame) {
|
|
jb_err("cannot allocate frame\n");
|
|
return;
|
|
}
|
|
|
|
frame->data = data;
|
|
frame->ts = ts;
|
|
frame->ms = ms;
|
|
frame->type = type;
|
|
frame->codec = codec;
|
|
|
|
data = NULL; //to avoid stealing the memory location
|
|
/*
|
|
* frames are a circular list, jb->voiceframes points to to the lowest ts,
|
|
* jb->voiceframes->prev points to the highest ts
|
|
*/
|
|
if(!jb->voiceframes) { /* queue is empty */
|
|
jb->voiceframes = frame;
|
|
frame->next = frame;
|
|
frame->prev = frame;
|
|
}
|
|
else {
|
|
p = jb->voiceframes;
|
|
if(ts < p->prev->ts) { //frame is out of order
|
|
jb->info.frames_ooo++;
|
|
}
|
|
if (ts < p->ts) { //frame is lowest, let voiceframes point to it!
|
|
jb->voiceframes = frame;
|
|
}
|
|
else {
|
|
while(ts < p->prev->ts ) {
|
|
p = p->prev;
|
|
}
|
|
}
|
|
frame->next = p;
|
|
frame->prev = p->prev;
|
|
frame->next->prev = frame;
|
|
frame->prev->next = frame;
|
|
}
|
|
}
|
|
|
|
|
|
/***********
|
|
* puts the timestamps of a received packet in the history of *jb
|
|
* for later calculations of the size of jitterbuffer *jb.
|
|
*
|
|
* summary of function:
|
|
* - calculate delay difference
|
|
* - delete old value from hist & sorted_history_delay & sorted_history_timestamp if needed
|
|
* - add new value to history & sorted_history_delay & sorted_history_timestamp
|
|
* - we keep sorted_history_delay for calculations
|
|
* - we keep sorted_history_timestamp for ensuring each timestamp isn't put twice in the buffer.
|
|
*/
|
|
static void put_history(jitterbuffer *jb, long ts, long now, long ms, int codec)
|
|
{
|
|
jb_hist_element out, in;
|
|
long max_index, pointer, location;
|
|
|
|
// max_index is the highest possible index
|
|
max_index = (jb->hist_pointer < JB_HISTORY_SIZE) ? jb->hist_pointer : JB_HISTORY_SIZE-1;
|
|
location = (jb->hist_pointer % JB_HISTORY_SIZE);
|
|
|
|
// we want to delete a value from the jitterbuffer
|
|
// only when we are through the history.
|
|
if (jb->hist_pointer > JB_HISTORY_SIZE-1) {
|
|
/* the value we need to delete from sorted histories */
|
|
out = jb->hist[location];
|
|
//delete delay from hist_sorted_delay
|
|
pointer = find_pointer(&jb->hist_sorted_delay[0], max_index, out.delay);
|
|
/* move over pointer is the position of kicked*/
|
|
if (pointer<max_index) { //only move if we have something to move
|
|
memmove( &(jb->hist_sorted_delay[pointer]),
|
|
&(jb->hist_sorted_delay[pointer+1]),
|
|
((JB_HISTORY_SIZE-(pointer+1)) * sizeof(long)) );
|
|
}
|
|
|
|
//delete timestamp from hist_sorted_timestamp
|
|
pointer = find_pointer(&jb->hist_sorted_timestamp[0], max_index, out.ts);
|
|
/* move over pointer is the position of kicked*/
|
|
if (pointer<max_index) { //only move if we have something to move
|
|
memmove( &(jb->hist_sorted_timestamp[pointer]),
|
|
&(jb->hist_sorted_timestamp[pointer+1]),
|
|
((JB_HISTORY_SIZE-(pointer+1)) * sizeof(long)) );
|
|
}
|
|
}
|
|
|
|
in.delay = now - ts; //delay of current packet
|
|
in.ts = ts; //timestamp of current packet
|
|
in.ms = ms; //length of current packet
|
|
in.codec = codec; //codec of current packet
|
|
|
|
/* adding the new delay to the sorted history
|
|
* first special cases:
|
|
* - delay is the first history stamp
|
|
* - delay > highest history stamp
|
|
*/
|
|
if (max_index==0 || in.delay >= jb->hist_sorted_delay[max_index-1]) {
|
|
jb->hist_sorted_delay[max_index] = in.delay;
|
|
}
|
|
else {
|
|
pointer = find_pointer(&jb->hist_sorted_delay[0], (max_index-1), in.delay);
|
|
/* move over and add delay */
|
|
memmove( &(jb->hist_sorted_delay[pointer+1]),
|
|
&(jb->hist_sorted_delay[pointer]),
|
|
((JB_HISTORY_SIZE-(pointer+1)) * sizeof(long)) );
|
|
jb->hist_sorted_delay[pointer] = in.delay;
|
|
}
|
|
|
|
/* adding the new timestamp to the sorted history
|
|
* first special cases:
|
|
* - timestamp is the first history stamp
|
|
* - timestamp > highest history stamp
|
|
*/
|
|
if (max_index==0 || in.ts >= jb->hist_sorted_timestamp[max_index-1]) {
|
|
jb->hist_sorted_timestamp[max_index] = in.ts;
|
|
}
|
|
else {
|
|
|
|
pointer = find_pointer(&jb->hist_sorted_timestamp[0], (max_index-1), in.ts);
|
|
/* move over and add timestamp */
|
|
memmove( &(jb->hist_sorted_timestamp[pointer+1]),
|
|
&(jb->hist_sorted_timestamp[pointer]),
|
|
((JB_HISTORY_SIZE-(pointer+1)) * sizeof(long)) );
|
|
jb->hist_sorted_timestamp[pointer] = in.ts;
|
|
}
|
|
|
|
/* put the jb_hist_element in the history
|
|
* then increase hist_pointer for next time
|
|
*/
|
|
jb->hist[location] = in;
|
|
jb->hist_pointer++;
|
|
}
|
|
|
|
|
|
/***********
|
|
* this tries to make a jitterbuffer that behaves like
|
|
* the jitterbuffer proposed in this article:
|
|
* Adaptive Playout Buffer Algorithm for Enhancing Perceived Quality of Streaming Applications
|
|
* by: Kouhei Fujimoto & Shingo Ata & Masayuki Murata
|
|
* http://www.nal.ics.es.osaka-u.ac.jp/achievements/web2002/pdf/journal/k-fujimo02TSJ-AdaptivePlayoutBuffer.pdf
|
|
*
|
|
* it calculates jitter and minimum delay
|
|
* get the best delay for the specified codec
|
|
|
|
*/
|
|
static void calculate_info(jitterbuffer *jb, long ts, long now, int codec)
|
|
{
|
|
long diff, size, max_index, d, d1, d2, n;
|
|
float p, p1, p2, A, B;
|
|
//size = how many items there in the history
|
|
size = (jb->hist_pointer < JB_HISTORY_SIZE) ? jb->hist_pointer : JB_HISTORY_SIZE;
|
|
max_index = size-1;
|
|
|
|
/*
|
|
* the Inter-Quartile Range can be used for estimating jitter
|
|
* http://www.slac.stanford.edu/comp/net/wan-mon/tutorial.html#variable
|
|
* just take the square root of the iqr for jitter
|
|
*/
|
|
jb->info.iqr = jb->hist_sorted_delay[max_index*3/4] - jb->hist_sorted_delay[max_index/4];
|
|
|
|
|
|
/*
|
|
* The RTP way of calculating jitter.
|
|
* This one is used at the moment, although it is not correct.
|
|
* But in this way the other side understands us.
|
|
*/
|
|
diff = now - ts - jb->last_delay;
|
|
if (!jb->last_delay) {
|
|
diff = 0; //this to make sure we won't get odd jitter due first ts.
|
|
}
|
|
jb->last_delay = now - ts;
|
|
if (diff <0) {
|
|
diff = -diff;
|
|
}
|
|
jb->info.jitter = jb->info.jitter + (diff - jb->info.jitter)/16;
|
|
|
|
/* jb->min is minimum delay in hist_sorted_delay, we don't look at the lowest 2% */
|
|
/* because sometimes there are odd delays in there */
|
|
jb->min = jb->hist_sorted_delay[(max_index*2/100)];
|
|
|
|
/*
|
|
* calculating the preferred size of the jitterbuffer:
|
|
* instead of calculating the optimum delay using the Pareto equation
|
|
* I use look at the array of sorted delays and choose my optimum from there
|
|
* always walk trough a percentage of the history this because imagine following tail:
|
|
* [...., 12, 300, 301 ,302]
|
|
* her we want to discard last three but that won't happen if we won't walk the array
|
|
* the number of frames we walk depends on how scattered the sorted delays are.
|
|
* For that we look at the iqr. The dependencies of the iqr are based on
|
|
* tests we've done here in the lab. But are not optimized.
|
|
*/
|
|
//init:
|
|
//the higest delay..
|
|
d = d1= d2 = jb->hist_sorted_delay[max_index]- jb->min;
|
|
A=B=LONG_MIN;
|
|
p = p2 =0;
|
|
n=0;
|
|
p1 = 5; //always look at the top 5%
|
|
if (jb->info.iqr >200) { //with more jitter look at more delays
|
|
p1=25;
|
|
}
|
|
else if (jb->info.iqr >100) {
|
|
p1=20;
|
|
}
|
|
else if (jb->info.iqr >50) {
|
|
p1=11;
|
|
}
|
|
|
|
//find the optimum delay..
|
|
while(max_index>10 && (B > A ||p2<p1)) { // By MDI: from ">=" to ">"
|
|
//the packetloss with this delay
|
|
p2 =(n*100.0f/size);
|
|
// estimate MOS-value
|
|
B = jb_guess_mos(p2,d2,codec);
|
|
if (B > A) {
|
|
p = p2;
|
|
d = d2;
|
|
A = B;
|
|
}
|
|
d1 = d2;
|
|
//find next delay != delay so the same delay isn't calculated twice
|
|
//don't look further if we have seen half of the history
|
|
while((d2>=d1) && ((n*2)<max_index) ) {
|
|
n++;
|
|
d2 = jb->hist_sorted_delay[(max_index-n)] - jb->min;
|
|
}
|
|
}
|
|
//the targeted size of the jitterbuffer
|
|
if (jb->settings.min_jb && (jb->settings.min_jb > d) ) {
|
|
jb->target = jb->min + jb->settings.min_jb;
|
|
}
|
|
else if (jb->settings.max_jb && (jb->settings.max_jb > d) ) {
|
|
jb->target = jb->min + jb->settings.max_jb;
|
|
}
|
|
else {
|
|
jb->target = jb->min + d;
|
|
}
|
|
}
|
|
|
|
|
|
/***********
|
|
* if there is a nonvoice frame it will be returned [*data] and the frame
|
|
* will be made free
|
|
*/
|
|
static int get_control(jitterbuffer *jb, void **data)
|
|
{
|
|
jb_frame *frame;
|
|
int result;
|
|
|
|
frame = jb->controlframes;
|
|
if (frame) {
|
|
jb_dbg("gC");
|
|
*data = frame->data;
|
|
frame->data = NULL;
|
|
jb->controlframes = frame->next;
|
|
frame_free(frame);
|
|
result = JB_OK;
|
|
}
|
|
else {
|
|
result = JB_NOFRAME;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/***********
|
|
* returns JB_OK if a frame is available and *data points to the packet
|
|
* returns JB_NOFRAME if it's no time to play voice and or no frame available
|
|
* returns JB_INTERP if interpolating is required
|
|
* returns JB_EMPTY if no voice frame is in the jitterbuffer (only during silence)
|
|
*
|
|
* if the next frame is a silence frame we will go in silence-mode
|
|
* each new instance of the jitterbuffer will start in silence mode
|
|
* in silence mode we will set the jitterbuffer to the size we want
|
|
* when we are not in silence mode get_voicecase will handle the rest.
|
|
*/
|
|
static int get_voice(jitterbuffer *jb, void **data, long now, long interpl)
|
|
{
|
|
jb_frame *frame;
|
|
long diff;
|
|
int result;
|
|
|
|
diff = jb->target - jb->current;
|
|
|
|
//if the next frame is a silence frame, go in silence mode...
|
|
if((get_next_frametype(jb, now - jb->current) == JB_TYPE_SILENCE) ) {
|
|
jb_dbg("gs");
|
|
frame = get_frame(jb, now - jb->current);
|
|
*data = frame->data;
|
|
frame->data = NULL;
|
|
jb->info.silence =1;
|
|
jb->silence_begin_ts = frame->ts;
|
|
frame_free(frame);
|
|
result = JB_OK;
|
|
}
|
|
else {
|
|
if(jb->info.silence) { // we are in silence
|
|
/*
|
|
* During silence we can set the jitterbuffer size to the size
|
|
* we want...
|
|
*/
|
|
if (diff) {
|
|
jb->current = jb->target;
|
|
}
|
|
frame = get_frame(jb, now - jb->current);
|
|
if (frame) {
|
|
if (jb->silence_begin_ts && frame->ts < jb->silence_begin_ts) {
|
|
jb_dbg("gL");
|
|
/* voice frame is late, next!*/
|
|
jb->info.frames_late++;
|
|
frame_free(frame);
|
|
result = get_voice(jb, data, now, interpl);
|
|
}
|
|
else {
|
|
jb_dbg("gP");
|
|
/* voice frame */
|
|
jb->info.silence = 0;
|
|
jb->silence_begin_ts = 0;
|
|
jb->next_voice_time = frame->ts + frame->ms;
|
|
jb->info.last_voice_ms = frame->ms;
|
|
*data = frame->data;
|
|
frame->data = NULL;
|
|
frame_free(frame);
|
|
result = JB_OK;
|
|
}
|
|
}
|
|
else { //no frame
|
|
jb_dbg("gS");
|
|
result = JB_EMPTY;
|
|
}
|
|
}
|
|
else { //voice case
|
|
result = get_voicecase(jb,data,now,interpl,diff);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/***********
|
|
* The voicecase has four 'options'
|
|
* - difference is way off, reset
|
|
* - diff > 0, we may need to grow
|
|
* - diff < 0, we may need to shrink
|
|
* - everything else
|
|
*/
|
|
static int get_voicecase(jitterbuffer *jb, void **data, long now, long interpl, long diff)
|
|
{
|
|
jb_frame *frame;
|
|
int result;
|
|
|
|
// * - difference is way off, reset
|
|
if (diff > jb->settings.max_diff || -diff > jb->settings.max_diff) {
|
|
jb_err("wakko diff in get_voicecase\n");
|
|
reset(jb); //reset hist because the timestamps are wakko.
|
|
result = JB_NOFRAME;
|
|
//- diff > 0, we may need to grow
|
|
}
|
|
else if ((diff > 0) &&
|
|
(now > (jb->last_adjustment + jb->settings.wait_grow)
|
|
|| (now + jb->current + interpl) < get_next_framets(jb) ) ) { //grow
|
|
/* first try to grow */
|
|
if (diff<interpl/2) {
|
|
jb_dbg("ag");
|
|
jb->current +=diff;
|
|
}
|
|
else {
|
|
jb_dbg("aG");
|
|
/* grow by interp frame len */
|
|
jb->current += interpl;
|
|
}
|
|
jb->last_adjustment = now;
|
|
result = get_voice(jb, data, now, interpl);
|
|
//- diff < 0, we may need to shrink
|
|
}
|
|
else if ( (diff < 0)
|
|
&& (now > (jb->last_adjustment + jb->settings.wait_shrink))
|
|
&& ((-diff) > jb->settings.extra_delay) ) {
|
|
/* now try to shrink
|
|
* if there is a frame shrink by frame length
|
|
* otherwise shrink by interpl
|
|
*/
|
|
jb->last_adjustment = now;
|
|
|
|
frame = get_frame(jb, now - jb->current);
|
|
if(frame) {
|
|
jb_dbg("as");
|
|
/* shrink by frame size we're throwing out */
|
|
jb->info.frames_dropped++;
|
|
jb->current -= frame->ms;
|
|
frame_free(frame);
|
|
}
|
|
else {
|
|
jb_dbg("aS");
|
|
/* shrink by interpl */
|
|
jb->current -= interpl;
|
|
}
|
|
result = get_voice(jb, data, now, interpl);
|
|
}
|
|
else {
|
|
/* if it is not the time to play a result = JB_NOFRAME
|
|
* else We try to play a frame if a frame is available
|
|
* and not late it is played otherwise
|
|
* if available it is dropped and the next is tried
|
|
* last option is interpolating
|
|
*/
|
|
if (now - jb->current < jb->next_voice_time) {
|
|
jb_dbg("aN");
|
|
result = JB_NOFRAME;
|
|
}
|
|
else {
|
|
frame = get_frame(jb, now - jb->current);
|
|
if (frame) { //there is a frame
|
|
/* voice frame is late */
|
|
if(frame->ts < jb->next_voice_time) { //late
|
|
jb_dbg("aL");
|
|
jb->info.frames_late++;
|
|
frame_free(frame);
|
|
result = get_voice(jb, data, now, interpl);
|
|
}
|
|
else {
|
|
jb_dbg("aP");
|
|
/* normal case; return the frame, increment stuff */
|
|
*data = frame->data;
|
|
frame->data = NULL;
|
|
jb->next_voice_time = frame->ts + frame->ms;
|
|
jb->cnt_successive_interp = 0;
|
|
frame_free(frame);
|
|
result = JB_OK;
|
|
}
|
|
}
|
|
else { // no frame, thus interpolate
|
|
jb->cnt_successive_interp++;
|
|
/* assume silence instead of continuing to interpolate */
|
|
if (jb->settings.max_successive_interp && jb->cnt_successive_interp >= jb->settings.max_successive_interp) {
|
|
jb->info.silence = 1;
|
|
jb->silence_begin_ts = jb->next_voice_time;
|
|
}
|
|
jb_dbg("aI");
|
|
jb->next_voice_time += interpl;
|
|
result = JB_INTERP;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
/***********
|
|
* if there are frames and next frame->ts is smaller or equal ts
|
|
* return type of next frame.
|
|
* else return 0
|
|
*/
|
|
static int get_next_frametype(jitterbuffer *jb, long ts)
|
|
{
|
|
jb_frame *frame;
|
|
int result;
|
|
|
|
result = 0;
|
|
frame = jb->voiceframes;
|
|
if (frame && frame->ts <= ts) {
|
|
result = frame->type;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/***********
|
|
* returns ts from next frame in jb->voiceframes
|
|
* or returns LONG_MAX if there is no frame
|
|
*/
|
|
static long get_next_framets(jitterbuffer *jb)
|
|
{
|
|
if (jb->voiceframes) {
|
|
return jb->voiceframes->ts;
|
|
}
|
|
return LONG_MAX;
|
|
}
|
|
|
|
|
|
/***********
|
|
* if there is a frame in jb->voiceframes and
|
|
* has a timestamp smaller/equal to ts
|
|
* this frame will be returned and
|
|
* removed from the queue
|
|
*/
|
|
static jb_frame *get_frame(jitterbuffer *jb, long ts)
|
|
{
|
|
jb_frame *frame;
|
|
|
|
frame = jb->voiceframes;
|
|
if (frame && frame->ts <= ts) {
|
|
if(frame->next == frame) {
|
|
jb->voiceframes = NULL;
|
|
}
|
|
else {
|
|
/* remove this frame */
|
|
frame->prev->next = frame->next;
|
|
frame->next->prev = frame->prev;
|
|
jb->voiceframes = frame->next;
|
|
}
|
|
return frame;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/***********
|
|
* if there is a frame in jb->voiceframes
|
|
* this frame will be unconditionally returned and
|
|
* removed from the queue
|
|
*/
|
|
static jb_frame *get_all_frames(jitterbuffer *jb)
|
|
{
|
|
jb_frame *frame;
|
|
|
|
frame = jb->voiceframes;
|
|
if (frame) {
|
|
if(frame->next == frame) {
|
|
jb->voiceframes = NULL;
|
|
}
|
|
else {
|
|
/* remove this frame */
|
|
frame->prev->next = frame->next;
|
|
frame->next->prev = frame->prev;
|
|
jb->voiceframes = frame->next;
|
|
}
|
|
return frame;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
#endif // !(HAVE_SPEEX_DSP && HAVE_SPEEX_JB)
|