Adds support for sending and receiving "trunk timestamps" # # Patch managed by http://www.holgerschurig.de/patcher.html # --- asterisk/channels/iax2.h~iax2-trunktimestamps +++ asterisk/channels/iax2.h @@ -138,6 +138,9 @@ #define IAX_META_TRUNK 1 /* Trunk meta-message */ #define IAX_META_VIDEO 2 /* Video frame */ +#define IAX_META_TRUNK_SUPERMINI 0 /* This trunk frame contains classic supermini frames */ +#define IAX_META_TRUNK_MINI 1 /* This trunk frame contains trunked mini frames */ + #define IAX_RATE_8KHZ (1 << 0) /* 8khz sampling (default if absent) */ #define IAX_RATE_11KHZ (1 << 1) /* 11.025khz sampling */ #define IAX_RATE_16KHZ (1 << 2) /* 16khz sampling */ @@ -209,6 +212,12 @@ unsigned short len; /* Length of data for this callno */ } __attribute__ ((__packed__)); +/* When trunktimestamps are used, we use this format instead */ +struct ast_iax2_meta_trunk_mini { + unsigned short len; + struct ast_iax2_mini_hdr mini; /* this is an actual miniframe */ +} __attribute__ ((__packed__)); + #define IAX_FIRMWARE_MAGIC 0x69617879 struct ast_iax2_firmware_header { --- asterisk/channels/chan_iax2.c~iax2-trunktimestamps +++ asterisk/channels/chan_iax2.c @@ -118,6 +118,7 @@ static int maxjitterbuffer=1000; static int jittershrinkrate=2; static int trunkfreq = 20; +static int send_trunktimestamps = 1; static int authdebug = 1; static int autokill = 0; static int iaxcompat = 0; @@ -3297,12 +3298,16 @@ return tpeer; } -static int iax2_trunk_queue(struct chan_iax2_pvt *pvt, struct ast_frame *f) +static int iax2_trunk_queue(struct chan_iax2_pvt *pvt, struct iax_frame *fr) { + struct ast_frame *f; struct iax2_trunk_peer *tpeer; void *tmp, *ptr; struct ast_iax2_meta_trunk_entry *met; + struct ast_iax2_meta_trunk_mini *mtm; char iabuf[INET_ADDRSTRLEN]; + + f = &fr->af; tpeer = find_tpeer(&pvt->addr, pvt->sockfd); if (tpeer) { if (tpeer->trunkdatalen + f->datalen + 4 >= tpeer->trunkdataalloc) { @@ -3324,19 +3329,29 @@ return -1; } } - + /* Append to meta frame */ ptr = tpeer->trunkdata + IAX2_TRUNK_PREFACE + tpeer->trunkdatalen; - met = (struct ast_iax2_meta_trunk_entry *)ptr; - /* Store call number and length in meta header */ - met->callno = htons(pvt->callno); - met->len = htons(f->datalen); - /* Advance pointers/decrease length past trunk entry header */ - ptr += sizeof(struct ast_iax2_meta_trunk_entry); - tpeer->trunkdatalen += sizeof(struct ast_iax2_meta_trunk_entry); + if(send_trunktimestamps) { + mtm = (struct ast_iax2_meta_trunk_mini *)ptr; + mtm->len = htons(f->datalen); + mtm->mini.callno = htons(pvt->callno); + mtm->mini.ts = htons(0xffff & fr->ts); + ptr += sizeof(struct ast_iax2_meta_trunk_mini); + tpeer->trunkdatalen += sizeof(struct ast_iax2_meta_trunk_mini); + } else { + met = (struct ast_iax2_meta_trunk_entry *)ptr; + /* Store call number and length in meta header */ + met->callno = htons(pvt->callno); + met->len = htons(f->datalen); + /* Advance pointers/decrease length past trunk entry header */ + ptr += sizeof(struct ast_iax2_meta_trunk_entry); + tpeer->trunkdatalen += sizeof(struct ast_iax2_meta_trunk_entry); + } /* Copy actual trunk data */ memcpy(ptr, f->data, f->datalen); tpeer->trunkdatalen += f->datalen; + tpeer->calls++; ast_mutex_unlock(&tpeer->lock); } @@ -3609,7 +3624,7 @@ res = iax2_transmit(fr); } else { if (ast_test_flag(pvt, IAX_TRUNK)) { - iax2_trunk_queue(pvt, &fr->af); + iax2_trunk_queue(pvt, fr); res = 0; } else if (fr->af.frametype == AST_FRAME_VIDEO) { /* Video frame have no sequence number */ @@ -5369,7 +5384,10 @@ /* We're actually sending a frame, so fill the meta trunk header and meta header */ meta->zeros = 0; meta->metacmd = IAX_META_TRUNK; - meta->cmddata = 0; + if(send_trunktimestamps) + meta->cmddata = IAX_META_TRUNK_MINI; + else + meta->cmddata = IAX_META_TRUNK_SUPERMINI; mth->ts = htonl(calc_txpeerstamp(tpeer, trunkfreq, now)); /* And the rest of the ast_iax2 header */ fr->direction = DIRECTION_OUTGRESS; @@ -5682,6 +5700,7 @@ struct ast_iax2_video_hdr *vh = (struct ast_iax2_video_hdr *)buf; struct ast_iax2_meta_trunk_hdr *mth; struct ast_iax2_meta_trunk_entry *mte; + struct ast_iax2_meta_trunk_mini *mtm; char dblbuf[4096]; /* Declaration of dblbuf must immediately *preceed* fr on the stack */ struct iax_frame fr; struct iax_frame *cur; @@ -5724,6 +5743,7 @@ fr.callno = find_callno(ntohs(vh->callno) & ~0x8000, dcallno, &sin, new, 1, fd); minivid = 1; } else if (meta->zeros == 0) { + unsigned char metatype; /* This is a meta header */ switch(meta->metacmd) { case IAX_META_TRUNK: @@ -5733,6 +5753,7 @@ } mth = (struct ast_iax2_meta_trunk_hdr *)(meta->data); ts = ntohl(mth->ts); + metatype = meta->cmddata; res -= (sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr)); ptr = mth->data; tpeer = find_tpeer(&sin, fd); @@ -5749,14 +5770,30 @@ ast_mutex_unlock(&tpeer->lock); while(res >= sizeof(struct ast_iax2_meta_trunk_entry)) { /* Process channels */ - mte = (struct ast_iax2_meta_trunk_entry *)ptr; - ptr += sizeof(struct ast_iax2_meta_trunk_entry); - res -= sizeof(struct ast_iax2_meta_trunk_entry); - len = ntohs(mte->len); + unsigned short callno, trunked_ts, len; + + if(metatype == IAX_META_TRUNK_MINI) { + mtm = (struct ast_iax2_meta_trunk_mini *)ptr; + ptr += sizeof(struct ast_iax2_meta_trunk_mini); + res -= sizeof(struct ast_iax2_meta_trunk_mini); + len = ntohs(mtm->len); + callno = ntohs(mtm->mini.callno); + trunked_ts = ntohs(mtm->mini.ts); + } else if ( metatype == IAX_META_TRUNK_SUPERMINI ) { + mte = (struct ast_iax2_meta_trunk_entry *)ptr; + ptr += sizeof(struct ast_iax2_meta_trunk_entry); + res -= sizeof(struct ast_iax2_meta_trunk_entry); + len = ntohs(mte->len); + callno = ntohs(mte->callno); + trunked_ts = 0; + } else { + ast_log(LOG_WARNING, "Unknown meta trunk cmd from '%s:%d': dropping\n", ast_inet_ntoa(iabuf, sizeof(iabuf), sin.sin_addr), ntohs(sin.sin_port)); + break; + } /* Stop if we don't have enough data */ if (len > res) break; - fr.callno = find_callno(ntohs(mte->callno) & ~IAX_FLAG_FULL, 0, &sin, NEW_PREVENT, 1, fd); + fr.callno = find_callno(callno & ~IAX_FLAG_FULL, 0, &sin, NEW_PREVENT, 1, fd); if (fr.callno) { ast_mutex_lock(&iaxsl[fr.callno]); /* If it's a valid call, deliver the contents. If not, we @@ -5772,7 +5809,10 @@ f.data = ptr; else f.data = NULL; - fr.ts = fix_peerts(&rxtrunktime, fr.callno, ts); + if(trunked_ts) + fr.ts = trunked_ts; + else + fr.ts = fix_peerts(&rxtrunktime, fr.callno, ts); /* Don't pass any packets until we're started */ if ((iaxs[fr.callno]->state & IAX_STATE_STARTED)) { /* Common things */ @@ -7381,6 +7421,8 @@ strncpy(peer->dbsecret, v->value, sizeof(peer->dbsecret)-1); else if (!strcasecmp(v->name, "mailboxdetail")) ast_set2_flag(peer, ast_true(v->value), IAX_MESSAGEDETAIL); + else if (!strcasecmp(v->name, "trunktimestamps")) + send_trunktimestamps = ast_true(v->value); else if (!strcasecmp(v->name, "trunk")) { ast_set2_flag(peer, ast_true(v->value), IAX_TRUNK); if (ast_test_flag(peer, IAX_TRUNK) && (timingfd < 0)) { --- asterisk/configs/iax.conf.sample~iax2-trunktimestamps +++ asterisk/configs/iax.conf.sample @@ -97,6 +97,16 @@ ;jittershrinkrate=1 ;trunkfreq=20 ; How frequently to send trunk msgs (in ms) + +; Should we send timestamps for the individual sub-frames within trunk frames? +; There is a small bandwidth use for these (less than 1kbps/call), but they ensure +; that frame timestamps get sent end-to-end properly. If both ends of all your trunks +; go directly to TDM, _and_ your trunkfreq equals the frame length for your codecs, you +; can probably suppress these. The receiver must also support this feature, although +; they do not also need to have it enabled. +; +; trunktimestamps=yes + ; ; ; We can register with another IAX server to let him know where we are Add Codec PLC support to Asterisk # # Patch managed by http://www.holgerschurig.de/patcher.html # --- asterisk/codecs/codec_adpcm.c~codecplc4 +++ asterisk/codecs/codec_adpcm.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,6 +38,8 @@ static char *tdesc = "Adaptive Differential PCM Coder/Decoder"; +static int useplc=1; + /* Sample frame data */ #include "slin_adpcm_ex.h" @@ -236,6 +240,7 @@ short outbuf[BUFFER_SIZE]; /* Decoded signed linear values */ struct adpcm_state state; int tail; + plc_state_t plc; }; /* @@ -258,6 +263,7 @@ { memset(tmp, 0, sizeof(*tmp)); tmp->tail = 0; + plc_init(&tmp->plc); localusecnt++; ast_update_use_count (); } @@ -292,8 +298,8 @@ /* * AdpcmToLin_FrameIn - * Fill an input buffer with packed 4-bit ADPCM values if there is room - * left. + * Take an input buffer with packed 4-bit ADPCM values and put decoded PCM in outbuf, + * if there is room left. * * Results: * Foo @@ -309,7 +315,19 @@ int x; unsigned char *b; - if (f->datalen * 4 > sizeof(tmp->outbuf)) { + if(f->datalen == 0) { /* perform PLC with nominal framesize of 20ms/160 samples */ + if((tmp->tail + 160) > sizeof(tmp->outbuf) / 2) { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + if(useplc) { + plc_fillin(&tmp->plc, tmp->outbuf+tmp->tail, 160); + tmp->tail += 160; + } + return 0; + } + + if (f->datalen * 4 + tmp->tail * 2 > sizeof(tmp->outbuf)) { ast_log(LOG_WARNING, "Out of buffer space\n"); return -1; } @@ -321,6 +339,8 @@ tmp->outbuf[tmp->tail++] = decode(b[x] & 0x0f, &tmp->state); } + if(useplc) plc_rx(&tmp->plc, tmp->outbuf+tmp->tail-f->datalen*2, f->datalen*2); + return 0; } @@ -538,6 +558,33 @@ lintoadpcm_sample }; +static void +parse_config(void) +{ + struct ast_config *cfg; + struct ast_variable *var; + int res; + if ((cfg = ast_config_load("codecs.conf"))) { + if ((var = ast_variable_browse(cfg, "plc"))) { + while (var) { + if (!strcasecmp(var->name, "genericplc")) { + useplc = ast_true(var->value) ? 1 : 0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "CODEC ULAW: %susing generic PLC\n", useplc ? "" : "not "); + } + var = var->next; + } + } + } +} + +int +reload(void) +{ + parse_config(); + return 0; +} + int unload_module (void) { @@ -556,6 +603,7 @@ load_module (void) { int res; + parse_config(); res = ast_register_translator (&adpcmtolin); if (!res) res = ast_register_translator (&lintoadpcm); --- asterisk/codecs/codec_alaw.c~codecplc4 +++ asterisk/codecs/codec_alaw.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,6 +32,8 @@ static char *tdesc = "A-law Coder/Decoder"; +static int useplc = 1; + /* Sample frame data (Mu data is okay) */ #include "slin_ulaw_ex.h" @@ -57,6 +61,7 @@ char offset[AST_FRIENDLY_OFFSET]; /* Space to build offset */ short outbuf[BUFFER_SIZE]; /* Decoded signed linear values */ int tail; + plc_state_t plc; }; /* @@ -79,6 +84,7 @@ { memset(tmp, 0, sizeof(*tmp)); tmp->tail = 0; + plc_init(&tmp->plc); localusecnt++; ast_update_use_count (); } @@ -130,6 +136,18 @@ int x; unsigned char *b; + if(f->datalen == 0) { /* perform PLC with nominal framesize of 20ms/160 samples */ + if((tmp->tail + 160) * 2 > sizeof(tmp->outbuf)) { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + if(useplc) { + plc_fillin(&tmp->plc, tmp->outbuf+tmp->tail, 160); + tmp->tail += 160; + } + return 0; + } + if ((tmp->tail + f->datalen) * 2 > sizeof(tmp->outbuf)) { ast_log(LOG_WARNING, "Out of buffer space\n"); return -1; @@ -140,6 +158,8 @@ for (x=0;xdatalen;x++) tmp->outbuf[tmp->tail + x] = AST_ALAW(b[x]); + if(useplc) plc_rx(&tmp->plc, tmp->outbuf+tmp->tail, f->datalen); + tmp->tail += f->datalen; return 0; } @@ -327,6 +347,33 @@ lintoalaw_sample }; +static void +parse_config(void) +{ + struct ast_config *cfg; + struct ast_variable *var; + int res; + if ((cfg = ast_config_load("codecs.conf"))) { + if ((var = ast_variable_browse(cfg, "plc"))) { + while (var) { + if (!strcasecmp(var->name, "genericplc")) { + useplc = ast_true(var->value) ? 1 : 0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "CODEC ULAW: %susing generic PLC\n", useplc ? "" : "not "); + } + var = var->next; + } + } + } +} + +int +reload(void) +{ + parse_config(); + return 0; +} + int unload_module (void) { @@ -345,6 +392,7 @@ load_module (void) { int res; + parse_config(); res = ast_register_translator (&alawtolin); if (!res) res = ast_register_translator (&lintoalaw); --- asterisk/codecs/codec_g726.c~codecplc4 +++ asterisk/codecs/codec_g726.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -49,6 +51,8 @@ static char *tdesc = "ITU G.726-32kbps G726 Transcoder"; +static int useplc = 1; + /* Sample frame data */ #include "slin_g726_ex.h" @@ -694,6 +698,7 @@ short outbuf[BUFFER_SIZE]; /* Decoded signed linear values */ struct g726_state g726; int tail; + plc_state_t plc; }; /* @@ -716,6 +721,7 @@ { memset(tmp, 0, sizeof(*tmp)); tmp->tail = 0; + plc_init(&tmp->plc); localusecnt++; g726_init_state(&tmp->g726); ast_update_use_count (); @@ -769,6 +775,18 @@ unsigned char *b; int x; + if(f->datalen == 0) { /* perform PLC with nominal framesize of 20ms/160 samples */ + if((tmp->tail + 160) > BUFFER_SIZE) { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + if(useplc) { + plc_fillin(&tmp->plc, tmp->outbuf+tmp->tail, 160); + tmp->tail += 160; + } + return 0; + } + b = f->data; for (x=0;xdatalen;x++) { if (tmp->tail >= BUFFER_SIZE) { @@ -783,6 +801,8 @@ tmp->outbuf[tmp->tail++] = g726_decode(b[x] & 0x0f, &tmp->g726); } + if(useplc) plc_rx(&tmp->plc, tmp->outbuf+tmp->tail-f->datalen*2, f->datalen*2); + return 0; } @@ -974,6 +994,33 @@ lintog726_sample }; +static void +parse_config(void) +{ + struct ast_config *cfg; + struct ast_variable *var; + int res; + if ((cfg = ast_config_load("codecs.conf"))) { + if ((var = ast_variable_browse(cfg, "plc"))) { + while (var) { + if (!strcasecmp(var->name, "genericplc")) { + useplc = ast_true(var->value) ? 1 : 0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "CODEC ULAW: %susing generic PLC\n", useplc ? "" : "not "); + } + var = var->next; + } + } + } +} + +int +reload(void) +{ + parse_config(); + return 0; +} + int unload_module (void) { @@ -992,6 +1039,7 @@ load_module (void) { int res; + parse_config(); res = ast_register_translator (&g726tolin); if (!res) res = ast_register_translator (&lintog726); --- asterisk/codecs/codec_gsm.c~codecplc4 +++ asterisk/codecs/codec_gsm.c @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include #include @@ -43,6 +45,8 @@ static char *tdesc = "GSM/PCM16 (signed linear) Codec Translator"; +static int useplc = 1; + struct ast_translator_pvt { gsm gsm; struct ast_frame f; @@ -53,6 +57,7 @@ /* Enough to store a full second */ short buf[8000]; int tail; + plc_state_t plc; }; #define gsm_coder_pvt ast_translator_pvt @@ -67,6 +72,7 @@ tmp = NULL; } tmp->tail = 0; + plc_init(&tmp->plc); localusecnt++; } return tmp; @@ -131,6 +137,18 @@ unsigned char data[66]; int msgsm=0; + if(f->datalen == 0) { /* perform PLC with nominal framesize of 20ms/160 samples */ + if((tmp->tail + 160) > sizeof(tmp->buf) / 2) { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + if(useplc) { + plc_fillin(&tmp->plc, tmp->buf+tmp->tail, 160); + tmp->tail += 160; + } + return 0; + } + if ((f->datalen % 33) && (f->datalen % 65)) { ast_log(LOG_WARNING, "Huh? A GSM frame that isn't a multiple of 33 or 65 bytes long from %s (%d)?\n", f->src, f->datalen); return -1; @@ -171,6 +189,10 @@ } } } + + /* just add the last 20ms frame; there must have been at least one */ + if(useplc) plc_rx(&tmp->plc, tmp->buf+tmp->tail-160, 160); + return 0; } @@ -249,6 +271,32 @@ lintogsm_sample }; + +static void parse_config(void) +{ + struct ast_config *cfg; + struct ast_variable *var; + int res; + if ((cfg = ast_config_load("codecs.conf"))) { + if ((var = ast_variable_browse(cfg, "plc"))) { + while (var) { + if (!strcasecmp(var->name, "genericplc")) { + useplc = ast_true(var->value) ? 1 : 0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "CODEC ULAW: %susing generic PLC\n", useplc ? "" : "not "); + } + var = var->next; + } + } + } +} + +int reload(void) +{ + parse_config(); + return 0; +} + int unload_module(void) { int res; @@ -265,6 +313,7 @@ int load_module(void) { int res; + parse_config(); res=ast_register_translator(&gsmtolin); if (!res) res=ast_register_translator(&lintogsm); --- asterisk/codecs/codec_ilbc.c~codecplc4 +++ asterisk/codecs/codec_ilbc.c @@ -141,7 +141,19 @@ the tail location. Read in as many frames as there are */ int x,i; float tmpf[240]; - + + if (f->datalen == 0) { /* native PLC */ + if (tmp->tail + 240 < sizeof(tmp->buf)/2) { + iLBC_decode(tmpf, NULL, &tmp->dec, 0); + for (i=0;i<240;i++) + tmp->buf[tmp->tail + i] = tmpf[i]; + tmp->tail+=240; + } else { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + } + if (f->datalen % 50) { ast_log(LOG_WARNING, "Huh? An ilbc frame that isn't a multiple of 50 bytes long from %s (%d)?\n", f->src, f->datalen); return -1; --- asterisk/codecs/codec_lpc10.c~codecplc4 +++ asterisk/codecs/codec_lpc10.c @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include #include @@ -47,6 +49,8 @@ static char *tdesc = "LPC10 2.4kbps (signed linear) Voice Coder"; +static int useplc = 1; + struct ast_translator_pvt { union { struct lpc10_encoder_state *enc; @@ -61,6 +65,7 @@ short buf[8000]; int tail; int longer; + plc_state_t plc; /* god only knows why I bothered to implement PLC for LPC10 :) */ }; #define lpc10_coder_pvt ast_translator_pvt @@ -92,6 +97,7 @@ } tmp->tail = 0; tmp->longer = 0; + plc_init(&tmp->plc); localusecnt++; } return tmp; @@ -199,6 +205,19 @@ float tmpbuf[LPC10_SAMPLES_PER_FRAME]; short *sd; INT32 bits[LPC10_BITS_IN_COMPRESSED_FRAME]; + + if(f->datalen == 0) { /* perform PLC with nominal framesize of LPC10_SAMPLES_PER_FRAME */ + if((tmp->tail + LPC10_SAMPLES_PER_FRAME) > sizeof(tmp->buf)/2) { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + if(useplc) { + plc_fillin(&tmp->plc, tmp->buf+tmp->tail, LPC10_SAMPLES_PER_FRAME); + tmp->tail += LPC10_SAMPLES_PER_FRAME; + } + return 0; + } + while(len + LPC10_BYTES_IN_COMPRESSED_FRAME <= f->datalen) { if (tmp->tail + LPC10_SAMPLES_PER_FRAME < sizeof(tmp->buf)/2) { sd = tmp->buf + tmp->tail; @@ -211,6 +230,8 @@ /* Convert to a real between -1.0 and 1.0 */ sd[x] = 32768.0 * tmpbuf[x]; } + + if(useplc) plc_rx(&tmp->plc, tmp->buf + tmp->tail, LPC10_SAMPLES_PER_FRAME); tmp->tail+=LPC10_SAMPLES_PER_FRAME; } else { @@ -326,6 +347,32 @@ lintolpc10_sample }; +static void parse_config(void) +{ + struct ast_config *cfg; + struct ast_variable *var; + int res; + if ((cfg = ast_config_load("codecs.conf"))) { + if ((var = ast_variable_browse(cfg, "plc"))) { + while (var) { + if (!strcasecmp(var->name, "genericplc")) { + useplc = ast_true(var->value) ? 1 : 0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "CODEC ULAW: %susing generic PLC\n", useplc ? "" : "not "); + } + var = var->next; + } + } + } +} + +int reload(void) +{ + parse_config(); + return 0; +} + + int unload_module(void) { int res; @@ -342,6 +389,7 @@ int load_module(void) { int res; + parse_config(); res=ast_register_translator(&lpc10tolin); if (!res) res=ast_register_translator(&lintolpc10); --- asterisk/codecs/codec_speex.c~codecplc4 +++ asterisk/codecs/codec_speex.c @@ -181,6 +181,21 @@ int x; int res; float fout[1024]; + + if(f->datalen == 0) { /* Native PLC interpolation */ + if(tmp->tail + tmp->framesize > sizeof(tmp->buf) / 2) { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + speex_decode(tmp->speex, NULL, fout); + for (x=0;xframesize;x++) { + tmp->buf[tmp->tail + x] = fout[x]; + } + tmp->tail += tmp->framesize; + return 0; + } + + /* Read in bits */ speex_bits_read_from(&tmp->bits, f->data, f->datalen); for(;;) { @@ -357,11 +372,12 @@ ast_mutex_unlock(&localuser_lock); } else if (!strcasecmp(var->name, "abr")) { res = abs(atoi(var->value)); - if (option_verbose > 2) + if (option_verbose > 2) { if(res > 0) ast_verbose(VERBOSE_PREFIX_3 "CODEC SPEEX: Setting ABR target bitrate to %d\n",res); else - ast_verbose(VERBOSE_PREFIX_3 "CODEC SPEEX: Disabling ABR\n",res); + ast_verbose(VERBOSE_PREFIX_3 "CODEC SPEEX: Disabling ABR\n"); + } if (res >= 0) { ast_mutex_lock(&localuser_lock); abr = res; --- asterisk/codecs/codec_ulaw.c~codecplc4 +++ asterisk/codecs/codec_ulaw.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,6 +32,8 @@ static char *tdesc = "Mu-law Coder/Decoder"; +static int useplc = 1; + /* Sample frame data */ #include "slin_ulaw_ex.h" @@ -57,6 +61,7 @@ char offset[AST_FRIENDLY_OFFSET]; /* Space to build offset */ short outbuf[BUFFER_SIZE]; /* Decoded signed linear values */ int tail; + plc_state_t plc; }; /* @@ -79,6 +84,7 @@ { memset(tmp, 0, sizeof(*tmp)); tmp->tail = 0; + plc_init(&tmp->plc); localusecnt++; ast_update_use_count (); } @@ -130,6 +136,18 @@ int x; unsigned char *b; + if(f->datalen == 0) { /* perform PLC with nominal framesize of 20ms/160 samples */ + if((tmp->tail + 160) * 2 > sizeof(tmp->outbuf)) { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + if(useplc) { + plc_fillin(&tmp->plc, tmp->outbuf+tmp->tail, 160); + tmp->tail += 160; + } + return 0; + } + if ((tmp->tail + f->datalen) * 2 > sizeof(tmp->outbuf)) { ast_log(LOG_WARNING, "Out of buffer space\n"); return -1; @@ -140,6 +158,8 @@ for (x=0;xdatalen;x++) tmp->outbuf[tmp->tail + x] = AST_MULAW(b[x]); + if(useplc) plc_rx(&tmp->plc, tmp->outbuf+tmp->tail, f->datalen); + tmp->tail += f->datalen; return 0; } @@ -327,6 +347,34 @@ lintoulaw_sample }; +static void +parse_config(void) +{ + struct ast_config *cfg; + struct ast_variable *var; + int res; + if ((cfg = ast_config_load("codecs.conf"))) { + if ((var = ast_variable_browse(cfg, "plc"))) { + while (var) { + if (!strcasecmp(var->name, "genericplc")) { + useplc = ast_true(var->value) ? 1 : 0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "CODEC ULAW: %susing generic PLC\n", useplc ? "" : "not "); + } + var = var->next; + } + } + } +} + +int +reload(void) +{ + parse_config(); + return 0; +} + + int unload_module (void) { @@ -345,6 +393,7 @@ load_module (void) { int res; + parse_config(); res = ast_register_translator (&ulawtolin); if (!res) res = ast_register_translator (&lintoulaw); --- /dev/null +++ asterisk/include/asterisk/plc.h @@ -0,0 +1,160 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * plc.h + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This version may be optionally licenced under the GNU LGPL licence. + * This version is disclaimed to DIGIUM for inclusion in the Asterisk project. + */ + +/*! \file */ + +#if !defined(_PLC_H_) +#define _PLC_H_ + +#ifdef SOLARIS +#include +#else +#include +#endif + +/*! \page plc_page Packet loss concealment +\section plc_page_sec_1 What does it do? +The packet loss concealment module provides a suitable synthetic fill-in signal, +to minimise the audible effect of lost packets in VoIP applications. It is not +tied to any particular codec, and could be used with almost any codec which does not +specify its own procedure for packet loss concealment. + +Where a codec specific concealment procedure exists, the algorithm is usually built +around knowledge of the characteristics of the particular codec. It will, therefore, +generally give better results for that particular codec than this generic concealer will. + +\section plc_page_sec_2 How does it work? +While good packets are being received, the plc_rx() routine keeps a record of the trailing +section of the known speech signal. If a packet is missed, plc_fillin() is called to produce +a synthetic replacement for the real speech signal. The average mean difference function +(AMDF) is applied to the last known good signal, to determine its effective pitch. +Based on this, the last pitch period of signal is saved. Essentially, this cycle of speech +will be repeated over and over until the real speech resumes. However, several refinements +are needed to obtain smooth pleasant sounding results. + +- The two ends of the stored cycle of speech will not always fit together smoothly. This can + cause roughness, or even clicks, at the joins between cycles. To soften this, the + 1/4 pitch period of real speech preceeding the cycle to be repeated is blended with the last + 1/4 pitch period of the cycle to be repeated, using an overlap-add (OLA) technique (i.e. + in total, the last 5/4 pitch periods of real speech are used). + +- The start of the synthetic speech will not always fit together smoothly with the tail of + real speech passed on before the erasure was identified. Ideally, we would like to modify + the last 1/4 pitch period of the real speech, to blend it into the synthetic speech. However, + it is too late for that. We could have delayed the real speech a little, but that would + require more buffer manipulation, and hurt the efficiency of the no-lost-packets case + (which we hope is the dominant case). Instead we use a degenerate form of OLA to modify + the start of the synthetic data. The last 1/4 pitch period of real speech is time reversed, + and OLA is used to blend it with the first 1/4 pitch period of synthetic speech. The result + seems quite acceptable. + +- As we progress into the erasure, the chances of the synthetic signal being anything like + correct steadily fall. Therefore, the volume of the synthesized signal is made to decay + linearly, such that after 50ms of missing audio it is reduced to silence. + +- When real speech resumes, an extra 1/4 pitch period of sythetic speech is blended with the + start of the real speech. If the erasure is small, this smoothes the transition. If the erasure + is long, and the synthetic signal has faded to zero, the blending softens the start up of the + real signal, avoiding a kind of "click" or "pop" effect that might occur with a sudden onset. + +\section plc_page_sec_3 How do I use it? +Before audio is processed, call plc_init() to create an instance of the packet loss +concealer. For each received audio packet that is acceptable (i.e. not including those being +dropped for being too late) call plc_rx() to record the content of the packet. Note this may +modify the packet a little after a period of packet loss, to blend real synthetic data smoothly. +When a real packet is not available in time, call plc_fillin() to create a sythetic substitute. +That's it! +*/ + +#define SAMPLE_RATE 8000 + +/*! Minimum allowed pitch (66 Hz) */ +#define PLC_PITCH_MIN 120 +/*! Maximum allowed pitch (200 Hz) */ +#define PLC_PITCH_MAX 40 +/*! Maximum pitch OLA window */ +#define PLC_PITCH_OVERLAP_MAX (PLC_PITCH_MIN >> 2) +/*! The length over which the AMDF function looks for similarity (20 ms) */ +#define CORRELATION_SPAN 160 +/*! History buffer length. The buffer much also be at leat 1.25 times + PLC_PITCH_MIN, but that is much smaller than the buffer needs to be for + the pitch assessment. */ +#define PLC_HISTORY_LEN (CORRELATION_SPAN + PLC_PITCH_MIN) + +typedef struct +{ + /*! Consecutive erased samples */ + int missing_samples; + /*! Current offset into pitch period */ + int pitch_offset; + /*! Pitch estimate */ + int pitch; + /*! Buffer for a cycle of speech */ + float pitchbuf[PLC_PITCH_MIN]; + /*! History buffer */ + int16_t history[PLC_HISTORY_LEN]; + /*! Current pointer into the history buffer */ + int buf_ptr; +} plc_state_t; + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! Process a block of received audio samples. + \brief Process a block of received audio samples. + \param s The packet loss concealer context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples in the buffer. */ +int plc_rx(plc_state_t *s, int16_t amp[], int len); + +/*! Fill-in a block of missing audio samples. + \brief Fill-in a block of missing audio samples. + \param s The packet loss concealer context. + \param amp The audio sample buffer. + \param len The number of samples to be synthesised. + \return The number of samples synthesized. */ +int plc_fillin(plc_state_t *s, int16_t amp[], int len); + +/*! Process a block of received V.29 modem audio samples. + \brief Process a block of received V.29 modem audio samples. + \param s The packet loss concealer context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return A pointer to the he packet loss concealer context. */ +plc_state_t *plc_init(plc_state_t *s); + +#ifdef __cplusplus +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ --- asterisk/include/asterisk/translate.h~codecplc4 +++ asterisk/include/asterisk/translate.h @@ -21,6 +21,7 @@ #endif #include +#include /* Declared by individual translators */ struct ast_translator_pvt; @@ -103,7 +104,6 @@ */ extern struct ast_frame *ast_translate(struct ast_trans_pvt *tr, struct ast_frame *f, int consume); - #if defined(__cplusplus) || defined(c_plusplus) } #endif --- asterisk/Makefile~codecplc4 +++ asterisk/Makefile @@ -231,7 +231,7 @@ cdr.o tdd.o acl.o rtp.o manager.o asterisk.o ast_expr.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 config_old.o + utils.o config_old.o plc.o ifeq (${OSARCH},Darwin) OBJS+=poll.o dlfcn.o ASTLINK=-Wl,-dynamic --- /dev/null +++ asterisk/plc.c @@ -0,0 +1,251 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * plc.c + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This version may be optionally licenced under the GNU LGPL licence. + * This version is disclaimed to DIGIUM for inclusion in the Asterisk project. + */ + +/*! \file */ + +#include +#include +#include +#include +#include + +#include + +#if !defined(FALSE) +#define FALSE 0 +#endif +#if !defined(TRUE) +#define TRUE (!FALSE) +#endif + +/* We do a straight line fade to zero volume in 50ms when we are filling in for missing data. */ +#define ATTENUATION_INCREMENT 0.0025 /* Attenuation per sample */ + +#define ms_to_samples(t) (((t)*SAMPLE_RATE)/1000) + +static inline int16_t fsaturate(double damp) +{ + if (damp > 32767.0) + return INT16_MAX; + if (damp < -32768.0) + return INT16_MIN; + return (int16_t) rint(damp); +} + +static void save_history(plc_state_t *s, int16_t *buf, int len) +{ + if (len >= PLC_HISTORY_LEN) + { + /* Just keep the last part of the new data, starting at the beginning of the buffer */ + memcpy(s->history, buf + len - PLC_HISTORY_LEN, sizeof(int16_t)*PLC_HISTORY_LEN); + s->buf_ptr = 0; + return; + } + if (s->buf_ptr + len > PLC_HISTORY_LEN) + { + /* Wraps around - must break into two sections */ + memcpy(s->history + s->buf_ptr, buf, sizeof(int16_t)*(PLC_HISTORY_LEN - s->buf_ptr)); + len -= (PLC_HISTORY_LEN - s->buf_ptr); + memcpy(s->history, buf + (PLC_HISTORY_LEN - s->buf_ptr), sizeof(int16_t)*len); + s->buf_ptr = len; + return; + } + /* Can use just one section */ + memcpy(s->history + s->buf_ptr, buf, sizeof(int16_t)*len); + s->buf_ptr += len; +} +/*- End of function --------------------------------------------------------*/ + +static void normalise_history(plc_state_t *s) +{ + int16_t tmp[PLC_HISTORY_LEN]; + + if (s->buf_ptr == 0) + return; + memcpy(tmp, s->history, sizeof(int16_t)*s->buf_ptr); + memcpy(s->history, s->history + s->buf_ptr, sizeof(int16_t)*(PLC_HISTORY_LEN - s->buf_ptr)); + memcpy(s->history + PLC_HISTORY_LEN - s->buf_ptr, tmp, sizeof(int16_t)*s->buf_ptr); + s->buf_ptr = 0; +} +/*- End of function --------------------------------------------------------*/ + +static int __inline__ amdf_pitch(int min_pitch, int max_pitch, int16_t amp[], int len) +{ + int i; + int j; + int acc; + int min_acc; + int pitch; + + pitch = min_pitch; + min_acc = INT_MAX; + for (i = max_pitch; i <= min_pitch; i++) + { + acc = 0; + for (j = 0; j < len; j++) + acc += abs(amp[i + j] - amp[j]); + if (acc < min_acc) + { + min_acc = acc; + pitch = i; + } + } + return pitch; +} +/*- End of function --------------------------------------------------------*/ + +int plc_rx(plc_state_t *s, int16_t amp[], int len) +{ + int i; + int overlap_len; + int pitch_overlap; + float old_step; + float new_step; + float old_weight; + float new_weight; + float gain; + + if (s->missing_samples) + { + /* Although we have a real signal, we need to smooth it to fit well + with the synthetic signal we used for the previous block */ + + /* The start of the real data is overlapped with the next 1/4 cycle + of the synthetic data. */ + pitch_overlap = s->pitch >> 2; + if (pitch_overlap > len) + pitch_overlap = len; + gain = 1.0 - s->missing_samples*ATTENUATION_INCREMENT; + if (gain < 0.0) + gain = 0.0; + new_step = 1.0/pitch_overlap; + old_step = new_step*gain; + new_weight = new_step; + old_weight = (1.0 - new_step)*gain; + for (i = 0; i < pitch_overlap; i++) + { + amp[i] = fsaturate(old_weight*s->pitchbuf[s->pitch_offset] + new_weight*amp[i]); + if (++s->pitch_offset >= s->pitch) + s->pitch_offset = 0; + new_weight += new_step; + old_weight -= old_step; + if (old_weight < 0.0) + old_weight = 0.0; + } + s->missing_samples = 0; + } + save_history(s, amp, len); + return len; +} +/*- End of function --------------------------------------------------------*/ + +int plc_fillin(plc_state_t *s, int16_t amp[], int len) +{ + int16_t tmp[PLC_PITCH_OVERLAP_MAX]; + int i; + int pitch_overlap; + float old_step; + float new_step; + float old_weight; + float new_weight; + float gain; + int16_t *orig_amp; + int orig_len; + + orig_amp = amp; + orig_len = len; + if (s->missing_samples == 0) + { + /* As the gap in real speech starts we need to assess the last known pitch, + and prepare the synthetic data we will use for fill-in */ + normalise_history(s); + s->pitch = amdf_pitch(PLC_PITCH_MIN, PLC_PITCH_MAX, s->history + PLC_HISTORY_LEN - CORRELATION_SPAN - PLC_PITCH_MIN, CORRELATION_SPAN); + /* We overlap a 1/4 wavelength */ + pitch_overlap = s->pitch >> 2; + /* Cook up a single cycle of pitch, using a single of the real signal with 1/4 + cycle OLA'ed to make the ends join up nicely */ + /* The first 3/4 of the cycle is a simple copy */ + for (i = 0; i < s->pitch - pitch_overlap; i++) + s->pitchbuf[i] = s->history[PLC_HISTORY_LEN - s->pitch + i]; + /* The last 1/4 of the cycle is overlapped with the end of the previous cycle */ + new_step = 1.0/pitch_overlap; + new_weight = new_step; + for ( ; i < s->pitch; i++) + { + s->pitchbuf[i] = s->history[PLC_HISTORY_LEN - s->pitch + i]*(1.0 - new_weight) + s->history[PLC_HISTORY_LEN - 2*s->pitch + i]*new_weight; + new_weight += new_step; + } + /* We should now be ready to fill in the gap with repeated, decaying cycles + of what is in pitchbuf */ + + /* We need to OLA the first 1/4 wavelength of the synthetic data, to smooth + it into the previous real data. To avoid the need to introduce a delay + in the stream, reverse the last 1/4 wavelength, and OLA with that. */ + gain = 1.0; + new_step = 1.0/pitch_overlap; + old_step = new_step; + new_weight = new_step; + old_weight = 1.0 - new_step; + for (i = 0; i < pitch_overlap; i++) + { + amp[i] = fsaturate(old_weight*s->history[PLC_HISTORY_LEN - 1 - i] + new_weight*s->pitchbuf[i]); + new_weight += new_step; + old_weight -= old_step; + if (old_weight < 0.0) + old_weight = 0.0; + } + s->pitch_offset = i; + } + else + { + gain = 1.0 - s->missing_samples*ATTENUATION_INCREMENT; + i = 0; + } + for ( ; gain > 0.0 && i < len; i++) + { + amp[i] = s->pitchbuf[s->pitch_offset]*gain; + gain -= ATTENUATION_INCREMENT; + if (++s->pitch_offset >= s->pitch) + s->pitch_offset = 0; + } + for ( ; i < len; i++) + amp[i] = 0; + s->missing_samples += orig_len; + save_history(s, amp, len); + return len; +} +/*- End of function --------------------------------------------------------*/ + +plc_state_t *plc_init(plc_state_t *s) +{ + memset(s, 0, sizeof(*s)); + return s; +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/ --- asterisk/configs/codecs.conf.sample~codecplc4 +++ asterisk/configs/codecs.conf.sample @@ -15,3 +15,9 @@ vbr_quality => 5 ; true / false dtx => false + +[plc] +; for all codecs which do not support native PLC +; this determines whether to perform generic PLC +; there is a minor performance penalty for this +genericplc => true Adds the Jitterbuffer to asterisk's core. # # Patch managed by http://www.holgerschurig.de/patcher.html # --- /dev/null +++ asterisk/jitterbuf.c @@ -0,0 +1,698 @@ +/* + * jitterbuf: an application-independent jitterbuffer + * + * Copyrights: + * Copyright (C) 2004-2005, Horizon Wimba, Inc. + * + * Contributors: + * Steve Kann + * + * This program is free software, distributed under the terms of + * the GNU Lesser (Library) General Public License + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + */ + +#include "jitterbuf.h" +#include +#include +#include + +/* define these here, just for ancient compiler systems */ +#define JB_LONGMAX 2147483647L +#define JB_LONGMIN (-JB_LONGMAX - 1L) + +#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) + +#ifdef DEEP_DEBUG +#define jb_dbg2(...) (dbgf ? dbgf(__VA_ARGS__) : (void)0) +#else +#define jb_dbg2(...) ((void)0) +#endif + +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; +} + +static void increment_losspct(jitterbuf *jb) +{ + jb->info.losspct = (100000 + 499 * jb->info.losspct)/500; +} + +static void decrement_losspct(jitterbuf *jb) +{ + jb->info.losspct = (499 * jb->info.losspct)/500; +} + + +static void jb_dbginfo(jitterbuf *jb); + + +void jb_reset(jitterbuf *jb) +{ + memset(jb,0,sizeof(jitterbuf)); + + /* initialize length */ + jb->info.current = jb->info.target = 0; + jb->info.silence = 1; +} + +jitterbuf * jb_new() +{ + jitterbuf *jb; + + + jb = malloc(sizeof(jitterbuf)); + if(!jb) return NULL; + + jb_reset(jb); + + jb_dbg2("jb_new() = %x\n", jb); + return jb; +} + +void jb_destroy(jitterbuf *jb) +{ + jb_frame *frame; + jb_dbg2("jb_destroy(%x)\n", jb); + + /* free all the frames on the "free list" */ + frame = jb->free; + while(frame != NULL) { + jb_frame *next = frame->next; + free(frame); + frame = next; + } + + /* free ourselves! */ + free(jb); +} + + + +/* simple history manipulation */ +/* maybe later we can make the history buckets variable size, or something? */ +/* drop parameter determines whether we will drop outliers to minimize + * delay */ +static int longcmp(const void *a, const void *b) +{ + return *(long *)a - *(long *)b; +} + +static void history_put(jitterbuf *jb, long ts, long now) +{ + long delay = now - ts; + long kicked; + + /* don't add special/negative times to history */ + if(ts <= 0) return; + + kicked = jb->history[jb->hist_ptr & JB_HISTORY_SZ]; + + jb->history[(jb->hist_ptr++) % JB_HISTORY_SZ] = delay; + + /* optimization; the max/min buffers don't need to be recalculated, if this packet's + * entry doesn't change them. This happens if this packet is not involved, _and_ any packet + * that got kicked out of the history is also not involved + * We do a number of comparisons, but it's probably still worthwhile, because it will usually + * succeed, and should be a lot faster than going through all 500 packets in history */ + if(!jb->hist_maxbuf_valid) + return; + + /* don't do this until we've filled history + * (reduces some edge cases below) */ + if(jb->hist_ptr < JB_HISTORY_SZ) + goto invalidate; + + /* if the new delay would go into min */ + if(delay < jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) + goto invalidate; + + /* or max.. */ + if(delay > jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) + goto invalidate; + + /* or the kicked delay would be in min */ + if(kicked <= jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) + goto invalidate; + + if(kicked >= jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) + goto invalidate; + + /* if we got here, we don't need to invalidate, 'cause this delay didn't + * affect things */ + return; + /* end optimization */ + + +invalidate: + jb->hist_maxbuf_valid = 0; + return; +} + +static void history_calc_maxbuf(jitterbuf *jb) +{ + int i,j; + + if(jb->hist_ptr == 0) return; + + + /* initialize maxbuf/minbuf to the latest value */ + for(i=0;ihist_maxbuf[i] = jb->history[(jb->hist_ptr-1) % JB_HISTORY_SZ]; + * jb->hist_minbuf[i] = jb->history[(jb->hist_ptr-1) % JB_HISTORY_SZ]; + */ + jb->hist_maxbuf[i] = JB_LONGMIN; + jb->hist_minbuf[i] = JB_LONGMAX; + } + + /* use insertion sort to populate maxbuf */ + /* we want it to be the top "n" values, in order */ + + /* start at the beginning, or JB_HISTORY_SZ frames ago */ + i = (jb->hist_ptr > JB_HISTORY_SZ) ? (jb->hist_ptr - JB_HISTORY_SZ) : 0; + + for(;ihist_ptr;i++) { + long toins = jb->history[i % JB_HISTORY_SZ]; + + /* if the maxbuf should get this */ + if(toins > jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) { + + /* insertion-sort it into the maxbuf */ + for(j=0;j jb->hist_maxbuf[j]) { + /* move over */ + memmove(jb->hist_maxbuf+j+1,jb->hist_maxbuf+j, (JB_HISTORY_MAXBUF_SZ-(j+1)) * sizeof(long)); + /* insert */ + jb->hist_maxbuf[j] = toins; + + break; + } + } + } + + /* if the minbuf should get this */ + if(toins < jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) { + + /* insertion-sort it into the maxbuf */ + for(j=0;jhist_minbuf[j]) { + /* move over */ + memmove(jb->hist_minbuf+j+1,jb->hist_minbuf+j, (JB_HISTORY_MAXBUF_SZ-(j+1)) * sizeof(long)); + /* insert */ + jb->hist_minbuf[j] = toins; + + break; + } + } + } + + if(0) { + int k; + fprintf(stderr, "toins = %ld\n", toins); + fprintf(stderr, "maxbuf ="); + for(k=0;khist_maxbuf[k]); + fprintf(stderr, "\nminbuf ="); + for(k=0;khist_minbuf[k]); + fprintf(stderr, "\n"); + } + } + + jb->hist_maxbuf_valid = 1; +} + +static void history_get(jitterbuf *jb) +{ + long max, min, jitter; + int index; + int count; + + if(!jb->hist_maxbuf_valid) + history_calc_maxbuf(jb); + + /* count is how many items in history we're examining */ + count = (jb->hist_ptr < JB_HISTORY_SZ) ? jb->hist_ptr : JB_HISTORY_SZ; + + /* index is the "n"ths highest/lowest that we'll look for */ + index = count * JB_HISTORY_DROPPCT / 100; + + /* sanity checks for index */ + if(index > (JB_HISTORY_MAXBUF_SZ - 1)) index = JB_HISTORY_MAXBUF_SZ - 1; + + + if(index < 0) { + jb->info.min = 0; + jb->info.jitter = 0; + return; + } + + max = jb->hist_maxbuf[index]; + min = jb->hist_minbuf[index]; + + jitter = max - min; + + /* these debug stmts compare the difference between looking at the absolute jitter, and the + * values we get by throwing away the outliers */ + /* + fprintf(stderr, "[%d] min=%d, max=%d, jitter=%d\n", index, min, max, jitter); + fprintf(stderr, "[%d] min=%d, max=%d, jitter=%d\n", 0, jb->hist_minbuf[0], jb->hist_maxbuf[0], jb->hist_maxbuf[0]-jb->hist_minbuf[0]); + */ + + jb->info.min = min; + jb->info.jitter = jitter; +} + +static void queue_put(jitterbuf *jb, void *data, int type, long ms, long ts) +{ + jb_frame *frame; + jb_frame *p; + + frame = jb->free; + if(frame) { + jb->free = frame->next; + } else { + frame = malloc(sizeof(jb_frame)); + } + + if(!frame) { + jb_err("cannot allocate frame\n"); + return; + } + + jb->info.frames_cur++; + + frame->data = data; + frame->ts = ts; + frame->ms = ms; + frame->type = type; + + /* + * frames are a circular list, jb-frames points to to the lowest ts, + * jb->frames->prev points to the highest ts + */ + + if(!jb->frames) { /* queue is empty */ + jb->frames = frame; + frame->next = frame; + frame->prev = frame; + } else if(ts < jb->frames->ts) { + frame->next = jb->frames; + frame->prev = jb->frames->prev; + + frame->next->prev = frame; + frame->prev->next = frame; + + jb->frames = frame; + } else { + p = jb->frames; + + /* frame is out of order */ + if(ts < p->prev->ts) jb->info.frames_ooo++; + + while(ts < p->prev->ts && p->prev != jb->frames) + p = p->prev; + + frame->next = p; + frame->prev = p->prev; + + frame->next->prev = frame; + frame->prev->next = frame; + } +} + +static long queue_next(jitterbuf *jb) +{ + if(jb->frames) return jb->frames->ts; + else return -1; +} + +static long queue_last(jitterbuf *jb) +{ + if(jb->frames) return jb->frames->prev->ts; + else return -1; +} + +static jb_frame *_queue_get(jitterbuf *jb, long ts, int all) +{ + jb_frame *frame; + frame = jb->frames; + + if(!frame) + return NULL; + + /*jb_warn("queue_get: ASK %ld FIRST %ld\n", ts, frame->ts); */ + + if(all || ts > frame->ts) { + /* remove this frame */ + frame->prev->next = frame->next; + frame->next->prev = frame->prev; + + if(frame->next == frame) + jb->frames = NULL; + else + jb->frames = frame->next; + + + /* insert onto "free" single-linked list */ + frame->next = jb->free; + jb->free = frame; + + jb->info.frames_cur--; + + /* we return the frame pointer, even though it's on free list, + * but caller must copy data */ + return frame; + } + + return NULL; +} + +static jb_frame *queue_get(jitterbuf *jb, long ts) +{ + return _queue_get(jb,ts,0); +} + +static jb_frame *queue_getall(jitterbuf *jb) +{ + return _queue_get(jb,0,1); +} + +/* some diagnostics */ +static void jb_dbginfo(jitterbuf *jb) +{ + if(dbgf == NULL) return; + + jb_dbg("\njb info: fin=%ld fout=%ld flate=%ld flost=%ld fdrop=%ld fcur=%ld\n", + jb->info.frames_in, jb->info.frames_out, jb->info.frames_late, jb->info.frames_lost, jb->info.frames_dropped, jb->info.frames_cur); + + jb_dbg(" jitter=%ld current=%ld target=%ld min=%ld sil=%d len=%d len/fcur=%ld\n", + jb->info.jitter, jb->info.current, jb->info.target, jb->info.min, jb->info.silence, jb->info.current - jb->info.min, + jb->info.frames_cur ? (jb->info.current - jb->info.min)/jb->info.frames_cur : -8); + if(jb->info.frames_in > 0) + jb_dbg("jb info: Loss PCT = %ld%%, Late PCT = %ld%%\n", + jb->info.frames_lost * 100/(jb->info.frames_in + jb->info.frames_lost), + jb->info.frames_late * 100/jb->info.frames_in); + jb_dbg("jb info: queue %d -> %d. last_ts %d (queue len: %d) last_ms %d\n", + queue_next(jb), + queue_last(jb), + jb->info.last_voice_ts, + queue_last(jb) - queue_next(jb), + jb->info.last_voice_ms); +} + +#ifdef DEEP_DEBUG +static void jb_chkqueue(jitterbuf *jb) +{ + int i=0; + jb_frame *p = jb->frames; + + if(!p) { + return; + } + + do { + if(p->next == NULL) { + jb_err("Queue is BROKEN at item [%d]", i); + } + i++; + p=p->next; + } while (p->next != jb->frames); +} + +static void jb_dbgqueue(jitterbuf *jb) +{ + int i=0; + jb_frame *p = jb->frames; + + jb_dbg("queue: "); + + if(!p) { + jb_dbg("EMPTY\n"); + return; + } + + do { + jb_dbg("[%d]=%ld ", i++, p->ts); + p=p->next; + } while (p->next != jb->frames); + + jb_dbg("\n"); +} +#endif + +int jb_put(jitterbuf *jb, void *data, int type, long ms, long ts, long now) +{ + jb_dbg2("jb_put(%x,%x,%ld,%ld,%ld)\n", jb, data, ms, ts, now); + + jb->info.frames_in++; + + if(type == JB_TYPE_VOICE) { + /* presently, I'm only adding VOICE frames to history and drift calculations; mostly because with the + * IAX integrations, I'm sending retransmitted control frames with their awkward timestamps through */ + history_put(jb,ts,now); + } + + queue_put(jb,data,type,ms,ts); + + return JB_OK; +} + + +static int _jb_get(jitterbuf *jb, jb_frame *frameout, long now) +{ + jb_frame *frame; + long diff; + + /*if((now - jb_next(jb)) > 2 * jb->info.last_voice_ms) jb_warn("SCHED: %ld", (now - jb_next(jb))); */ + /* get jitter info */ + history_get(jb); + + + /* target */ + jb->info.target = jb->info.jitter + jb->info.min + 2 * jb->info.last_voice_ms; + + /* if a hard clamp was requested, use it */ + if((jb->info.max_jitterbuf) && ((jb->info.target - jb->info.min) > jb->info.max_jitterbuf)) { + jb_dbg("clamping target from %d to %d\n", (jb->info.target - jb->info.min), jb->info.max_jitterbuf); + jb->info.target = jb->info.min + jb->info.max_jitterbuf; + } + + diff = jb->info.target - jb->info.current; + + /* jb_warn("diff = %d lms=%d last = %d now = %d\n", diff, */ + /* jb->info.last_voice_ms, jb->info.last_adjustment, now); */ + + /* move up last_voice_ts; it is now the expected voice ts */ + jb->info.last_voice_ts += jb->info.last_voice_ms; + + /* let's work on non-silent case first */ + if(!jb->info.silence) { + /* we want to grow */ + if( (diff > 0) && + /* we haven't grown in 2 frames' length */ + (((jb->info.last_adjustment + 2 * jb->info.last_voice_ms ) < now) || + /* we need to grow more than the "length" we have left */ + (diff > queue_last(jb) - queue_next(jb)) ) ) { + jb->info.current += jb->info.last_voice_ms; + jb->info.last_adjustment = now; + jb_dbg("G"); + return JB_INTERP; + } + + frame = queue_get(jb, jb->info.last_voice_ts - jb->info.current); + + /* not a voice frame; just return it. */ + if(frame && frame->type != JB_TYPE_VOICE) { + /* rewind last_voice_ts, since this isn't voice */ + jb->info.last_voice_ts -= jb->info.last_voice_ms; + + if(frame->type == JB_TYPE_SILENCE) + jb->info.silence = 1; + + *frameout = *frame; + jb->info.frames_out++; + jb_dbg("o"); + return JB_OK; + } + + + /* voice frame is late */ + if(frame && frame->ts + jb->info.current < jb->info.last_voice_ts - jb->info.last_voice_ms ) { + *frameout = *frame; + /* rewind last_voice, since we're just dumping */ + jb->info.last_voice_ts -= jb->info.last_voice_ms; + jb->info.frames_out++; + decrement_losspct(jb); + jb->info.frames_late++; + jb->info.frames_lost--; + jb_dbg("l"); + /*jb_warn("\nlate: wanted=%ld, this=%ld, next=%ld\n", jb->info.last_voice_ts - jb->info.current, frame->ts, queue_next(jb)); + jb_warninfo(jb); */ + return JB_DROP; + } + + /* keep track of frame sizes, to allow for variable sized-frames */ + if(frame && frame->ms > 0) { + jb->info.last_voice_ms = frame->ms; + } + + /* we want to shrink; shrink at 1 frame / 500ms */ + if(diff < -2 * jb->info.last_voice_ms && + ((!frame && jb->info.last_adjustment + 80 < now) || + (jb->info.last_adjustment + 500 < now))) { + + /* don't increment last_ts ?? */ + jb->info.last_voice_ts -= jb->info.last_voice_ms; + jb->info.current -= jb->info.last_voice_ms; + jb->info.last_adjustment = now; + + if(frame) { + *frameout = *frame; + jb->info.frames_out++; + decrement_losspct(jb); + jb->info.frames_dropped++; + jb_dbg("s"); + return JB_DROP; + } else { + increment_losspct(jb); + jb_dbg("S"); + return JB_NOFRAME; + } + } + + /* lost frame */ + if(!frame) { + /* this is a bit of a hack for now, but if we're close to + * target, and we find a missing frame, it makes sense to + * grow, because the frame might just be a bit late; + * otherwise, we presently get into a pattern where we return + * INTERP for the lost frame, then it shows up next, and we + * throw it away because it's late */ + /* I've recently only been able to replicate this using + * iaxclient talking to app_echo on asterisk. In this case, + * my outgoing packets go through asterisk's (old) + * jitterbuffer, and then might get an unusual increasing delay + * there if it decides to grow?? */ + /* Update: that might have been a different bug, that has been fixed.. + * But, this still seemed like a good idea, except that it ended up making a single actual + * lost frame get interpolated two or more times, when there was "room" to grow, so it might + * be a bit of a bad idea overall */ + /*if(diff > -1 * jb->info.last_voice_ms) { + jb->info.current += jb->info.last_voice_ms; + jb->info.last_adjustment = now; + jb_warn("g"); + return JB_INTERP; + } */ + jb->info.frames_lost++; + increment_losspct(jb); + jb_dbg("L"); + return JB_INTERP; + } + + /* normal case; return the frame, increment stuff */ + *frameout = *frame; + jb->info.frames_out++; + decrement_losspct(jb); + jb_dbg("v"); + return JB_OK; + } else { + /* TODO: after we get the non-silent case down, we'll make the + * silent case -- basically, we'll just grow and shrink faster + * here, plus handle last_voice_ts a bit differently */ + + /* to disable silent special case altogether, just uncomment this: */ + /* jb->info.silence = 0; */ + + frame = queue_get(jb, now - jb->info.current); + if(!frame) { + return JB_NOFRAME; + } + if(frame && frame->type == JB_TYPE_VOICE) { + /* try setting current to target right away here */ + jb->info.current = jb->info.target; + jb->info.silence = 0; + jb->info.last_voice_ts = frame->ts + jb->info.current + frame->ms; + jb->info.last_voice_ms = frame->ms; + *frameout = *frame; + jb_dbg("V"); + return JB_OK; + } + /* normal case; in silent mode, got a non-voice frame */ + *frameout = *frame; + return JB_OK; + } +} + +long jb_next(jitterbuf *jb) +{ + if(jb->info.silence) { + long next = queue_next(jb); + if(next > 0) { + history_get(jb); + return next + jb->info.target; + } + else return JB_LONGMAX; + } else { + return jb->info.last_voice_ts + jb->info.last_voice_ms; + } +} + +int jb_get(jitterbuf *jb, jb_frame *frameout, long now) +{ + int ret = _jb_get(jb,frameout,now); +#if 0 + static int lastts=0; + int thists = ((ret == JB_OK) || (ret == JB_DROP)) ? frameout->ts : 0; + jb_warn("jb_get(%x,%x,%ld) = %d (%d)\n", jb, frameout, now, ret, thists); + if(thists && thists < lastts) jb_warn("XXXX timestamp roll-back!!!\n"); + lastts = thists; +#endif + return ret; +} + +int jb_getall(jitterbuf *jb, jb_frame *frameout) +{ + jb_frame *frame; + frame = queue_getall(jb); + + if(!frame) { + return JB_NOFRAME; + } + + *frameout = *frame; + return JB_OK; +} + + +int jb_getinfo(jitterbuf *jb, jb_info *stats) +{ + + history_get(jb); + + *stats = jb->info; + + return JB_OK; +} + +int jb_setinfo(jitterbuf *jb, jb_info *settings) +{ + /* take selected settings from the struct */ + + jb->info.max_jitterbuf = settings->max_jitterbuf; + + return JB_OK; +} + + --- /dev/null +++ asterisk/jitterbuf.h @@ -0,0 +1,140 @@ +/* + * jitterbuf: an application-independent jitterbuffer + * + * Copyrights: + * Copyright (C) 2004-2005, Horizon Wimba, Inc. + * + * Contributors: + * Steve Kann + * + * This program is free software, distributed under the terms of + * the GNU Lesser (Library) General Public License + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + */ + +#ifndef _JITTERBUF_H_ +#define _JITTERBUF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* configuration constants */ + /* Number of historical timestamps to use in calculating jitter and drift */ +#define JB_HISTORY_SZ 500 + /* what percentage of timestamps should we drop from the history when we examine it; + * this might eventually be something made configurable */ +#define JB_HISTORY_DROPPCT 3 + /* the maximum droppct we can handle (say it was configurable). */ +#define JB_HISTORY_DROPPCT_MAX 4 + /* the size of the buffer we use to keep the top and botton timestamps for dropping */ +#define JB_HISTORY_MAXBUF_SZ JB_HISTORY_SZ * JB_HISTORY_DROPPCT_MAX / 100 + + +/* return codes */ +#define JB_OK 0 +#define JB_EMPTY 1 +#define JB_NOFRAME 2 +#define JB_INTERP 3 +#define JB_DROP 4 + +/* frame types */ +#define JB_TYPE_CONTROL 0 +#define JB_TYPE_VOICE 1 +#define JB_TYPE_VIDEO 2 /* reserved */ +#define JB_TYPE_SILENCE 3 + +typedef struct jb_info { + /* statistics */ + long frames_in; /* number of frames input to the jitterbuffer.*/ + long frames_out; /* number of frames output from the jitterbuffer.*/ + long frames_late; /* number of frames which were too late, and dropped.*/ + long frames_lost; /* number of missing frames.*/ + long frames_dropped; /* number of frames dropped (shrinkage) */ + long frames_ooo; /* number of frames received out-of-order */ + long frames_cur; /* number of frames presently in jb, awaiting delivery.*/ + long jitter; /* jitter measured within current history interval*/ + long min; /* minimum lateness within current history interval */ + long current; /* the present jitterbuffer adjustment */ + long target; /* the target jitterbuffer adjustment */ + long losspct; /* recent lost frame percentage (* 1000) */ + long last_voice_ts; /* the last ts that was read from the jb - in receiver's time */ + long last_voice_ms; /* the duration of the last voice frame */ + long silence; /* we are presently playing out silence */ + long last_adjustment; /* the time of the last adjustment */ + + /* settings */ + long max_jitterbuf; /* defines a hard clamp to use in setting the jitter buffer delay */ +} jb_info; + +typedef struct jb_frame { + void *data; /* the frame data */ + long ts; /* the relative delivery time expected */ + long ms; /* the time covered by this frame, in sec/8000 */ + int type; /* the type of frame */ + struct jb_frame *next, *prev; +} jb_frame; + +typedef struct jitterbuf { + jb_info info; + + /* history */ + long history[JB_HISTORY_SZ]; /* history */ + int hist_ptr; /* points to index in history for next entry */ + long hist_maxbuf[JB_HISTORY_MAXBUF_SZ]; /* a sorted buffer of the max delays (highest first) */ + long hist_minbuf[JB_HISTORY_MAXBUF_SZ]; /* a sorted buffer of the min delays (lowest first) */ + int hist_maxbuf_valid; /* are the "maxbuf"/minbuf valid? */ + + + jb_frame *frames; /* queued frames */ + jb_frame *free; /* free frames (avoid malloc?) */ +} jitterbuf; + + +/* new jitterbuf */ +jitterbuf * jb_new(void); + +/* destroy jitterbuf */ +void jb_destroy(jitterbuf *jb); + +/* reset jitterbuf */ +/* NOTE: The jitterbuffer should be empty before you call this, otherwise + * you will leak queued frames, and some internal structures */ +void jb_reset(jitterbuf *jb); + +/* queue a frame data=frame data, timings (in ms): ms=length of frame (for voice), ts=ts (sender's time) + * now=now (in receiver's time)*/ +int jb_put(jitterbuf *jb, void *data, int type, long ms, long ts, long now); + +/* get a frame for time now (receiver's time) return value is one of + * JB_OK: You've got frame! + * JB_DROP: Here's an audio frame you should just drop. Ask me again for this time.. + * JB_NOFRAME: There's no frame scheduled for this time. + * JB_INTERP: Please interpolate an audio frame for this time (either we need to grow, or there was a lost frame + * JB_EMPTY: The jb is empty. + */ +int jb_get(jitterbuf *jb, jb_frame *frame, long now); + +/* unconditionally get frames from jitterbuf until empty */ +int jb_getall(jitterbuf *jb, jb_frame *frameout); + +/* when is the next frame due out, in receiver's time (0=EMPTY) + * This value may change as frames are added (esp non-audio frames) */ +long jb_next(jitterbuf *jb); + +/* get jitterbuf info: only "statistics" may be valid */ +int jb_getinfo(jitterbuf *jb, jb_info *stats); + +/* set jitterbuf info: only "settings" may be honored */ +int jb_setinfo(jitterbuf *jb, jb_info *settings); + +typedef void (*jb_output_function_t)(const char *fmt, ...); +void jb_setoutput(jb_output_function_t warn, jb_output_function_t err, jb_output_function_t dbg); + +#ifdef __cplusplus +} +#endif + + +#endif --- asterisk/Makefile~jitterbuf +++ asterisk/Makefile @@ -231,7 +231,7 @@ cdr.o tdd.o acl.o rtp.o manager.o asterisk.o ast_expr.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 config_old.o plc.o + utils.o config_old.o plc.o jitterbuf.o ifeq (${OSARCH},Darwin) OBJS+=poll.o dlfcn.o ASTLINK=-Wl,-dynamic Integrates the Jitterbuffer with chan_iax2 # # Patch managed by http://www.holgerschurig.de/patcher.html # --- asterisk/channels/chan_iax2.c~jitterbuf-iax2 +++ asterisk/channels/chan_iax2.c @@ -70,6 +70,14 @@ #include "iax2-provision.h" #include "../astconf.h" +/* Define NEWJB to use the new channel independent jitterbuffer, + * otherwise, use the old jitterbuffer */ +#define NEWJB + +#ifdef NEWJB +#include "../jitterbuf.h" +#endif + #ifndef IPTOS_MINCOST #define IPTOS_MINCOST 0x02 #endif @@ -172,6 +180,8 @@ static int iaxtrunkdebug = 0; +static int test_losspct = 0; + static char accountcode[20]; static int amaflags = 0; static int delayreject = 0; @@ -410,6 +420,12 @@ struct timeval offset; /* timeval that we base our delivery on */ struct timeval rxcore; +#ifdef NEWJB + /* The jitterbuffer */ + jitterbuf *jb; + /* active jb read scheduler id */ + int jbid; +#else /* Historical delivery time */ int history[MEMORY_SIZE]; /* Current base jitterbuffer */ @@ -418,6 +434,7 @@ int jitter; /* Historic jitter value */ int historicjitter; +#endif /* LAG */ int lag; /* Error, as discovered by the manager */ @@ -589,6 +606,46 @@ ast_log(LOG_WARNING, "%s", data); } +#ifdef NEWJB +static void jb_error_output(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + vsnprintf(buf, 1024, fmt, args); + va_end(args); + + ast_log(LOG_ERROR, buf); +} + +static void jb_warning_output(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + vsnprintf(buf, 1024, fmt, args); + va_end(args); + + ast_log(LOG_WARNING, buf); +} + +static void jb_debug_output(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + if(!iaxdebug) return; + + va_start(args, fmt); + vsnprintf(buf, 1024, fmt, args); + va_end(args); + + ast_verbose(buf); +} +#endif + + /* XXX We probably should use a mutex when working with this XXX */ static struct chan_iax2_pvt *iaxs[IAX_MAX_CALLS]; static ast_mutex_t iaxsl[IAX_MAX_CALLS]; @@ -778,6 +835,16 @@ /* strncpy(tmp->context, context, sizeof(tmp->context)-1); */ strncpy(tmp->exten, "s", sizeof(tmp->exten)-1); strncpy(tmp->host, host, sizeof(tmp->host)-1); +#ifdef NEWJB + { + jb_info jbinfo; + + tmp->jb = jb_new(); + tmp->jbid = -1; + jbinfo.max_jitterbuf = maxjitterbuffer; + jb_setinfo(tmp->jb,&jbinfo); + } +#endif } return tmp; } @@ -1439,6 +1506,11 @@ ast_sched_del(sched, pvt->authid); if (pvt->initid > -1) ast_sched_del(sched, pvt->initid); +#ifdef NEWJB + if (pvt->jbid > -1) + ast_sched_del(sched, pvt->jbid); + pvt->jbid = -1; +#endif pvt->pingid = -1; pvt->lagid = -1; pvt->autoid = -1; @@ -1511,6 +1583,11 @@ ast_sched_del(sched, pvt->authid); if (pvt->initid > -1) ast_sched_del(sched, pvt->initid); +#ifdef NEWJB + if (pvt->jbid > -1) + ast_sched_del(sched, pvt->jbid); + pvt->jbid = -1; +#endif pvt->pingid = -1; pvt->lagid = -1; pvt->autoid = -1; @@ -1542,6 +1619,14 @@ ast_variables_destroy(pvt->vars); pvt->vars = NULL; } +#ifdef NEWJB + { + jb_frame frame; + while(jb_getall(pvt->jb,&frame) == JB_OK) + iax2_frame_free(frame.data); + jb_destroy(pvt->jb); + } +#endif free(pvt); } } @@ -1664,6 +1749,10 @@ static int iax2_set_jitter(int fd, int argc, char *argv[]) { +#ifdef NEWJB + ast_cli(fd, "sorry, this command is deprecated\n"); + return RESULT_SUCCESS; +#else if ((argc != 4) && (argc != 5)) return RESULT_SHOWUSAGE; if (argc == 4) { @@ -1684,6 +1773,7 @@ } } return RESULT_SUCCESS; +#endif } static char jitter_usage[] = @@ -1717,6 +1807,16 @@ return RESULT_SUCCESS; } +static int iax2_test_losspct(int fd, int argc, char *argv[]) +{ + if (argc != 4) + return RESULT_SHOWUSAGE; + + test_losspct = atoi(argv[3]); + + return RESULT_SUCCESS; +} + /*--- iax2_show_peer: Show one peer in detail ---*/ static int iax2_show_peer(int fd, int argc, char *argv[]) { @@ -1764,6 +1864,15 @@ ast_cli(fd, "none"); ast_cli(fd, ")\n"); +static int iax2_test_losspct(int fd, int argc, char *argv[]) +{ + if (argc != 4) + return RESULT_SHOWUSAGE; + + test_losspct = atoi(argv[3]); + + return RESULT_SUCCESS; +} ast_cli(fd, " Status : "); if (peer->lastms < 0) @@ -1987,9 +2096,119 @@ } } +#ifdef NEWJB +static int get_from_jb(void *p); + +static void update_jbsched(struct chan_iax2_pvt *pvt) { + int when; + struct timeval tv; + + gettimeofday(&tv,NULL); + + when = (tv.tv_sec - pvt->rxcore.tv_sec) * 1000 + + (tv.tv_usec - pvt->rxcore.tv_usec) / 1000; + + /* fprintf(stderr, "now = %d, next=%d\n", when, jb_next(pvt->jb)); */ + + when = jb_next(pvt->jb) - when; + /* fprintf(stderr, "when = %d\n", when); */ + + if(pvt->jbid > -1) ast_sched_del(sched, pvt->jbid); + + if(when <= 0) { + /* XXX should really just empty until when > 0.. */ + when = 1; + } + + pvt->jbid = ast_sched_add(sched, when, get_from_jb, (void *)pvt); +} + +static int get_from_jb(void *p) { + /* make sure pvt is valid! */ + struct chan_iax2_pvt *pvt = p; + struct iax_frame *fr; + jb_frame frame; + int ret; + long now; + long next; + struct timeval tv; + + ast_mutex_lock(&iaxsl[pvt->callno]); + /* fprintf(stderr, "get_from_jb called\n"); */ + pvt->jbid = -1; + + gettimeofday(&tv,NULL); + + now = (tv.tv_sec - pvt->rxcore.tv_sec) * 1000 + + (tv.tv_usec - pvt->rxcore.tv_usec) / 1000; + + if(now > (next = jb_next(pvt->jb))) { + ret = jb_get(pvt->jb,&frame,now); + switch(ret) { + case JB_OK: + /*if(frame.type == JB_TYPE_VOICE && next + 20 != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not %ld+20!\n", jb_next(pvt->jb), next); */ + fr = frame.data; + __do_deliver(fr); + break; + case JB_INTERP: + { + struct ast_frame af; + + /*if(next + 20 != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not %ld+20!\n", jb_next(pvt->jb), next); */ + + /* create an interpolation frame */ + /*fprintf(stderr, "Making Interpolation frame\n"); */ + af.frametype = AST_FRAME_VOICE; + af.subclass = pvt->voiceformat; + af.datalen = 0; + af.samples = frame.ms * 8; + af.mallocd = 0; + af.src = "IAX2 JB interpolation"; + af.data = NULL; + af.delivery.tv_sec = pvt->rxcore.tv_sec; + af.delivery.tv_usec = pvt->rxcore.tv_usec; + af.delivery.tv_sec += next / 1000; + af.delivery.tv_usec += (next % 1000) * 1000; + af.offset=AST_FRIENDLY_OFFSET; + if (af.delivery.tv_usec >= 1000000) { + af.delivery.tv_usec -= 1000000; + af.delivery.tv_sec += 1; + } + + /* queue the frame: For consistency, we would call __do_deliver here, but __do_deliver wants an iax_frame, + * which we'd need to malloc, and then it would free it. That seems like a drag */ + if (iaxs[pvt->callno] && !ast_test_flag(iaxs[pvt->callno], IAX_ALREADYGONE)) + iax2_queue_frame(pvt->callno, &af); + } + break; + case JB_DROP: + /*if(next != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not next %ld!\n", jb_next(pvt->jb), next); */ + iax2_frame_free(frame.data); + break; + case JB_NOFRAME: + case JB_EMPTY: + /* do nothing */ + break; + default: + /* shouldn't happen */ + break; + } + } + update_jbsched(pvt); + ast_mutex_unlock(&iaxsl[pvt->callno]); + return 0; +} +#endif + +/* while we transition from the old JB to the new one, we can either make two schedule_delivery functions, or + * make preprocessor swiss-cheese out of this one. I'm not sure which is less revolting.. */ static int schedule_delivery(struct iax_frame *fr, int reallydeliver, int updatehistory, int fromtrunk) { - int ms,x; + int x; +#ifdef NEWJB + int type, len; +#else + int ms; int delay; unsigned int orig_ts; int drops[MEMORY_SIZE]; @@ -1999,6 +2218,7 @@ prevjitterbuffer = iaxs[fr->callno]->jitterbuffer; /* Similarly for the frame timestamp */ orig_ts = fr->ts; +#endif #if 0 if (option_debug) @@ -2031,7 +2251,7 @@ iaxs[fr->callno]->last = 0; /* should we also empty history? */ } - +#ifndef NEWJB /* ms is a measure of the "lateness" of the frame relative to the "reference" frame we received. (initially the very first, but also see code just above here). Understand that "ms" can easily be -ve if lag improves since the reference frame. @@ -2044,10 +2264,13 @@ iaxs[fr->callno]->history[x] = iaxs[fr->callno]->history[x+1]; /* Add a history entry for this one */ iaxs[fr->callno]->history[x] = ms; - +#endif } +#ifndef NEWJB else ms = 0; +#endif + /* delivery time is sender's sent timestamp converted back into absolute time according to our clock */ if ( (!fromtrunk) && (iaxs[fr->callno]->rxcore.tv_sec || iaxs[fr->callno]->rxcore.tv_usec) ) { @@ -2069,6 +2292,7 @@ fr->af.delivery.tv_usec = 0; } +#ifndef NEWJB /* Initialize the minimum to reasonable values. It's too much work to do the same for the maximum, repeatedly */ min=iaxs[fr->callno]->history[0]; @@ -2099,6 +2323,35 @@ drops[z] = maxone; #endif } +#endif + +#ifdef NEWJB + if(!reallydeliver) + return 0; + + type = JB_TYPE_CONTROL; + len = 0; + + if(fr->af.frametype == AST_FRAME_VOICE) { + type = JB_TYPE_VOICE; + len = get_samples(&fr->af)/8; + } else if(fr->af.frametype == AST_FRAME_CNG) { + type = JB_TYPE_SILENCE; + } + + if ( (!ast_test_flag(iaxs[fr->callno], IAX_USEJITTERBUF)) ) { + __do_deliver(fr); + return 0; + } + + /* insert into jitterbuffer */ + /* TODO: Perhaps we could act immediately if it's not droppable and late */ + if(jb_put(iaxs[fr->callno]->jb, fr, type, len, fr->ts, + calc_rxstamp(iaxs[fr->callno],fr->ts)) == JB_DROP) { + iax2_frame_free(fr); + } + update_jbsched(iaxs[fr->callno]); +#else /* Just for reference, keep the "jitter" value, the difference between the earliest and the latest. */ if (max >= min) @@ -2180,6 +2433,7 @@ ast_log(LOG_DEBUG, "schedule_delivery: Scheduling delivery in %d ms\n", delay); fr->retrans = ast_sched_add(sched, delay, do_deliver, fr); } +#endif return 0; } @@ -3113,6 +3367,7 @@ int genuine = 0; struct timeval *delivery = NULL; + /* What sort of frame do we have?: voice is self-explanatory "genuine" means an IAX frame - things like LAGRQ/RP, PING/PONG, ACK non-genuine frames are CONTROL frames [ringing etc], DTMF @@ -3534,6 +3789,13 @@ /* Calculate actual timestamp */ fts = calc_timestamp(pvt, ts, f); + /* Bail here if this is an "interp" frame; we don't want or need to send these placeholders out + * (the endpoint should detect the lost packet itself). But, we want to do this here, so that we + * increment the "predicted timestamps" for voice, if we're predecting */ + if(f->frametype == AST_FRAME_VOICE && f->datalen == 0) + return 0; + + if ((ast_test_flag(pvt, IAX_TRUNK) || ((fts & 0xFFFF0000L) == (lastsent & 0xFFFF0000L))) /* High two bytes are the same on timestamp, or sending on a trunk */ && (f->frametype == AST_FRAME_VOICE) @@ -3914,6 +4176,7 @@ #undef FORMAT2 } +#ifndef NEWJB static int jitterbufsize(struct chan_iax2_pvt *pvt) { int min, i; min = 99999999; @@ -3926,6 +4189,7 @@ else return pvt->jitterbuffer - min; } +#endif static int iax2_show_channels(int fd, int argc, char *argv[]) { @@ -3935,6 +4199,7 @@ int x; int numchans = 0; char iabuf[INET_ADDRSTRLEN]; + if (argc != 3) return RESULT_SHOWUSAGE; ast_cli(fd, FORMAT2, "Channel", "Peer", "Username", "ID (Lo/Rem)", "Seq (Tx/Rx)", "Lag", "Jitter", "JitBuf", "Format"); @@ -3952,16 +4217,35 @@ iaxs[x]->bridgecallno ); else #endif + { + int lag, jitter, localdelay; +#ifdef NEWJB + jb_info jbinfo; + + if(ast_test_flag(iaxs[x], IAX_USEJITTERBUF)) { + jb_getinfo(iaxs[x]->jb, &jbinfo); + jitter = jbinfo.jitter; + localdelay = jbinfo.current - jbinfo.min; + } else { + jitter = -1; + localdelay = 0; + } +#else + jitter = iaxs[x]->jitter; + localdelay = ast_test_flag(iaxs[x], IAX_USEJITTERBUF) ? jitterbufsize(iaxs[x]) : 0; +#endif + lag = iaxs[x]->remote_rr.delay; ast_cli(fd, FORMAT, iaxs[x]->owner ? iaxs[x]->owner->name : "(None)", ast_inet_ntoa(iabuf, sizeof(iabuf), iaxs[x]->addr.sin_addr), !ast_strlen_zero(iaxs[x]->username) ? iaxs[x]->username : "(None)", iaxs[x]->callno, iaxs[x]->peercallno, iaxs[x]->oseqno, iaxs[x]->iseqno, - iaxs[x]->lag, - iaxs[x]->jitter, - ast_test_flag(iaxs[x], IAX_USEJITTERBUF) ? jitterbufsize(iaxs[x]) : 0, + lag, + jitter, + localdelay, ast_getformatname(iaxs[x]->voiceformat) ); + } numchans++; } ast_mutex_unlock(&iaxsl[x]); @@ -3990,15 +4274,47 @@ iaxs[x]->owner ? iaxs[x]->owner->name : "(None)"); else #endif + { + int localjitter, localdelay, locallost, locallosspct, localdropped, localooo; +#ifdef NEWJB + jb_info jbinfo; + + if(ast_test_flag(iaxs[x], IAX_USEJITTERBUF)) { + jb_getinfo(iaxs[x]->jb, &jbinfo); + localjitter = jbinfo.jitter; + localdelay = jbinfo.current - jbinfo.min; + locallost = jbinfo.frames_lost; + locallosspct = jbinfo.losspct/1000; + localdropped = jbinfo.frames_dropped; + localooo = jbinfo.frames_ooo; + } else { + localjitter = -1; + localdelay = 0; + locallost = -1; + locallosspct = -1; + localdropped = 0; + localooo = -1; + } +#else + localjitter = iaxs[x]->jitter; + if(ast_test_flag(iaxs[x], IAX_USEJITTERBUF)) + { + localdelay = jitterbufsize(iaxs[x]); + localdropped = iaxs[x]->frames_dropped; + } else { + localdelay = localdropped = 0; + } + locallost = locallosspct = localooo = -1; +#endif ast_cli(fd, "%-25.25s %4d %4d %4d %5d %3d %5d %4d %6d %4d %4d %5d %3d %5d %4d %6d\n", iaxs[x]->owner ? iaxs[x]->owner->name : "(None)", iaxs[x]->pingtime, - iaxs[x]->jitter, - ast_test_flag(iaxs[x], IAX_USEJITTERBUF) ? jitterbufsize(iaxs[x]) : 0, - -1, - -1, - -1, - -1, + localjitter, + localdelay, + locallost, + locallosspct, + localdropped, + localooo, iaxs[x]->frames_received/1000, iaxs[x]->remote_rr.jitter, iaxs[x]->remote_rr.delay, @@ -4008,6 +4324,7 @@ iaxs[x]->remote_rr.ooo, iaxs[x]->remote_rr.packets/1000 ); + } numchans++; } ast_mutex_unlock(&iaxsl[x]); @@ -4030,6 +4347,9 @@ if (argc != 2) return RESULT_SHOWUSAGE; iaxdebug = 1; +#ifdef NEWJB + jb_setoutput(jb_error_output, jb_warning_output, jb_debug_output); +#endif ast_cli(fd, "IAX2 Debugging Enabled\n"); return RESULT_SUCCESS; } @@ -4039,6 +4359,9 @@ if (argc != 3) return RESULT_SHOWUSAGE; iaxdebug = 0; +#ifdef NEWJB + jb_setoutput(jb_error_output, jb_warning_output, NULL); +#endif ast_cli(fd, "IAX2 Debugging Disabled\n"); return RESULT_SUCCESS; } @@ -4084,6 +4407,10 @@ "Usage: iax2 trunk debug\n" " Requests current status of IAX trunking\n"; +static char iax2_test_losspct_usage[] = +"Usage: iax2 test losspct \n" +" For testing, throws away percent of incoming packets\n"; + static struct ast_cli_entry cli_show_users = { { "iax2", "show", "users", NULL }, iax2_show_users, "Show defined IAX users", show_users_usage }; static struct ast_cli_entry cli_show_firmware = @@ -4102,6 +4429,8 @@ { { "iax2", "trunk", "debug", NULL }, iax2_do_trunk_debug, "Request IAX trunk debug", debug_trunk_usage }; static struct ast_cli_entry cli_no_debug = { { "iax2", "no", "debug", NULL }, iax2_no_debug, "Disable IAX debugging", no_debug_usage }; +static struct ast_cli_entry cli_test_losspct = + { { "iax2", "test", "losspct", NULL }, iax2_test_losspct, "Set IAX2 incoming frame loss percentage", iax2_test_losspct_usage }; static int iax2_write(struct ast_channel *c, struct ast_frame *f) { @@ -4873,10 +5202,20 @@ pvt->transfercallno = -1; memset(&pvt->rxcore, 0, sizeof(pvt->rxcore)); memset(&pvt->offset, 0, sizeof(pvt->offset)); +#ifdef NEWJB + { /* reset jitterbuffer */ + jb_frame frame; + while(jb_getall(pvt->jb,&frame) == JB_OK) + iax2_frame_free(frame.data); + + jb_reset(pvt->jb); + } +#else memset(&pvt->history, 0, sizeof(pvt->history)); pvt->jitterbuffer = 0; pvt->jitter = 0; pvt->historicjitter = 0; +#endif pvt->lag = 0; pvt->last = 0; pvt->lastsent = 0; @@ -5265,6 +5604,11 @@ if (iaxs[callno]->authid > -1) ast_sched_del(sched, iaxs[callno]->authid); iaxs[callno]->authid = -1; +#ifdef NEWJB + if (iaxs[callno]->jbid > -1) + ast_sched_del(sched, iaxs[callno]->jbid); + iaxs[callno]->jbid = -1; +#endif return 0; } @@ -5661,6 +6005,20 @@ static void construct_rr(struct chan_iax2_pvt *pvt, struct iax_ie_data *iep) { +#ifdef NEWJB + jb_info stats; + jb_getinfo(pvt->jb, &stats); + + memset(iep, 0, sizeof(*iep)); + + iax_ie_append_int(iep,IAX_IE_RR_JITTER, stats.jitter); + if(stats.frames_in == 0) stats.frames_in = 1; + iax_ie_append_int(iep,IAX_IE_RR_LOSS, ((0xff & (stats.losspct/1000)) << 24 | (stats.frames_lost & 0x00ffffff))); + iax_ie_append_int(iep,IAX_IE_RR_PKTS, stats.frames_in); + iax_ie_append_short(iep,IAX_IE_RR_DELAY, stats.current - stats.min); + iax_ie_append_int(iep,IAX_IE_RR_DROPPED, stats.frames_dropped); + iax_ie_append_int(iep,IAX_IE_RR_OOO, stats.frames_ooo); +#else memset(iep, 0, sizeof(*iep)); iax_ie_append_int(iep,IAX_IE_RR_JITTER, pvt->jitter); iax_ie_append_int(iep,IAX_IE_RR_PKTS, pvt->frames_received); @@ -5671,7 +6029,7 @@ iax_ie_append_int(iep,IAX_IE_RR_DROPPED, pvt->frames_dropped); /* don't know, don't send! iax_ie_append_int(&ied,IAX_IE_RR_OOO, 0); */ /* don't know, don't send! iax_ie_append_int(&ied,IAX_IE_RR_LOSS, 0); */ - +#endif } static void save_rr(struct iax_frame *fr, struct iax_ies *ies) @@ -5734,6 +6092,11 @@ handle_error(); return 1; } + if(test_losspct) { /* simulate random loss condition */ + if( (100.0*rand()/(RAND_MAX+1.0)) < test_losspct) + return 1; + + } if (res < sizeof(struct ast_iax2_mini_hdr)) { ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, (int)sizeof(struct ast_iax2_mini_hdr)); return 1; @@ -8488,6 +8851,7 @@ ast_cli_unregister(&cli_debug); ast_cli_unregister(&cli_trunk_debug); ast_cli_unregister(&cli_no_debug); + ast_cli_unregister(&cli_test_losspct); ast_cli_unregister(&cli_set_jitter); ast_cli_unregister(&cli_show_stats); ast_cli_unregister(&cli_show_cache); @@ -8525,6 +8889,9 @@ iax_set_output(iax_debug_output); iax_set_error(iax_error_output); +#ifdef NEWJB + jb_setoutput(jb_error_output, jb_warning_output, NULL); +#endif /* Seed random number generator */ srand(time(NULL)); @@ -8572,6 +8939,7 @@ ast_cli_register(&cli_debug); ast_cli_register(&cli_trunk_debug); ast_cli_register(&cli_no_debug); + ast_cli_register(&cli_test_losspct); ast_cli_register(&cli_set_jitter); ast_cli_register(&cli_show_stats); ast_cli_register(&cli_show_cache); Adds a new property to channel technologies called "wantsjitter", which basically is set for VoIP technologies, and not set for TDM/non-VoIP technologies. # # Patch managed by http://www.holgerschurig.de/patcher.html # --- asterisk/include/asterisk/channel.h~channel_properties +++ asterisk/include/asterisk/channel.h @@ -82,8 +82,12 @@ const char * const type; const char * const description; + /*! Bitmap of formats this channel can handle */ int capabilities; + /*! Technology Properties */ + int properties; + struct ast_channel *(* const requester)(const char *type, int format, void *data, int *cause); int (* const devicestate)(void *data); @@ -311,6 +315,10 @@ }; +/* Channel tech properties: */ +/* Channels have this property if they can accept input with jitter; i.e. most VoIP channels */ +#define AST_CHAN_TP_WANTSJITTER (1 << 0) + #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 */ #define AST_FLAG_WRITE_INT (1 << 2) /* if write should be interrupt generator */ --- asterisk/channels/chan_iax2.c~channel_properties +++ asterisk/channels/chan_iax2.c @@ -687,6 +687,7 @@ .type = channeltype, .description = tdesc, .capabilities = IAX_CAPABILITY_FULLBANDWIDTH, + .properties = AST_CHAN_TP_WANTSJITTER, .requester = iax2_request, .devicestate = iax2_devicestate, .send_digit = iax2_digit, --- asterisk/channels/chan_sip.c~channel_properties +++ asterisk/channels/chan_sip.c @@ -631,6 +631,7 @@ .type = channeltype, .description = "Session Initiation Protocol (SIP)", .capabilities = ((AST_FORMAT_MAX_AUDIO << 1) - 1), + .properties = AST_CHAN_TP_WANTSJITTER, .requester = sip_request, .devicestate = sip_devicestate, .call = sip_call, --- asterisk/channels/chan_h323.c~channel_properties +++ asterisk/channels/chan_h323.c @@ -197,6 +197,7 @@ .type = type, .description = tdesc, .capabilities = AST_FORMAT_ULAW, + .properties = AST_CHAN_TP_WANTSJITTER, .requester = oh323_request, .send_digit = oh323_digit, .call = oh323_call, --- asterisk/channels/chan_mgcp.c~channel_properties +++ asterisk/channels/chan_mgcp.c @@ -479,6 +479,7 @@ .type = type, .description = tdesc, .capabilities = AST_FORMAT_ULAW, + .properties = AST_CHAN_TP_WANTSJITTER, .requester = mgcp_request, .call = mgcp_call, .hangup = mgcp_hangup, --- asterisk/channels/chan_skinny.c~channel_properties +++ asterisk/channels/chan_skinny.c @@ -809,6 +809,7 @@ .type = type, .description = tdesc, .capabilities = AST_FORMAT_ULAW, + .properties = AST_CHAN_TP_WANTSJITTER, .requester = skinny_request, .call = skinny_call, .hangup = skinny_hangup, Adds a new boolean config option "forcejitterbuffer" for iax2. Makes the default to _not_ use JB when bridged to a technology which has the "wantsjitter" flag. setting forcejitterbuffer either globally or for a peer/user will override this behavior, and force the jitterbuffer to be used in this case. # # Patch managed by http://www.holgerschurig.de/patcher.html # --- asterisk/channels/chan_iax2.c~jitterbuf-iax2-notalways +++ asterisk/channels/chan_iax2.c @@ -220,6 +220,7 @@ #define IAX_RTCACHEFRIENDS (1 << 17) /* let realtime stay till your reload */ #define IAX_RTNOUPDATE (1 << 18) /* Don't send a realtime update */ #define IAX_RTAUTOCLEAR (1 << 19) /* erase me on expire */ +#define IAX_FORCEJITTERBUF (1 << 20) /* Force jitterbuffer, even when bridged to a channel that can take jitter */ static int global_rtautoclear = 120; @@ -1067,7 +1068,7 @@ iaxs[x]->pingid = ast_sched_add(sched, ping_time * 1000, send_ping, (void *)(long)x); iaxs[x]->lagid = ast_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)(long)x); iaxs[x]->amaflags = amaflags; - ast_copy_flags(iaxs[x], (&globalflags), IAX_NOTRANSFER | IAX_USEJITTERBUF); + ast_copy_flags(iaxs[x], (&globalflags), IAX_NOTRANSFER | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); strncpy(iaxs[x]->accountcode, accountcode, sizeof(iaxs[x]->accountcode)-1); } else { ast_log(LOG_WARNING, "Out of resources\n"); @@ -2345,6 +2346,31 @@ return 0; } + /* if the user hasn't requested we force the use of the jitterbuffer, and we're bridged to + * a channel that can accept jitter, then flush and suspend the jb, and send this frame straight through */ + if( (!ast_test_flag(iaxs[fr->callno], IAX_FORCEJITTERBUF)) && + iaxs[fr->callno]->owner && ast_bridged_channel(iaxs[fr->callno]->owner) && + (ast_bridged_channel(iaxs[fr->callno]->owner)->tech->properties & AST_CHAN_TP_WANTSJITTER)) { + jb_frame frame; + + /* deliver any frames in the jb */ + while(jb_getall(iaxs[fr->callno]->jb,&frame) == JB_OK) + __do_deliver(frame.data); + + jb_reset(iaxs[fr->callno]->jb); + + if (iaxs[fr->callno]->jbid > -1) + ast_sched_del(sched, iaxs[fr->callno]->jbid); + + iaxs[fr->callno]->jbid = -1; + + /* deliver this frame now */ + __do_deliver(fr); + return 0; + + } + + /* insert into jitterbuffer */ /* TODO: Perhaps we could act immediately if it's not droppable and late */ if(jb_put(iaxs[fr->callno]->jb, fr, type, len, fr->ts, @@ -2626,7 +2652,7 @@ static int create_addr(struct sockaddr_in *sin, int *capability, int *sendani, int *maxtime, char *peer, char *context, int *trunk, - int *notransfer, int *usejitterbuf, int *encmethods, + int *notransfer, int *usejitterbuf, int *forcejitterbuf, int *encmethods, char *username, int usernlen, char *secret, int seclen, int *ofound, char *peercontext, char *timezone, int tzlen, char *pref_str, size_t pref_size, int *sockfd) @@ -2681,6 +2707,8 @@ *notransfer = ast_test_flag(p, IAX_NOTRANSFER); if (usejitterbuf) *usejitterbuf = ast_test_flag(p, IAX_USEJITTERBUF); + if (forcejitterbuf) + *forcejitterbuf = ast_test_flag(p, IAX_FORCEJITTERBUF); if (secret) { if (!ast_strlen_zero(p->dbsecret)) { char *family, *key=NULL; @@ -2830,7 +2858,7 @@ strsep(&stringp, ":"); portno = strsep(&stringp, ":"); } - if (create_addr(&sin, NULL, NULL, NULL, hname, context, NULL, NULL, NULL, &encmethods, storedusern, sizeof(storedusern) - 1, storedsecret, sizeof(storedsecret) - 1, NULL, peercontext, tz, sizeof(tz), out_prefs, sizeof(out_prefs), NULL)) { + if (create_addr(&sin, NULL, NULL, NULL, hname, context, NULL, NULL, NULL, NULL, &encmethods, storedusern, sizeof(storedusern) - 1, storedsecret, sizeof(storedsecret) - 1, NULL, peercontext, tz, sizeof(tz), out_prefs, sizeof(out_prefs), NULL)) { ast_log(LOG_WARNING, "No address associated with '%s'\n", hname); return -1; } @@ -4690,7 +4718,7 @@ iaxs[callno]->amaflags = user->amaflags; if (!ast_strlen_zero(user->language)) strncpy(iaxs[callno]->language, user->language, sizeof(iaxs[callno]->language)-1); - ast_copy_flags(iaxs[callno], user, IAX_NOTRANSFER | IAX_USEJITTERBUF); + ast_copy_flags(iaxs[callno], user, IAX_NOTRANSFER | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); /* Keep this check last */ if (!ast_strlen_zero(user->dbsecret)) { char *family, *key=NULL; @@ -7391,7 +7419,7 @@ if (end) memcpy(&sin, end, sizeof(sin)); else { - if (create_addr(&sin, NULL, NULL, NULL, dest, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, NULL, NULL, NULL, 0, NULL, 0, &sockfd)) + if (create_addr(&sin, NULL, NULL, NULL, dest, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, NULL, NULL, NULL, 0, NULL, 0, &sockfd)) return -1; } /* Build the rest of the message */ @@ -7566,6 +7594,8 @@ int sockfd = defaultsockfd; int notransfer = ast_test_flag((&globalflags), IAX_NOTRANSFER); int usejitterbuf = ast_test_flag((&globalflags), IAX_USEJITTERBUF); + int forcejitterbuf = ast_test_flag((&globalflags), IAX_FORCEJITTERBUF); + strncpy(s, (char *)data, sizeof(s)-1); /* FIXME The next two lines seem useless */ stringp=s; @@ -7587,7 +7617,7 @@ } /* Populate our address from the given */ - if (create_addr(&sin, &capability, &sendani, &maxtime, hostname, NULL, &trunk, ¬ransfer, &usejitterbuf, NULL, NULL, 0, NULL, 0, &found, NULL, NULL, 0, NULL, 0, &sockfd)) { + if (create_addr(&sin, &capability, &sendani, &maxtime, hostname, NULL, &trunk, ¬ransfer, &usejitterbuf, &forcejitterbuf, NULL, NULL, 0, NULL, 0, &found, NULL, NULL, 0, NULL, 0, &sockfd)) { *cause = AST_CAUSE_UNREGISTERED; return NULL; } @@ -7610,6 +7640,7 @@ iaxs[callno]->maxtime = maxtime; ast_set2_flag(iaxs[callno], notransfer, IAX_NOTRANSFER); ast_set2_flag(iaxs[callno], usejitterbuf, IAX_USEJITTERBUF); + ast_set2_flag(iaxs[callno], forcejitterbuf, IAX_FORCEJITTERBUF); if (found) strncpy(iaxs[callno]->host, hostname, sizeof(iaxs[callno]->host) - 1); c = ast_iax2_new(callno, AST_STATE_DOWN, capability); @@ -7762,7 +7793,7 @@ } } if (peer) { - ast_copy_flags(peer, (&globalflags), IAX_MESSAGEDETAIL | IAX_USEJITTERBUF); + ast_copy_flags(peer, (&globalflags), IAX_MESSAGEDETAIL | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); peer->encmethods = iax2_encryption; peer->secret[0] = '\0'; if (!found) { @@ -7801,6 +7832,8 @@ ast_set2_flag(peer, ast_true(v->value), IAX_NOTRANSFER); } else if (!strcasecmp(v->name, "jitterbuffer")) { ast_set2_flag(peer, ast_true(v->value), IAX_USEJITTERBUF); + } else if (!strcasecmp(v->name, "forcejitterbuffer")) { + ast_set2_flag(peer, ast_true(v->value), IAX_FORCEJITTERBUF); } else if (!strcasecmp(v->name, "host")) { if (!strcasecmp(v->value, "dynamic")) { /* They'll register with us */ @@ -7949,6 +7982,7 @@ strncpy(user->name, name, sizeof(user->name)-1); strncpy(user->language, language, sizeof(user->language) - 1); ast_copy_flags(user, (&globalflags), IAX_USEJITTERBUF); + ast_copy_flags(user, (&globalflags), IAX_FORCEJITTERBUF); ast_copy_flags(user, (&globalflags), IAX_CODEC_USER_FIRST); ast_copy_flags(user, (&globalflags), IAX_CODEC_NOPREFS); ast_copy_flags(user, (&globalflags), IAX_CODEC_NOCAP); @@ -8002,6 +8036,8 @@ } } else if (!strcasecmp(v->name, "jitterbuffer")) { ast_set2_flag(user, ast_true(v->value), IAX_USEJITTERBUF); + } else if (!strcasecmp(v->name, "forcejitterbuffer")) { + ast_set2_flag(user, ast_true(v->value), IAX_FORCEJITTERBUF); } else if (!strcasecmp(v->name, "dbsecret")) { strncpy(user->dbsecret, v->value, sizeof(user->dbsecret)-1); } else if (!strcasecmp(v->name, "secret")) { @@ -8260,6 +8296,8 @@ } } else if (!strcasecmp(v->name, "jitterbuffer")) ast_set2_flag((&globalflags), ast_true(v->value), IAX_USEJITTERBUF); + else if (!strcasecmp(v->name, "forcejitterbuffer")) + ast_set2_flag((&globalflags), ast_true(v->value), IAX_FORCEJITTERBUF); else if (!strcasecmp(v->name, "delayreject")) delayreject = ast_true(v->value); else if (!strcasecmp(v->name, "mailboxdetail")) @@ -8389,6 +8427,7 @@ delayreject = 0; ast_clear_flag((&globalflags), IAX_NOTRANSFER); ast_clear_flag((&globalflags), IAX_USEJITTERBUF); + ast_clear_flag((&globalflags), IAX_FORCEJITTERBUF); srand(time(NULL)); delete_users(); set_config(config,1); @@ -8460,7 +8499,7 @@ host = st; } /* Populate our address from the given */ - if (create_addr(&sin, NULL, NULL, NULL, host, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, NULL, NULL, NULL, 0, NULL, 0, &sockfd)) { + if (create_addr(&sin, NULL, NULL, NULL, host, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, NULL, NULL, NULL, 0, NULL, 0, &sockfd)) { return -1; } ast_log(LOG_DEBUG, "host: %s, user: %s, password: %s, context: %s\n", host, username, password, context); --- asterisk/configs/iax.conf.sample~jitterbuf-iax2-notalways +++ asterisk/configs/iax.conf.sample @@ -58,6 +58,12 @@ ; The jitter buffer's function is to compensate for varying ; network delay. ; +; There are presently two jitterbuffer implementations available for * and chan_iax2; +; the classic and the new, channel/application independent implementation. These +; are controlled at compile-time. The new jitterbuffer additionally has support for PLC +; which greatly improves quality as the jitterbuffer adapts size, and in compensating for lost +; packets. +; ; All the jitter buffer settings except dropcount are in milliseconds. ; The jitter buffer works for INCOMING audio - the outbound audio ; will be dejittered by the jitter buffer at the other end. @@ -65,9 +71,16 @@ ; jitterbuffer=yes|no: global default as to whether you want ; the jitter buffer at all. ; +; forcejitterbuffer=yes|no: in the ideal world, when we bridge VoIP channels +; we don't want to do jitterbuffering on the switch, since the endpoints +; can each handle this. However, some endpoints may have poor jitterbuffers +; themselves, so this option will force * to always jitterbuffer, even in this case. +; [This option presently applies only to the new jitterbuffer implementation] +; ; dropcount: the jitter buffer is sized such that no more than "dropcount" ; frames would have been "too late" over the last 2 seconds. ; Set to a small number. "3" represents 1.5% of frames dropped +; [This option is not applicable to, and ignored by the new jitterbuffer implementation] ; ; maxjitterbuffer: a maximum size for the jitter buffer. ; Setting a reasonable maximum here will prevent the call delay @@ -78,18 +91,22 @@ ; the jitter buffer can end up bigger than necessary. If it ends up ; more than "maxexcessbuffer" bigger than needed, Asterisk will start ; gradually decreasing the amount of jitter buffering. +; [This option is not applicable to, and ignored by the new jitterbuffer implementation] ; ; minexcessbuffer: Sets a desired mimimum amount of headroom in ; the jitter buffer. If Asterisk has less headroom than this, then ; it will start gradually increasing the amount of jitter buffering. +; [This option is not applicable to, and ignored by the new jitterbuffer implementation] ; ; jittershrinkrate: when the jitter buffer is being gradually shrunk ; (or enlarged), how many millisecs shall we take off per 20ms frame ; received? Use a small number, or you will be able to hear it ; changing. An example: if you set this to 2, then the jitter buffer ; size will change by 100 millisecs per second. +; [This option is not applicable to, and ignored by the new jitterbuffer implementation] jitterbuffer=no +forcejitterbuffer=no ;dropcount=2 ;maxjitterbuffer=500 ;maxexcessbuffer=80