diff -urN --exclude .svn asterisk/Makefile asterisk-jb/Makefile --- asterisk/Makefile 2006-01-26 13:25:18.000000000 +0000 +++ asterisk-jb/Makefile 2006-01-26 13:19:35.000000000 +0000 @@ -79,6 +79,9 @@ # Uncomment next one to enable ast_frame tracing (for debugging) TRACE_FRAMES = #-DTRACE_FRAMES +# Uncomment next one to enable the asterisk generic jitterbuffer +GENERIC_JB = #-DAST_JB + # Uncomment next one to enable malloc debugging # You can view malloc debugging with: # *CLI> show memory allocations [filename] @@ -334,6 +337,7 @@ ASTCFLAGS+= $(DEBUG_THREADS) ASTCFLAGS+= $(TRACE_FRAMES) +ASTCFLAGS+= $(GENERIC_JB) ASTCFLAGS+= $(MALLOC_DEBUG) ASTCFLAGS+= $(BUSYDETECT) ASTCFLAGS+= $(OPTIONS) @@ -349,7 +353,7 @@ cdr.o tdd.o acl.o rtp.o udptl.o manager.o asterisk.o \ dsp.o chanvars.o indications.o autoservice.o db.o privacy.o \ astmm.o enum.o srv.o dns.o aescrypt.o aestab.o aeskey.o \ - utils.o plc.o jitterbuf.o dnsmgr.o devicestate.o \ + utils.o plc.o jitterbuf.o scx_jitterbuf.o abstract_jb.o dnsmgr.o devicestate.o \ netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \ cryptostub.o diff -urN --exclude .svn asterisk/abstract_jb.c asterisk-jb/abstract_jb.c --- asterisk/abstract_jb.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-jb/abstract_jb.c 2006-01-26 13:19:35.000000000 +0000 @@ -0,0 +1,878 @@ +/* + * abstract_jb: common implementation-independent jitterbuffer stuff + * + * Copyright (C) 2005, Attractel OOD + * + * Contributors: + * Slav Klenov + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + * + * 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. + */ + +/*! \file + * + * \brief Common implementation-independent jitterbuffer stuff. + * + */ + +#include +#include +#include + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 1.1 $") + +#include "asterisk/frame.h" +#include "asterisk/channel.h" +#include "asterisk/logger.h" +#include "asterisk/term.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" + +#ifdef AST_JB + +#include "asterisk/abstract_jb.h" +#include "scx_jitterbuf.h" +#include "jitterbuf.h" + + +/* Internal jb flags */ +#define JB_USE (1 << 0) +#define JB_TIMEBASE_INITIALIZED (1 << 1) +#define JB_CREATED (1 << 2) + + +/* Hooks for the abstract jb implementation */ + +/* Create */ +typedef void * (*jb_create_impl)(struct ast_jb_conf *general_config, long resynch_threshold); +/* Destroy */ +typedef void (*jb_destroy_impl)(void *jb); +/* Put first frame */ +typedef int (*jb_put_first_impl)(void *jb, struct ast_frame *fin, long now); +/* Put frame */ +typedef int (*jb_put_impl)(void *jb, struct ast_frame *fin, long now); +/* Get frame for now */ +typedef int (*jb_get_impl)(void *jb, struct ast_frame **fout, long now, long interpl); +/* Get next */ +typedef long (*jb_next_impl)(void *jb); +/* Remove first frame */ +typedef int (*jb_remove_impl)(void *jb, struct ast_frame **fout); +/* Force resynch */ +typedef void (*jb_force_resynch_impl)(void *jb); + + +/*! + * \brief Jitterbuffer implementation private struct. + */ +struct ast_jb_impl +{ + char name[AST_JB_IMPL_NAME_SIZE]; + jb_create_impl create; + jb_destroy_impl destroy; + jb_put_first_impl put_first; + jb_put_impl put; + jb_get_impl get; + jb_next_impl next; + jb_remove_impl remove; + jb_force_resynch_impl force_resync; +}; + +/* Implementation functions */ +/* scx */ +static void * jb_create_scx(struct ast_jb_conf *general_config, long resynch_threshold); +static void jb_destroy_scx(void *jb); +static int jb_put_first_scx(void *jb, struct ast_frame *fin, long now); +static int jb_put_scx(void *jb, struct ast_frame *fin, long now); +static int jb_get_scx(void *jb, struct ast_frame **fout, long now, long interpl); +static long jb_next_scx(void *jb); +static int jb_remove_scx(void *jb, struct ast_frame **fout); +static void jb_force_resynch_scx(void *jb); +/* stevek */ +static void * jb_create_stevek(struct ast_jb_conf *general_config, long resynch_threshold); +static void jb_destroy_stevek(void *jb); +static int jb_put_first_stevek(void *jb, struct ast_frame *fin, long now); +static int jb_put_stevek(void *jb, struct ast_frame *fin, long now); +static int jb_get_stevek(void *jb, struct ast_frame **fout, long now, long interpl); +static long jb_next_stevek(void *jb); +static int jb_remove_stevek(void *jb, struct ast_frame **fout); +static void jb_force_resynch_stevek(void *jb); + +/* Available jb implementations */ +static struct ast_jb_impl avail_impl[] = +{ + { + .name = "fixed", + .create = jb_create_scx, + .destroy = jb_destroy_scx, + .put_first = jb_put_first_scx, + .put = jb_put_scx, + .get = jb_get_scx, + .next = jb_next_scx, + .remove = jb_remove_scx, + .force_resync = jb_force_resynch_scx + }, + { + .name = "adaptive", + .create = jb_create_stevek, + .destroy = jb_destroy_stevek, + .put_first = jb_put_first_stevek, + .put = jb_put_stevek, + .get = jb_get_stevek, + .next = jb_next_stevek, + .remove = jb_remove_stevek, + .force_resync = jb_force_resynch_stevek + } +}; + +static int default_impl = 0; + + +/* Abstract return codes */ +#define JB_IMPL_OK 0 +#define JB_IMPL_DROP 1 +#define JB_IMPL_INTERP 2 +#define JB_IMPL_NOFRAME 3 + +/* Translations between impl and abstract return codes */ +static int scx_to_abstract_code[] = + {JB_IMPL_OK, JB_IMPL_DROP, JB_IMPL_INTERP, JB_IMPL_NOFRAME}; +static int stevek_to_abstract_code[] = + {JB_IMPL_OK, JB_IMPL_NOFRAME, JB_IMPL_NOFRAME, JB_IMPL_INTERP, JB_IMPL_DROP, JB_IMPL_OK}; + +/* JB_GET actions (used only for the frames log) */ +static char *jb_get_actions[] = {"Delivered", "Dropped", "Interpolated", "No"}; + +/* Macros for JB logs */ +/*#define jb_verbose(...) ast_verbose(VERBOSE_PREFIX_3 " ***[JB LOG]*** " __VA_ARGS__)*/ +#define jb_verbose(...) if(1){\ + char tmp[192];\ + char msg[128];\ + snprintf(msg, sizeof(msg), VERBOSE_PREFIX_3 "***[JB LOG]*** " __VA_ARGS__);\ + ast_verbose("%s\n", term_color(tmp, msg, COLOR_BRGREEN, 0, sizeof(tmp)));} + +/* Macros for the frame log files */ +#define jb_framelog(...) \ +if(jb->logfile) \ +{ \ + fprintf(jb->logfile, __VA_ARGS__); \ + fflush(jb->logfile); \ +} \ + + +/* Internal utility functions */ +static void jb_choose_impl(struct ast_channel *chan); +static void jb_get_and_deliver(struct ast_channel *chan); +static int create_jb(struct ast_channel *chan, struct ast_frame *first_frame); +static long get_now(struct ast_jb *jb, struct timeval *tv); + + +/* Interface ast jb functions impl */ + + +static void jb_choose_impl(struct ast_channel *chan) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_conf *jbconf = &jb->conf; + struct ast_jb_impl *test_impl; + int i, avail_impl_count = sizeof(avail_impl) / sizeof(avail_impl[0]); + + jb->impl = &avail_impl[default_impl]; + + if(*jbconf->impl == '\0') + { + return; + } + + for(i=0; iimpl, test_impl->name) == 0) + { + jb->impl = test_impl; + return; + } + } +} + + +void ast_jb_do_usecheck(struct ast_channel *c0, struct ast_channel *c1) +{ + struct ast_jb *jb0 = &c0->jb; + struct ast_jb *jb1 = &c1->jb; + struct ast_jb_conf *conf0 = &jb0->conf; + struct ast_jb_conf *conf1 = &jb1->conf; + int c0_wants_jitter = c0->tech->properties & AST_CHAN_TP_WANTSJITTER; + int c0_creates_jitter = c0->tech->properties & AST_CHAN_TP_CREATESJITTER; + int c0_jb_enabled = ast_test_flag(conf0, AST_JB_ENABLED); + int c0_force_jb = ast_test_flag(conf0, AST_JB_FORCED); + int c0_jb_timebase_initialized = ast_test_flag(jb0, JB_TIMEBASE_INITIALIZED); + int c0_jb_created = ast_test_flag(jb0, JB_CREATED); + int c1_wants_jitter = c1->tech->properties & AST_CHAN_TP_WANTSJITTER; + int c1_creates_jitter = c1->tech->properties & AST_CHAN_TP_CREATESJITTER; + int c1_jb_enabled = ast_test_flag(conf1, AST_JB_ENABLED); + int c1_force_jb = ast_test_flag(conf1, AST_JB_FORCED); + int c1_jb_timebase_initialized = ast_test_flag(jb1, JB_TIMEBASE_INITIALIZED); + int c1_jb_created = ast_test_flag(jb1, JB_CREATED); + + if(((!c0_wants_jitter && c1_creates_jitter) || c0_force_jb) && c0_jb_enabled) + { + ast_set_flag(jb0, JB_USE); + if(!c0_jb_timebase_initialized) + { + if(c1_jb_timebase_initialized) + { + memcpy(&jb0->timebase, &jb1->timebase, sizeof(struct timeval)); + } + else + { + gettimeofday(&jb0->timebase, NULL); + } + ast_set_flag(jb0, JB_TIMEBASE_INITIALIZED); + } + + if(!c0_jb_created) + { + jb_choose_impl(c0); + } + } + + if(((!c1_wants_jitter && c0_creates_jitter) || c1_force_jb) && c1_jb_enabled) + { + ast_set_flag(jb1, JB_USE); + if(!c1_jb_timebase_initialized) + { + if(c0_jb_timebase_initialized) + { + memcpy(&jb1->timebase, &jb0->timebase, sizeof(struct timeval)); + } + else + { + gettimeofday(&jb1->timebase, NULL); + } + ast_set_flag(jb1, JB_TIMEBASE_INITIALIZED); + } + + if(!c1_jb_created) + { + jb_choose_impl(c1); + } + } +} + + +int ast_jb_get_when_to_wakeup(struct ast_channel *c0, struct ast_channel *c1, int time_left) +{ + struct ast_jb *jb0 = &c0->jb; + struct ast_jb *jb1 = &c1->jb; + int c0_use_jb = ast_test_flag(jb0, JB_USE); + int c0_jb_is_created = ast_test_flag(jb0, JB_CREATED); + int c1_use_jb = ast_test_flag(jb1, JB_USE); + int c1_jb_is_created = ast_test_flag(jb1, JB_CREATED); + int wait, wait0, wait1; + struct timeval tv_now; + + if(time_left == 0) + { + /* No time left - the bridge will be retried */ + /* TODO: Test disable this */ + /*return 0;*/ + } + + if(time_left < 0) + { + time_left = INT_MAX; + } + + gettimeofday(&tv_now, NULL); + + wait0 = (c0_use_jb && c0_jb_is_created) ? jb0->next - get_now(jb0, &tv_now) : time_left; + wait1 = (c1_use_jb && c1_jb_is_created) ? jb1->next - get_now(jb1, &tv_now) : time_left; + + wait = wait0 < wait1 ? wait0 : wait1; + wait = wait < time_left ? wait : time_left; + + if(wait == INT_MAX) + { + wait = -1; + } + else if(wait < 1) + { + /* don't let wait=0, because this can cause the pbx thread to loop without any sleeping at all */ + wait = 1; + } + + return wait; +} + + +int ast_jb_put(struct ast_channel *chan, struct ast_frame *f) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_impl *jbimpl = jb->impl; + void *jbobj = jb->jbobj; + struct ast_frame *frr; + long now = 0; + + if(!ast_test_flag(jb, JB_USE)) + { + return -1; + } + + if(f->frametype != AST_FRAME_VOICE) + { + if(f->frametype == AST_FRAME_DTMF && ast_test_flag(jb, JB_CREATED)) + { + jb_framelog("JB_PUT {now=%ld}: Received DTMF frame. Force resynching jb...\n", now); + jbimpl->force_resync(jbobj); + } + + return -1; + } + + if(!f->has_timing_info || f->len < 2) + { + /* TODO: Shouldn't we disable the jb here? Or we can produce timestamp and seqno? */ + return -1; + } + + frr = ast_frisolate(f); + if(frr == NULL) + { + ast_log(LOG_ERROR, "Failed to isolate frame for the jitterbuffer on channel '%s'\n", chan->name); + return -1; + } + + if(!ast_test_flag(jb, JB_CREATED)) + { + if(create_jb(chan, frr)) + { + ast_frfree(frr); + /* Disable the jitterbuffer */ + ast_clear_flag(jb, JB_USE); + return -1; + } + + ast_set_flag(jb, JB_CREATED); + return 0; + } + else + { + now = get_now(jb, NULL); + if(jbimpl->put(jbobj, frr, now) != JB_IMPL_OK) + { + jb_framelog("JB_PUT {now=%ld}: Dropped frame with ts=%ld and len=%ld\n", now, frr->ts, frr->len); + ast_frfree(frr); + /*return -1;*/ + /* TODO: Check this fix - should return 0 here, because the dropped frame shouldn't + be delivered at all */ + return 0; + } + + jb->next = jbimpl->next(jbobj); + + jb_framelog("JB_PUT {now=%ld}: Queued frame with ts=%ld and len=%ld\n", now, frr->ts, frr->len); + + return 0; + } +} + + +void ast_jb_get_and_deliver(struct ast_channel *c0, struct ast_channel *c1) +{ + struct ast_jb *jb0 = &c0->jb; + struct ast_jb *jb1 = &c1->jb; + int c0_use_jb = ast_test_flag(jb0, JB_USE); + int c0_jb_is_created = ast_test_flag(jb0, JB_CREATED); + int c1_use_jb = ast_test_flag(jb1, JB_USE); + int c1_jb_is_created = ast_test_flag(jb1, JB_CREATED); + + if(c0_use_jb && c0_jb_is_created) + { + jb_get_and_deliver(c0); + } + + if(c1_use_jb && c1_jb_is_created) + { + jb_get_and_deliver(c1); + } +} + + +static void jb_get_and_deliver(struct ast_channel *chan) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_impl *jbimpl = jb->impl; + void *jbobj = jb->jbobj; + struct ast_frame *f, finterp; + long now; + int interpolation_len, res; + + now = get_now(jb, NULL); + jb->next = jbimpl->next(jbobj); + if(now < jb->next) + { + jb_framelog("\tJB_GET {now=%ld}: now < next=%ld\n", now, jb->next); + return; + } + + while(now >= jb->next) + { + interpolation_len = ast_codec_interp_len(jb->last_format); + + res = jbimpl->get(jbobj, &f, now, interpolation_len); + + switch(res) + { + case JB_IMPL_OK: + /* deliver the frame */ + ast_write(chan, f); + case JB_IMPL_DROP: + jb_framelog("\tJB_GET {now=%ld}: %s frame with ts=%ld and len=%ld\n", + now, jb_get_actions[res], f->ts, f->len); + jb->last_format = f->subclass; + ast_frfree(f); + break; + case JB_IMPL_INTERP: + /* interpolate a frame */ + f = &finterp; + f->frametype = AST_FRAME_VOICE; + f->subclass = jb->last_format; + f->datalen = 0; + f->samples = interpolation_len * 8; + f->mallocd = 0; + f->src = "JB interpolation"; + f->data = NULL; + f->delivery = ast_tvadd(jb->timebase, ast_samp2tv(jb->next, 1000)); + f->offset=AST_FRIENDLY_OFFSET; + /* deliver the interpolated frame */ + ast_write(chan, f); + jb_framelog("\tJB_GET {now=%ld}: Interpolated frame with len=%d\n", now, interpolation_len); + break; + case JB_IMPL_NOFRAME: + ast_log(LOG_WARNING, + "JB_IMPL_NOFRAME is retuned from the %s jb when now=%ld >= next=%ld, jbnext=%ld!\n", + jbimpl->name, now, jb->next, jbimpl->next(jbobj)); + jb_framelog("\tJB_GET {now=%ld}: No frame for now!?\n", now); + return; + default: + ast_log(LOG_ERROR, "This should never happen!\n"); + CRASH; + break; + } + + jb->next = jbimpl->next(jbobj); + } +} + + +static int create_jb(struct ast_channel *chan, struct ast_frame *frr) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_conf *jbconf = &jb->conf; + struct ast_jb_impl *jbimpl = jb->impl; + void *jbobj; + struct ast_channel *bridged; + long now; + char logfile_pathname[20 + AST_JB_IMPL_NAME_SIZE + 2*AST_CHANNEL_NAME + 1]; + char name1[AST_CHANNEL_NAME], name2[AST_CHANNEL_NAME], *tmp; + int res; + + jbobj = jb->jbobj = jbimpl->create(jbconf, jbconf->resync_threshold); + if(jbobj == NULL) + { + ast_log(LOG_WARNING, "Failed to create jitterbuffer on channel '%s'\n", chan->name); + return -1; + } + + now = get_now(jb, NULL); + res = jbimpl->put_first(jbobj, frr, now); + + /* The result of putting the first frame should not differ from OK. However, its possible + some implementations (i.e. stevek's when resynch_threshold is specified) to drop it. */ + if(res != JB_IMPL_OK) + { + ast_log(LOG_WARNING, "Failed to put first frame in the jitterbuffer on channel '%s'\n", chan->name); + /* + jbimpl->destroy(jbobj); + return -1; + */ + } + + /* Init next */ + jb->next = jbimpl->next(jbobj); + + /* Init last format for a first time. */ + jb->last_format = frr->subclass; + + /* Create a frame log file */ + if(ast_test_flag(jbconf, AST_JB_LOG)) + { + snprintf(name2, sizeof(name2), "%s", chan->name); + tmp = strchr(name2, '/'); + if(tmp != NULL) + { + *tmp = '#'; + } + bridged = ast_bridged_channel(chan); + if(bridged == NULL) + { + /* We should always have bridged chan if a jitterbuffer is in use */ + CRASH; + } + snprintf(name1, sizeof(name1), "%s", bridged->name); + tmp = strchr(name1, '/'); + if(tmp != NULL) + { + *tmp = '#'; + } + snprintf(logfile_pathname, sizeof(logfile_pathname), + "/tmp/ast_%s_jb_%s--%s.log", jbimpl->name, name1, name2); + jb->logfile = fopen(logfile_pathname, "w+b"); + + if(jb->logfile == NULL) + { + ast_log(LOG_WARNING, "Failed to create frame log file with pathname '%s'\n", logfile_pathname); + } + + if(res == JB_IMPL_OK) + { + jb_framelog("JB_PUT_FIRST {now=%ld}: Queued frame with ts=%ld and len=%ld\n", + now, frr->ts, frr->len); + } + else + { + jb_framelog("JB_PUT_FIRST {now=%ld}: Dropped frame with ts=%ld and len=%ld\n", + now, frr->ts, frr->len); + } + } + + jb_verbose("%s jitterbuffer created on channel %s", jbimpl->name, chan->name); + + /* Free the frame if it has not been queued in the jb */ + if(res != JB_IMPL_OK) + { + ast_frfree(frr); + } + + return 0; +} + + +void ast_jb_destroy(struct ast_channel *chan) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_impl *jbimpl = jb->impl; + void *jbobj = jb->jbobj; + struct ast_frame *f; + + if(jb->logfile != NULL) + { + fclose(jb->logfile); + jb->logfile = NULL; + } + + if(ast_test_flag(jb, JB_CREATED)) + { + /* Remove and free all frames still queued in jb */ + while(jbimpl->remove(jbobj, &f) == JB_IMPL_OK) + { + ast_frfree(f); + } + + jbimpl->destroy(jbobj); + jb->jbobj = NULL; + + ast_clear_flag(jb, JB_CREATED); + + jb_verbose("%s jitterbuffer destroyed on channel %s", jbimpl->name, chan->name); + } +} + + +static long get_now(struct ast_jb *jb, struct timeval *tv) +{ + struct timeval now; + + if(tv == NULL) + { + tv = &now; + gettimeofday(tv, NULL); + } + + return (long) ((tv->tv_sec - jb->timebase.tv_sec) * 1000) + + (long) ((double) (tv->tv_usec - jb->timebase.tv_usec) / 1000.0); + + /* TODO: For asterisk complience, we should use: */ + /* return ast_tvdiff_ms(*tv, jb->timebase); */ +} + + +int ast_jb_read_conf(struct ast_jb_conf *conf, char *varname, char *value) +{ + int prefixlen = sizeof(AST_JB_CONF_PREFIX) - 1; + char *name; + int tmp; + + if(memcmp(AST_JB_CONF_PREFIX, varname, prefixlen) != 0) + { + return -1; + } + + name = varname + prefixlen; + + if(strcmp(name, AST_JB_CONF_ENABLE) == 0) + { + if(ast_true(value)) + { + conf->flags |= AST_JB_ENABLED; + } + } + else if(strcmp(name, AST_JB_CONF_FORCE) == 0) + { + if(ast_true(value)) + { + conf->flags |= AST_JB_FORCED; + } + } + else if(strcmp(name, AST_JB_CONF_MAX_SIZE) == 0) + { + if((tmp = atoi(value)) > 0) + { + conf->max_size = tmp; + } + } + else if(strcmp(name, AST_JB_CONF_RESYNCH_THRESHOLD) == 0) + { + if((tmp = atoi(value)) > 0) + { + conf->resync_threshold = tmp; + } + } + else if(strcmp(name, AST_JB_CONF_IMPL) == 0) + { + if(*value) + { + snprintf(conf->impl, sizeof(conf->impl), "%s", value); + } + } + else if(strcmp(name, AST_JB_CONF_LOG) == 0) + { + if(ast_true(value)) + { + conf->flags |= AST_JB_LOG; + } + } + else + { + return -1; + } + + return 0; +} + + +void ast_jb_configure(struct ast_channel *chan, struct ast_jb_conf *conf) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_conf *jbconf = &jb->conf; + + memcpy(jbconf, conf, sizeof(struct ast_jb_conf)); +} + + +void ast_jb_get_config(struct ast_channel *chan, struct ast_jb_conf *conf) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_conf *jbconf = &jb->conf; + + memcpy(conf, jbconf, sizeof(struct ast_jb_conf)); +} + + +/* Implementation functions */ + +/* scx */ + +static void * jb_create_scx(struct ast_jb_conf *general_config, long resynch_threshold) +{ + struct scx_jb_conf conf; + + conf.jbsize = general_config->max_size; + conf.resync_threshold = resynch_threshold; + + return scx_jb_new(&conf); +} + + +static void jb_destroy_scx(void *jb) +{ + struct scx_jb *scxjb = (struct scx_jb *) jb; + + /* destroy the jb */ + scx_jb_destroy(scxjb); +} + + +static int jb_put_first_scx(void *jb, struct ast_frame *fin, long now) +{ + struct scx_jb *scxjb = (struct scx_jb *) jb; + int res; + + res = scx_jb_put_first(scxjb, fin, fin->len, fin->ts, now); + + return scx_to_abstract_code[res]; +} + + +static int jb_put_scx(void *jb, struct ast_frame *fin, long now) +{ + struct scx_jb *scxjb = (struct scx_jb *) jb; + int res; + + res = scx_jb_put(scxjb, fin, fin->len, fin->ts, now); + + return scx_to_abstract_code[res]; +} + + +static int jb_get_scx(void *jb, struct ast_frame **fout, long now, long interpl) +{ + struct scx_jb *scxjb = (struct scx_jb *) jb; + struct scx_jb_frame frame; + int res; + + res = scx_jb_get(scxjb, &frame, now, interpl); + *fout = frame.data; + + return scx_to_abstract_code[res]; +} + + +static long jb_next_scx(void *jb) +{ + struct scx_jb *scxjb = (struct scx_jb *) jb; + + return scx_jb_next(scxjb); +} + + +static int jb_remove_scx(void *jb, struct ast_frame **fout) +{ + struct scx_jb *scxjb = (struct scx_jb *) jb; + struct scx_jb_frame frame; + int res; + + res = scx_jb_remove(scxjb, &frame); + *fout = frame.data; + + return scx_to_abstract_code[res]; +} + + +static void jb_force_resynch_scx(void *jb) +{ + struct scx_jb *scxjb = (struct scx_jb *) jb; + + scx_jb_set_force_resynch(scxjb); +} + + +/* stevek */ + +static void * jb_create_stevek(struct ast_jb_conf *general_config, long resynch_threshold) +{ + jb_conf jbconf; + jitterbuf *stevekjb; + + stevekjb = jb_new(); + if(stevekjb != NULL) + { + jbconf.max_jitterbuf = general_config->max_size; + jbconf.resync_threshold = general_config->resync_threshold; + jbconf.max_contig_interp = 10; + jb_setconf(stevekjb, &jbconf); + } + + return stevekjb; +} + + +static void jb_destroy_stevek(void *jb) +{ + jitterbuf *stevekjb = (jitterbuf *) jb; + + jb_destroy(stevekjb); +} + + +static int jb_put_first_stevek(void *jb, struct ast_frame *fin, long now) +{ + return jb_put_stevek(jb, fin, now); +} + + +static int jb_put_stevek(void *jb, struct ast_frame *fin, long now) +{ + jitterbuf *stevekjb = (jitterbuf *) jb; + int res; + + res = jb_put(stevekjb, fin, JB_TYPE_VOICE, fin->len, fin->ts, now); + + return stevek_to_abstract_code[res]; +} + + +static int jb_get_stevek(void *jb, struct ast_frame **fout, long now, long interpl) +{ + jitterbuf *stevekjb = (jitterbuf *) jb; + jb_frame frame; + int res; + + res = jb_get(stevekjb, &frame, now, interpl); + *fout = frame.data; + + return stevek_to_abstract_code[res]; +} + + +static long jb_next_stevek(void *jb) +{ + jitterbuf *stevekjb = (jitterbuf *) jb; + + return jb_next(stevekjb); +} + + +static int jb_remove_stevek(void *jb, struct ast_frame **fout) +{ + jitterbuf *stevekjb = (jitterbuf *) jb; + jb_frame frame; + int res; + + res = jb_getall(stevekjb, &frame); + *fout = frame.data; + + return stevek_to_abstract_code[res]; +} + + +static void jb_force_resynch_stevek(void *jb) +{ +} + + +#endif /* AST_JB */ + + diff -urN --exclude .svn asterisk/apps/app_morsecode.c asterisk-jb/apps/app_morsecode.c --- asterisk/apps/app_morsecode.c 2006-01-26 13:24:54.000000000 +0000 +++ asterisk-jb/apps/app_morsecode.c 2006-01-19 15:44:10.000000000 +0000 @@ -125,6 +125,14 @@ return 0; } + /* + * Keep in mind that (from http://en.wikipedia.org/wiki/Morse_code) + * + * "In text-book, full-speed Morse, a dah is conventionally 3 times + * as long as a dit. Spacing between dits and dahs in a character + * is the length of one dit. Spacing between letters in a word is + * the length of a dah (3 dits). Spacing between words is 7 dits." + */ for (digit = data; *digit; digit++) { char *dahdit; if (*digit < 0) { @@ -135,6 +143,9 @@ playtone(chan, TONE, 3); } else if (*dahdit == '.') { playtone(chan, TONE, 1); + } else if (*dahdit == ' ') { + /* Pause between words */ + playtone(chan, 0, 7); } else { /* Account for ditlen of silence immediately following */ playtone(chan, 0, 2); @@ -144,7 +155,7 @@ playtone(chan, 0, 1); } /* Pause between characters */ - playtone(chan, 0, 2); + playtone(chan, 0, 3); } LOCAL_USER_REMOVE(u); diff -urN --exclude .svn asterisk/channel.c asterisk-jb/channel.c --- asterisk/channel.c 2006-01-26 13:25:18.000000000 +0000 +++ asterisk-jb/channel.c 2006-01-26 13:23:52.000000000 +0000 @@ -988,6 +988,11 @@ while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) ast_var_delete(vardata); +#ifdef AST_JB + /* Destroy the jitterbuffer */ + ast_jb_destroy(chan); +#endif /* AST_JB */ + free(chan); AST_LIST_UNLOCK(&channels); @@ -3279,6 +3284,10 @@ int watch_c0_dtmf; int watch_c1_dtmf; void *pvt0, *pvt1; +#ifdef AST_JB + /* Indicates whether a frame was queued into a jitterbuffer */ + int frame_put_in_jb; +#endif /* AST_JB */ int to; cs[0] = c0; @@ -3290,6 +3299,11 @@ watch_c0_dtmf = config->flags & AST_BRIDGE_DTMF_CHANNEL_0; watch_c1_dtmf = config->flags & AST_BRIDGE_DTMF_CHANNEL_1; +#ifdef AST_JB + /* Check the need of a jitterbuffer for each channel */ + ast_jb_do_usecheck(c0, c1); +#endif /* AST_JB */ + for (;;) { if ((c0->tech_pvt != pvt0) || (c1->tech_pvt != pvt1) || (o0nativeformats != c0->nativeformats) || @@ -3306,8 +3320,17 @@ } } else to = -1; +#ifdef AST_JB + /* Calculate the appropriate max sleep interval - in general, this is the time, + left to the closest jb delivery moment */ + to = ast_jb_get_when_to_wakeup(c0, c1, to); +#endif /* AST_JB */ who = ast_waitfor_n(cs, 2, &to); if (!who) { +#ifdef AST_JB + /* No frame received within the specified timeout - check if we have to deliver now */ + ast_jb_get_and_deliver(c0, c1); +#endif /* AST_JB */ ast_log(LOG_DEBUG, "Nobody there, continuing...\n"); if (c0->_softhangup == AST_SOFTHANGUP_UNBRIDGE || c1->_softhangup == AST_SOFTHANGUP_UNBRIDGE) { if (c0->_softhangup == AST_SOFTHANGUP_UNBRIDGE) @@ -3328,6 +3351,11 @@ break; } +#ifdef AST_JB + /* Try add the frame info the who's bridged channel jitterbuff */ + frame_put_in_jb = !ast_jb_put((who == c0) ? c1 : c0, f); +#endif /* AST_JB */ + if ((f->frametype == AST_FRAME_CONTROL) && !(config->flags & AST_BRIDGE_IGNORE_SIGS)) { if ((f->subclass == AST_CONTROL_HOLD) || (f->subclass == AST_CONTROL_UNHOLD) || (f->subclass == AST_CONTROL_VIDUPDATE)) { @@ -3368,7 +3396,18 @@ last = who; #endif tackygoto: +#ifdef AST_JB + /* Write immediately frames, not passed through jb */ + if(!frame_put_in_jb) + { + ast_write((who == c0) ? c1 : c0, f); + } + + /* Check if we have to deliver now */ + ast_jb_get_and_deliver(c0, c1); +#else /* AST_JB */ ast_write((who == c0) ? c1 : c0, f); +#endif /* AST_JB */ } } ast_frfree(f); diff -urN --exclude .svn asterisk/channels/chan_iax2.c asterisk-jb/channels/chan_iax2.c --- asterisk/channels/chan_iax2.c 2006-01-26 13:24:53.000000000 +0000 +++ asterisk-jb/channels/chan_iax2.c 2006-01-26 13:19:35.000000000 +0000 @@ -1410,6 +1410,9 @@ the IAX thread with the iaxsl lock held. */ struct iax_frame *fr = data; fr->retrans = -1; +#ifdef AST_JB + fr->af.has_timing_info = 0; +#endif /* AST_JB */ if (iaxs[fr->callno] && !ast_test_flag(iaxs[fr->callno], IAX_ALREADYGONE)) iax2_queue_frame(fr->callno, &fr->af); /* Free our iax frame */ diff -urN --exclude .svn asterisk/channels/chan_sip.c asterisk-jb/channels/chan_sip.c --- asterisk/channels/chan_sip.c 2006-01-26 13:24:53.000000000 +0000 +++ asterisk-jb/channels/chan_sip.c 2006-01-26 13:21:17.000000000 +0000 @@ -154,6 +154,19 @@ #define SIP_MAX_LINES 64 /*!< Max amount of lines in SIP attachment (like SDP) */ +#ifdef AST_JB +#include "asterisk/abstract_jb.h" +/* Global jitterbuffer configuration - by default, jb is disabled */ +static struct ast_jb_conf default_jbconf = +{ + .flags = 0, + .max_size = -1, + .resync_threshold = -1, + .impl = "" +}; +static struct ast_jb_conf global_jbconf; +#endif /* AST_JB */ + static const char desc[] = "Session Initiation Protocol (SIP)"; static const char channeltype[] = "SIP"; static const char config[] = "sip.conf"; @@ -711,6 +724,9 @@ struct ast_variable *chanvars; /*!< Channel variables to set for call */ struct sip_pvt *next; /*!< Next dialog in chain */ struct sip_invite_param *options; /*!< Options for INVITE */ +#ifdef AST_JB + struct ast_jb_conf jbconf; +#endif /* AST_JB */ } *iflist = NULL; #define FLAG_RESPONSE (1 << 0) @@ -952,7 +968,11 @@ .type = channeltype, .description = "Session Initiation Protocol (SIP)", .capabilities = ((AST_FORMAT_MAX_AUDIO << 1) - 1), +#ifdef AST_JB + .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, +#else /* AST_JB */ .properties = AST_CHAN_TP_WANTSJITTER, +#endif /* AST_JB */ .requester = sip_request_call, .devicestate = sip_devicestate, .call = sip_call, @@ -2899,6 +2919,14 @@ for (v = i->chanvars ; v ; v = v->next) pbx_builtin_setvar_helper(tmp,v->name,v->value); +#ifdef AST_JB + /* Configure the new channel jb */ + if(tmp != NULL && i != NULL && i->rtp != NULL) + { + ast_jb_configure(tmp, &i->jbconf); + } +#endif /* AST_JB */ + return tmp; } @@ -3195,6 +3223,11 @@ p->noncodeccapability |= AST_RTP_DTMF; ast_string_field_set(p, context, default_context); +#ifdef AST_JB + /* Assign default jb conf to the new sip_pvt */ + memcpy(&p->jbconf, &global_jbconf, sizeof(struct ast_jb_conf)); +#endif /* AST_JB */ + /* Add to active dialog list */ ast_mutex_lock(&iflock); p->next = iflist; @@ -12151,6 +12184,14 @@ v = v->next; continue; } +#ifdef AST_JB + /* handle jb conf */ + if(ast_jb_read_conf(&global_jbconf, v->name, v->value) == 0) + { + v = v->next; + continue; + } +#endif /* AST_JB */ if (realtime && !strcasecmp(v->name, "regseconds")) { if (sscanf(v->value, "%ld", (time_t *)®seconds) != 1) @@ -12417,6 +12458,11 @@ /* Misc settings for the channel */ relaxdtmf = 0; callevents = 0; + +#ifdef AST_JB + /* Copy the default jb config over global_jbconf */ + memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); +#endif /* AST_JB */ /* Read the [general] config section of sip.conf (or from realtime config) */ for (v = ast_variable_browse(cfg, "general"); v; v = v->next) { diff -urN --exclude .svn asterisk/channels/chan_zap.c asterisk-jb/channels/chan_zap.c --- asterisk/channels/chan_zap.c 2006-01-26 13:24:53.000000000 +0000 +++ asterisk-jb/channels/chan_zap.c 2006-01-26 13:19:35.000000000 +0000 @@ -101,6 +101,19 @@ #include "asterisk/utils.h" #include "asterisk/transcap.h" +#ifdef AST_JB +#include "asterisk/abstract_jb.h" +/* Global jitterbuffer configuration - by default, jb is disabled */ +static struct ast_jb_conf default_jbconf = +{ + .flags = 0, + .max_size = -1, + .resync_threshold = -1, + .impl = "" +}; +static struct ast_jb_conf global_jbconf; +#endif /* AST_JB */ + #ifndef ZT_SIG_EM_E1 #error "Your zaptel is too old. please cvs update" #endif @@ -689,6 +702,9 @@ #endif int polarity; int dsp_features; +#ifdef AST_JB + struct ast_jb_conf jbconf; +#endif /* AST_JB */ } *iflist = NULL, *ifend = NULL; @@ -5200,6 +5216,13 @@ } } else ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); +#ifdef AST_JB + /* Configure the new channel jb */ + if(tmp != NULL && i != NULL) + { + ast_jb_configure(tmp, &i->jbconf); + } +#endif /* AST_JB */ return tmp; } @@ -6983,6 +7006,10 @@ for (x=0;x<3;x++) tmp->subs[x].zfd = -1; tmp->channel = channel; +#ifdef AST_JB + /* Assign default jb conf to the new zt_pvt */ + memcpy(&tmp->jbconf, &global_jbconf, sizeof(struct ast_jb_conf)); +#endif /* AST_JB */ } if (tmp) { @@ -10309,8 +10336,20 @@ } } #endif +#ifdef AST_JB + /* Copy the default jb config over global_jbconf */ + memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); +#endif /* AST_JB */ v = ast_variable_browse(cfg, "channels"); while(v) { +#ifdef AST_JB + /* handle jb conf */ + if(ast_jb_read_conf(&global_jbconf, v->name, v->value) == 0) + { + v = v->next; + continue; + } +#endif /* AST_JB */ /* Create the interface list */ if (!strcasecmp(v->name, "channel") #ifdef ZAPATA_PRI diff -urN --exclude .svn asterisk/configs/sip.conf.sample asterisk-jb/configs/sip.conf.sample --- asterisk/configs/sip.conf.sample 2006-01-26 13:25:16.000000000 +0000 +++ asterisk-jb/configs/sip.conf.sample 2006-01-26 13:19:35.000000000 +0000 @@ -243,6 +243,32 @@ ; destinations which do not have a prior ; account relationship with your server. +;------------------------------ JITTER BUFFER CONFIGURATION -------------------------- +; jb-enable = yes ; Enables the use of a jitterbuffer on the receiving side of a + ; SIP channel. Defaults to "no". An enabled jitterbuffer will + ; be used only if the sending side can create and the receiving + ; side can not accept jitter. The SIP channel can accept jitter, + ; thus a jitterbuffer on the receive SIP side will be used only + ; if it is forced and enabled. + +; jb-force = no ; Forces the use of a jitterbuffer on the receive side of a SIP + ; channel. Defaults to "no". + +; jb-max-size = 200 ; Max length of the jitterbuffer in milliseconds. + +; jb-resynch-threshold = 1000 ; Jump in the frame timestamps over which the jitterbuffer is + ; resynchronized. Useful to improve the quality of the voice, with + ; big jumps in/broken timestamps, usualy sent from exotic devices + ; and programs. Defaults to 1000. + +; jb-impl = fixed ; Jitterbuffer implementation, used on the receiving side of a SIP + ; channel. Two implementation are currenlty available - "fixed" + ; (with size always equals to jb-max-size) and "adaptive" (with + ; variable size, actually the new jb of IAX2). Defaults to fixed. + +; jb-log = no ; Enables jitterbuffer frame logging. Defaults to "no". +;----------------------------------------------------------------------------------- + [authentication] ; Global credentials for outbound calls, i.e. when a proxy challenges your ; Asterisk server for authentication. These credentials override diff -urN --exclude .svn asterisk/configs/zapata.conf.sample asterisk-jb/configs/zapata.conf.sample --- asterisk/configs/zapata.conf.sample 2006-01-26 13:25:16.000000000 +0000 +++ asterisk-jb/configs/zapata.conf.sample 2006-01-26 13:19:35.000000000 +0000 @@ -476,6 +476,33 @@ ; ;jitterbuffers=4 ; +;------------------------------ JITTER BUFFER CONFIGURATION -------------------------- +; jb-enable = yes ; Enables the use of a jitterbuffer on the receiving side of a + ; ZAP channel. Defaults to "no". An enabled jitterbuffer will + ; be used only if the sending side can create and the receiving + ; side can not accept jitter. The ZAP channel can't accept jitter, + ; thus an enabled jitterbuffer on the receive ZAP side will always + ; be used if the sending side can create jitter or if ZAP jb is + ; forced. + +; jb-force = no ; Forces the use of a jitterbuffer on the receive side of a ZAP + ; channel. Defaults to "no". + +; jb-max-size = 200 ; Max length of the jitterbuffer in milliseconds. + +; jb-resynch-threshold = 1000 ; Jump in the frame timestamps over which the jitterbuffer is + ; resynchronized. Useful to improve the quality of the voice, with + ; big jumps in/broken timestamps, usualy sent from exotic devices + ; and programs. Defaults to 1000. + +; jb-impl = fixed ; Jitterbuffer implementation, used on the receiving side of a SIP + ; channel. Two implementation are currenlty available - "fixed" + ; (with size always equals to jb-max-size) and "adaptive" (with + ; variable size, actually the new jb of IAX2). Defaults to fixed. + +; jb-log = no ; Enables jitterbuffer frame logging. Defaults to "no". +;----------------------------------------------------------------------------------- +; ; You can define your own custom ring cadences here. You can define up to 8 ; pairs. If the silence is negative, it indicates where the callerid spill is ; to be placed. Also, if you define any custom cadences, the default cadences diff -urN --exclude .svn asterisk/frame.c asterisk-jb/frame.c --- asterisk/frame.c 2006-01-26 13:25:18.000000000 +0000 +++ asterisk-jb/frame.c 2006-01-26 13:19:35.000000000 +0000 @@ -319,6 +319,16 @@ out->offset = fr->offset; out->src = NULL; out->data = fr->data; +#ifdef AST_JB + /* Copy the timing data */ + out->has_timing_info = fr->has_timing_info; + if(fr->has_timing_info) + { + out->ts = fr->ts; + out->len = fr->len; + out->seqno = fr->seqno; + } +#endif /* AST_JB */ } else { out = fr; } @@ -382,6 +392,15 @@ out->prev = NULL; out->next = NULL; memcpy(out->data, f->data, out->datalen); +#ifdef AST_JB + out->has_timing_info = f->has_timing_info; + if(f->has_timing_info) + { + out->ts = f->ts; + out->len = f->len; + out->seqno = f->seqno; + } +#endif /* AST_JB */ return out; } diff -urN --exclude .svn asterisk/include/asterisk/abstract_jb.h asterisk-jb/include/asterisk/abstract_jb.h --- asterisk/include/asterisk/abstract_jb.h 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-jb/include/asterisk/abstract_jb.h 2006-01-26 13:19:35.000000000 +0000 @@ -0,0 +1,215 @@ +/* + * abstract_jb: common implementation-independent jitterbuffer stuff + * + * Copyright (C) 2005, Attractel OOD + * + * Contributors: + * Slav Klenov + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + * + * 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. + */ + +/*! \file + * + * \brief Common implementation-independent jitterbuffer stuff. + * + */ + +#ifndef _ABSTRACT_JB_H_ +#define _ABSTRACT_JB_H_ + +#include +#include + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +struct ast_channel; +struct ast_frame; + + +/* Configuration flags */ +#define AST_JB_ENABLED (1 << 0) +#define AST_JB_FORCED (1 << 1) +#define AST_JB_LOG (1 << 2) + +#define AST_JB_IMPL_NAME_SIZE 12 + +/*! + * \brief General jitterbuffer configuration. + */ +struct ast_jb_conf +{ + /*! \brief Combination of the AST_JB_ENABLED, AST_JB_FORCED and AST_JB_LOG flags. */ + unsigned int flags; + /*! \brief Max size of the jitterbuffer implementation. */ + long max_size; + /*! \brief Resynchronization threshold of the jitterbuffer implementation. */ + long resync_threshold; + /*! \brief Name of the jitterbuffer implementation to be used. */ + char impl[AST_JB_IMPL_NAME_SIZE]; +}; + + +/* Jitterbuffer configuration property names */ +#define AST_JB_CONF_PREFIX "jb-" +#define AST_JB_CONF_ENABLE "enable" +#define AST_JB_CONF_FORCE "force" +#define AST_JB_CONF_MAX_SIZE "max-size" +#define AST_JB_CONF_RESYNCH_THRESHOLD "resynch-threshold" +#define AST_JB_CONF_IMPL "impl" +#define AST_JB_CONF_LOG "log" + + +struct ast_jb_impl; + + +/*! + * \brief General jitterbuffer state. + */ +struct ast_jb +{ + /*! \brief Jitterbuffer configuration. */ + struct ast_jb_conf conf; + /*! \brief Jitterbuffer implementation to be used. */ + struct ast_jb_impl *impl; + /*! \brief Jitterbuffer object, passed to the implementation. */ + void *jbobj; + /*! \brief The time the jitterbuffer was created. */ + struct timeval timebase; + /*! \brief The time the next frame should be played. */ + long next; + /*! \brief Voice format of the last frame in. */ + int last_format; + /*! \brief File for frame timestamp tracing. */ + FILE *logfile; + /*! \brief Jitterbuffer internal state flags. */ + unsigned int flags; +}; + + +/*! + * \brief Checks the need of a jb use in a generic bridge. + * \param c0 first bridged channel. + * \param c1 second bridged channel. + * + * Called from ast_generic_bridge() when two channels are entering in a bridge. + * The function checks the need of a jitterbuffer, depending on both channel's + * configuration and technology properties. As a result, this function sets + * appropriate internal jb flags to the channels, determining further behaviour + * of the bridged jitterbuffers. + */ +void ast_jb_do_usecheck(struct ast_channel *c0, struct ast_channel *c1); + + +/*! + * \brief Calculates the time, left to the closest delivery moment in a bridge. + * \param c0 first bridged channel. + * \param c1 second bridged channel. + * \param time_left bridge time limit, or -1 if not set. + * + * Called from ast_generic_bridge() to determine the maximum time to wait for + * activity in ast_waitfor_n() call. If neihter of the channels is using jb, + * this function returns the time limit passed. + * + * \return maximum time to wait. + */ +int ast_jb_get_when_to_wakeup(struct ast_channel *c0, struct ast_channel *c1, int time_left); + + +/*! + * \brief Puts a frame into a channel jitterbuffer. + * \param chan channel. + * \param frame frame. + * + * Called from ast_generic_bridge() to put a frame into a channel's jitterbuffer. + * The function will successfuly enqueue a frame if and only if: + * 1. the channel is using a jitterbuffer (as determined by ast_jb_do_usecheck()), + * 2. the frame's type is AST_FRAME_VOICE, + * 3. the frame has timing info set and has length >= 2 ms, + * 4. there is no some internal error happened (like failed memory allocation). + * Frames, successfuly queued, should be delivered by the channel's jitterbuffer, + * when their delivery time has came. + * Frames, not successfuly queued, should be delivered immediately. + * Dropped by the jb implementation frames are considered successfuly enqueued as + * far as they should not be delivered at all. + * + * \return zero if the frame was queued, -1 if not. + */ +int ast_jb_put(struct ast_channel *chan, struct ast_frame *f); + + +/*! + * \brief Deliver the queued frames that should be delivered now for both channels. + * \param c0 first bridged channel. + * \param c1 second bridged channel. + * + * Called from ast_generic_bridge() to deliver any frames, that should be delivered + * for the moment of invocation. Does nothing if neihter of the channels is using jb + * or has any frames currently queued in. The function delivers frames usig ast_write() + * each of the channels. + */ +void ast_jb_get_and_deliver(struct ast_channel *c0, struct ast_channel *c1); + + +/*! + * \brief Destroys jitterbuffer on a channel. + * \param chan channel. + * + * Called from ast_channel_free() when a channel is destroyed. + */ +void ast_jb_destroy(struct ast_channel *chan); + + +/*! + * \brief Sets jitterbuffer configuration property. + * \param conf configuration to store the property in. + * \param varname property name. + * \param value property value. + * + * Called from a channel driver to build a jitterbuffer configuration tipically when + * reading a configuration file. It is not neccessary for a channel driver to know + * each of the jb configuration property names. The jitterbuffer itself knows them. + * The channel driver can pass each config var it reads through this function. It will + * return 0 if the variable was consumed from the jb conf. + * + * \return zero if the property was set to the configuration, -1 if not. + */ +int ast_jb_read_conf(struct ast_jb_conf *conf, char *varname, char *value); + + +/*! + * \brief Configures a jitterbuffer on a channel. + * \param chan channel to configure. + * \param conf configuration to apply. + * + * Called from a channel driver when a channel is created and its jitterbuffer needs + * to be configured. + */ +void ast_jb_configure(struct ast_channel *chan, struct ast_jb_conf *conf); + + +/*! + * \brief Copies a channel's jitterbuffer configuration. + * \param chan channel. + * \param conf destination. + */ +void ast_jb_get_config(struct ast_channel *chan, struct ast_jb_conf *conf); + + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* _ABSTRACT_JB_H_ */ diff -urN --exclude .svn asterisk/include/asterisk/channel.h asterisk-jb/include/asterisk/channel.h --- asterisk/include/asterisk/channel.h 2006-01-26 13:24:55.000000000 +0000 +++ asterisk-jb/include/asterisk/channel.h 2006-01-26 13:22:19.000000000 +0000 @@ -86,6 +86,10 @@ #ifndef _ASTERISK_CHANNEL_H #define _ASTERISK_CHANNEL_H +#ifdef AST_JB +#include "asterisk/abstract_jb.h" +#endif /* AST_JB */ + #include #ifdef POLLCOMPAT #include "asterisk/poll-compat.h" @@ -412,6 +416,11 @@ /*! For easy linking */ AST_LIST_ENTRY(ast_channel) list; + +#ifdef AST_JB + /*! The jitterbuffer state */ + struct ast_jb jb; +#endif /* AST_JB */ }; /* \defgroup chanprop Channel tech properties: @@ -419,6 +428,13 @@ /* @{ */ #define AST_CHAN_TP_WANTSJITTER (1 << 0) +#ifdef AST_JB +/* \defgroup chanprop Channel tech properties: + \brief Channels have this property if they can create jitter; i.e. most VoIP channels */ +/* @{ */ +#define AST_CHAN_TP_CREATESJITTER (1 << 1) +#endif /* AST_JB */ + /* This flag has been deprecated by the transfercapbilty data member in struct ast_channel */ /* #define AST_FLAG_DIGITAL (1 << 0) */ /* if the call is a digital ISDN call */ #define AST_FLAG_DEFER_DTMF (1 << 1) /*!< if dtmf should be deferred */ diff -urN --exclude .svn asterisk/include/asterisk/frame.h asterisk-jb/include/asterisk/frame.h --- asterisk/include/asterisk/frame.h 2006-01-26 13:24:55.000000000 +0000 +++ asterisk-jb/include/asterisk/frame.h 2006-01-26 13:19:35.000000000 +0000 @@ -109,6 +109,16 @@ struct ast_frame *prev; /*! Next/Prev for linking stand alone frames */ struct ast_frame *next; +#ifdef AST_JB + /*! Timing data flag */ + int has_timing_info; + /*! Timestamp in milliseconds */ + long ts; + /*! Length in milliseconds */ + long len; + /*! Sequence number */ + int seqno; +#endif /* AST_JB */ }; #define AST_FRIENDLY_OFFSET 64 /*! It's polite for a a new frame to diff -urN --exclude .svn asterisk/rtp.c asterisk-jb/rtp.c --- asterisk/rtp.c 2006-01-26 13:25:18.000000000 +0000 +++ asterisk-jb/rtp.c 2006-01-26 13:19:35.000000000 +0000 @@ -430,7 +430,10 @@ int padding; int mark; int ext; + /* Remove the variable for the pointless loop */ +#ifndef AST_JB int x; +#endif /* AST_JB */ char iabuf[INET_ADDRSTRLEN]; unsigned int timestamp; unsigned int *rtpheader; @@ -571,6 +574,8 @@ if (!rtp->lastrxts) rtp->lastrxts = timestamp; + /* Remove this pointless loop - it will generate unnecessary CPU load on a big jump in seqno. */ +#ifndef AST_JB if (rtp->rxseqno) { for (x=rtp->rxseqno + 1; x < seqno; x++) { /* Queue empty frames */ @@ -582,6 +587,7 @@ rtp->f.src = "RTPMissedFrame"; } } +#endif /* AST_JB */ rtp->rxseqno = seqno; if (rtp->dtmfcount) { @@ -613,6 +619,13 @@ if (rtp->f.subclass == AST_FORMAT_SLINEAR) ast_frame_byteswap_be(&rtp->f); calc_rxstamp(&rtp->f.delivery, rtp, timestamp, mark); +#ifdef AST_JB + /* Add timing data to let ast_generic_bridge() put the frame into a jitterbuf */ + rtp->f.has_timing_info = 1; + rtp->f.ts = timestamp / 8; + rtp->f.len = rtp->f.samples / 8; + rtp->f.seqno = seqno; +#endif /* AST_JB */ } else { /* Video -- samples is # of samples vs. 90000 */ if (!rtp->lastividtimestamp) diff -urN --exclude .svn asterisk/scx_jitterbuf.c asterisk-jb/scx_jitterbuf.c --- asterisk/scx_jitterbuf.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-jb/scx_jitterbuf.c 2006-01-26 13:19:35.000000000 +0000 @@ -0,0 +1,376 @@ +/* + * scx_jitterbuf: jitterbuffering algorithm + * + * Copyright (C) 2005, Attractel OOD + * + * Contributors: + * Slav Klenov + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + * + * 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. + */ + +/*! \file + * + * \brief Jitterbuffering algorithm. + * + */ + +#include +#include +#include +#include +#include "scx_jitterbuf.h" + + +/* private scx_jb structure */ +struct scx_jb +{ + struct scx_jb_frame *frames; + struct scx_jb_frame *tail; + struct scx_jb_conf conf; + long rxcore; + long delay; + long next_delivery; + int force_resynch; +}; + + +static struct scx_jb_frame * alloc_jb_frame(struct scx_jb *jb); +static void release_jb_frame(struct scx_jb *jb, struct scx_jb_frame *frame); +static void get_jb_head(struct scx_jb *jb, struct scx_jb_frame *frame); +static int resynch_jb(struct scx_jb *jb, void *data, long ms, long ts, long now); + + +static struct scx_jb_frame * alloc_jb_frame(struct scx_jb *jb) +{ + return (struct scx_jb_frame *) calloc(1, sizeof(struct scx_jb_frame)); +} + + +static void release_jb_frame(struct scx_jb *jb, struct scx_jb_frame *frame) +{ + free(frame); +} + + +static void get_jb_head(struct scx_jb *jb, struct scx_jb_frame *frame) +{ + struct scx_jb_frame *fr; + + /* unlink the frame */ + fr = jb->frames; + jb->frames = fr->next; + if(jb->frames != NULL) + { + 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 scx_jb_frame)); + + /* and release the frame */ + release_jb_frame(jb, fr); +} + + +struct scx_jb * scx_jb_new(struct scx_jb_conf *conf) +{ + struct scx_jb *jb; + + jb = calloc(1, sizeof(struct scx_jb)); + if(jb == NULL) + { + return NULL; + } + + /* First copy our config */ + memcpy(&jb->conf, conf, sizeof(struct scx_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 = SCX_JB_SIZE_DEFAULT; + } + if(conf->resync_threshold < 1) + { + conf->resync_threshold = SCX_JB_RESYNCH_THRESHOLD_DEFAULT; + } + + /* Set the constant delay to the jitterbuf */ + jb->delay = conf->jbsize; + + return jb; +} + + +void scx_jb_destroy(struct scx_jb *jb) +{ + /* jitterbuf MUST be empty before it can be destroyed */ + assert(jb->frames == NULL); + + free(jb); +} + + +static int resynch_jb(struct scx_jb *jb, void *data, long ms, long ts, long now) +{ + long diff, offset; + struct scx_jb_frame *frame; + + /* If jb is empty, just reinitialize the jb */ + if(jb->frames == NULL) + { + /* debug check: tail should also be NULL */ + assert(jb->tail == NULL); + + return scx_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 SCX_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 scx_jb_put(jb, data, ms, ts, now); +} + + +void scx_jb_set_force_resynch(struct scx_jb *jb) +{ + jb->force_resynch = 1; +} + + +int scx_jb_put_first(struct scx_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 scx_jb_put(jb, data, ms, ts, now); +} + + +int scx_jb_put(struct scx_jb *jb, void *data, long ms, long ts, long now) +{ + struct scx_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 != NULL && 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 != NULL) + { + next = frame->next; + frame->next = newframe; + if(next != NULL) + { + 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 SCX_JB_OK; + } + else if(jb->frames == NULL) + { + /* 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 SCX_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 SCX_JB_OK; + } +} + + +int scx_jb_get(struct scx_jb *jb, struct scx_jb_frame *frame, long now, long interpl) +{ + long halflen; + + assert(now >= 0); + assert(interpl >= 2); + + if(now < jb->next_delivery) + { + /* too early for the next frame */ + return SCX_JB_NOFRAME; + } + + /* Is the jb empty? */ + if(jb->frames == NULL) + { + /* should interpolate a frame */ + /* update next */ + jb->next_delivery += interpl; + + return SCX_JB_INTERP; + } + + halflen = (long) ((double) jb->frames->ms / 2.0); + + /* Isn't it too late for the first frame available in the jb? */ + if(now > jb->frames->delivery + halflen) + { + /* yes - should drop this frame and update next to point the next frame (get_jb_head() does it) */ + get_jb_head(jb, frame); + + return SCX_JB_DROP; + } + + /* isn't it too early to play the first frame available? */ + if(now < jb->frames->delivery - halflen) + { + /* yes - should interpolate one frame */ + /* update next */ + jb->next_delivery += interpl; + + return SCX_JB_INTERP; + } + + /* we have a frame for playing now (get_jb_head() updates next) */ + get_jb_head(jb, frame); + + return SCX_JB_OK; +} + + +long scx_jb_next(struct scx_jb *jb) +{ + return jb->next_delivery; +} + + +int scx_jb_remove(struct scx_jb *jb, struct scx_jb_frame *frameout) +{ + if(jb->frames == NULL) + { + return SCX_JB_NOFRAME; + } + + get_jb_head(jb, frameout); + + return SCX_JB_OK; +} + + + diff -urN --exclude .svn asterisk/scx_jitterbuf.h asterisk-jb/scx_jitterbuf.h --- asterisk/scx_jitterbuf.h 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-jb/scx_jitterbuf.h 2006-01-26 13:19:35.000000000 +0000 @@ -0,0 +1,93 @@ +/* + * scx_jitterbuf: jitterbuffering algorithm + * + * Copyright (C) 2005, Attractel OOD + * + * Contributors: + * Slav Klenov + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + * + * 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. + */ + +/*! \file + * + * \brief Jitterbuffering algorithm. + * + */ + +#ifndef _SCX_JITTERBUF_H_ +#define _SCX_JITTERBUF_H_ + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + + +/* return codes */ +#define SCX_JB_OK 0 +#define SCX_JB_DROP 1 +#define SCX_JB_INTERP 2 +#define SCX_JB_NOFRAME 3 + + +/* defaults */ +#define SCX_JB_SIZE_DEFAULT 200 +#define SCX_JB_RESYNCH_THRESHOLD_DEFAULT 1000 + + +/* jb configuration properties */ +struct scx_jb_conf +{ + long jbsize; + long resync_threshold; +}; + + +struct scx_jb_frame +{ + void *data; + long ts; + long ms; + long delivery; + struct scx_jb_frame *next; + struct scx_jb_frame *prev; +}; + + +struct scx_jb; + + +/* jb interface */ + +struct scx_jb * scx_jb_new(struct scx_jb_conf *conf); + +void scx_jb_destroy(struct scx_jb *jb); + +int scx_jb_put_first(struct scx_jb *jb, void *data, long ms, long ts, long now); + +int scx_jb_put(struct scx_jb *jb, void *data, long ms, long ts, long now); + +int scx_jb_get(struct scx_jb *jb, struct scx_jb_frame *frame, long now, long interpl); + +long scx_jb_next(struct scx_jb *jb); + +int scx_jb_remove(struct scx_jb *jb, struct scx_jb_frame *frameout); + +void scx_jb_set_force_resynch(struct scx_jb *jb); + + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* _SCX_JITTERBUF_H_ */ diff -urN --exclude .svn asterisk/translate.c asterisk-jb/translate.c --- asterisk/translate.c 2006-01-26 13:25:18.000000000 +0000 +++ asterisk-jb/translate.c 2006-01-26 13:19:35.000000000 +0000 @@ -156,6 +156,17 @@ struct ast_trans_pvt *p; struct ast_frame *out; struct timeval delivery; +#ifdef AST_JB + int has_timing_info; + long ts; + long len; + int seqno; + + has_timing_info = f->has_timing_info; + ts = f->ts; + len = f->len; + seqno = f->seqno; +#endif /* AST_JB */ p = path; /* Feed the first frame into the first translator */ p->step->framein(p->state, f); @@ -210,6 +221,15 @@ /* Invalidate prediction if we're entering a silence period */ if (out->frametype == AST_FRAME_CNG) path->nextout = ast_tv(0, 0); +#ifdef AST_JB + out->has_timing_info = has_timing_info; + if(has_timing_info) + { + out->ts = ts; + out->len = len; + out->seqno = seqno; + } +#endif /* AST_JB */ return out; } p = p->next;