/* * Copyright (C) 2005, Attractel OOD * * Contributors: * Slav Klenov * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. * * A license has been granted to Digium (via disclaimer) for the use of * this code. */ /*! \file * * \brief Jitterbuffering algorithm. * * \author Slav Klenov */ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include #include #include #include #include #include "asterisk/utils.h" #include "fixedjitterbuf.h" #undef FIXED_JB_DEBUG #ifdef FIXED_JB_DEBUG #define ASSERT(a) #else #define ASSERT(a) assert(a) #endif /*! \brief private fixed_jb structure */ struct fixed_jb { struct fixed_jb_frame *frames; struct fixed_jb_frame *tail; struct fixed_jb_conf conf; long rxcore; long delay; long next_delivery; int force_resynch; }; static struct fixed_jb_frame *alloc_jb_frame(struct fixed_jb *jb); static void release_jb_frame(struct fixed_jb *jb, struct fixed_jb_frame *frame); static void get_jb_head(struct fixed_jb *jb, struct fixed_jb_frame *frame); static int resynch_jb(struct fixed_jb *jb, void *data, long ms, long ts, long now); static inline struct fixed_jb_frame *alloc_jb_frame(struct fixed_jb *jb) { return ast_calloc(1, sizeof(struct fixed_jb_frame)); } static inline void release_jb_frame(struct fixed_jb *jb, struct fixed_jb_frame *frame) { free(frame); } static void get_jb_head(struct fixed_jb *jb, struct fixed_jb_frame *frame) { struct fixed_jb_frame *fr; /* unlink the frame */ fr = jb->frames; jb->frames = fr->next; if (jb->frames) { jb->frames->prev = NULL; } else { /* the jb is empty - update tail */ jb->tail = NULL; } /* update next */ jb->next_delivery = fr->delivery + fr->ms; /* copy the destination */ memcpy(frame, fr, sizeof(struct fixed_jb_frame)); /* and release the frame */ release_jb_frame(jb, fr); } struct fixed_jb *fixed_jb_new(struct fixed_jb_conf *conf) { struct fixed_jb *jb; if (!(jb = ast_calloc(1, sizeof(*jb)))) return NULL; /* First copy our config */ memcpy(&jb->conf, conf, sizeof(struct fixed_jb_conf)); /* we dont need the passed config anymore - continue working with the saved one */ conf = &jb->conf; /* validate the configuration */ if (conf->jbsize < 1) conf->jbsize = FIXED_JB_SIZE_DEFAULT; if (conf->resync_threshold < 1) conf->resync_threshold = FIXED_JB_RESYNCH_THRESHOLD_DEFAULT; /* Set the constant delay to the jitterbuf */ jb->delay = conf->jbsize; return jb; } void fixed_jb_destroy(struct fixed_jb *jb) { /* jitterbuf MUST be empty before it can be destroyed */ ASSERT(jb->frames == NULL); free(jb); } static int resynch_jb(struct fixed_jb *jb, void *data, long ms, long ts, long now) { long diff, offset; struct fixed_jb_frame *frame; /* If jb is empty, just reinitialize the jb */ if (!jb->frames) { /* debug check: tail should also be NULL */ ASSERT(jb->tail == NULL); return fixed_jb_put_first(jb, data, ms, ts, now); } /* Adjust all jb state just as the new frame is with delivery = the delivery of the last frame (e.g. this one with max delivery) + the length of the last frame. */ /* Get the diff in timestamps */ diff = ts - jb->tail->ts; /* Ideally this should be just the length of the last frame. The deviation is the desired offset */ offset = diff - jb->tail->ms; /* Do we really need to resynch, or this is just a frame for dropping? */ if (!jb->force_resynch && (offset < jb->conf.resync_threshold && offset > -jb->conf.resync_threshold)) return FIXED_JB_DROP; /* Reset the force resynch flag */ jb->force_resynch = 0; /* apply the offset to the jb state */ jb->rxcore -= offset; frame = jb->frames; while (frame) { frame->ts += offset; frame = frame->next; } /* now jb_put() should add the frame at a last position */ return fixed_jb_put(jb, data, ms, ts, now); } void fixed_jb_set_force_resynch(struct fixed_jb *jb) { jb->force_resynch = 1; } int fixed_jb_put_first(struct fixed_jb *jb, void *data, long ms, long ts, long now) { /* this is our first frame - set the base of the receivers time */ jb->rxcore = now - ts; /* init next for a first time - it should be the time the first frame should be played */ jb->next_delivery = now + jb->delay; /* put the frame */ return fixed_jb_put(jb, data, ms, ts, now); } int fixed_jb_put(struct fixed_jb *jb, void *data, long ms, long ts, long now) { struct fixed_jb_frame *frame, *next, *newframe; long delivery; /* debug check the validity of the input params */ ASSERT(data != NULL); /* do not allow frames shorter than 2 ms */ ASSERT(ms >= 2); ASSERT(ts >= 0); ASSERT(now >= 0); delivery = jb->rxcore + jb->delay + ts; /* check if the new frame is not too late */ if (delivery < jb->next_delivery) { /* should drop the frame, but let first resynch_jb() check if this is not a jump in ts, or the force resynch flag was not set. */ return resynch_jb(jb, data, ms, ts, now); } /* what if the delivery time is bigger than next + delay? Seems like a frame for the future. However, allow more resync_threshold ms in advance */ if (delivery > jb->next_delivery + jb->delay + jb->conf.resync_threshold) { /* should drop the frame, but let first resynch_jb() check if this is not a jump in ts, or the force resynch flag was not set. */ return resynch_jb(jb, data, ms, ts, now); } /* find the right place in the frames list, sorted by delivery time */ frame = jb->tail; while (frame && frame->delivery > delivery) { frame = frame->prev; } /* Check if the new delivery time is not covered already by the chosen frame */ if (frame && (frame->delivery == delivery || delivery < frame->delivery + frame->ms || (frame->next && delivery + ms > frame->next->delivery))) { /* TODO: Should we check for resynch here? Be careful to do not allow threshold smaller than the size of the jb */ /* should drop the frame, but let first resynch_jb() check if this is not a jump in ts, or the force resynch flag was not set. */ return resynch_jb(jb, data, ms, ts, now); } /* Reset the force resynch flag */ jb->force_resynch = 0; /* Get a new frame */ newframe = alloc_jb_frame(jb); newframe->data = data; newframe->ts = ts; newframe->ms = ms; newframe->delivery = delivery; /* and insert it right on place */ if (frame) { next = frame->next; frame->next = newframe; if (next) { newframe->next = next; next->prev = newframe; } else { /* insert after the last frame - should update tail */ jb->tail = newframe; newframe->next = NULL; } newframe->prev = frame; return FIXED_JB_OK; } else if (!jb->frames) { /* the frame list is empty or thats just the first frame ever */ /* tail should also be NULL is that case */ ASSERT(jb->tail == NULL); jb->frames = jb->tail = newframe; newframe->next = NULL; newframe->prev = NULL; return FIXED_JB_OK; } else { /* insert on a first position - should update frames head */ newframe->next = jb->frames; newframe->prev = NULL; jb->frames->prev = newframe; jb->frames = newframe; return FIXED_JB_OK; } } int fixed_jb_get(struct fixed_jb *jb, struct fixed_jb_frame *frame, long now, long interpl) { ASSERT(now >= 0); ASSERT(interpl >= 2); if (now < jb->next_delivery) { /* too early for the next frame */ return FIXED_JB_NOFRAME; } /* Is the jb empty? */ if (!jb->frames) { /* should interpolate a frame */ /* update next */ jb->next_delivery += interpl; return FIXED_JB_INTERP; } /* Isn't it too late for the first frame available in the jb? */ if (now > jb->frames->delivery + jb->frames->ms) { /* yes - should drop this frame and update next to point the next frame (get_jb_head() does it) */ get_jb_head(jb, frame); return FIXED_JB_DROP; } /* isn't it too early to play the first frame available? */ if (now < jb->frames->delivery) { /* yes - should interpolate one frame */ /* update next */ jb->next_delivery += interpl; return FIXED_JB_INTERP; } /* we have a frame for playing now (get_jb_head() updates next) */ get_jb_head(jb, frame); return FIXED_JB_OK; } long fixed_jb_next(struct fixed_jb *jb) { return jb->next_delivery; } int fixed_jb_remove(struct fixed_jb *jb, struct fixed_jb_frame *frameout) { if (!jb->frames) return FIXED_JB_NOFRAME; get_jb_head(jb, frameout); return FIXED_JB_OK; }