From 70600fa715f586f2a2f1780984f0eed7b347792f Mon Sep 17 00:00:00 2001 From: Christian Spielberger Date: Wed, 4 Oct 2023 08:49:16 +0200 Subject: [PATCH] jbuf: move jbuf to baresip (#971) * jbuf: move jbuf to baresip * docs: remove obsolete jbuf doc --- CMakeLists.txt | 9 - docs/jbuf/README.md | 188 ----------- docs/jbuf/jbuf.plot | 79 ----- include/re.h | 1 - include/re_jbuf.h | 40 --- src/jbuf/jbuf.c | 802 -------------------------------------------- test/CMakeLists.txt | 1 - test/jbuf.c | 400 ---------------------- test/test.c | 3 - test/test.h | 3 - 10 files changed, 1526 deletions(-) delete mode 100644 docs/jbuf/README.md delete mode 100755 docs/jbuf/jbuf.plot delete mode 100644 include/re_jbuf.h delete mode 100644 src/jbuf/jbuf.c delete mode 100644 test/jbuf.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8229cbfaf..6f10699dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,6 @@ include(CheckCCompilerFlag) option(USE_REM "Enable Librem" ON) option(USE_BFCP "Enable BFCP" ON) -option(USE_JBUF "Enable JBUF" ON) option(USE_PCP "Enable PCP" ON) option(USE_RTMP "Enable RTMP" ON) option(USE_SIP "Enable SIP" ON) @@ -142,7 +141,6 @@ set(HEADERS include/re_http.h include/re_httpauth.h include/re_ice.h - include/re_jbuf.h include/re_json.h include/re_list.h include/re_main.h @@ -446,13 +444,6 @@ if(USE_BFCP) endif() -if(USE_JBUF) - list(APPEND SRCS - src/jbuf/jbuf.c - ) -endif() - - if(USE_PCP) list(APPEND SRCS src/pcp/msg.c diff --git a/docs/jbuf/README.md b/docs/jbuf/README.md deleted file mode 100644 index 7413c2eac..000000000 --- a/docs/jbuf/README.md +++ /dev/null @@ -1,188 +0,0 @@ -This adaptive jitter buffer implementation increases the number of packets in -the buffer during periods of high network jitter. It reduces the number of -packets if the network condition improves. - - -## Computing the jitter - -The network jitter is computed similar to the proposition in RFC-3550 RTP -section A.8 and similar how wireshark does it. The jitter is a moving average -of the difference *d* between the real time and the RTP timestamps. Or more -concretely we compute at each `jbuf_put()` the difference - -*d = Dtr - Dts*, - -where *Dtr* is the real time elapsed from last call to -`jbuf_put` and *Dts* is the difference of the timestamps. Then -with a predefined speed *s* we compute the jitter *j* as moving average - -*j = j + s (|d| - j)*. - -We choose a higher value for speed *s* if *|d| > j*. Thus the jitter rises fast -if e.g. suddenly a network jitter appears. In contrast when the network -condition improves the jitter value slowly shrinks. The reason for different -rising and falling speed is that we have to react fast to avoid buffer -under-runs, whereas reducing of the latency may be done a while after the -network condition improved. - -In the following sections we will describe how the computed jitter is used to -detect situations where the buffered packets should be increased due to a high -jitter. We call this situations **Low** situations. When the jitter shrinks -below some specific value it is a good idea to reduce the buffer to reduce the -audio latency. We call this situations **High** situations. Surely, the -Low/High situations have to be decided somehow. - -## Reduce/Increase buffered packets - -When a Low situation is detected we increase the number of packets in jbuf by -holding back a packet during one call to function `jbuf_get()`. While when -a High situation is detected we reduce the number of packets by telling baresip -to immediately call `jbuf_get()` a second time. For this purpose `jbuf_get()` -returns EAGAIN. Then baresip decodes two RTP packets and thus the audio buffer -increases by one frame. By means of a silence detection baresip is able to drop -frames that are not important for the speech quality. This reduces the audio -latency down to the value before the High situation. - - -## Computing a smooth latency - -The RTP packets that are buffered at a concrete point in time in the jbuf lead -to a temporary latency value lc. Let *p0, ..., -pm* be the RTP packets currently stored in the jbuf. Then - -*lc = tm - t0 + tp*, - -where *ti* is the RTP timestamp of packet *pi* and -*tp* is the packet time (`ptime` in jbuf.c). The packet time is a -constant that is specified at the beginning of a call. In baresip it is -specified in the account file. - -The temporary latency *lc* is discontinuous over time and not -adequate for deciding or detecting Low or High situations. Therefore we again -use a moving average to smooth *lc*. Let *s* be an adequate moving -average speed, then the smoothed latency - -*l = l + s (lc - l)*. - -Low/High situations are decided when the smoothed latency *l* runs out of some -boundaries that are computed from the jitter. - -## Deciding Low/High situations - -During each iteration (each `jbuf_put()`) the jitter and the latency are -computed. Additionally we compute the bottom boundary *lb* and the -top boundary *lt* with - -*lb = 1.25 j* and - -*lt = 2.2 j*. - -Since we want to respect also the parameter `min` of function `jbuf_alloc()` we -extend these formulas. Let *mm* be the parameter `min`. Then - -*lb = max(mm 2 tp / 3, 1.25 j)* and - -*lt = max(mm 11 tp / 3, 2.2 j)*, - -where *tp* is the `ptime` as defined already. Finally we have -everything for deciding Low and High situations. That is if *l* moves out of -the boundaries - -*lb < l < lt* - -then we fire a Low/High. - -## Early adjustment of the latency - -Finally, if we detect a Low/High situation we increase/reduce the number of -packets. Now we immediately increment/decrement the smoothed latency *l* by -one packet time. So early adjustment for a Low situation is - -*l = l + lp* and for a High situation -*l = l - lp*. - - -This avoids multiple Low/High detections in a row. - - -## Silence detection - -It is preferable to hold back an RTP packet in `jbuf_get()` only during a -period of silence. But deciding if there is currently silence in the RTP stream -can only be done by investigating the audio frames after decoding the RTP -packet. Decoding is done in baresip. Thus we add a function `jbuf_silence()` -that sets the flag `silence`. This function has to be called by baresip. - - -## Math symbols vs. C-variables - -In order to avoid float computation we use a constant factor -```JBUF_JITTER_PERIOD``` for all time based variables in jbuf.c. Apart from -that the symbols used here are mapped to the C-variables like this table shows: - - -Symbol|Variable -------|-------- -*d* | `d` -*j* | `jitter` -*l* | `avbuftime` -*lc* | `buftime` -*lb* | `bufmin` -*lt* | `bufmax` -*tp* | `ptime` - - -## Wish size - -We introduce also the parameter `wish` and a setter function `jbuf_set_wish()`. -The wish size is the number of packets that will be collected at the beginning -of an RTP stream before `jbuf_get()` will return the first packet. If the user -passes `wish=0` then it is set internally to `min`. If the user knows that the -network contains jitter he may set the wish size to some adequate value. This -avoids underflows at the beginning of the stream. The `min` parameter can be -left at some lower value. When the network situation improves, the buffer is -reduced down to `min`. Thus the latency is reduced to the specified minimum. - - -## How to test jbuf - -- In jbuf.c set DEBUG\_LEVEL to 6, build and install libre again! - -- Add bridge interface linked to your Ethernet/WiFi interface! Suppose baresip -is connected to the network interface *eth0*. Replace *eth0* with your physical -network interface! See the man pages of "ip" and "tc" for further details! - -``` -sudo ip link add ifb1 type ifb || : -sudo ip link set ifb1 up -sudo tc qdisc add dev eth0 handle ffff: ingress -sudo tc filter add dev eth0 parent ffff: u32 match u32 0 0 action mirred egress redirect dev ifb1 -``` - -This redirects the incoming eth0 traffic to a new ifb interface. - -- How to activate the jitter. Here we set the delay to 100ms ± 50ms. -``` -sudo tc qdisc add dev ifb1 root netem delay 100ms 50ms -``` - -- How to deactivate the jitter. -``` -sudo tc qdisc del dev ifb1 root -``` - -- See/Use jbuf.plot to generate a plot! - -Note: -- Activate and deactivate the network jitter during a call to see how the -adaptive jitter buffer algorithm works! -- You need a very new kernel or at least some patches for the sch_netem kernel -module. There was a problem which lead to many reordered and very late RTP -packets. Be sure that you have this commit in the kernel: -``` -commit eadd1befdd778a1eca57fad058782bd22b4db804 -Author: Aleksandr Nogikh -Date: Wed Oct 28 17:07:31 2020 +0000 - - netem: fix zero division in tabledist -``` diff --git a/docs/jbuf/jbuf.plot b/docs/jbuf/jbuf.plot deleted file mode 100755 index 5d681456d..000000000 --- a/docs/jbuf/jbuf.plot +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/gnuplot -# -# How to generate a plot -# ====================== -# This gnuplot script plots DEBUG_LEVEL 6 output of jbuf.c. You have to -# increment the DEBUG_LEVEL in jbuf.c if you want to get the table for -# jbuf.dat. Then call baresip like this: -# -# ./baresip 2>&1 | grep -Eo "plot_stat.*" jbuf.log > jbuf.dat -# -# Call this script. Then compare the plot legend with the variables in jbuf.c! -# -# -# Description of the plot -# ======================= -# The plot is a time based diagram. The values avbuftime should lie between -# bufmin and bufmax. If it runs somewhere out of these boundaries (and stays -# outside for a while) a "Low" / "High" situation is detected. -# -# "Good" means: The number of packets in the jitter buffer is ok. -# -# "Low" means: The number is too low. Then the packets are incremented by -# holding one packet back in jbuf_get(). -# -# "High" means: The number is to high. Then packets are decremented by dropping -# one packet in jbuf_put(). This reduces the audio delay. -# -# The number of "Low"/"High" situations should be low while buffer under-runs -# should be avoided completely. - -# On the x-axes of the plot there is the time in milliseconds. See function -# jbuf_jitter_calc()! We note the variables in jbuf.c here in parentheses. -# E.g. (var jitter). -# -# - The orange line is the computed network jitter (var jitter). This is a -# moving average of the difference (var d) between the real time diff -# (var tr - var tr0) and the RTP timestamps diff (var ts - var ts0). -# See RFC-3550 RTP - A.8! -# We suggest a fast rise of the moving average and a slow shrink. Thus -# avoiding buffer under-runs have a higher priority than reducing the audio -# delay. -# -# - The buftime (var buftime) is the difference of the timestamps between the -# last RTP packet and the first RTP packet stored in the jbuf plus one packet -# time (var ptime) for the last packet. -# The buftime (light-grey) changes very fast during periods of jitter. To be -# applicable for detecting "Low" or "High" situations it has to be smoothed. -# The blue line avbuftime (var avbuftime) is a moving average of the buftime -# and is used to detect "Low"/"High". Thus the jbuf algorithm tries to keep -# the avbuftime between the following boundaries. -# -# - The green lines bufmin and bufmax (var bufmin, bufmax) are boundaries for -# avbuftime.They are computed by constant factors (> 1.) from the jitter. -# -# -# Copyright (C) 2020 commend.com - Christian Spielberger, Michael Peitler - - -# Choose your preferred gnuplot terminal or use e.g. evince to view the -# jbuf.eps! - -#set terminal x11 -set terminal postscript eps size 15,10 enhanced color -set output 'jbuf.eps' -#set terminal png size 1280,480 -#set output 'jbuf.png' -set datafile separator "," -set key outside -plot \ -'jbuf.dat' using 2:4 title 'jitter' with linespoints linecolor "orange", \ -'jbuf.dat' using 2:6 title 'avbuftime' with linespoints linecolor "skyblue", \ -'jbuf.dat' using 2:7 title 'bufmin' with linespoints linecolor "sea-green", \ -'jbuf.dat' using 2:8 title 'bufmax' with linespoints linecolor "sea-green", \ -'jbuf.dat' using 2:($9*10) title 'Good/Empty/Low/High' linecolor "red", \ -'jbuf.dat' using 2:5 title 'buftime' linecolor "light-grey", \ -10 title "Empty=10" linecolor "red", \ -20 title "Low=20" linecolor "red", \ -30 title "High=30" linecolor "red" - diff --git a/include/re.h b/include/re.h index 83e352b31..2167f9bba 100644 --- a/include/re.h +++ b/include/re.h @@ -34,7 +34,6 @@ extern "C" { #include "re_http.h" #include "re_httpauth.h" #include "re_ice.h" -#include "re_jbuf.h" #include "re_net.h" #include "re_main.h" #include "re_md5.h" diff --git a/include/re_jbuf.h b/include/re_jbuf.h deleted file mode 100644 index f9698b485..000000000 --- a/include/re_jbuf.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @file re_jbuf.h Interface to Jitter Buffer - * - * Copyright (C) 2010 Creytiv.com - */ -struct jbuf; -struct rtp_header; - -/** Jitter buffer statistics */ -struct jbuf_stat { - uint32_t n_put; /**< Number of frames put into jitter buffer */ - uint32_t n_get; /**< Number of frames got from jitter buffer */ - uint32_t n_oos; /**< Number of out-of-sequence frames */ - uint32_t n_dups; /**< Number of duplicate frames detected */ - uint32_t n_late; /**< Number of frames arriving too late */ - uint32_t n_lost; /**< Number of lost frames */ - uint32_t n_overflow; /**< Number of overflows */ - uint32_t n_underflow; /**< Number of underflows */ - uint32_t n_flush; /**< Number of times jitter buffer flushed */ -}; - - -/** Jitter buffer type */ -enum jbuf_type { - JBUF_OFF, - JBUF_FIXED, - JBUF_ADAPTIVE -}; - - -int jbuf_alloc(struct jbuf **jbp, uint32_t min, uint32_t max); -int jbuf_set_type(struct jbuf *jb, enum jbuf_type jbtype); -int jbuf_put(struct jbuf *jb, const struct rtp_header *hdr, void *mem); -int jbuf_get(struct jbuf *jb, struct rtp_header *hdr, void **mem); -int jbuf_drain(struct jbuf *jb, struct rtp_header *hdr, void **mem); -void jbuf_flush(struct jbuf *jb); -int jbuf_stats(const struct jbuf *jb, struct jbuf_stat *jstat); -int jbuf_debug(struct re_printf *pf, const struct jbuf *jb); -uint32_t jbuf_frames(const struct jbuf *jb); -uint32_t jbuf_packets(const struct jbuf *jb); diff --git a/src/jbuf/jbuf.c b/src/jbuf/jbuf.c deleted file mode 100644 index d2a32dc56..000000000 --- a/src/jbuf/jbuf.c +++ /dev/null @@ -1,802 +0,0 @@ -/** - * @file jbuf.c Jitter Buffer implementation - * - * This is an adaptive jitter buffer implementation. See doc/jbuf for further - * details! - * - * Copyright (C) 2010 Creytiv.com - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define DEBUG_MODULE "jbuf" -#define DEBUG_LEVEL 5 -#include - - -#ifndef RELEASE -#define JBUF_STAT 1 /**< Jitter buffer statistics */ -#endif - - -#if JBUF_STAT -#define STAT_ADD(var, value) (jb->stat.var) += (value) /**< Stats add */ -#define STAT_INC(var) ++(jb->stat.var) /**< Stats inc */ -#else -#define STAT_ADD(var, value) -#define STAT_INC(var) -#endif - -enum { - JBUF_RDIFF_EMA_COEFF = 1024, - JBUF_RDIFF_UP_SPEED = 512, - JBUF_PUT_TIMEOUT = 400, -}; - - -/** Defines a packet frame */ -struct packet { - struct le le; /**< Linked list element */ - struct rtp_header hdr; /**< RTP Header */ - void *mem; /**< Reference counted pointer */ -}; - - -/** - * Defines a jitter buffer - * - * The jitter buffer is for incoming RTP packets, which are sorted by - * sequence number. - */ -struct jbuf { - struct list pooll; /**< List of free packets in pool */ - struct list packetl; /**< List of buffered packets */ - uint32_t n; /**< [# packets] Current # of packets in buffer */ - uint32_t nf; /**< [# frames] Current # of frames in buffer */ - uint32_t min; /**< [# frames] Minimum # of frames to buffer */ - uint32_t max; /**< [# frames] Maximum # of frames to buffer */ - uint32_t wish; /**< [# frames] Wish size for adaptive mode */ - uint16_t seq_put; /**< Sequence number for last jbuf_put() */ - uint16_t seq_get; /**< Sequence number of last played frame */ - uint32_t ssrc; /**< Previous ssrc */ - uint64_t tr; /**< Time of previous jbuf_put() */ - int pt; /**< Payload type */ - bool running; /**< Jitter buffer is running */ - int32_t rdiff; /**< Average out of order reverse diff */ - struct tmr tmr; /**< Rdiff down timer */ - - mtx_t *lock; /**< Makes jitter buffer thread safe */ - enum jbuf_type jbtype; /**< Jitter buffer type */ -#if JBUF_STAT - struct jbuf_stat stat; /**< Jitter buffer Statistics */ -#endif -#ifdef RE_JBUF_TRACE - uint64_t tr00; /**< Arrival of first packet */ - char buf[136]; /**< Buffer for trace */ -#endif -}; - - -/** Is x less than y? */ -static inline bool seq_less(uint16_t x, uint16_t y) -{ - return ((int16_t)(x - y)) < 0; -} - - -#ifdef RE_JBUF_TRACE -static void plot_jbuf(struct jbuf *jb, uint64_t tr) -{ - uint32_t treal; - uint32_t rdiff = (uint32_t)(jb->rdiff / (float)JBUF_RDIFF_EMA_COEFF); - - if (!jb->tr00) - jb->tr00 = tr; - - treal = (uint32_t) (tr - jb->tr00); - re_snprintf(jb->buf, sizeof(jb->buf), - "%s, 0x%p, %u, %u, %u, %u, %u", - __func__, /* row 1 - grep */ - jb, /* row 2 - grep optional */ - treal, /* row 3 - plot x-axis */ - rdiff, /* row 4 - plot */ - jb->wish, /* row 5 - plot */ - jb->n, /* row 6 - plot */ - jb->nf); /* row 7 - plot */ - re_trace_event("jbuf", "plot", 'P', NULL, 0, RE_TRACE_ARG_STRING_COPY, - "line", jb->buf); -} - - -static void plot_jbuf_event(struct jbuf *jb, char ph) -{ - uint32_t treal; - uint64_t tr; - - tr = tmr_jiffies(); - if (!jb->tr00) - jb->tr00 = tr; - - treal = (uint32_t) (tr - jb->tr00); - re_snprintf(jb->buf, sizeof(jb->buf), "%s, 0x%p, %u, %i", - __func__, /* row 1 - grep */ - jb, /* row 2 - grep optional */ - treal, /* row 3 - plot x-axis */ - 1); /* row 4 - plot */ - re_trace_event("jbuf", "plot", ph, NULL, 0, RE_TRACE_ARG_STRING_COPY, - "line", jb->buf); -} -#else -static void plot_jbuf_event(struct jbuf *jb, char ph) -{ - (void)jb; - (void)ph; -} -#endif - - -/** - * Get a frame from the pool - */ -static void packet_alloc(struct jbuf *jb, struct packet **f) -{ - struct le *le; - - le = jb->pooll.head; - if (le) { - list_unlink(le); - ++jb->n; - } - else { - struct packet *f0; - - /* Steal an old frame */ - le = jb->packetl.head; - f0 = le->data; - -#if JBUF_STAT - STAT_INC(n_overflow); - DEBUG_WARNING("drop 1 old frame seq=%u (total dropped %u)\n", - f0->hdr.seq, jb->stat.n_overflow); -#else - DEBUG_WARNING("drop 1 old frame seq=%u\n", f0->hdr.seq); -#endif - - plot_jbuf_event(jb, 'O'); - f0->mem = mem_deref(f0->mem); - list_unlink(le); - } - - *f = le->data; -} - - -/** - * Release a packet, put it back in the pool - */ -static void packet_deref(struct jbuf *jb, struct packet *f) -{ - f->mem = mem_deref(f->mem); - list_unlink(&f->le); - list_append(&jb->pooll, &f->le, f); - --jb->n; -} - - -static void jbuf_destructor(void *data) -{ - struct jbuf *jb = data; - - tmr_cancel(&jb->tmr); - jbuf_flush(jb); - - /* Free all packets in the pool list */ - list_flush(&jb->pooll); - mem_deref(jb->lock); -} - - -/** - * Allocate a new jitter buffer - * - * @param jbp Pointer to returned jitter buffer - * @param min Minimum delay in [frames] - * @param max Maximum delay in [packets] - * - * @return 0 if success, otherwise errorcode - */ -int jbuf_alloc(struct jbuf **jbp, uint32_t min, uint32_t max) -{ - struct jbuf *jb; - uint32_t i; - int err = 0; - - if (!jbp || ( min > max)) - return EINVAL; - - /* self-test: x < y (also handle wrap around) */ - if (!seq_less(10, 20) || seq_less(20, 10) || !seq_less(65535, 0)) { - DEBUG_WARNING("seq_less() is broken\n"); - return ENOSYS; - } - - jb = mem_zalloc(sizeof(*jb), NULL); - if (!jb) - return ENOMEM; - - list_init(&jb->pooll); - list_init(&jb->packetl); - - jb->jbtype = JBUF_FIXED; - jb->min = min; - jb->max = max; - jb->wish = min; - tmr_init(&jb->tmr); - - DEBUG_INFO("alloc: delay=%u-%u frames/packets\n", min, max); - - jb->pt = -1; - err = mutex_alloc(&jb->lock); - if (err) - goto out; - - mem_destructor(jb, jbuf_destructor); - - /* Allocate all packets now */ - for (i=0; imax; i++) { - struct packet *f = mem_zalloc(sizeof(*f), NULL); - if (!f) { - err = ENOMEM; - break; - } - - list_append(&jb->pooll, &f->le, f); - DEBUG_INFO("alloc: adding to pool list %u\n", i); - } - -out: - if (err) - mem_deref(jb); - else - *jbp = jb; - - return err; -} - - -/** - * Set jitter buffer type. - * - * @param jb The jitter buffer. - * @param jbtype The jitter buffer type. - * - * @return 0 if success, otherwise errorcode - */ -int jbuf_set_type(struct jbuf *jb, enum jbuf_type jbtype) -{ - if (!jb) - return EINVAL; - - jb->jbtype = jbtype; - - return 0; -} - - -static void wish_down(void *arg) -{ - struct jbuf *jb = arg; - - if (jb->wish > jb->min) { - DEBUG_INFO("wish size changed %u --> %u\n", jb->wish, - jb->wish - 1); - --jb->wish; - } -} - - -static void calc_rdiff(struct jbuf *jb, uint16_t seq) -{ - int32_t rdiff; - int32_t adiff; - int32_t s; /**< EMA coefficient */ - float ratio = 1.0; /**< Frame packet ratio */ - uint32_t wish; - uint32_t max = jb->max; - bool down = false; - - if (jb->jbtype != JBUF_ADAPTIVE) - return; - - if (!jb->seq_get) - return; - - if (jb->nf) { - ratio = (float)jb->n / (float)jb->nf; - max = (uint32_t)(max / ratio); - } - - rdiff = (int16_t)(jb->seq_put + 1 - seq); - adiff = abs(rdiff * JBUF_RDIFF_EMA_COEFF); - s = adiff > jb->rdiff ? JBUF_RDIFF_UP_SPEED : - jb->wish > 2 ? 1 : - jb->wish > 1 ? 2 : 3; - jb->rdiff += (adiff - jb->rdiff) * s / JBUF_RDIFF_EMA_COEFF; - - wish = (uint32_t)(jb->rdiff / (float)JBUF_RDIFF_EMA_COEFF / ratio); - if (wish < jb->min) - wish = jb->min; - - if (max && wish >= max) - wish = max - 1; - - if (wish > jb->wish) { - DEBUG_INFO("wish size changed %u --> %u\n", jb->wish, wish); - jb->wish = wish; - } - else if (wish < jb->wish) { - uint32_t dt = wish + 1 == jb->wish ? 6000 : 1000; - if (!tmr_isrunning(&jb->tmr) || tmr_get_expire(&jb->tmr) > dt) - tmr_start(&jb->tmr, dt, wish_down, jb); - - down = true; - } - - if (!down && tmr_isrunning(&jb->tmr)) - tmr_cancel(&jb->tmr); -} - - -/** - * Put one packet into the jitter buffer - * - * @param jb Jitter buffer - * @param hdr RTP Header - * @param mem Memory pointer - will be referenced - * - * @return 0 if success, otherwise errorcode - */ -int jbuf_put(struct jbuf *jb, const struct rtp_header *hdr, void *mem) -{ - struct packet *f; - struct packet *fc; - struct le *le, *tail; - uint16_t seq; - uint64_t tr, dt; - bool equal; - int err = 0; - - if (!jb || !hdr) - return EINVAL; - - seq = hdr->seq; - if (jb->pt == -1) - jb->pt = hdr->pt; - - if (jb->ssrc && jb->ssrc != hdr->ssrc) { - DEBUG_INFO("ssrc changed %u %u\n", jb->ssrc, hdr->ssrc); - jbuf_flush(jb); - } - - tr = tmr_jiffies(); - dt = tr - jb->tr; - if (jb->tr && dt > JBUF_PUT_TIMEOUT) { - DEBUG_INFO("put timeout %lu ms, marker %d\n", dt, hdr->m); - if (hdr->m) - jbuf_flush(jb); - } - - jb->tr = tr; - - mtx_lock(jb->lock); - jb->ssrc = hdr->ssrc; - - if (jb->running) { - - if (jb->jbtype == JBUF_ADAPTIVE) - calc_rdiff(jb, seq); - - /* Packet arrived too late to be put into buffer */ - if (jb->seq_get && seq_less(seq, jb->seq_get + 1)) { - STAT_INC(n_late); - plot_jbuf_event(jb, 'L'); - DEBUG_INFO("packet too late: seq=%u " - "(seq_put=%u seq_get=%u)\n", - seq, jb->seq_put, jb->seq_get); - err = ETIMEDOUT; - goto out; - } - - } - - STAT_INC(n_put); - - packet_alloc(jb, &f); - - tail = jb->packetl.tail; - - /* If buffer is empty -> append to tail - Frame is later than tail -> append to tail - */ - if (!tail || seq_less(((struct packet *)tail->data)->hdr.seq, seq)) { - list_append(&jb->packetl, &f->le, f); - goto success; - } - - /* Out-of-sequence, find right position */ - for (le = tail; le; le = le->prev) { - const uint16_t seq_le = ((struct packet *)le->data)->hdr.seq; - - if (seq_less(seq_le, seq)) { /* most likely */ - DEBUG_PRINTF("put: out-of-sequence" - " - inserting after seq=%u (seq=%u)\n", - seq_le, seq); - list_insert_after(&jb->packetl, le, &f->le, f); - break; - } - else if (seq == seq_le) { /* less likely */ - /* Detect duplicates */ - DEBUG_INFO("duplicate: seq=%u\n", seq); - STAT_INC(n_dups); - plot_jbuf_event(jb, 'D'); - list_insert_after(&jb->packetl, le, &f->le, f); - packet_deref(jb, f); - err = EALREADY; - goto out; - } - - /* sequence number less than current seq, continue */ - } - - /* no earlier sequence found, put in head */ - if (!le) { - DEBUG_PRINTF("put: out-of-sequence" - " - put in head (seq=%u)\n", seq); - list_prepend(&jb->packetl, &f->le, f); - } - - STAT_INC(n_oos); - plot_jbuf_event(jb, 'S'); - -success: - /* Update last sequence */ - jb->running = true; - jb->seq_put = seq; - - /* Success */ - f->hdr = *hdr; - f->mem = mem_ref(mem); - - equal = false; - if (f->le.prev) { - fc = f->le.prev->data; - equal = (fc->hdr.ts == f->hdr.ts); - } - - if (!equal && f->le.next) { - fc = f->le.next->data; - equal = (fc->hdr.ts == f->hdr.ts); - } - - if (!equal) - ++jb->nf; - -out: -#ifdef RE_JBUF_TRACE - plot_jbuf(jb, tr); -#endif - mtx_unlock(jb->lock); - return err; -} - - -/** - * Get one packet from the jitter buffer - * - * @param jb Jitter buffer - * @param hdr Returned RTP Header - * @param mem Pointer to memory object storage - referenced on success - * - * @return 0 if success, EAGAIN if it should be called again in order to avoid - * a jitter buffer overflow, otherwise errorcode - */ -int jbuf_get(struct jbuf *jb, struct rtp_header *hdr, void **mem) -{ - struct packet *f; - int err = 0; - - if (!jb || !hdr || !mem) - return EINVAL; - - mtx_lock(jb->lock); - STAT_INC(n_get); - - if (jb->nf <= jb->wish || !jb->packetl.head) { - DEBUG_INFO("not enough buffer packets - wait.. " - "(n=%u wish=%u)\n", jb->n, jb->wish); - STAT_INC(n_underflow); - plot_jbuf_event(jb, 'U'); - err = ENOENT; - goto out; - } - - /* When we get one packet P[i], check that the next packet P[i+1] - is present and have a seq no. of seq[i] + 1. - If not, we should consider that packet lost. */ - - f = jb->packetl.head->data; - -#if JBUF_STAT - /* Check sequence of previously played packet */ - if (jb->seq_get) { - const int16_t seq_diff = f->hdr.seq - jb->seq_get; - if (seq_less(f->hdr.seq, jb->seq_get)) { - DEBUG_WARNING("get: seq=%u too late\n", f->hdr.seq); - } - else if (seq_diff > 1) { - STAT_ADD(n_lost, 1); - plot_jbuf_event(jb, 'T'); - DEBUG_INFO("get: n_lost: diff=%d,seq=%u,seq_get=%u\n", - seq_diff, f->hdr.seq, jb->seq_get); - } - } -#endif - - /* Update sequence number for 'get' */ - jb->seq_get = f->hdr.seq; - - *hdr = f->hdr; - *mem = mem_ref(f->mem); - - /* decrease not equal frames */ - if (f->le.next) { - struct packet *next_f = f->le.next->data; - - if (f->hdr.ts != next_f->hdr.ts) - --jb->nf; - } - else { - --jb->nf; - } - - packet_deref(jb, f); - - if (jb->nf > jb->wish) { - DEBUG_INFO("reducing jitter buffer " - "(nf=%u min=%u wish=%u max=%u)\n", - jb->nf, jb->min, jb->wish, jb->max); - err = EAGAIN; - } - -out: - mtx_unlock(jb->lock); - return err; -} - -/** - * Get one packet from the jitter buffer, even if it becomes depleted - * - * @param jb Jitter buffer - * @param hdr Returned RTP Header - * @param mem Pointer to memory object storage - referenced on success - * - * @return 0 if success, otherwise errorcode - */ -int jbuf_drain(struct jbuf *jb, struct rtp_header *hdr, void **mem) -{ - struct packet *f; - int err = 0; - - if (!jb || !hdr || !mem) - return EINVAL; - - mtx_lock(jb->lock); - - if (jb->n <= 0 || !jb->packetl.head) { - err = ENOENT; - goto out; - } - - /* When we get one packet P[i], check that the next packet P[i+1] - is present and have a seq no. of seq[i] + 1. - If not, we should consider that packet lost. */ - - f = jb->packetl.head->data; - - /* Update sequence number for 'get' */ - jb->seq_get = f->hdr.seq; - - *hdr = f->hdr; - *mem = mem_ref(f->mem); - - /* decrease not equal frames */ - if (f->le.next) { - struct packet *next_f = f->le.next->data; - - if (f->hdr.ts != next_f->hdr.ts) - --jb->nf; - } - else { - --jb->nf; - } - - packet_deref(jb, f); - -out: - mtx_unlock(jb->lock); - return err; -} - -/** - * Flush all frames in the jitter buffer - * - * @param jb Jitter buffer - */ -void jbuf_flush(struct jbuf *jb) -{ - struct le *le; -#if JBUF_STAT - uint32_t n_flush; -#endif - - if (!jb) - return; - - mtx_lock(jb->lock); - if (jb->packetl.head) { - DEBUG_INFO("flush: %u frames\n", jb->n); - } - - /* put all buffered frames back in free list */ - for (le = jb->packetl.head; le; le = jb->packetl.head) { - DEBUG_INFO(" flush frame: seq=%u\n", - ((struct packet *)(le->data))->hdr.seq); - - packet_deref(jb, le->data); - } - - jb->n = 0; - jb->nf = 0; - jb->running = false; - - jb->seq_get = 0; -#if JBUF_STAT - n_flush = STAT_INC(n_flush); - memset(&jb->stat, 0, sizeof(jb->stat)); - jb->stat.n_flush = n_flush; - plot_jbuf_event(jb, 'F'); -#endif - mtx_unlock(jb->lock); -} - - -/** - * Get number of current packets - * - * @param jb Jitter buffer - * - * @return number of packets - */ -uint32_t jbuf_packets(const struct jbuf *jb) -{ - if (!jb) - return 0; - - mtx_lock(jb->lock); - uint32_t n = jb->n; - mtx_unlock(jb->lock); - - return n; -} - - -/** - * Get number of current frames - * - * @param jb Jitter buffer - * - * @return number of frames - */ -uint32_t jbuf_frames(const struct jbuf *jb) -{ - if (!jb) - return 0; - - mtx_lock(jb->lock); - uint32_t n = jb->nf; - mtx_unlock(jb->lock); - - return n; -} - - -/** - * Get jitter buffer statistics - * - * @param jb Jitter buffer - * @param jstat Pointer to statistics storage - * - * @return 0 if success, otherwise errorcode - */ -int jbuf_stats(const struct jbuf *jb, struct jbuf_stat *jstat) -{ - if (!jb || !jstat) - return EINVAL; - -#if JBUF_STAT - mtx_lock(jb->lock); - *jstat = jb->stat; - mtx_unlock(jb->lock); - - return 0; -#else - return ENOSYS; -#endif -} - - -/** - * Debug the jitter buffer. This function is thread safe with short blocking - * - * @param pf Print handler - * @param jb Jitter buffer - * - * @return 0 if success, otherwise errorcode - */ -int jbuf_debug(struct re_printf *pf, const struct jbuf *jb) -{ - int err = 0; - struct mbuf *mb = mbuf_alloc(512); - - if (!jb) - return 0; - - err |= mbuf_printf(mb, "--- jitter buffer debug---\n"); - - mtx_lock(jb->lock); - err |= mbuf_printf(mb, " running=%d", jb->running); - err |= mbuf_printf(mb, " min=%u cur=%u/%u max=%u [frames/packets]\n", - jb->min, jb->nf, jb->n, jb->max); - err |= mbuf_printf(mb, " seq_put=%u\n", jb->seq_put); - -#if JBUF_STAT - err |= mbuf_printf(mb, " Stat: put=%u", jb->stat.n_put); - err |= mbuf_printf(mb, " get=%u", jb->stat.n_get); - err |= mbuf_printf(mb, " oos=%u", jb->stat.n_oos); - err |= mbuf_printf(mb, " dup=%u", jb->stat.n_dups); - err |= mbuf_printf(mb, " late=%u", jb->stat.n_late); - err |= mbuf_printf(mb, " or=%u", jb->stat.n_overflow); - err |= mbuf_printf(mb, " ur=%u", jb->stat.n_underflow); - err |= mbuf_printf(mb, " flush=%u", jb->stat.n_flush); - err |= mbuf_printf(mb, " put/get_ratio=%u%%", jb->stat.n_get ? - 100*jb->stat.n_put/jb->stat.n_get : 0); - err |= mbuf_printf(mb, " lost=%u (%u.%02u%%)\n", - jb->stat.n_lost, - jb->stat.n_put ? - 100*jb->stat.n_lost/jb->stat.n_put : 0, - jb->stat.n_put ? - 10000*jb->stat.n_lost/jb->stat.n_put%100 : 0); -#endif - mtx_unlock(jb->lock); - - if (err) - goto out; - - err = re_hprintf(pf, "%b", mb->buf, mb->pos); - -out: - mem_deref(mb); - return err; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3c13531e8..c61cfbb96 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -91,7 +91,6 @@ set(SRCS http.c httpauth.c ice.c - jbuf.c json.c list.c main.c diff --git a/test/jbuf.c b/test/jbuf.c deleted file mode 100644 index e7d3dad12..000000000 --- a/test/jbuf.c +++ /dev/null @@ -1,400 +0,0 @@ -/** - * @file jbuf.c Jitterbuffer Testcode - * - * Copyright (C) 2010 Creytiv.com - */ -#include -#include -#include "test.h" - - -#define DEBUG_MODULE "test jbuf" -#define DEBUG_LEVEL 5 -#include - -int test_jbuf(void) -{ - struct rtp_header hdr, hdr2; - struct jbuf *jb; - char *frv[3]; - uint32_t i; - void *mem = NULL; - int err; - - memset(frv, 0, sizeof(frv)); - - err = jbuf_alloc(&jb, 0, 10); - if (err) - return err; - - for (i=0; i min reached, read first frame\n"); - err = jbuf_get(jb, &hdr2, &mem); - TEST_ERR(err); - TEST_EQUALS(160, hdr2.seq); - TEST_EQUALS(mem, frv[0]); - mem = mem_deref(mem); - - DEBUG_INFO("n <= min, leads to ENOENT\n"); - TEST_EQUALS(ENOENT, jbuf_get(jb, &hdr2, &mem)); - - /* Four frames */ - DEBUG_INFO("test frame: Four frames\n"); - jbuf_flush(jb); - hdr.seq = 1; - hdr.ts = 100; - err = jbuf_put(jb, &hdr, frv[0]); - TEST_ERR(err); - - hdr.seq = 2; - hdr.ts = 200; - err = jbuf_put(jb, &hdr, frv[1]); - TEST_ERR(err); - - hdr.seq = 3; - hdr.ts = 300; - err = jbuf_put(jb, &hdr, frv[2]); - TEST_ERR(err); - - hdr.seq = 4; - hdr.ts = 400; - err = jbuf_put(jb, &hdr, frv[3]); - TEST_ERR(err); - - err = jbuf_get(jb, &hdr2, &mem); - TEST_EQUALS(EAGAIN, err); - TEST_EQUALS(1, hdr2.seq); - TEST_EQUALS(mem, frv[0]); - mem = mem_deref(mem); - - err = jbuf_get(jb, &hdr2, &mem); - TEST_EQUALS(EAGAIN, err); - TEST_EQUALS(2, hdr2.seq); - TEST_EQUALS(mem, frv[1]); - mem = mem_deref(mem); - - err = jbuf_get(jb, &hdr2, &mem); - TEST_EQUALS(0, err); - TEST_EQUALS(3, hdr2.seq); - TEST_EQUALS(mem, frv[2]); - mem = mem_deref(mem); - - err = jbuf_get(jb, &hdr2, &mem); - TEST_EQUALS(ENOENT, err); - - err = 0; - - out: - mem_deref(jb); - mem_deref(mem); - for (i=0; i