/* * STFU (S)ort (T)ransportable (F)ramed (U)tterances * Copyright (c) 2007 Anthony Minessale II * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * THOSE WHO DISAGREE MAY CERTAINLY STFU */ #include "stfu.h" //#define DB_JB 1 #ifndef UINT_MAX # define UINT_MAX 4294967295U #endif #ifndef UINT16_MAX # define UINT16_MAX 65535 #endif #ifdef _MSC_VER /* warning C4706: assignment within conditional expression*/ #pragma warning(disable: 4706) /* warning C4996: 'strdup': The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _strdup. See online help for details. */ #pragma warning(disable:4996) #endif #define least1(_z) (_z ? _z : 1) static int stfu_log_level = 7; struct stfu_queue { struct stfu_frame *array; struct stfu_frame int_frame; uint32_t real_array_size; uint32_t array_size; uint32_t array_len; uint32_t wr_len; uint32_t last_index; int32_t last_jitter; }; typedef struct stfu_queue stfu_queue_t; struct stfu_instance { struct stfu_queue a_queue; struct stfu_queue b_queue; struct stfu_queue c_queue; struct stfu_queue *in_queue; struct stfu_queue *out_queue; struct stfu_queue *old_queue; struct stfu_frame *last_frame; uint32_t cur_ts; uint32_t last_wr_ts; uint32_t last_rd_ts; uint32_t samples_per_packet; uint32_t samples_per_second; uint32_t miss_count; uint32_t max_plc; uint32_t qlen; uint32_t most_qlen; uint32_t max_qlen; uint32_t orig_qlen; uint32_t packet_count; uint32_t consecutive_good_count; uint32_t consecutive_bad_count; uint32_t period_good_count; uint32_t period_bad_count; uint32_t period_packet_in_count; uint32_t period_packet_out_count; uint32_t period_missing_count; uint32_t period_need_range; uint32_t period_need_range_avg; uint32_t period_clean_count; uint32_t session_clean_count; uint32_t session_missing_count; uint32_t session_packet_in_count; uint32_t session_packet_out_count; uint32_t sync_out; uint32_t sync_in; int32_t ts_offset; int32_t ts_drift; int32_t max_drift; uint32_t drift_dropped_packets; uint32_t drift_max_dropped; int32_t ts_diff; int32_t last_ts_diff; int32_t same_ts; uint32_t period_time; uint32_t decrement_time; uint32_t plc_len; uint32_t plc_pt; uint32_t diff; uint32_t diff_total; uint8_t ready; uint8_t debug; char *name; stfu_n_call_me_t callback; void *udata; }; static void stfu_n_reset_counters(stfu_instance_t *i); static void null_logger(const char *file, const char *func, int line, int level, const char *fmt, ...); static void default_logger(const char *file, const char *func, int line, int level, const char *fmt, ...); stfu_logger_t stfu_log = null_logger; int32_t stfu_n_get_drift(stfu_instance_t *i) { return i->ts_drift; } int32_t stfu_n_get_most_qlen(stfu_instance_t *i) { return i->most_qlen; } void stfu_global_set_logger(stfu_logger_t logger) { if (logger) { stfu_log = logger; } else { stfu_log = null_logger; } } void stfu_global_set_default_logger(int level) { if (level < 0 || level > 7) { level = 7; } stfu_log = default_logger; stfu_log_level = level; } static stfu_status_t stfu_n_resize_aqueue(stfu_queue_t *queue, uint32_t qlen) { unsigned char *m; if (qlen <= queue->real_array_size) { queue->array_size = qlen; if (queue->array_len > qlen) { queue->array_len = qlen; } } else { m = realloc(queue->array, qlen * sizeof(struct stfu_frame)); assert(m); memset(m + queue->array_size * sizeof(struct stfu_frame), 0, (qlen * sizeof(struct stfu_frame)) - (queue->array_size * sizeof(struct stfu_frame))); queue->array = (struct stfu_frame *) m; queue->real_array_size = queue->array_size = qlen; } return STFU_IT_WORKED; } static void stfu_n_init_aqueue(stfu_queue_t *queue, uint32_t qlen) { queue->array = calloc(qlen, sizeof(struct stfu_frame)); assert(queue->array != NULL); memset(queue->array, 0, sizeof(struct stfu_frame) * qlen); queue->real_array_size = queue->array_size = qlen; queue->int_frame.plc = 1; memset(queue->int_frame.data, 255, sizeof(queue->int_frame.data)); } void stfu_n_call_me(stfu_instance_t *i, stfu_n_call_me_t callback, void *udata) { i->callback = callback; i->udata = udata; } void stfu_n_destroy(stfu_instance_t **i) { stfu_instance_t *ii; if (i && *i) { ii = *i; *i = NULL; if (ii->name) free(ii->name); free(ii->a_queue.array); free(ii->b_queue.array); free(ii->c_queue.array); free(ii); } } void stfu_n_debug(stfu_instance_t *i, const char *name) { if (i->name) free(i->name); if (name) { i->name = strdup(name); i->debug = 1; } else { i->name = strdup("none"); i->debug = 0; } } void stfu_n_report(stfu_instance_t *i, stfu_report_t *r) { assert(i); r->qlen = i->qlen; r->packet_in_count = i->period_packet_in_count; r->clean_count = i->period_clean_count; r->consecutive_good_count = i->consecutive_good_count; r->consecutive_bad_count = i->consecutive_bad_count; } stfu_status_t stfu_n_resize(stfu_instance_t *i, uint32_t qlen) { stfu_status_t s; if (i->qlen == i->max_qlen) { return STFU_IT_FAILED; } if (i->max_qlen && qlen > i->max_qlen) { if (i->qlen < i->max_qlen) { qlen = i->max_qlen; } else { return STFU_IT_FAILED; } } if ((s = stfu_n_resize_aqueue(&i->a_queue, qlen)) == STFU_IT_WORKED) { s = stfu_n_resize_aqueue(&i->b_queue, qlen); s = stfu_n_resize_aqueue(&i->c_queue, qlen); if (qlen > i->most_qlen) { i->most_qlen = qlen; } i->qlen = qlen; i->max_plc = 5; i->last_frame = NULL; } return s; } stfu_instance_t *stfu_n_init(uint32_t qlen, uint32_t max_qlen, uint32_t samples_per_packet, uint32_t samples_per_second, uint32_t max_drift_ms) { struct stfu_instance *i; i = malloc(sizeof(*i)); if (!i) { return NULL; } memset(i, 0, sizeof(*i)); i->qlen = qlen; i->max_qlen = max_qlen; i->orig_qlen = qlen; i->samples_per_packet = samples_per_packet; stfu_n_init_aqueue(&i->a_queue, qlen); stfu_n_init_aqueue(&i->b_queue, qlen); stfu_n_init_aqueue(&i->c_queue, qlen); i->max_drift = (int32_t)(max_drift_ms * (samples_per_second / 1000) * -1); if (max_drift_ms && samples_per_packet) { i->drift_max_dropped = (samples_per_second * 2) / samples_per_packet; } i->in_queue = &i->a_queue; i->out_queue = &i->b_queue; i->old_queue = &i->c_queue; i->name = strdup("none"); i->max_plc = i->qlen / 2; i->samples_per_second = samples_per_second ? samples_per_second : 8000; i->period_time = ((i->samples_per_second * 20) / i->samples_per_packet); i->decrement_time = ((i->samples_per_second * 15) / i->samples_per_packet); return i; } static void stfu_n_reset_counters(stfu_instance_t *i) { if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s COUNTER RESET........\n", i->name); } if (i->callback) { i->callback(i, i->udata); } i->consecutive_good_count = 0; i->consecutive_bad_count = 0; i->period_good_count = 0; i->period_clean_count = 0; i->period_bad_count = 0; i->period_packet_in_count = 0; i->period_packet_out_count = 0; i->period_missing_count = 0; i->period_need_range = 0; i->period_need_range_avg = 0; i->diff = 0; i->diff_total = 0; } void stfu_n_reset(stfu_instance_t *i) { if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s RESET\n", i->name); } i->ready = 0; i->in_queue = &i->a_queue; i->out_queue = &i->b_queue; i->old_queue = &i->c_queue; i->in_queue->array_len = 0; i->out_queue->array_len = 0; i->out_queue->wr_len = 0; i->last_frame = NULL; i->in_queue->last_jitter = 0; i->out_queue->last_jitter = 0; stfu_n_reset_counters(i); stfu_n_sync(i, 1); i->cur_ts = 0; i->last_wr_ts = 0; i->last_rd_ts = 0; i->miss_count = 0; i->packet_count = 0; } stfu_status_t stfu_n_sync(stfu_instance_t *i, uint32_t packets) { if (packets > i->qlen) { stfu_n_reset(i); } else { i->sync_out = packets; i->sync_in = packets; } return STFU_IT_WORKED; } static void stfu_n_swap(stfu_instance_t *i) { stfu_queue_t *last_in = i->in_queue, *last_out = i->out_queue, *last_old = i->old_queue; i->ready = 1; i->in_queue = last_out; i->out_queue = last_old; i->old_queue = last_in; i->in_queue->array_len = 0; i->out_queue->wr_len = 0; i->last_frame = NULL; i->miss_count = 0; i->in_queue->last_index = 0; i->out_queue->last_index = 0; i->out_queue->last_jitter = 0; } stfu_status_t stfu_n_add_data(stfu_instance_t *i, uint32_t ts, uint32_t pt, void *data, size_t datalen, uint32_t timer_ts, int last) { uint32_t index = 0; stfu_frame_t *frame; size_t cplen = 0; int good_ts = 0; if (!i->samples_per_packet && ts && i->last_rd_ts) { i->ts_diff = ts - i->last_rd_ts; if (i->last_ts_diff == i->ts_diff) { if (++i->same_ts == 5) { i->samples_per_packet = i->ts_diff; if (i->max_drift && i->samples_per_packet) { i->drift_max_dropped = (i->samples_per_second * 2) / i->samples_per_packet; } } } else { i->same_ts = 0; } i->last_ts_diff = i->ts_diff; if (!i->samples_per_packet) { i->last_rd_ts = ts; return STFU_IT_FAILED; } } if (timer_ts) { if (ts && !i->ts_offset) { i->ts_offset = timer_ts - ts; } i->ts_drift = ts + (i->ts_offset - timer_ts); if (i->ts_offset && i->ts_drift > 0) { i->ts_offset = timer_ts - ts; i->ts_drift = ts + (i->ts_offset - timer_ts); } if (i->max_drift) { if (i->ts_drift < i->max_drift) { if (++i->drift_dropped_packets < i->drift_max_dropped) { stfu_log(STFU_LOG_EMERG, "%s TOO LATE !!! %u \n\n\n", i->name, ts); return STFU_ITS_TOO_LATE; } } else { i->drift_dropped_packets = 0; } } } if (i->sync_in) { good_ts = 1; i->sync_in = 0; } else { if ((ts && ts == i->last_rd_ts + i->samples_per_packet) || (i->last_rd_ts > 4294900000 && ts < 5000)) { good_ts = 1; } if (i->last_wr_ts) { if ((ts <= i->last_wr_ts && (i->last_wr_ts != UINT_MAX || ts == i->last_wr_ts))) { if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s TOO LATE !!! %u \n\n\n", i->name, ts); } if (i->in_queue->array_len < i->in_queue->array_size) { i->in_queue->array_len++; } return STFU_ITS_TOO_LATE; } } } if (good_ts) { i->period_clean_count++; i->session_clean_count++; } i->period_packet_in_count++; i->session_packet_in_count++; i->period_need_range_avg = i->period_need_range / least1(i->period_missing_count); if (i->period_missing_count > i->qlen * 2) { if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s resize %u %u\n", i->name, i->qlen, i->qlen + 1); } stfu_n_resize(i, i->qlen + 1); stfu_n_reset_counters(i); } else { if (i->qlen > i->orig_qlen && (i->consecutive_good_count > i->decrement_time || i->period_clean_count > i->decrement_time)) { stfu_n_resize(i, i->qlen - 1); stfu_n_reset_counters(i); stfu_n_sync(i, i->qlen); } } i->diff = 0; if (i->last_wr_ts) { if (ts < 1000 && i->last_wr_ts > (UINT_MAX - 1000)) { i->diff = abs(((UINT_MAX - i->last_wr_ts) + ts) / i->samples_per_packet); } else if (ts) { i->diff = abs(i->last_wr_ts - ts) / i->samples_per_packet; } } i->diff_total += i->diff; if ((i->period_packet_in_count > i->period_time)) { //uint32_t avg; //avg = i->diff_total / least1(i->period_packet_in_count); i->period_packet_in_count = 0; if (i->period_missing_count == 0 && i->qlen > i->orig_qlen) { stfu_n_resize(i, i->qlen - 1); stfu_n_sync(i, i->qlen); } stfu_n_reset_counters(i); } if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "I: %s %u/%u i=%u/%u - g:%u/%u c:%u/%u b:%u - %u:%u - %u %d %u %u %d %d %d/%d\n", i->name, i->qlen, i->max_qlen, i->period_packet_in_count, i->period_time, i->consecutive_good_count, i->decrement_time, i->period_clean_count, i->decrement_time, i->consecutive_bad_count, ts, ts / i->samples_per_packet, i->period_missing_count, i->period_need_range_avg, i->last_wr_ts, ts, i->diff, i->diff_total / least1(i->period_packet_in_count), i->ts_drift, i->max_drift); } if (last || i->in_queue->array_len == i->in_queue->array_size) { stfu_n_swap(i); } if (last) { return STFU_IM_DONE; } index = i->in_queue->array_len++; assert(index < i->in_queue->array_size); frame = &i->in_queue->array[index]; if (i->in_queue->array_len == i->in_queue->array_size) { stfu_n_swap(i); } if ((cplen = datalen) > sizeof(frame->data)) { cplen = sizeof(frame->data); } i->last_rd_ts = ts; i->packet_count++; memcpy(frame->data, data, cplen); frame->pt = pt; frame->ts = ts; frame->dlen = cplen; frame->was_read = 0; return STFU_IT_WORKED; } static int stfu_n_find_any_frame(stfu_instance_t *in, stfu_queue_t *queue, stfu_frame_t **r_frame) { uint32_t i = 0; stfu_frame_t *frame = NULL; assert(r_frame); *r_frame = NULL; for(i = 0; i < queue->real_array_size; i++) { frame = &queue->array[i]; if (!frame->was_read) { *r_frame = frame; queue->last_index = i; frame->was_read = 1; in->period_packet_out_count++; in->session_packet_out_count++; return 1; } } return 0; } static int stfu_n_find_frame(stfu_instance_t *in, stfu_queue_t *queue, uint32_t ts, stfu_frame_t **r_frame) { uint32_t i = 0; stfu_frame_t *frame = NULL; if (r_frame) { *r_frame = NULL; } for(i = 0; i < queue->array_size; i++) { frame = &queue->array[i]; if (frame->ts == ts) { if (r_frame) { *r_frame = frame; queue->last_index = i; frame->was_read = 1; in->period_packet_out_count++; in->session_packet_out_count++; } return 1; } } return 0; } stfu_frame_t *stfu_n_read_a_frame(stfu_instance_t *i) { stfu_frame_t *rframe = NULL; int found = 0; if (!i->samples_per_packet) { return NULL; } if (!i->ready) { if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s JITTERBUFFER NOT READY: IGNORING FRAME\n", i->name); } return NULL; } if (i->cur_ts == 0 && i->last_wr_ts < 1000) { uint32_t x = 0; for (x = 0; x < i->out_queue->array_len; x++) { if (!i->out_queue->array[x].was_read) { i->cur_ts = i->out_queue->array[x].ts; break; } if (i->cur_ts == 0) { if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s JITTERBUFFER ERROR: PUNTING\n", i->name); return NULL; } } } } else { i->cur_ts = i->cur_ts + i->samples_per_packet; } found = stfu_n_find_frame(i, i->out_queue, i->cur_ts, &rframe); if (found) { if (i->out_queue->array_len) { i->out_queue->array_len--; } } else { found = stfu_n_find_frame(i, i->in_queue, i->cur_ts, &rframe); if (!found) { found = stfu_n_find_frame(i, i->old_queue, i->cur_ts, &rframe); } } if (i->sync_out) { if (!found) { if ((found = stfu_n_find_any_frame(i, i->out_queue, &rframe))) { i->cur_ts = rframe->ts; } if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s SYNC %u %u:%u\n", i->name, i->sync_out, i->cur_ts, i->cur_ts / i->samples_per_packet); } } i->sync_out = 0; } if (!i->cur_ts) { if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s NO TS\n", i->name); } return NULL; } if (!found && i->samples_per_packet) { uint32_t y; stfu_frame_t *frame = NULL; int32_t delay = i->last_rd_ts - i->cur_ts; uint32_t need = abs(i->last_rd_ts - i->cur_ts) / i->samples_per_packet; i->period_missing_count++; i->session_missing_count++; i->period_need_range += need; if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s MISSING %u:%u %u %u %d %u %d\n", i->name, i->cur_ts, i->cur_ts / i->samples_per_packet, i->packet_count, i->last_rd_ts, delay, i->qlen, need); } if (i->packet_count > i->orig_qlen * 100 && delay > 0 && need > i->qlen && need < (i->qlen + 5)) { i->packet_count = 0; } if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s ", i->name); for(y = 0; y < i->out_queue->array_size; y++) { if ((y % 5) == 0) stfu_log(STFU_LOG_EMERG, "\n%s ", i->name); frame = &i->out_queue->array[y]; stfu_log(STFU_LOG_EMERG, "%u:%u\t", frame->ts, frame->ts / i->samples_per_packet); } stfu_log(STFU_LOG_EMERG, "\n%s ", i->name); for(y = 0; y < i->in_queue->array_size; y++) { if ((y % 5) == 0) stfu_log(STFU_LOG_EMERG, "\n%s ", i->name); frame = &i->in_queue->array[y]; stfu_log(STFU_LOG_EMERG, "%u:%u\t", frame->ts, frame->ts / i->samples_per_packet); } stfu_log(STFU_LOG_EMERG, "\n%s\n\n\n", i->name); } if (delay < 0) { stfu_n_reset(i); return NULL; } } if (stfu_log != null_logger && i->debug) { if (found) { stfu_log(STFU_LOG_EMERG, "%s O: %u:%u %u\n", i->name, rframe->ts, rframe->ts / i->samples_per_packet, rframe->plc); } } if (found) { i->consecutive_good_count++; i->period_good_count++; i->consecutive_bad_count = 0; } else { i->consecutive_bad_count++; i->period_bad_count++; i->consecutive_good_count = 0; } if (found) { i->last_frame = rframe; i->out_queue->wr_len++; i->last_wr_ts = rframe->ts; i->miss_count = 0; if (rframe->dlen) { i->plc_len = rframe->dlen; } i->plc_pt = rframe->pt; } else { i->last_wr_ts = i->cur_ts; rframe = &i->out_queue->int_frame; rframe->dlen = i->plc_len; rframe->pt = i->plc_pt; rframe->ts = i->cur_ts; i->miss_count++; if (stfu_log != null_logger && i->debug) { stfu_log(STFU_LOG_EMERG, "%s PLC %d %d %ld %u:%u\n", i->name, i->miss_count, rframe->plc, rframe->dlen, rframe->ts, rframe->ts / i->samples_per_packet); } if (i->miss_count > i->max_plc) { stfu_n_reset(i); rframe = NULL; } } return rframe; } #ifdef WIN32 #ifndef vsnprintf #define vsnprintf _vsnprintf #endif #endif int vasprintf(char **ret, const char *format, va_list ap); int stfu_vasprintf(char **ret, const char *fmt, va_list ap) { #if !defined(WIN32) && !defined(__sun) return vasprintf(ret, fmt, ap); #else char *buf; int len; size_t buflen; va_list ap2; char *tmp = NULL; #ifdef _MSC_VER #if _MSC_VER >= 1500 /* hack for incorrect assumption in msvc header files for code analysis */ __analysis_assume(tmp); #endif ap2 = ap; #else va_copy(ap2, ap); #endif len = vsnprintf(tmp, 0, fmt, ap2); if (len > 0 && (buf = malloc((buflen = (size_t) (len + 1)))) != NULL) { len = vsnprintf(buf, buflen, fmt, ap); *ret = buf; } else { *ret = NULL; len = -1; } va_end(ap2); return len; #endif } int stfu_snprintf(char *buffer, size_t count, const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); ret = vsnprintf(buffer, count-1, fmt, ap); if (ret < 0) buffer[count-1] = '\0'; va_end(ap); return ret; } static void null_logger(const char *file, const char *func, int line, int level, const char *fmt, ...) { if (file && func && line && level && fmt) { return; } return; } static const char *LEVEL_NAMES[] = { "EMERG", "ALERT", "CRIT", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG", NULL }; static const char *cut_path(const char *in) { const char *p, *ret = in; char delims[] = "/\\"; char *i; for (i = delims; *i; i++) { p = in; while ((p = strchr(p, *i)) != 0) { ret = ++p; } } return ret; } static void default_logger(const char *file, const char *func, int line, int level, const char *fmt, ...) { const char *fp; char *data; va_list ap; int ret; if (level < 0 || level > 7) { level = 7; } if (level > stfu_log_level) { return; } fp = cut_path(file); va_start(ap, fmt); ret = stfu_vasprintf(&data, fmt, ap); if (ret != -1) { fprintf(stderr, "[%s] %s:%d %s() %s", LEVEL_NAMES[level], fp, line, func, data); free(data); } va_end(ap); } /* For Emacs: * Local Variables: * mode:c * indent-tabs-mode:nil * tab-width:4 * c-basic-offset:4 * End: * For VIM: * vim:set softtabstop=4 shiftwidth=4 tabstop=4: */