falconia has submitted this change. ( https://gerrit.osmocom.org/c/libosmo-netif/+/39280?usp=email )
Change subject: bring twjit into libosmo-netif
......................................................................
bring twjit into libosmo-netif
twjit is the jitter buffer portion of Themyscira Wireless RTP
endpoint implementation, an alternative to Belledonne libortp.
Unlike Belledonne software, ThemWi RTP library was developed
specifically for use in Osmocom-based GSM, ISDN and IP-PSTN
network elements, and is built on top of libosmocore primitives -
thus it can function more natively in Osmocom universe than ortp.
This ThemWi library was initially developed externally to Osmocom,
but is now being brought into libosmo-netif so it can be used by
native Osmocom projects, particularly OsmoBTS.
Related: OS#6474
Change-Id: Ia3be5834571ca18b68939abbcf1ce3a879156658
---
M configure.ac
M contrib/libosmo-netif.spec.in
M include/osmocom/netif/Makefile.am
A include/osmocom/netif/twjit.h
A include/osmocom/netif/twjit_private.h
M src/Makefile.am
A src/twjit.c
A src/twjit_conf.c
8 files changed, 1,318 insertions(+), 2 deletions(-)
Approvals:
laforge: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/configure.ac b/configure.ac
index 454bd13..6455319 100644
--- a/configure.ac
+++ b/configure.ac
@@ -92,6 +92,7 @@
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec >= 1.11.0)
+PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0)
AC_ARG_ENABLE([libsctp], [AS_HELP_STRING([--disable-libsctp], [Do not enable socket multiaddr APIs requiring libsctp])],
[ENABLE_LIBSCTP=$enableval], [ENABLE_LIBSCTP="yes"])
diff --git a/contrib/libosmo-netif.spec.in b/contrib/libosmo-netif.spec.in
index 2c1385b..5b182ff 100644
--- a/contrib/libosmo-netif.spec.in
+++ b/contrib/libosmo-netif.spec.in
@@ -27,6 +27,7 @@
BuildRequires: pkgconfig(libosmocore) >= 1.11.0
BuildRequires: pkgconfig(libosmogsm) >= 1.11.0
BuildRequires: pkgconfig(libosmocodec) >= 1.11.0
+BuildRequires: pkgconfig(libosmovty) >= 1.11.0
%description
Network interface demuxer library for OsmoCom projects.
diff --git a/include/osmocom/netif/Makefile.am b/include/osmocom/netif/Makefile.am
index 3771859..19678dc 100644
--- a/include/osmocom/netif/Makefile.am
+++ b/include/osmocom/netif/Makefile.am
@@ -1,5 +1,6 @@
noinst_HEADERS = \
stream_private.h \
+ twjit_private.h \
$(NULL)
version.h: version.h.tpl
@@ -26,6 +27,7 @@
rs232.h \
rtp.h \
stream.h \
+ twjit.h \
version.h \
$(NULL)
diff --git a/include/osmocom/netif/twjit.h b/include/osmocom/netif/twjit.h
new file mode 100644
index 0000000..a12420f
--- /dev/null
+++ b/include/osmocom/netif/twjit.h
@@ -0,0 +1,196 @@
+/*
+ * Themyscira Wireless RTP jitter buffer implementation:
+ * public API definition for Osmocom-integrated version.
+ *
+ * This code was contributed to Osmocom Cellular Network Infrastructure
+ * project by Mother Mychaela N. Falconia of Themyscira Wireless.
+ * Mother Mychaela's contributions are NOT subject to copyright:
+ * no rights reserved, all rights relinquished.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/msgb.h>
+
+/*! \defgroup twjit Themyscira Wireless RTP jitter buffer implementation
+ * @{
+ *
+ * The present twjit layer is an interface mechanism from an incoming
+ * RTP stream to an output application that has fixed timing requirements,
+ * e.g., the Tx side of GSM Um TCH or a T1/E1 TDM interface.
+ *
+ * There also exists a detailed document titled _Guide to ThemWi RTP
+ * endpoint library_, located here:
+ * https://www.freecalypso.org/TW-doc/twrtp-guide-latest.pdf
+ * (See TW-doc directory listing for other formats and previous versions.)
+ * This document is required reading for anyone seeking to properly
+ * understand the present jitter buffer facility, its domain of application
+ * and how to use it. Specific section references to this document
+ * will be made in subsequent comments.
+ *
+ * FIXME: create an Osmocom-controlled version of this document
+ * that describes the version of twrtp+twjit modified for inclusion
+ * in Osmocom.
+ */
+
+/*! Each instance of twjit in the present version exists as struct osmo_twjit.
+ * This structure is opaque, and always constitutes a talloc context. */
+struct osmo_twjit;
+
+/*! A config structure is also needed, containing tunable settings
+ * that are usually managed via vty. This config structure is separate
+ * from individual twjit instances because there will be many more
+ * twjit instances than config instances: there will be a separate
+ * twjit instance for every call or other RTP stream handled by the
+ * application, but only one twjit config structure in the app's
+ * vty config tree, or a few such config instances as appropriate
+ * per app design.
+ *
+ * This config structure is likewise opaque and also constitutes a talloc
+ * context. */
+struct osmo_twjit_config;
+
+/*! Stats collected during the lifetime of a twjit instance.
+ * For a detailed description of each of these counters, see Chapter 3
+ * of twrtp guide document.
+ *
+ * This stats structure is never allocated or accessed in a writable
+ * manner by applications; instead it is allocated inside the library
+ * as part of opaque struct osmo_twjit, while applications are given
+ * const pointers to these structs.
+ */
+struct osmo_twjit_stats {
+ /* For ABI reasons, none of the following fields may be deleted
+ * or reordered! */
+
+ /* normal operation */
+ uint32_t rx_packets;
+ uint32_t delivered_pkt;
+ uint32_t handovers_in;
+ uint32_t handovers_out;
+ uint32_t marker_resets;
+ /* undesirable, but not totally unexpected */
+ uint32_t too_old;
+ uint32_t underruns;
+ uint32_t ho_underruns;
+ uint32_t output_gaps;
+ uint32_t thinning_drops;
+ /* unusual error events */
+ uint32_t bad_packets;
+ uint32_t duplicate_ts;
+ /* independent analysis of Rx packet stream */
+ uint32_t ssrc_changes;
+ uint32_t seq_skips;
+ uint32_t seq_backwards;
+ uint32_t seq_repeats;
+ uint32_t intentional_gaps;
+ uint32_t ts_resets;
+ uint32_t jitter_max;
+ /* New fields may be added here at the end; once added, they become
+ * permanent like the initially defined ones. */
+};
+
+/*! Info collected from the incoming RTP data stream
+ * for the purpose of generating RTCP reception report blocks.
+ * See twrtp guide document section 5.1.
+ *
+ * Key point: unlike the counters in struct osmo_twjit_stats,
+ * all RR info is reset to initial whenever incoming SSRC changes,
+ * as necessitated by RTCP data model being organized per SSRC.
+ *
+ * The same ABI considerations apply to this struct as osmo_twjit_stats.
+ */
+struct osmo_twjit_rr_info {
+ /* For ABI reasons, none of the following fields may be deleted
+ * or reordered! */
+
+ /*! received SSRC to which all following info applies */
+ uint32_t ssrc;
+ /*! count of "received packets" for RTCP RR packet loss calculation */
+ uint32_t rx_packets;
+ /*! "base" sequence number for "expected packets" computation */
+ uint32_t base_seq;
+ /*! "extended highest sequence number" field of RTCP RR */
+ uint32_t max_seq_ext;
+ /*! count of "expected packets" for RTCP RR packet loss calculation */
+ uint32_t expected_pkt;
+ /*! "interarrival jitter" measure of RFC 3550, accumulator for the
+ * leaky integrator algorithm prescribed by the RFC, sans-FP version.
+ * Right-shift this accumulator by 4 bits when emitting RTCP RR. */
+ uint32_t jitter_accum;
+ /* New fields may be added here at the end; once added, they become
+ * permanent like the initially defined ones. */
+};
+
+/* twjit API: managing configuration structures */
+
+struct osmo_twjit_config *osmo_twjit_config_alloc(void *ctx);
+void osmo_twjit_config_free(struct osmo_twjit_config *conf);
+
+int osmo_twjit_config_set_buffer_depth(struct osmo_twjit_config *conf,
+ uint16_t bd_start, uint16_t bd_hiwat);
+int osmo_twjit_config_set_thinning_int(struct osmo_twjit_config *conf,
+ uint16_t thinning_int);
+int osmo_twjit_config_set_max_future_sec(struct osmo_twjit_config *conf,
+ uint16_t max_future_sec);
+int osmo_twjit_config_set_start_min_delta(struct osmo_twjit_config *conf,
+ uint16_t delta_ms);
+int osmo_twjit_config_set_start_max_delta(struct osmo_twjit_config *conf,
+ uint16_t delta_ms);
+int osmo_twjit_config_set_handover_on_marker(struct osmo_twjit_config *conf,
+ bool hom);
+
+/* twjit API: actual twjit instances */
+
+struct osmo_twjit *osmo_twjit_create(void *ctx, uint16_t clock_khz,
+ uint16_t quantum_ms,
+ const struct osmo_twjit_config *config);
+void osmo_twjit_destroy(struct osmo_twjit *twjit);
+
+int osmo_twjit_set_config(struct osmo_twjit *twjit,
+ const struct osmo_twjit_config *config);
+void osmo_twjit_reset(struct osmo_twjit *twjit);
+
+/* RTP input, takes ownership of msgb */
+void osmo_twjit_input(struct osmo_twjit *twjit, struct msgb *msg);
+
+/* output function, to be called by TDM/GSM/etc fixed-timing side */
+struct msgb *osmo_twjit_output(struct osmo_twjit *twjit);
+
+/* Stats and RR info structures are contained inside opaque struct osmo_twjit.
+ * We need to provide access to these stats and RR info structures to API
+ * users, but we don't want to make the whole twjit instance struct public.
+ * Also we would like to have fast external access to these stats, hence an API
+ * that copies our stats to caller-provided storage would be very inefficient.
+ * Compromise: we allow direct external access to just these selected parts
+ * of the full internal state structure by providing API functions that
+ * return pointers to these selected parts.
+ */
+const struct osmo_twjit_stats *
+osmo_twjit_get_stats(struct osmo_twjit *twjit);
+
+const struct osmo_twjit_rr_info *
+osmo_twjit_get_rr_info(struct osmo_twjit *twjit);
+
+/* When we compose outgoing RTCP packets in the upper layer of twrtp,
+ * we need to know whether or not we have received at least one valid
+ * RTP data packet so far. If we haven't received any RTP yet, then
+ * we have no Rx SSRC, all data in struct osmo_twjit_rr_info are invalid,
+ * and we cannot send RTCP reception reports.
+ */
+bool osmo_twjit_rr_info_valid(struct osmo_twjit *twjit);
+
+/* vty configuration functions */
+
+void osmo_twjit_vty_init(int twjit_node);
+
+struct vty;
+
+int osmo_twjit_config_write(struct vty *vty,
+ const struct osmo_twjit_config *conf,
+ const char *prefix);
+
+/*! @} */
diff --git a/include/osmocom/netif/twjit_private.h b/include/osmocom/netif/twjit_private.h
new file mode 100644
index 0000000..8050be2
--- /dev/null
+++ b/include/osmocom/netif/twjit_private.h
@@ -0,0 +1,61 @@
+/*
+ * Themyscira Wireless RTP jitter buffer implementation:
+ * internal config structure.
+ *
+ * This code was contributed to Osmocom Cellular Network Infrastructure
+ * project by Mother Mychaela N. Falconia of Themyscira Wireless.
+ * Mother Mychaela's contributions are NOT subject to copyright:
+ * no rights reserved, all rights relinquished.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/*! \cond private */
+
+/*! twjit configuration tunings, usually set via vty.
+ * This config structure always has to be provided in order to
+ * create a twjit instance. However, due to ABI concerns
+ * (retaining ability to add new fields to this structure),
+ * the struct itself has been made opaque, with actual definition
+ * visible only inside the library.
+ *
+ * In most twjit-using applications, the library's vty module
+ * will take care of both displaying and changing these tunable
+ * settings. However, setter APIs are also provided for non-vty
+ * users.
+ *
+ * The set of configurable parameters contained in this structure
+ * is covered in twrtp guide document section 2.4.
+ */
+struct osmo_twjit_config {
+ /*! buffer depth: starting minimum, formally called flow-starting
+ * fill level. Document section: 2.3.3. */
+ uint16_t bd_start;
+ /*! buffer depth: high water mark, formally called high water mark
+ * fill level. Document section: 2.3.4.2. */
+ uint16_t bd_hiwat;
+ /*! interval for thinning of too-deep standing queue;
+ * document section: 2.3.4.2. */
+ uint16_t thinning_int;
+ /*! guard against time traveler RTP packets, 1 s units;
+ * document section: 2.3.4.3. */
+ uint16_t max_future_sec;
+ /*! min time delta in starting state, 1 ms units, 0 means not set;
+ * document section: 2.3.3.2. */
+ uint16_t start_min_delta;
+ /*! max time delta in starting state, 1 ms units, 0 means not set;
+ * document section: 2.3.3.2. */
+ uint16_t start_max_delta;
+ /*! Osmocom addition, not in ThemWi original: should RTP packets
+ * with M bit set cause a handover or HUNT state reset just like
+ * an SSRC change? With this option enabled, M bit is treated
+ * like an SSRC change in that the timestamp is not considered
+ * at all, on the reasoning that the sender may have switched
+ * to an entirely unrelated source of timestamps. */
+ bool handover_on_marker;
+};
+
+/*! \endcond */
diff --git a/src/Makefile.am b/src/Makefile.am
index 98dc276..73b497f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,12 +3,14 @@
LIBVERSION=14:0:3
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)
-AM_CFLAGS= -fPIC -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(COVERAGE_CFLAGS) $(LIBSCTP_CFLAGS)
+AM_CFLAGS= -fPIC -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) $(LIBSCTP_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
lib_LTLIBRARIES = libosmonetif.la
-libosmonetif_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBSCTP_LIBS)
+libosmonetif_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) $(LIBSCTP_LIBS)
libosmonetif_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBVERSION) -no-undefined
libosmonetif_la_SOURCES = amr.c \
@@ -26,6 +28,8 @@
stream.c \
stream_cli.c \
stream_srv.c \
+ twjit.c \
+ twjit_conf.c \
$(NULL)
if ENABLE_LIBSCTP
diff --git a/src/twjit.c b/src/twjit.c
new file mode 100644
index 0000000..4079ce9
--- /dev/null
+++ b/src/twjit.c
@@ -0,0 +1,706 @@
+/*
+ * Themyscira Wireless RTP jitter buffer implementation: main body.
+ *
+ * This code was contributed to Osmocom Cellular Network Infrastructure
+ * project by Mother Mychaela N. Falconia of Themyscira Wireless.
+ * Mother Mychaela's contributions are NOT subject to copyright:
+ * no rights reserved, all rights relinquished.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h> /* for network byte order functions */
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/netif/twjit.h>
+#include <osmocom/netif/twjit_private.h>
+#include <osmocom/netif/rtp.h>
+
+/*! \addgroup twjit
+ * @{
+ */
+
+/*! \cond private */
+
+/*! Each twjit instance has two sub-buffers; each subbuf is a queue of
+ * received RTP packets that have the same SSRC and whose timestamps
+ * increment in the expected cadence, with each ts delta being an
+ * integral multiple of the samples-per-quantum constant.
+ * See document section 2.3.2.
+ */
+struct twjit_subbuf {
+ /*! SSRC of the stream portion captured in this subbuf */
+ uint32_t ssrc;
+ /*! Current timestamp at the head of the queue */
+ uint32_t head_ts;
+ /*! Queue of packets held by this subbuf */
+ struct llist_head queue;
+ /*! Current depth as defined in document section 2.3.2 */
+ uint32_t depth;
+ /*! Time delta in ms between arrival times of the two most recently
+ * received packets, used only in starting state */
+ uint32_t delta_ms;
+ /*! thinning mechanism: countdown before next quantum deletion */
+ uint16_t drop_int_count;
+};
+
+/*! Each twjit instance is in one of 4 fundamental states at any moment,
+ * as enumerated here. See document section 2.3.1 for state transition
+ * diagram.
+ */
+enum twjit_state {
+ /*! completely empty: neither subbuf is valid */
+ TWJIT_STATE_EMPTY,
+ /*! one subbuf is non-empty, but it hasn't started flowing out yet */
+ TWJIT_STATE_HUNT,
+ /*! one subbuf is both flowing out and accepting new packets */
+ TWJIT_STATE_FLOWING,
+ /*! one subbuf is flowing out while another receives new packets */
+ TWJIT_STATE_HANDOVER,
+};
+
+/*! Main structure for one instance of twjit */
+struct osmo_twjit {
+ /*! twjit tuning config, can be changed with osmo_twjit_set_config() */
+ struct osmo_twjit_config config;
+ /*! count of RTP timestamp units per quantum */
+ uint32_t ts_quantum;
+ /*! quanta per second, used to scale max_future_sec */
+ uint16_t quanta_per_sec;
+ /* scaling factors for time delta conversions */
+ /*! RTP timestamp units per millisecond */
+ uint16_t ts_units_per_ms;
+ /*! RTP timestamp units per second */
+ uint32_t ts_units_per_sec;
+ /*! divisor to go from nanoseconds to RTP timestamp units */
+ uint32_t ns_to_ts_units;
+ /*! current fundamental state of this twjit instance */
+ enum twjit_state state;
+ /*! the two sub-buffers */
+ struct twjit_subbuf sb[2];
+ /*! current subbuf being read, 0 or 1 */
+ uint8_t read_sb;
+ /*! current subbuf being written, 0 or 1 */
+ uint8_t write_sb;
+ /*! RTP timestamp of the most recently received packet */
+ uint32_t last_ts;
+ /*! RTP sequence number of the most recently received packet */
+ uint16_t last_seq;
+ /*! Have we received at least one packet? This bool serves as
+ * the validity flag for last_ts, last_ts and rr_info. */
+ bool got_first_packet;
+ /*! CLOCK_MONOTONIC time of last packet arrival */
+ struct timespec last_arrival;
+ /*! Delta between the two most recent RTP packet arrival times,
+ * converted to RTP timestamp units. */
+ uint32_t last_arrival_delta;
+ /*! analytics for RTCP RR, also remembers last SSRC */
+ struct osmo_twjit_rr_info rr_info;
+ /*! stats over lifetime of this instance */
+ struct osmo_twjit_stats stats;
+};
+
+/*! \endcond */
+
+/* create and destroy functions */
+
+/*! Create a twjit instance
+ *
+ * \param[in] ctx Parent talloc context under which struct osmo_twjit
+ * should be allocated.
+ * \param[in] clock_khz RTP clock rate in kHz, i.e., number of RTP timestamp
+ * units per millisecond. The most common value is 8.
+ * \param[in] quantum_ms Duration of a single quantum (unit of speech or data
+ * carried in one RTP packet) in milliseconds. The most common value is 20.
+ * \param[in] config Set of tunable configuration parameters to be used.
+ * \returns pointer to the newly created twjit instance, or NULL on errors.
+ *
+ * In contrast to the original twrtp-proto version, this version of
+ * osmo_twjit_create() copies the config structure, thus the application
+ * is not required to maintain it in the originally passed memory.
+ */
+struct osmo_twjit *osmo_twjit_create(void *ctx, uint16_t clock_khz,
+ uint16_t quantum_ms,
+ const struct osmo_twjit_config *config)
+{
+ struct osmo_twjit *twjit;
+
+ /* Note to developers comparing this function against its twrtp-native
+ * counterpart: the original version performed a validity check on
+ * the passed config structure at this point, but that check has been
+ * omitted in this version. Rationale: since the config structure
+ * has been made opaque, it is now impossible for applications
+ * to pass in a config structure that has not been constructed
+ * by the library itself and thus known to be valid. */
+
+ twjit = talloc_zero(ctx, struct osmo_twjit);
+ if (!twjit)
+ return NULL;
+
+ memcpy(&twjit->config, config, sizeof(struct osmo_twjit_config));
+ twjit->state = TWJIT_STATE_EMPTY;
+ INIT_LLIST_HEAD(&twjit->sb[0].queue);
+ INIT_LLIST_HEAD(&twjit->sb[1].queue);
+ twjit->ts_quantum = (uint32_t) quantum_ms * clock_khz;
+ twjit->quanta_per_sec = 1000 / quantum_ms;
+ twjit->ts_units_per_ms = clock_khz;
+ twjit->ts_units_per_sec = (uint32_t) clock_khz * 1000;
+ twjit->ns_to_ts_units = 1000000 / clock_khz;
+
+ return twjit;
+}
+
+/*! Destroy a twjit instance
+ *
+ * \param[in] twjit Instance to be freed
+ *
+ * Memory freed by this function includes not only the instance structure,
+ * but also any msgbs that are held by this instance.
+ */
+void osmo_twjit_destroy(struct osmo_twjit *twjit)
+{
+ if (!twjit)
+ return;
+ msgb_queue_free(&twjit->sb[0].queue);
+ msgb_queue_free(&twjit->sb[1].queue);
+ talloc_free(twjit);
+}
+
+/*! Change twjit config parameters
+ *
+ * \param[in] twjit Instance to be reconfigured
+ * \param[in] config Structure with new tuning parameters to be used
+ * \returns 0 if successful, negative on errors
+ *
+ * The intended use for this API is applications that can use one of
+ * several different twjit config profiles depending on various
+ * conditions, but might not know the correct choice of profile
+ * at the time they create the twjit instance - or more likely,
+ * the containing twrtp instance.
+ */
+int osmo_twjit_set_config(struct osmo_twjit *twjit,
+ const struct osmo_twjit_config *config)
+{
+ /* Note to developers comparing this function against its twrtp-native
+ * counterpart: the original version performed a validity check on
+ * the passed config structure at this point, but that check has been
+ * omitted in this version. Rationale: since the config structure
+ * has been made opaque, it is now impossible for applications
+ * to pass in a config structure that has not been constructed
+ * by the library itself and thus known to be valid. */
+
+ memcpy(&twjit->config, config, sizeof(struct osmo_twjit_config));
+ return 0;
+}
+
+/*! Reset twjit instance to empty initial state
+ *
+ * \param[in] twjit Instance to be reset
+ *
+ * This reset function is intended to be called when the application
+ * stops doing regular (once every time quantum) reads from the jitter
+ * buffer, but may resume this activity later. All packet Rx state and
+ * queues are cleared, but "lifetime" statistical counters are NOT reset.
+ */
+void osmo_twjit_reset(struct osmo_twjit *twjit)
+{
+ msgb_queue_free(&twjit->sb[0].queue);
+ msgb_queue_free(&twjit->sb[1].queue);
+ twjit->state = TWJIT_STATE_EMPTY;
+ twjit->sb[0].depth = 0;
+ twjit->sb[1].depth = 0;
+ twjit->got_first_packet = false;
+}
+
+/* input processing of received RTP packets */
+
+/*! \cond private */
+
+/* raw analytics on the Rx packet stream */
+
+/* This "init" function is called for the very first RTP packet we receive,
+ * as well as for received RTP packets that exhibit a change of SSRC
+ * from the previously received stream.
+ */
+static void analytics_init(struct osmo_twjit *twjit, uint32_t rx_ssrc,
+ uint16_t rx_seq)
+{
+ struct osmo_twjit_rr_info *rri = &twjit->rr_info;
+
+ rri->ssrc = rx_ssrc;
+ rri->rx_packets = 1;
+ rri->base_seq = rx_seq;
+ rri->max_seq_ext = rx_seq;
+ rri->expected_pkt = 1;
+ rri->jitter_accum = 0;
+}
+
+/* This "continue" function is called for newly received RTP packets that
+ * follow previously received ones with the same SSRC, not necessarily
+ * in order.
+ */
+static void analytics_cont(struct osmo_twjit *twjit, uint16_t rx_seq,
+ uint32_t rx_ts, const struct timespec *now)
+{
+ struct osmo_twjit_rr_info *rri = &twjit->rr_info;
+ uint16_t seq_ext_lo = rri->max_seq_ext;
+ uint16_t seq_ext_hi = rri->max_seq_ext >> 16;
+ int16_t seq_delta = (int16_t)(rx_seq - twjit->last_seq);
+ int16_t seq_delta2 = (int16_t)(rx_seq - seq_ext_lo);
+ int32_t ts_delta = (int32_t)(rx_ts - twjit->last_ts);
+ struct timespec time_delta;
+ uint32_t time_delta_tsu;
+ int32_t jitter_new, ts_delta_clamp;
+
+ /* analytics for our own stats */
+ if (seq_delta < 0)
+ twjit->stats.seq_backwards++;
+ else if (seq_delta == 0)
+ twjit->stats.seq_repeats++;
+ else if (seq_delta == 1) {
+ if (ts_delta != twjit->ts_quantum) {
+ if (ts_delta > 0 && (ts_delta % twjit->ts_quantum) == 0)
+ twjit->stats.intentional_gaps++;
+ else
+ twjit->stats.ts_resets++;
+ }
+ } else
+ twjit->stats.seq_skips++;
+
+ /* analytics for RTCP RR: packet counts */
+ rri->rx_packets++;
+ if (seq_delta2 > 0) {
+ if (rx_seq < seq_ext_lo)
+ seq_ext_hi++;
+ seq_ext_lo = rx_seq;
+ rri->max_seq_ext = ((uint32_t) seq_ext_hi << 16) | seq_ext_lo;
+ rri->expected_pkt = rri->max_seq_ext - rri->base_seq + 1;
+ }
+
+ /* time-of-arrival analytics */
+ time_delta.tv_sec = now->tv_sec - twjit->last_arrival.tv_sec;
+ time_delta.tv_nsec = now->tv_nsec - twjit->last_arrival.tv_nsec;
+ if (time_delta.tv_nsec < 0) {
+ time_delta.tv_sec--;
+ time_delta.tv_nsec += 1000000000;
+ }
+ /* to avoid overflows in downstream math, clamp to 1 hour */
+ if (time_delta.tv_sec >= 3600) {
+ time_delta.tv_sec = 3600;
+ time_delta.tv_nsec = 0;
+ }
+ /* convert to RTP timestamp units */
+ time_delta_tsu = time_delta.tv_sec * twjit->ts_units_per_sec +
+ time_delta.tv_nsec / twjit->ns_to_ts_units;
+ twjit->last_arrival_delta = time_delta_tsu;
+ /* jitter calculation for RTCP RR */
+ ts_delta_clamp = twjit->ts_units_per_sec * 3600;
+ if (ts_delta > ts_delta_clamp)
+ ts_delta = ts_delta_clamp;
+ else if (ts_delta < -ts_delta_clamp)
+ ts_delta = -ts_delta_clamp;
+ jitter_new = time_delta_tsu - ts_delta;
+ if (jitter_new < 0)
+ jitter_new = -jitter_new;
+ /* RFC 3550 section 6.4.1 prescribes a very specific algorithm
+ * for computing the interarrival jitter reported via RTCP.
+ * This prescribed algorithm is a type of leaky integrator.
+ * Here we implement the fixed-point (no floating point operations)
+ * version presented in section A.8 of the same RFC. */
+ rri->jitter_accum += jitter_new - ((rri->jitter_accum + 8) >> 4);
+ if (jitter_new > twjit->stats.jitter_max)
+ twjit->stats.jitter_max = jitter_new;
+}
+
+/* actual twjit input logic */
+
+static void
+init_subbuf_first_packet(struct osmo_twjit *twjit, struct msgb *msg,
+ uint32_t rx_ssrc, uint32_t rx_ts)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+
+ OSMO_ASSERT(llist_empty(&sb->queue));
+ OSMO_ASSERT(sb->depth == 0);
+ /* all good, proceed */
+ sb->ssrc = rx_ssrc;
+ sb->head_ts = rx_ts;
+ msgb_enqueue(&sb->queue, msg);
+ sb->depth = 1;
+ sb->drop_int_count = 0;
+ /* The setting of delta_ms is needed in order to pacify the check
+ * in starting_sb_is_ready() in configurations with bd_start=1.
+ * An alternative would be to enforce start_min_delta being not set
+ * with bd_start=1, but the present solution is simpler than doing
+ * cross-enforcement between two different parameter settings in vty.
+ */
+ sb->delta_ms = UINT32_MAX;
+}
+
+enum input_decision {
+ INPUT_CONTINUE,
+ INPUT_TOO_OLD,
+ INPUT_RESET,
+};
+
+static enum input_decision
+check_input_for_subbuf(struct osmo_twjit *twjit, bool starting,
+ uint32_t rx_ssrc, uint32_t rx_ts, bool marker)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+ int32_t ts_delta;
+
+ if (rx_ssrc != sb->ssrc)
+ return INPUT_RESET;
+ if (marker && twjit->config.handover_on_marker) {
+ twjit->stats.marker_resets++;
+ return INPUT_RESET;
+ }
+ sb->delta_ms = twjit->last_arrival_delta / twjit->ts_units_per_ms;
+ ts_delta = (int32_t)(rx_ts - sb->head_ts);
+ if (ts_delta < 0)
+ return INPUT_TOO_OLD;
+ if (ts_delta % twjit->ts_quantum)
+ return INPUT_RESET;
+ if (starting) {
+ if (twjit->config.start_max_delta &&
+ sb->delta_ms > twjit->config.start_max_delta)
+ return INPUT_RESET;
+ } else {
+ uint32_t fwd = ts_delta / twjit->ts_quantum;
+
+ if (fwd >= (uint32_t) twjit->config.max_future_sec *
+ twjit->quanta_per_sec)
+ return INPUT_RESET;
+ }
+ return INPUT_CONTINUE;
+}
+
+static void toss_write_queue(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+
+ msgb_queue_free(&sb->queue);
+ sb->depth = 0;
+}
+
+static void insert_pkt_write_sb(struct osmo_twjit *twjit, struct msgb *new_msg,
+ uint32_t rx_ts)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+ uint32_t ts_delta = rx_ts - sb->head_ts;
+ uint32_t ins_depth = ts_delta / twjit->ts_quantum;
+ struct msgb *old_msg;
+ uint32_t old_ts_delta;
+
+ /* are we increasing total depth, and can we do simple tail append? */
+ if (ins_depth >= sb->depth) {
+ msgb_enqueue(&sb->queue, new_msg);
+ sb->depth = ins_depth + 1;
+ return;
+ }
+ /* nope - do it the hard way */
+ llist_for_each_entry(old_msg, &sb->queue, list) {
+ old_ts_delta = old_msg->cb[0] - sb->head_ts;
+ if (old_ts_delta == ts_delta) {
+ /* two packets with the same timestamp! */
+ twjit->stats.duplicate_ts++;
+ msgb_free(new_msg);
+ return;
+ }
+ if (old_ts_delta > ts_delta)
+ break;
+ }
+ llist_add_tail(&new_msg->list, &old_msg->list);
+}
+
+static void trim_starting_sb(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+ struct msgb *msg;
+ uint32_t msg_ts, ts_adv, quantum_adv;
+
+ while (sb->depth > twjit->config.bd_start) {
+ msg = msgb_dequeue(&sb->queue);
+ OSMO_ASSERT(msg);
+ msgb_free(msg);
+ OSMO_ASSERT(!llist_empty(&sb->queue));
+ msg = llist_entry(sb->queue.next, struct msgb, list);
+ msg_ts = msg->cb[0];
+ ts_adv = msg_ts - sb->head_ts;
+ quantum_adv = ts_adv / twjit->ts_quantum;
+ OSMO_ASSERT(sb->depth > quantum_adv);
+ sb->head_ts = msg_ts;
+ sb->depth -= quantum_adv;
+ }
+}
+
+/*! \endcond */
+
+/*! Feed received RTP packet to twjit
+ *
+ * \param[in] twjit Instance to which input is being fed
+ * \param[in] msg Message buffer containing the received packet
+ *
+ * The msgb fed to this API is always consumed by the called function:
+ * if it isn't freed for being invalid or too old, it is queued to be
+ * regurgitated some time later on the output side. The design of
+ * twjit assumes that this API will be called as soon as each incoming
+ * RTP packet is received from the IP network, without any additional
+ * delays; in most applications, thus function will be called by twrtp
+ * layer from osmo_io Rx callback path.
+ */
+void osmo_twjit_input(struct osmo_twjit *twjit, struct msgb *msg)
+{
+ bool got_previous_input = twjit->got_first_packet;
+ const struct rtp_hdr *rtph;
+ uint32_t rx_ssrc, rx_ts;
+ uint16_t rx_seq;
+ struct timespec now;
+ enum input_decision id;
+
+ rtph = osmo_rtp_get_hdr(msg);
+ if (!rtph) {
+ twjit->stats.bad_packets++;
+ msgb_free(msg);
+ return;
+ }
+ rx_ssrc = ntohl(rtph->ssrc);
+ rx_ts = ntohl(rtph->timestamp);
+ rx_seq = ntohs(rtph->sequence);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ if (!got_previous_input) {
+ analytics_init(twjit, rx_ssrc, rx_seq);
+ twjit->got_first_packet = true;
+ } else if (rx_ssrc != twjit->rr_info.ssrc) {
+ twjit->stats.ssrc_changes++;
+ analytics_init(twjit, rx_ssrc, rx_seq);
+ } else {
+ analytics_cont(twjit, rx_seq, rx_ts, &now);
+ }
+ twjit->last_seq = rx_seq;
+ twjit->last_ts = rx_ts;
+ memcpy(&twjit->last_arrival, &now, sizeof(struct timespec));
+ twjit->stats.rx_packets++;
+ msg->cb[0] = rx_ts;
+
+ switch (twjit->state) {
+ case TWJIT_STATE_EMPTY:
+ /* first packet into totally empty buffer */
+ if (got_previous_input)
+ twjit->stats.underruns++;
+ twjit->state = TWJIT_STATE_HUNT;
+ twjit->write_sb = 0;
+ init_subbuf_first_packet(twjit, msg, rx_ssrc, rx_ts);
+ return;
+ case TWJIT_STATE_HUNT:
+ case TWJIT_STATE_HANDOVER:
+ id = check_input_for_subbuf(twjit, true, rx_ssrc, rx_ts,
+ rtph->marker);
+ if (id == INPUT_TOO_OLD) {
+ msgb_free(msg);
+ return;
+ }
+ if (id == INPUT_RESET) {
+ toss_write_queue(twjit);
+ init_subbuf_first_packet(twjit, msg, rx_ssrc, rx_ts);
+ return;
+ }
+ insert_pkt_write_sb(twjit, msg, rx_ts);
+ trim_starting_sb(twjit);
+ return;
+ case TWJIT_STATE_FLOWING:
+ id = check_input_for_subbuf(twjit, false, rx_ssrc, rx_ts,
+ rtph->marker);
+ if (id == INPUT_TOO_OLD) {
+ twjit->stats.too_old++;
+ msgb_free(msg);
+ return;
+ }
+ if (id == INPUT_RESET) {
+ twjit->state = TWJIT_STATE_HANDOVER;
+ twjit->write_sb = !twjit->write_sb;
+ init_subbuf_first_packet(twjit, msg, rx_ssrc, rx_ts);
+ twjit->stats.handovers_in++;
+ return;
+ }
+ insert_pkt_write_sb(twjit, msg, rx_ts);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* output to the fixed timing system */
+
+/*! \cond private */
+
+static bool starting_sb_is_ready(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+
+ if (sb->depth < twjit->config.bd_start)
+ return false;
+ if (sb->delta_ms < twjit->config.start_min_delta)
+ return false;
+ return true;
+}
+
+static bool read_sb_is_empty(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->read_sb];
+
+ return sb->depth == 0;
+}
+
+static struct msgb *pull_from_read_sb(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->read_sb];
+ struct msgb *msg;
+
+ OSMO_ASSERT(!llist_empty(&sb->queue));
+ OSMO_ASSERT(sb->depth > 0);
+ msg = llist_entry(sb->queue.next, struct msgb, list);
+ if (msg->cb[0] == sb->head_ts) {
+ llist_del(&msg->list);
+ twjit->stats.delivered_pkt++;
+ } else {
+ msg = NULL;
+ twjit->stats.output_gaps++;
+ }
+ sb->head_ts += twjit->ts_quantum;
+ sb->depth--;
+ return msg;
+}
+
+static void read_sb_thinning(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->read_sb];
+ struct msgb *msg;
+
+ if (sb->drop_int_count) {
+ sb->drop_int_count--;
+ return;
+ }
+ if (sb->depth <= twjit->config.bd_hiwat)
+ return;
+ twjit->stats.thinning_drops++;
+ msg = pull_from_read_sb(twjit);
+ if (msg)
+ msgb_free(msg);
+ sb->drop_int_count = twjit->config.thinning_int - 2;
+}
+
+static void toss_read_queue(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->read_sb];
+
+ msgb_queue_free(&sb->queue);
+ sb->depth = 0;
+}
+
+/*! \endcond */
+
+/*! Fixed-timing output poll from twjit buffer
+ *
+ * \param[in] twjit Instance to poll
+ * \returns pointer to msgb holding a previously received RTP packet that
+ * was successfully mapped to the present quantum in the fixed-timing output,
+ * or NULL if no such packet is available.
+ */
+struct msgb *osmo_twjit_output(struct osmo_twjit *twjit)
+{
+ switch (twjit->state) {
+ case TWJIT_STATE_EMPTY:
+ return NULL;
+ case TWJIT_STATE_HUNT:
+ if (!starting_sb_is_ready(twjit))
+ return NULL;
+ twjit->state = TWJIT_STATE_FLOWING;
+ twjit->read_sb = twjit->write_sb;
+ return pull_from_read_sb(twjit);
+ case TWJIT_STATE_FLOWING:
+ if (read_sb_is_empty(twjit)) {
+ twjit->state = TWJIT_STATE_EMPTY;
+ return NULL;
+ }
+ read_sb_thinning(twjit);
+ return pull_from_read_sb(twjit);
+ case TWJIT_STATE_HANDOVER:
+ if (starting_sb_is_ready(twjit)) {
+ toss_read_queue(twjit);
+ twjit->stats.handovers_out++;
+ twjit->state = TWJIT_STATE_FLOWING;
+ twjit->read_sb = twjit->write_sb;
+ return pull_from_read_sb(twjit);
+ }
+ if (read_sb_is_empty(twjit)) {
+ twjit->state = TWJIT_STATE_HUNT;
+ twjit->stats.ho_underruns++;
+ return NULL;
+ }
+ read_sb_thinning(twjit);
+ return pull_from_read_sb(twjit);
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* simple information retrieval functions */
+
+/*! Retrieve lifetime stats from twjit instance
+ *
+ * \param[in] twjit Instance to query
+ * \returns pointer to lifetime stats structure
+ */
+const struct osmo_twjit_stats *
+osmo_twjit_get_stats(struct osmo_twjit *twjit)
+{
+ return &twjit->stats;
+}
+
+/*! Retrieve RR info from twjit instance
+ *
+ * \param[in] twjit Instance to query
+ * \returns pointer to RR info structure
+ *
+ * The structure retrieved with this API is called RR info because it contains
+ * info that is needed in order to constuct RTCP reception reports describing
+ * the RTP stream received by this twjit instance. But of course this info
+ * can also be used for other statistics-related or monitoring-related purposes.
+ *
+ * The structure returned by this API is fully valid only if
+ * osmo_twjit_rr_info_valid() returns true. If that API returns false,
+ * the RR info structure returned by the present API should be considered
+ * invalid. More precisely, the "invalid" RR info structure will be all
+ * zeros on a freshly created twjit, or stale info if this twjit received
+ * some RTP input prior to being reset. There may be some applications
+ * that retrieve the RR info structure to report some non-critical stats;
+ * such uses are allowed even when this structure is invalid in the strict
+ * sense.
+ */
+const struct osmo_twjit_rr_info *
+osmo_twjit_get_rr_info(struct osmo_twjit *twjit)
+{
+ return &twjit->rr_info;
+}
+
+/*! Did this twjit instance ever receive RTP input?
+ *
+ * \param[in] twjit Instance to query
+ * \returns true if this twjit instance received RTP input since it was
+ * created or last reset, false otherwise.
+ */
+bool osmo_twjit_rr_info_valid(struct osmo_twjit *twjit)
+{
+ return twjit->got_first_packet;
+}
+
+/*! @} */
diff --git a/src/twjit_conf.c b/src/twjit_conf.c
new file mode 100644
index 0000000..b00d3dc
--- /dev/null
+++ b/src/twjit_conf.c
@@ -0,0 +1,345 @@
+/*
+ * Themyscira Wireless RTP jitter buffer implementation:
+ * configuration by vty or otherwise.
+ *
+ * This code was contributed to Osmocom Cellular Network Infrastructure
+ * project by Mother Mychaela N. Falconia of Themyscira Wireless.
+ * Mother Mychaela's contributions are NOT subject to copyright:
+ * no rights reserved, all rights relinquished.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/netif/twjit.h>
+#include <osmocom/netif/twjit_private.h>
+
+/*! \addgroup twjit
+ * @{
+ */
+
+/*! Allocate and initialize twjit config structure
+ *
+ * \param[in] ctx Parent talloc context under which struct osmo_twjit_config
+ * should be allocated.
+ * \returns pointer to the newly created twjit config instance, or NULL on
+ * errors.
+ *
+ * A typical application will have a struct osmo_twjit_config somewhere
+ * in the application config data structures, editable via vty.
+ * More complex applications may even have several such twjit config
+ * structures, to be used in different contexts such as GSM vs PSTN.
+ * However, in the present Osmocom-integrated version of twjit, this config
+ * structure has been made opaque for ABI reasons - hence config instances
+ * now have to be allocated by the library, rather than merely initialized
+ * in content.
+ */
+struct osmo_twjit_config *osmo_twjit_config_alloc(void *ctx)
+{
+ struct osmo_twjit_config *config;
+
+ config = talloc_zero(ctx, struct osmo_twjit_config);
+ if (!config)
+ return NULL;
+
+ /* Initialize defaults, corresponding to twna_twjit_config_init()
+ * in twrtp-native version. */
+
+ /* While the theoretical minimum starting fill level is 1, the
+ * practically useful minimum (achieving lowest latency, but not
+ * incurring underruns in normal healthy operation) is 2 for typical
+ * network configurations that combine elements with "perfect" 20 ms
+ * timing (T1/E1 interfaces, external IP-PSTN links, software
+ * transcoders timed by system clock etc) and GSM-to-IP OsmoBTS
+ * whose 20 ms timing contains the small inherent jitter of TDMA. */
+ config->bd_start = 2;
+
+ /* The high water mark setting determines when the standing queue
+ * thinning mechanism kicks in. A standing queue that is longer
+ * than the starting fill level will occur when the flow starts
+ * during a network latency spike, but then the network latency
+ * goes down. If this setting is too high, deep standing queues
+ * will persist, adding needless latency to speech or CSD.
+ * If this setting is too low, the thinning mechanism will be
+ * too invasive, needlessly and perhaps frequently deleting a quantum
+ * of speech or data from the stream and incurring a phase shift.
+ * Starting fill level plus 2 seems like a good default. */
+ config->bd_hiwat = 4;
+
+ /* When the standing queue thinning mechanism does kick in,
+ * it drops every Nth packet, where N is the thinning interval.
+ * Given that this mechanism forcibly deletes a quantum of speech
+ * or data from the stream, these induced disruptions should be
+ * spaced out, and the managing operator should also keep in mind
+ * that the incurred phase shift may be a problem for some
+ * applications, particularly CSD. Our current default is
+ * a prime number, reducing the probability that the thinning
+ * mechanism will interfere badly with intrinsic features of the
+ * stream being thinned. 17 quantum units at 20 ms per quantum
+ * is 340 ms, which should be sufficiently long spacing to make
+ * speech quantum deletions tolerable. */
+ config->thinning_int = 17;
+
+ /* Guard against time traveler packets,
+ * see document section 2.3.4.3. */
+ config->max_future_sec = 10;
+
+ return config;
+}
+
+/*! Free a twjit config instance
+ *
+ * \param[in] conf Instance to be freed
+ */
+void osmo_twjit_config_free(struct osmo_twjit_config *conf)
+{
+ if (!conf)
+ return;
+ talloc_free(conf);
+}
+
+/*! Write out vty form of twjit config structure
+ *
+ * \param[in] vty The vty instance to which vty_out() calls should be made
+ * \param[in] conf The config structure to write out
+ * \param[in] prefix Additional indent prefix to be prepended to each output
+ * line, defaults to "" if NULL
+ * \returns CMD_SUCCESS for vty system
+ */
+int osmo_twjit_config_write(struct vty *vty,
+ const struct osmo_twjit_config *conf,
+ const char *prefix)
+{
+ if (!prefix)
+ prefix = "";
+ vty_out(vty, "%s buffer-depth %u %u%s", prefix, conf->bd_start,
+ conf->bd_hiwat, VTY_NEWLINE);
+ vty_out(vty, "%s thinning-interval %u%s", prefix, conf->thinning_int,
+ VTY_NEWLINE);
+ vty_out(vty, "%s max-future-sec %u%s", prefix, conf->max_future_sec,
+ VTY_NEWLINE);
+
+ if (conf->start_min_delta) {
+ vty_out(vty, "%s start-min-delta %u%s", prefix,
+ conf->start_min_delta, VTY_NEWLINE);
+ }
+ if (conf->start_max_delta) {
+ vty_out(vty, "%s start-max-delta %u%s", prefix,
+ conf->start_max_delta, VTY_NEWLINE);
+ }
+
+ vty_out(vty, "%s marker-handling %s%s", prefix,
+ conf->handover_on_marker ? "handover" : "ignore", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_buffer_depth, cfg_buffer_depth_cmd,
+ "buffer-depth <1-65535> <1-65535>",
+ "Buffer depth configuration\n"
+ "Minimum fill required to start flow\n"
+ "High water mark fill level\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ unsigned bd_start = atoi(argv[0]);
+ unsigned bd_hiwat = atoi(argv[1]);
+
+ if (bd_hiwat < bd_start) {
+ vty_out(vty, "%% Error: high water mark cannot be less than starting level%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ conf->bd_start = bd_start;
+ conf->bd_hiwat = bd_hiwat;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_thinning, cfg_thinning_cmd,
+ "thinning-interval <2-65535>",
+ "Standing queue thinning configuration\n"
+ "Drop every Nth packet\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->thinning_int = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_max_future, cfg_max_future_cmd,
+ "max-future-sec <1-65535>",
+ "Guard against time traveler packets\n"
+ "Maximum permissible number of seconds into the future\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->max_future_sec = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_start_min_delta, cfg_start_min_delta_cmd,
+ "start-min-delta <1-65535>",
+ "Minimum required delta in time-of-arrival to start flow\n"
+ "Time delta value in ms\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->start_min_delta = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_start_min_delta, cfg_no_start_min_delta_cmd,
+ "no start-min-delta",
+ NO_STR "Minimum required delta in time-of-arrival to start flow\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->start_min_delta = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_start_max_delta, cfg_start_max_delta_cmd,
+ "start-max-delta <1-65535>",
+ "Maximum permitted gap in time-of-arrival in starting state\n"
+ "Time delta value in ms\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->start_max_delta = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_start_max_delta, cfg_no_start_max_delta_cmd,
+ "no start-max-delta",
+ NO_STR "Maximum permitted gap in time-of-arrival in starting state\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->start_max_delta = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_marker_handling, cfg_marker_handling_cmd,
+ "marker-handling (handover|ignore)",
+ "How to handle RTP packets with marker bit set\n"
+ "Invoke handover handling, same as SSRC change\n"
+ "Ignore marker bit\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->handover_on_marker = (strcmp(argv[0], "handover") == 0);
+ return CMD_SUCCESS;
+}
+
+/* Install vty configuration elements for twjit
+ *
+ * \param[in] twjit_node The application-defined vty node ID for twjit
+ */
+void osmo_twjit_vty_init(int twjit_node)
+{
+ install_lib_element(twjit_node, &cfg_buffer_depth_cmd);
+ install_lib_element(twjit_node, &cfg_thinning_cmd);
+ install_lib_element(twjit_node, &cfg_max_future_cmd);
+ install_lib_element(twjit_node, &cfg_start_min_delta_cmd);
+ install_lib_element(twjit_node, &cfg_no_start_min_delta_cmd);
+ install_lib_element(twjit_node, &cfg_start_max_delta_cmd);
+ install_lib_element(twjit_node, &cfg_no_start_max_delta_cmd);
+ install_lib_element(twjit_node, &cfg_marker_handling_cmd);
+}
+
+/* config setter functions for non-vty users */
+
+/*! Non-vty function for buffer-depth setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] bd_start Flow-starting fill level, document section 2.3.3.
+ * \param[in] bd_hiwat High water mark fill level, document section 2.3.4.2.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_buffer_depth(struct osmo_twjit_config *conf,
+ uint16_t bd_start, uint16_t bd_hiwat)
+{
+ if (bd_start < 1)
+ return -EINVAL;
+ if (bd_hiwat < bd_start)
+ return -EINVAL;
+ conf->bd_start = bd_start;
+ conf->bd_hiwat = bd_hiwat;
+ return 0;
+}
+
+/*! Non-vty function for thinning-interval setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] thinning_int Thinning interval setting, document section 2.3.4.2.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_thinning_int(struct osmo_twjit_config *conf,
+ uint16_t thinning_int)
+{
+ if (thinning_int < 2)
+ return -EINVAL;
+ conf->thinning_int = thinning_int;
+ return 0;
+}
+
+/*! Non-vty function for max-future-sec setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] max_future_sec Maximum number of seconds into the future,
+ * document section 2.3.4.3.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_max_future_sec(struct osmo_twjit_config *conf,
+ uint16_t max_future_sec)
+{
+ if (max_future_sec < 1)
+ return -EINVAL;
+ conf->max_future_sec = max_future_sec;
+ return 0;
+}
+
+/*! Non-vty function for start-min-delta setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] delta_ms Minimum required ToA delta in ms, or 0 to disable
+ * this check; document section 2.3.3.2.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_start_min_delta(struct osmo_twjit_config *conf,
+ uint16_t delta_ms)
+{
+ conf->start_min_delta = delta_ms;
+ return 0;
+}
+
+/*! Non-vty function for start-max-delta setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] delta_ms Maximum permitted ToA gap in ms, or 0 to disable
+ * this check; document section 2.3.3.2.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_start_max_delta(struct osmo_twjit_config *conf,
+ uint16_t delta_ms)
+{
+ conf->start_max_delta = delta_ms;
+ return 0;
+}
+
+/*! Non-vty function for marker-handling setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] hom Handover on marker if true, ignore marker bit if false.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_handover_on_marker(struct osmo_twjit_config *conf,
+ bool hom)
+{
+ conf->handover_on_marker = hom;
+ return 0;
+}
+
+/*! @} */
--
To view, visit https://gerrit.osmocom.org/c/libosmo-netif/+/39280?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: libosmo-netif
Gerrit-Branch: master
Gerrit-Change-Id: Ia3be5834571ca18b68939abbcf1ce3a879156658
Gerrit-Change-Number: 39280
Gerrit-PatchSet: 8
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: jolly <andreas(a)eversberg.eu>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-CC: fixeria <axilirator(a)gmail.com>
falconia has submitted this change. ( https://gerrit.osmocom.org/c/libosmo-netif/+/39281?usp=email )
Change subject: bring twrtp into libosmo-netif
......................................................................
bring twrtp into libosmo-netif
twrtp is the top layer of Themyscira Wireless RTP endpoint
implementation, an alternative to Belledonne libortp.
Unlike Belledonne software, ThemWi RTP library was developed
specifically for use in Osmocom-based GSM, ISDN and IP-PSTN
network elements, and is built on top of libosmocore primitives -
thus it can function more natively in Osmocom universe than ortp.
This ThemWi library was initially developed externally to Osmocom,
but is now being brought into libosmo-netif so it can be used by
native Osmocom projects, particularly OsmoBTS.
Related: OS#6474
Change-Id: Ib63215aaf13ef8ab8f2e0c8d310164cd5c8824eb
---
M include/osmocom/netif/Makefile.am
A include/osmocom/netif/rtcp_defs.h
A include/osmocom/netif/twrtp.h
M src/Makefile.am
A src/twrtp.c
5 files changed, 1,559 insertions(+), 0 deletions(-)
Approvals:
Jenkins Builder: Verified
laforge: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
diff --git a/include/osmocom/netif/Makefile.am b/include/osmocom/netif/Makefile.am
index 19678dc..71f0931 100644
--- a/include/osmocom/netif/Makefile.am
+++ b/include/osmocom/netif/Makefile.am
@@ -1,4 +1,5 @@
noinst_HEADERS = \
+ rtcp_defs.h \
stream_private.h \
twjit_private.h \
$(NULL)
@@ -28,6 +29,7 @@
rtp.h \
stream.h \
twjit.h \
+ twrtp.h \
version.h \
$(NULL)
diff --git a/include/osmocom/netif/rtcp_defs.h b/include/osmocom/netif/rtcp_defs.h
new file mode 100644
index 0000000..ad552a6
--- /dev/null
+++ b/include/osmocom/netif/rtcp_defs.h
@@ -0,0 +1,49 @@
+/*
+ * Some definitions for RTCP, just enough to implement SR and RR
+ * generation and parsing in twrtp.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+struct rtcp_sr_rr_hdr {
+ uint8_t v_p_rc;
+ uint8_t pt;
+ uint16_t len;
+ uint32_t ssrc;
+} __attribute__((packed));
+
+struct rtcp_sr_block {
+ uint32_t ntp_sec;
+ uint32_t ntp_fract;
+ uint32_t rtp_ts;
+ uint32_t pkt_count;
+ uint32_t octet_count;
+} __attribute__((packed));
+
+struct rtcp_rr_block {
+ uint32_t ssrc;
+ uint32_t lost_word;
+ uint32_t max_seq_ext;
+ uint32_t jitter;
+ uint16_t lsr_sec;
+ uint16_t lsr_fract;
+ uint16_t dlsr_sec;
+ uint16_t dlsr_fract;
+} __attribute__((packed));
+
+#define RTCP_PT_SR 200
+#define RTCP_PT_RR 201
+#define RTCP_PT_SDES 202
+#define RTCP_PT_BYE 203
+#define RTCP_PT_APP 204
+
+#define SDES_ITEM_CNAME 1
+#define SDES_ITEM_NAME 2
+#define SDES_ITEM_EMAIL 3
+#define SDES_ITEM_PHONE 4
+#define SDES_ITEM_LOC 5
+#define SDES_ITEM_TOOL 6
+#define SDES_ITEM_NOTE 7
+#define SDES_ITEM_PRIV 8
diff --git a/include/osmocom/netif/twrtp.h b/include/osmocom/netif/twrtp.h
new file mode 100644
index 0000000..f1852ec
--- /dev/null
+++ b/include/osmocom/netif/twrtp.h
@@ -0,0 +1,234 @@
+/*
+ * Themyscira Wireless RTP endpoint implementation:
+ * public API definition for Osmocom-integrated version.
+ *
+ * This code was contributed to Osmocom Cellular Network Infrastructure
+ * project by Mother Mychaela N. Falconia of Themyscira Wireless.
+ * Mother Mychaela's contributions are NOT subject to copyright:
+ * no rights reserved, all rights relinquished.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+
+/*! \defgroup twrtp Themyscira Wireless RTP endpoint implementation
+ * @{
+ *
+ * osmo_twrtp is a complete RTP endpoint. It is primarily designed
+ * to be used together with twjit to build a bidirectional interface
+ * between an RTP stream and a fixed timing system such as GSM Um TCH
+ * or T1/E1 Abis, but it also has limited support for endpoints without
+ * twjit. A twrtp endpoint without twjit is either an output-only
+ * endpoint (playout of in-band tones and announcements etc), or one
+ * side of a pair of endpoints that forward RTP packets to each other
+ * without delay.
+ *
+ * The basic workflow is:
+ *
+ * 1. Create a twrtp instance with osmo_twrtp_create(). The decision
+ * to use or not use twjit is fixed at this time; if twjit is to be
+ * used, struct osmo_twjit_config needs to be provided.
+ *
+ * 2. Create and bind a pair of local UDP sockets for RTP and RTCP,
+ * or supply osmo_twrtp with an already-obtained pair of file descriptors
+ * for the same. Most users will find the high-level API
+ * osmo_twrtp_bind_local() most suitable, but some applications may
+ * prefer to use the low-level API osmo_twrtp_supply_fds() instead.
+ *
+ * 3. Set the IP:port address of the remote RTP end with
+ * osmo_twrtp_set_remote().
+ *
+ * 4. Traffic can now be sent and received as detailed below.
+ *
+ * Receiving RTP traffic, interworking to a fixed timing system:
+ *
+ * 1. Provide struct osmo_twjit_config to osmo_twrtp_create(), so the twrtp
+ * instance will be created with twjit included.
+ *
+ * 2. When you are ready to start receiving traffic, call
+ * osmo_twrtp_twjit_rx_ctrl() with rx_enable argument set to true.
+ *
+ * 3. Once you've made the above call, commit to calling
+ * osmo_twrtp_twjit_rx_poll() every 20 ms (or whatever your quantum
+ * duration is) as timed by your GSM Um TCH or TDM system, every tick
+ * without fail.
+ *
+ * 4. You can pause operation by calling osmo_twrtp_twjit_rx_ctrl() with
+ * rx_enable argument set to false, and then later restart by returning
+ * to step 2 above. When you pause, the Rx jitter buffer will be
+ * flushed, and when you restart Rx, twjit will restart from empty state.
+ *
+ * Sending RTP traffic, coming from a fixed timing system:
+ *
+ * 1. Make the first call to osmo_twrtp_tx_quantum() whenever you are ready
+ * to send out the first quantum.
+ *
+ * 2. Once you've made that first call, commit to sending either another
+ * quantum or an intentional gap (osmo_twrtp_tx_skip()) every 20 ms
+ * without fail, as timed by your GSM Um TCH, T1/E1 TDM or other fixed
+ * timing system.
+ *
+ * 3. If you need to pause Tx output and restart later, or if some
+ * discontinuity occurs in your fixed timing system where you know that
+ * your interval between quantum sends is not the proper 20 ms or whatever
+ * your quantum duration is, call osmo_twrtp_tx_restart(), telling the
+ * library to reset the RTP timescale in its subsequent output.
+ *
+ * No-delay forwarding operation from one twrtp endpoint to another:
+ *
+ * 1. On the receiving side, call osmo_twrtp_set_raw_rx_cb() to set up an
+ * unbuffered/non-delayed Rx callback function.
+ *
+ * 2. In that Rx callback function, forward the packet to the other endpoint
+ * with osmo_twrtp_tx_forward().
+ *
+ * 3. If you are building an MGW that mostly does forwarding as described here,
+ * but occasionally inserts its own in-band tones or announcements, you can
+ * switch in real time between just-described forwarding and "native"
+ * osmo_twrtp_tx_quantum() output. The receiving RTP end will see
+ * "handover" events as SSRC switches between the one emitted by twrtp
+ * and the one coming from the other remote party. Actual timing will
+ * also switch, as there is no realistic way your own 20 ms timing for
+ * announcement playout will exactly match the timing of the RTP stream
+ * switched from the other remote party.
+ *
+ * RTCP handling is mostly internal to the library - as a user, you don't need
+ * to concern yourself with it. More precisely, incoming RTCP packets are
+ * always handled internally; if you wish to send out RTCP, you have to set
+ * SDES and decide if you wish to send out SR or RR packets. Automatic
+ * emission of an SR packet after every so many RTP packets, with an RR block
+ * included in that SR, is the most common and most useful mode. OTOH, if
+ * your RTP application does not use RTCP, you don't need to concern yourself
+ * with RTCP at all: don't configure or enable RTCP sending, and ignore the
+ * existence of the built-in RTCP receiver. Any received RTCP packets will
+ * still be parsed, but you can ignore the data that result from this parsing.
+ *
+ * There also exists a detailed document titled _Guide to ThemWi RTP
+ * endpoint library_, located here:
+ * https://www.freecalypso.org/TW-doc/twrtp-guide-latest.pdf
+ * (See TW-doc directory listing for other formats and previous versions.)
+ * This document is required reading for anyone seeking to properly
+ * understand twrtp, its domain of application and all of its capabilities,
+ * beyond the brief summary given above. Specific section references to
+ * this document will be made in subsequent comments.
+ *
+ * FIXME: create an Osmocom-controlled version of this document
+ * that describes the version of twrtp+twjit modified for inclusion
+ * in Osmocom.
+ */
+
+/*! Each instance of twrtp in the present version exists as struct osmo_twrtp.
+ * This structure is opaque, and always constitutes a talloc context. */
+struct osmo_twrtp;
+
+/*! Stats collected during the lifetime of a twrtp instance.
+ * For a detailed description of each of these counters, see Chapter 6
+ * of twrtp guide document.
+ *
+ * This stats structure is never allocated or accessed in a writable
+ * manner by applications; instead it is allocated inside the library
+ * as part of opaque struct osmo_twrtp, while applications are given
+ * const pointers to these structs.
+ */
+struct osmo_twrtp_stats {
+ /* For ABI reasons, none of the following fields may be deleted
+ * or reordered! */
+ uint32_t rx_rtp_pkt;
+ uint32_t rx_rtp_badsrc;
+ uint32_t rx_rtcp_pkt;
+ uint32_t rx_rtcp_badsrc;
+ uint32_t rx_rtcp_invalid;
+ uint32_t rx_rtcp_wrong_ssrc;
+ uint32_t tx_rtp_pkt;
+ uint32_t tx_rtp_bytes;
+ uint32_t tx_rtcp_pkt;
+ /* New fields may be added here at the end; once added, they become
+ * permanent like the initially defined ones. */
+};
+
+/* declare structs that are used in our API */
+
+struct osmo_twjit;
+struct osmo_twjit_config;
+struct osmo_twjit_stats;
+struct osmo_twjit_rr_info;
+
+/* public API functions: create & destroy, local and remote addresses */
+
+struct osmo_twrtp *
+osmo_twrtp_create(void *ctx, uint16_t clock_khz, uint16_t quantum_ms,
+ bool random_ts_seq,
+ const struct osmo_twjit_config *twjit_config);
+void osmo_twrtp_destroy(struct osmo_twrtp *endp);
+
+int osmo_twrtp_supply_fds(struct osmo_twrtp *endp, int rtp_fd, int rtcp_fd);
+int osmo_twrtp_bind_local(struct osmo_twrtp *endp,
+ const struct osmo_sockaddr *rtp_addr, bool bind_rtcp);
+int osmo_twrtp_set_remote(struct osmo_twrtp *endp,
+ const struct osmo_sockaddr *rtp_addr);
+
+/* receiving incoming RTP via twjit */
+
+void osmo_twrtp_twjit_rx_ctrl(struct osmo_twrtp *endp, bool rx_enable);
+
+/* output function, to be called by TDM/GSM/etc fixed-timing side */
+struct msgb *osmo_twrtp_twjit_rx_poll(struct osmo_twrtp *endp);
+
+/* receiving incoming RTP without twjit */
+
+/* callback function takes ownership of msgb *if* it returns true */
+typedef bool (*osmo_twrtp_raw_rx_cb)(struct osmo_twrtp *endp, void *user_data,
+ struct msgb *msg);
+
+void osmo_twrtp_set_raw_rx_cb(struct osmo_twrtp *endp, osmo_twrtp_raw_rx_cb cb,
+ void *user_data);
+
+/* RTP Tx direction */
+
+int osmo_twrtp_tx_quantum(struct osmo_twrtp *endp, const uint8_t *payload,
+ unsigned payload_len, uint8_t payload_type,
+ bool marker, bool auto_marker, bool send_rtcp);
+void osmo_twrtp_tx_skip(struct osmo_twrtp *endp);
+void osmo_twrtp_tx_restart(struct osmo_twrtp *endp);
+
+int osmo_twrtp_tx_forward(struct osmo_twrtp *endp, struct msgb *msg);
+
+/* support for emitting RTCP SR & RR */
+
+int osmo_twrtp_set_sdes(struct osmo_twrtp *endp, const char *cname,
+ const char *name, const char *email, const char *phone,
+ const char *loc, const char *tool, const char *note);
+void osmo_twrtp_set_auto_rtcp_interval(struct osmo_twrtp *endp,
+ uint16_t interval);
+
+int osmo_twrtp_send_rtcp_rr(struct osmo_twrtp *endp);
+
+/* information retrieval functions */
+
+struct osmo_twjit *osmo_twrtp_get_twjit(struct osmo_twrtp *endp);
+
+const struct osmo_twrtp_stats *osmo_twrtp_get_stats(struct osmo_twrtp *endp);
+
+/* have we received at least one RTCP RR matching our RTP Tx output? */
+bool osmo_twrtp_got_rtcp_rr(struct osmo_twrtp *endp);
+
+/* retrieving RTCP RR info: valid only if above function returned true */
+uint32_t osmo_twrtp_rr_lost_word(struct osmo_twrtp *endp);
+int32_t osmo_twrtp_rr_lost_cumulative(struct osmo_twrtp *endp);
+uint32_t osmo_twrtp_rr_jitter_last(struct osmo_twrtp *endp);
+uint32_t osmo_twrtp_rr_jitter_max(struct osmo_twrtp *endp);
+
+/* socket-related miscellany */
+
+int osmo_twrtp_get_rtp_fd(struct osmo_twrtp *endp);
+int osmo_twrtp_get_rtcp_fd(struct osmo_twrtp *endp);
+
+int osmo_twrtp_set_dscp(struct osmo_twrtp *endp, uint8_t dscp);
+int osmo_twrtp_set_socket_prio(struct osmo_twrtp *endp, int prio);
+
+/*! @} */
diff --git a/src/Makefile.am b/src/Makefile.am
index 73b497f..b354e53 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -30,6 +30,7 @@
stream_srv.c \
twjit.c \
twjit_conf.c \
+ twrtp.c \
$(NULL)
if ENABLE_LIBSCTP
diff --git a/src/twrtp.c b/src/twrtp.c
new file mode 100644
index 0000000..1a1b7be
--- /dev/null
+++ b/src/twrtp.c
@@ -0,0 +1,1273 @@
+/*
+ * Themyscira Wireless RTP endpoint implementation: main body.
+ *
+ * This code was contributed to Osmocom Cellular Network Infrastructure
+ * project by Mother Mychaela N. Falconia of Themyscira Wireless.
+ * Mother Mychaela's contributions are NOT subject to copyright:
+ * no rights reserved, all rights relinquished.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <arpa/inet.h> /* for network byte order functions */
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/osmo_io.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/netif/twrtp.h>
+#include <osmocom/netif/twjit.h>
+#include <osmocom/netif/rtp.h>
+#include <osmocom/netif/rtcp_defs.h>
+
+/*! \addgroup twrtp
+ * @{
+ */
+
+/*! \cond private */
+
+/*! This structure captures info that has been extracted from
+ * received and decoded RTCP SR and/or RR packets.
+ * See twrtp guide document section 5.2.
+ */
+struct rtcp_rx_state {
+ /* sender info extracted from SR packets */
+ /*! sender SSRC from the most recently received SR packet */
+ uint32_t sr_ssrc;
+ /*! NTP timestamp from SR: lower 16 bits of seconds word */
+ uint16_t sr_ntp_sec;
+ /*! NTP timestamp from SR: upper 16 bits of fraction word */
+ uint16_t sr_ntp_fract;
+ /*! CLOCK_MONOTONIC time-of-arrival of most recently received SR */
+ struct timespec sr_rx_time;
+ /* info extracted from reception report blocks in SR/RR packets */
+ /*! lost packets word from most recently received RR block */
+ uint32_t rr_lost_word;
+ /*! interarrival jitter from most recently received RR block */
+ uint32_t rr_jitter;
+ /*! highest value received in the interarrival jitter word */
+ uint32_t rr_jitter_max;
+ /* bool flags at the end for structure packing optimization */
+ /*! received at least one SR packet */
+ bool got_sr;
+ /*! received at least one RR block */
+ bool got_rr;
+};
+
+/*! This structure holds state for emission of RTCP reception report
+ * blocks based on RR info from twjit. This additional state element
+ * (beyond struct osmo_twjit_rr_info) is needed in order to compute
+ * the "fraction lost" field, which is based on the delta since
+ * the last emitted report and thus requires memory of previous
+ * "received" and "expected" counts.
+ */
+struct rtcp_tx_state {
+ uint32_t last_received;
+ uint32_t last_expected;
+};
+
+/*! Main structure for one instance of twrtp */
+struct osmo_twrtp {
+ /*! UDP socket for RTP data packets */
+ struct osmo_io_fd *iofd_rtp;
+ /*! UDP socket for RTCP */
+ struct osmo_io_fd *iofd_rtcp;
+ /*! remote IP:port address for RTP data packets */
+ struct osmo_sockaddr rtp_remote;
+ /*! remote IP:port address for RTCP */
+ struct osmo_sockaddr rtcp_remote;
+ /*! count of RTP timestamp units per quantum */
+ uint32_t ts_quantum;
+ /* scaling factors for RTP Tx timestamp computation */
+ /*! RTP timestamp units per second */
+ uint32_t ts_units_per_sec;
+ /*! divisor to go from nanoseconds to RTP timestamp units */
+ uint32_t ns_to_ts_units;
+ /* RTP Rx path: twjit and raw options */
+ /*! twjit instance owned by the present twrtp instance */
+ struct osmo_twjit *twjit;
+ /*! registered callback for unbuffered/non-delayed Rx path */
+ osmo_twrtp_raw_rx_cb raw_rx_cb;
+ /*! user data for \ref raw_rx_cb */
+ void *raw_rx_cb_data;
+ /*! RTP Tx state */
+ struct {
+ /*! random SSRC chosen for RTP output we generate */
+ uint32_t ssrc;
+ /*! output stream current timestamp */
+ uint32_t ts;
+ /*! random addend for RTP timestamps we emit */
+ uint32_t ts_addend;
+ /*! output stream current sequence number */
+ uint16_t seq;
+ /*! we started output with osmo_twrtp_tx_quantum() */
+ bool started;
+ /*! output timestamp needs to be reset discontinuously */
+ bool restart;
+ } tx;
+ /* RTCP info */
+ /*! RTCP Rx state */
+ struct rtcp_rx_state rtcp_rx;
+ /*! RTCP Tx state */
+ struct rtcp_tx_state rtcp_tx;
+ /*! buffer holding SDES packet of RFC 3550 section 6.5 */
+ uint8_t *sdes_buf;
+ /*! length (in bytes) of SDES packet in \ref sdes_buf */
+ uint16_t sdes_len;
+ /*! automatically emit RTCP SR after this many RTP data packets */
+ uint16_t auto_rtcp_interval;
+ /*! current count within \ref auto_rtcp_interval */
+ uint16_t auto_rtcp_count;
+ /*! twrtp-level stats over lifetime of this instance */
+ struct osmo_twrtp_stats stats;
+ /* bool flags at the end for structure packing optimization */
+ /*! \ref rtp_remote and \ref rtcp_remote are valid */
+ bool remote_set;
+ /*! received RTP packets are to be fed to our subordinate twjit */
+ bool twjit_rx_enable;
+};
+
+/* We need to know maximum expected sizes of RTP and RTCP Rx packets
+ * for osmo_io msgb allocation. For RTP, the largest packet size in
+ * 3GPP and IP-PSTN applications is 176 bytes: 12 bytes of RTP header
+ * plus 160 bytes of payload for 20 ms of uncompressed G.711 audio
+ * or CSData. Of course there may be other applications that use
+ * larger RTP packets, in which case we may have to add an API function
+ * that overrides our default msgb alloc size setting - but let's
+ * cross that bridge if and when we actually have such users.
+ *
+ * In case of RTCP, we fully process all received packets inside
+ * the present library, hence we can set osmo_io msgb alloc size
+ * based on what our RTCP Rx code can parse and make use of. Any
+ * additional RTCP Rx data, such as very long SDES strings, will
+ * simply be truncated at osmo_io level - but the subsequent parsing
+ * code will never get to those bits anyway.
+ */
+
+#define MAX_RTP_RX_PACKET (sizeof(struct rtp_hdr) + 160)
+#define MAX_RTCP_RX_PACKET (sizeof(struct rtcp_sr_rr_hdr) + \
+ sizeof(struct rtcp_sr_block) + \
+ sizeof(struct rtcp_rr_block) * 31)
+
+/* RTCP includes NTP timestamps, hence we need to convert from Unix-style
+ * CLOCK_REALTIME into NTP time format. */
+
+#define NTP_EPOCH_MJD 15020
+#define UNIX_EPOCH_MJD 40587
+
+#define NTP_UNIX_EPOCH_DIFF ((UNIX_EPOCH_MJD-NTP_EPOCH_MJD) * 86400UL)
+#define TWO_TO_32_DOUBLE 4294967296.0
+
+/* forward declarations for internal functions */
+
+static void rtp_rx_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg,
+ const struct osmo_sockaddr *saddr);
+static void rtcp_rx_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg,
+ const struct osmo_sockaddr *saddr);
+static int send_rtcp_sr_rr(struct osmo_twrtp *endp, bool send_sr,
+ const struct timespec *utc, uint32_t rtp_ts);
+
+static const struct osmo_io_ops twrtp_iops_rtp = {
+ .recvfrom_cb = rtp_rx_cb,
+};
+
+static const struct osmo_io_ops twrtp_iops_rtcp = {
+ .recvfrom_cb = rtcp_rx_cb,
+};
+
+/*! \endcond */
+
+/*! Create a twrtp endpoint
+ *
+ * \param[in] ctx Parent talloc context under which struct osmo_twrtp
+ * should be allocated.
+ * \param[in] clock_khz RTP clock rate in kHz, i.e., number of RTP timestamp
+ * units per millisecond. The most common value is 8.
+ * \param[in] quantum_ms Duration of a single quantum (unit of speech or data
+ * carried in one RTP packet) in milliseconds. The most common value is 20.
+ * \param[in] random_ts_seq For RTP packets we generate and emit, randomize
+ * not only SSRC for this session, but also the starting timestamp and the
+ * starting sequence number. Pass true to satisfy the SHOULD directive in
+ * RFC 3550 and for feature parity with ortp, or false for ease of debugging.
+ * \param[in] twjit_config If this RTP endpoint is to be equipped with twjit,
+ * pass twjit config structure with tunable parameters here. If a sans-twjit
+ * RTP endpoint is to be created, pass NULL here.
+ * \returns pointer to the newly created twrtp instance, or NULL on errors.
+ *
+ * Every twrtp endpoint is always capable of sending and receiving RTP and RTCP
+ * packets on the IP network, but it may be either twjit-equipped or sans-twjit.
+ * The decision to have or not have a twjit instance as part of a newly created
+ * twrtp instance must be made at the time of creation with this function, by
+ * passing either a valid twjit config structure or NULL as \ref twjit_config.
+ *
+ * Parameters \ref clock_khz and \ref quantum_ms are passed through to twjit
+ * if a twjit instance is created inside the new twrtp instance, but they are
+ * mandatory even if no twjit instance is to be created. With or without twjit,
+ * these parameters are used for locally generated RTP packets, i.e., those
+ * emitted via osmo_twrtp_tx_quantum() as opposed to osmo_twrtp_tx_forward().
+ */
+struct osmo_twrtp *
+osmo_twrtp_create(void *ctx, uint16_t clock_khz, uint16_t quantum_ms,
+ bool random_ts_seq,
+ const struct osmo_twjit_config *twjit_config)
+{
+ struct osmo_twrtp *endp;
+
+ endp = talloc_zero(ctx, struct osmo_twrtp);
+ if (!endp)
+ return NULL;
+
+ if (twjit_config) {
+ endp->twjit = osmo_twjit_create(endp, clock_khz, quantum_ms,
+ twjit_config);
+ if (!endp->twjit) {
+ talloc_free(endp);
+ return NULL;
+ }
+ }
+
+ endp->ts_quantum = (uint32_t) quantum_ms * clock_khz;
+ endp->ts_units_per_sec = (uint32_t) clock_khz * 1000;
+ endp->ns_to_ts_units = 1000000 / clock_khz;
+
+ endp->tx.ssrc = random();
+ if (random_ts_seq) {
+ endp->tx.ts_addend = random();
+ endp->tx.seq = random();
+ }
+
+ return endp;
+}
+
+/*! Destroy a twrtp endpoint
+ *
+ * \param[in] endp Instance (endpoint) to be freed
+ */
+void osmo_twrtp_destroy(struct osmo_twrtp *endp)
+{
+ if (!endp)
+ return;
+ if (endp->iofd_rtp)
+ osmo_iofd_free(endp->iofd_rtp);
+ if (endp->iofd_rtcp)
+ osmo_iofd_free(endp->iofd_rtcp);
+ if (endp->twjit)
+ osmo_twjit_destroy(endp->twjit);
+ talloc_free(endp);
+}
+
+/*! Equip twrtp endpoint with RTP and RTCP sockets (supplied file descriptors)
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] rtp_fd OS file descriptor for UDP socket for RTP
+ * \param[in] rtcp_fd OS file descriptor for UDP socket for RTCP
+ * \returns 0 if successful, negative on errors
+ *
+ * This function equips a newly created twrtp endpoint with file descriptors
+ * for RTP and RTCP sockets. Most applications will use the high-level API
+ * osmo_twrtp_bind_local() that creates and binds the right type of sockets,
+ * then calls the present function - however, some applications may call this
+ * function directly. In Themyscira Wireless CN environment, there is a
+ * separate daemon process that manages the pool of local UDP ports for
+ * RTP+RTCP pairs, and that daemon passes allocated sockets to its clients
+ * via UNIX domain socket file descriptor passing mechanism - hence twrtp layer
+ * must have a public API that takes in already-bound file descriptor pairs.
+ *
+ * This function always "consumes" the two file descriptors that are passed
+ * to it. If the operation succeeds, each of these fds becomes wrapped in
+ * an osmo_io_fd subordinate to struct osmo_twrtp, and both will eventually
+ * be closed upon osmo_twrtp_destroy(). OTOH, if the present function fails,
+ * it closes both fds before returning its error indication. The latter
+ * behavior may seem wrong, but it is more convenient for all current users,
+ * and consistent with twrtp-native and twrtp-proto versions. If we get a
+ * user application that prefers the other alternative (keeping the fds intact
+ * on EBUSY or if osmo_iofd_setup() or osmo_iofd_register() operations fail),
+ * we can create another variant of this API with that alternative behavior.
+ *
+ * It is also possible to pass -1 as \ref rtcp_fd - in this case the twrtp
+ * endpoint will operate with only an RTP socket, and no ability to send
+ * or receive RTCP.
+ */
+int osmo_twrtp_supply_fds(struct osmo_twrtp *endp, int rtp_fd, int rtcp_fd)
+{
+ int rc;
+
+ if (endp->iofd_rtp) {
+ rc = -EBUSY;
+ goto close_both_ret;
+ }
+
+ endp->iofd_rtp = osmo_iofd_setup(endp, -1, NULL,
+ OSMO_IO_FD_MODE_RECVFROM_SENDTO,
+ &twrtp_iops_rtp, endp);
+ if (!endp->iofd_rtp) {
+ rc = -EIO;
+ goto close_both_ret;
+ }
+ osmo_iofd_set_alloc_info(endp->iofd_rtp, MAX_RTP_RX_PACKET, 0);
+ rc = osmo_iofd_register(endp->iofd_rtp, rtp_fd);
+ if (rc < 0) {
+ osmo_iofd_free(endp->iofd_rtp);
+ endp->iofd_rtp = NULL;
+ goto close_both_ret;
+ }
+
+ if (rtcp_fd >= 0) {
+ endp->iofd_rtcp = osmo_iofd_setup(endp, -1, NULL,
+ OSMO_IO_FD_MODE_RECVFROM_SENDTO,
+ &twrtp_iops_rtcp, endp);
+ if (!endp->iofd_rtcp) {
+ osmo_iofd_free(endp->iofd_rtp);
+ endp->iofd_rtp = NULL;
+ rc = -EIO;
+ goto close_rtcp_ret;
+ }
+ osmo_iofd_set_alloc_info(endp->iofd_rtcp, MAX_RTCP_RX_PACKET, 0);
+ rc = osmo_iofd_register(endp->iofd_rtcp, rtcp_fd);
+ if (rc < 0) {
+ osmo_iofd_free(endp->iofd_rtp);
+ osmo_iofd_free(endp->iofd_rtcp);
+ endp->iofd_rtp = NULL;
+ endp->iofd_rtcp = NULL;
+ goto close_rtcp_ret;
+ }
+ }
+
+ /* endp->iofd_rtp and possibly endp->iofd_rtcp becoming set
+ * is how subsequent API functions will know that this step
+ * has completed successfully, and also prevent future fd
+ * changes with EBUSY. */
+ return 0;
+
+close_both_ret:
+ close(rtp_fd);
+close_rtcp_ret:
+ if (rtcp_fd >= 0)
+ close(rtcp_fd);
+ return rc;
+}
+
+/*! Equip twrtp endpoint with locally bound RTP and RTCP sockets
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] rtp_addr IP:port address to be bound locally for RTP socket;
+ * the corresponding address for RTCP (port increment by 1) will be derived
+ * internally.
+ * \param[in] bind_rtcp Create and bind sockets for both RTP and RTCP if true,
+ * or for RTP only if false.
+ * \returns 0 if successful, negative on errors
+ *
+ * This function creates a pair of UDP sockets of the right address family
+ * (IPv4 or IPv6) for RTP and RTCP, binds them locally and installs them
+ * in the twrtp endpoint. (Or just one UDP socket for RTP, if \ref bind_rtcp
+ * is false.) Either the present API or the lower-level alternative
+ * osmo_twrtp_supply_fds() must be called after osmo_twrtp_create() in order
+ * for the endpoint to become functional, but neither function can be used
+ * again once this step is done.
+ */
+int osmo_twrtp_bind_local(struct osmo_twrtp *endp,
+ const struct osmo_sockaddr *rtp_addr, bool bind_rtcp)
+{
+ struct osmo_sockaddr rtcp_addr;
+ int rtp_fd, rtcp_fd;
+
+ switch (rtp_addr->u.sa.sa_family) {
+ case AF_INET:
+ memcpy(&rtcp_addr, rtp_addr, sizeof(struct sockaddr_in));
+ rtcp_addr.u.sin.sin_port =
+ htons(ntohs(rtp_addr->u.sin.sin_port) + 1);
+ break;
+ case AF_INET6:
+ memcpy(&rtcp_addr, rtp_addr, sizeof(struct sockaddr_in6));
+ rtcp_addr.u.sin6.sin6_port =
+ htons(ntohs(rtp_addr->u.sin6.sin6_port) + 1);
+ break;
+ default:
+ return -EAFNOSUPPORT;
+ }
+
+ rtp_fd = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, rtp_addr, NULL,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK);
+ if (rtp_fd < 0)
+ return rtp_fd;
+
+ if (bind_rtcp) {
+ rtcp_fd = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP,
+ &rtcp_addr, NULL,
+ OSMO_SOCK_F_BIND |
+ OSMO_SOCK_F_NONBLOCK);
+ if (rtcp_fd < 0) {
+ close(rtp_fd);
+ return rtcp_fd;
+ }
+ } else {
+ rtcp_fd = -1;
+ }
+
+ return osmo_twrtp_supply_fds(endp, rtp_fd, rtcp_fd);
+}
+
+static void set_remote_ipv4(struct osmo_twrtp *endp,
+ const struct sockaddr_in *user_addr)
+{
+ memcpy(&endp->rtp_remote.u.sin, user_addr, sizeof(struct sockaddr_in));
+ memcpy(&endp->rtcp_remote.u.sin, user_addr, sizeof(struct sockaddr_in));
+ endp->rtcp_remote.u.sin.sin_port =
+ htons(ntohs(user_addr->sin_port) + 1);
+}
+
+static void set_remote_ipv6(struct osmo_twrtp *endp,
+ const struct sockaddr_in6 *user_addr)
+{
+ memcpy(&endp->rtp_remote.u.sin6, user_addr,
+ sizeof(struct sockaddr_in6));
+ memcpy(&endp->rtcp_remote.u.sin6, user_addr,
+ sizeof(struct sockaddr_in6));
+ endp->rtcp_remote.u.sin6.sin6_port =
+ htons(ntohs(user_addr->sin6_port) + 1);
+}
+
+/*! Set RTP remote address
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] rtp_addr IP:port address to be set as the remote for RTP socket;
+ * the corresponding address for RTCP (port increment by 1) will be derived
+ * internally.
+ * \returns 0 if successful, negative on errors
+ *
+ * This function needs to be called at some point in order for the endpoint
+ * to become functional, but unlike osmo_twrtp_bind_local(), it can be called
+ * again to change the remote address as needed.
+ */
+int osmo_twrtp_set_remote(struct osmo_twrtp *endp,
+ const struct osmo_sockaddr *rtp_addr)
+{
+ switch (rtp_addr->u.sa.sa_family) {
+ case AF_INET:
+ set_remote_ipv4(endp, &rtp_addr->u.sin);
+ break;
+ case AF_INET6:
+ set_remote_ipv6(endp, &rtp_addr->u.sin6);
+ break;
+ default:
+ return -EAFNOSUPPORT;
+ }
+
+ endp->remote_set = true;
+ return 0;
+}
+
+/* RTP Rx path via osmo_io callback */
+
+static void rtp_rx_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg,
+ const struct osmo_sockaddr *saddr)
+{
+ struct osmo_twrtp *endp = osmo_iofd_get_data(iofd);
+
+ if (!msg)
+ return;
+ if (!endp->remote_set) {
+ msgb_free(msg);
+ return;
+ }
+ if (osmo_sockaddr_cmp(saddr, &endp->rtp_remote)) {
+ endp->stats.rx_rtp_badsrc++;
+ msgb_free(msg);
+ return;
+ }
+ endp->stats.rx_rtp_pkt++;
+ if (endp->raw_rx_cb) {
+ bool swallow;
+
+ swallow = endp->raw_rx_cb(endp, endp->raw_rx_cb_data, msg);
+ if (swallow) /* did the cb consume the msgb we fed it? */
+ return;
+ }
+ if (endp->twjit_rx_enable)
+ osmo_twjit_input(endp->twjit, msg);
+ else
+ msgb_free(msg);
+}
+
+/* Rx-related API functions */
+
+/*! Enable or disable Rx via twjit
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] rx_enable Self-explanatory Boolean flag
+ *
+ * This API is valid only for twrtp endpoints that were equipped with twjit
+ * at the time of creation.
+ */
+void osmo_twrtp_twjit_rx_ctrl(struct osmo_twrtp *endp, bool rx_enable)
+{
+ OSMO_ASSERT(endp->twjit);
+ endp->twjit_rx_enable = rx_enable;
+ if (!rx_enable)
+ osmo_twjit_reset(endp->twjit);
+}
+
+/*! Fixed-timing output poll from the twrtp endpoint's twjit buffer
+ *
+ * \param[in] endp Endpoint to poll
+ * \returns pointer to msgb holding a previously received RTP packet that
+ * was successfully mapped to the present quantum in the fixed-timing output,
+ * or NULL if no such packet is available.
+ *
+ * This API is valid only for twrtp endpoints that were equipped with twjit
+ * at the time of creation.
+ */
+struct msgb *osmo_twrtp_twjit_rx_poll(struct osmo_twrtp *endp)
+{
+ return osmo_twjit_output(endp->twjit);
+}
+
+/*! Set callback function for unbuffered/non-delayed Rx path
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] cb The callback function, or NULL to cancel this callback
+ * mechanism.
+ * \param[in] user_data Opaque user data for \ref cb function
+ *
+ * The callback function set with this API will be called from osmo_io Rx
+ * callback path whenever an RTP packet is received. If the callback function
+ * consumes (takes ownership of) the msgb passed to it, it must return true,
+ * otherwise it must return false. If the callback function returns true,
+ * osmo_io Rx processing ends there; if it returns false, twrtp's regular
+ * osmo_io Rx callback path passes the msgb to twjit if this endpoint is
+ * equipped with such and twjit Rx is enabled, or frees the msgb otherwise.
+ *
+ * It is possible to use twjit and this unbuffered/non-delayed Rx path
+ * at the same time. Consider a speech transcoder that supports AMR codec
+ * on the RAN side: such TC will use twjit to feed the incoming RTP stream
+ * to the speech decoder function that runs on fixed timing, but the
+ * non-delayed Rx path can also be used to "peek" at received RTP packets
+ * as they come in and extract the CMR field - to be fed to the speech
+ * encoder element, which is separate from the speech decoder fed via twjit.
+ */
+void osmo_twrtp_set_raw_rx_cb(struct osmo_twrtp *endp, osmo_twrtp_raw_rx_cb cb,
+ void *user_data)
+{
+ endp->raw_rx_cb = cb;
+ endp->raw_rx_cb_data = user_data;
+}
+
+/* RTP Tx path */
+
+static uint32_t gen_timestamp(const struct timespec *now,
+ const struct osmo_twrtp *endp)
+{
+ uint32_t ts;
+
+ ts = now->tv_sec * endp->ts_units_per_sec +
+ now->tv_nsec / endp->ns_to_ts_units;
+ ts += endp->tx.ts_addend;
+ return ts;
+}
+
+/*! Emit RTP packet carrying a locally sourced quantum of speech/data
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] payload The payload to emit in RTP, can be NULL iff
+ * \ref payload_len == 0.
+ * \param[in] payload_len The length of \ref payload in bytes.
+ * \param[in] payload_type The payload type number to be emitted in the
+ * generated RTP packet.
+ * \param[in] marker Value of the M bit to be emitted.
+ * \param[in] auto_marker Automatically set the M bit if the packet we are
+ * emitting is our very first or follows osmo_twrtp_tx_restart().
+ * \param[in] send_rtcp Emit RTCP SR along with this RTP data packet.
+ * \returns 0 if successful, negative on errors.
+ *
+ * The design of the library assumes that RTP payloads sent out via this API
+ * originate from a fixed timing system such as GSM Um TCH, T1/E1 TDM or a
+ * software application driven by a CLOCK_MONOTONIC timerfd, such that once
+ * the application calls the present function, subsequent calls to the same
+ * will follow every 20 ms (or whatever other quantum duration is set at the
+ * time of osmo_twrtp_create()) without fail.
+ *
+ * The M bit will be set in the generated RTP packet if \ref marker argument
+ * is true OR if \ref auto_marker is true and the conditions for automatic
+ * marker setting are met.
+ *
+ * RTCP SR packets are emitted by the endpoint only as a result of this
+ * function being called, and not along any other path. An RTCP SR will be
+ * emitted if \ref send_rtcp argument is true OR if automatic RTCP SR
+ * generation was enabled with osmo_twrtp_set_auto_rtcp_interval() and
+ * it is time to emit RTCP SR per the count of emitted RTP data packets.
+ */
+int osmo_twrtp_tx_quantum(struct osmo_twrtp *endp, const uint8_t *payload,
+ unsigned payload_len, uint8_t payload_type,
+ bool marker, bool auto_marker, bool send_rtcp)
+{
+ struct msgb *msg;
+ struct timespec now;
+ uint32_t restart_ts;
+ int32_t ts_delta;
+ struct rtp_hdr *rtph;
+ uint8_t *pl_out;
+ int rc;
+
+ if (!endp->iofd_rtp || !endp->remote_set)
+ return -EINVAL;
+ msg = msgb_alloc_c(endp, sizeof(struct rtp_hdr) + payload_len,
+ "ThemWi-RTP-Tx");
+ if (!msg) {
+ osmo_twrtp_tx_skip(endp);
+ return -ENOMEM;
+ }
+
+ /* timestamp generation is where we do some trickery */
+ osmo_clock_gettime(CLOCK_REALTIME, &now);
+ if (!endp->tx.started) {
+ endp->tx.ts = gen_timestamp(&now, endp);
+ endp->tx.started = true;
+ endp->tx.restart = false;
+ if (auto_marker)
+ marker = true;
+ } else if (endp->tx.restart) {
+ restart_ts = gen_timestamp(&now, endp);
+ ts_delta = (int32_t)(restart_ts - endp->tx.ts);
+ if (ts_delta <= 0) {
+ /* shouldn't happen, unless something funky w/clock */
+ endp->tx.ts++;
+ } else {
+ if (ts_delta % endp->ts_quantum == 0)
+ restart_ts++;
+ endp->tx.ts = restart_ts;
+ }
+ endp->tx.restart = false;
+ if (auto_marker)
+ marker = true;
+ }
+
+ rtph = (struct rtp_hdr *) msgb_put(msg, sizeof(struct rtp_hdr));
+ rtph->version = RTP_VERSION;
+ rtph->padding = 0;
+ rtph->extension = 0;
+ rtph->csrc_count = 0;
+ rtph->marker = marker;
+ rtph->payload_type = payload_type;
+ rtph->sequence = htons(endp->tx.seq);
+ rtph->timestamp = htonl(endp->tx.ts);
+ rtph->ssrc = htonl(endp->tx.ssrc);
+ pl_out = msgb_put(msg, payload_len);
+ memcpy(pl_out, payload, payload_len);
+ endp->tx.seq++;
+ endp->tx.ts += endp->ts_quantum;
+
+ rc = osmo_iofd_sendto_msgb(endp->iofd_rtp, msg, 0, &endp->rtp_remote);
+ if (rc < 0) {
+ msgb_free(msg);
+ return rc;
+ }
+ endp->stats.tx_rtp_pkt++;
+ endp->stats.tx_rtp_bytes += payload_len;
+
+ if (endp->auto_rtcp_interval) {
+ endp->auto_rtcp_count++;
+ if (endp->auto_rtcp_count >= endp->auto_rtcp_interval) {
+ endp->auto_rtcp_count = 0;
+ send_rtcp = true;
+ }
+ }
+ if (send_rtcp) {
+ send_rtcp_sr_rr(endp, true, &now,
+ endp->tx.ts - endp->ts_quantum);
+ }
+
+ return 0;
+}
+
+/*! Incur an intentional gap in the emitted RTP stream
+ *
+ * \param[in] endp Endpoint to operate on
+ *
+ * Many RTP profiles call for behavior where a stream sender incurs an
+ * intentional gap in its output (does not emit the otherwise-expected
+ * RTP packet for a given timestamp in the expected cadence of timestamp
+ * quantum increments) if the corresponding quantum carries speech silence,
+ * or if the data source has errors. Such operation is non-native to
+ * twrtp and generally recommended against (it creates an adverse condition
+ * for twjit on the receiving end), but the library provides mechanism
+ * rather than policy, hence the ability to incur intentional gaps is
+ * supported. This function advances the output timestamp by one quantum,
+ * thereby creating the requested intentional gap.
+ */
+void osmo_twrtp_tx_skip(struct osmo_twrtp *endp)
+{
+ if (!endp->tx.started || endp->tx.restart)
+ return;
+ endp->tx.ts += endp->ts_quantum;
+}
+
+/*! Reset output stream cadence
+ *
+ * \param[in] endp Endpoint to operate on
+ *
+ * This function needs to be called if the application wishes to restart
+ * or resume output after it previously stopped calling osmo_twrtp_tx_quantum()
+ * or osmo_twrtp_tx_skip() every 20 ms, and it should also be called if
+ * the cadence of quantum-sized timestamp increments needs to be broken
+ * for some reason.
+ */
+void osmo_twrtp_tx_restart(struct osmo_twrtp *endp)
+{
+ endp->tx.restart = true;
+}
+
+/*! Forward RTP packet between endpoints
+ *
+ * \param[in] endp Endpoint on which the packet should be sent out
+ * \param[in] msg RTP packet received from another endpoint
+ * \returns 0 if successful, negative on errors
+ *
+ * If an application needs to forward RTP packets from one endpoint to another
+ * without buffering delay, it should call the present function from the
+ * callback registered on the other endpoint with osmo_twrtp_set_raw_rx_cb().
+ *
+ * This function always consumes the msgb passed to it - if the sending
+ * operation fails, the msgb is freed here.
+ */
+int osmo_twrtp_tx_forward(struct osmo_twrtp *endp, struct msgb *msg)
+{
+ int rc;
+
+ if (!endp->iofd_rtp || !endp->remote_set) {
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ rc = osmo_iofd_sendto_msgb(endp->iofd_rtp, msg, 0, &endp->rtp_remote);
+ if (rc < 0)
+ msgb_free(msg);
+ return rc;
+}
+
+/* RTCP Rx path via osmo_io callback */
+
+static void parse_rtcp(struct osmo_twrtp *endp, struct msgb *msg)
+{
+ struct rtcp_rx_state *rxs = &endp->rtcp_rx;
+ struct rtcp_sr_rr_hdr *base_hdr;
+ struct rtcp_sr_block *sr;
+ struct rtcp_rr_block *rr;
+ unsigned rc, i;
+
+ if (msg->len < sizeof(struct rtcp_sr_rr_hdr)) {
+invalid: endp->stats.rx_rtcp_invalid++;
+ return;
+ }
+ base_hdr = (struct rtcp_sr_rr_hdr *) msg->data;
+ msgb_pull(msg, sizeof(struct rtcp_sr_rr_hdr));
+ if ((base_hdr->v_p_rc & 0xC0) != 0x80)
+ goto invalid;
+ switch (base_hdr->pt) {
+ case RTCP_PT_SR:
+ if (msg->len < sizeof(struct rtcp_sr_block))
+ goto invalid;
+ sr = (struct rtcp_sr_block *) msg->data;
+ msgb_pull(msg, sizeof(struct rtcp_sr_block));
+ rxs->got_sr = true;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &rxs->sr_rx_time);
+ rxs->sr_ssrc = ntohl(base_hdr->ssrc);
+ rxs->sr_ntp_sec = ntohl(sr->ntp_sec);
+ rxs->sr_ntp_fract = ntohl(sr->ntp_fract) >> 16;
+ break;
+ case RTCP_PT_RR:
+ break;
+ default:
+ goto invalid;
+ }
+ rc = base_hdr->v_p_rc & 0x1F;
+ if (msg->len < sizeof(struct rtcp_rr_block) * rc)
+ goto invalid;
+ for (i = 0; i < rc; i++) {
+ rr = (struct rtcp_rr_block *) msg->data;
+ msgb_pull(msg, sizeof(struct rtcp_rr_block));
+ if (ntohl(rr->ssrc) != endp->tx.ssrc) {
+ endp->stats.rx_rtcp_wrong_ssrc++;
+ continue;
+ }
+ rxs->got_rr = true;
+ rxs->rr_lost_word = ntohl(rr->lost_word);
+ rxs->rr_jitter = ntohl(rr->jitter);
+ if (rxs->rr_jitter > rxs->rr_jitter_max)
+ rxs->rr_jitter_max = rxs->rr_jitter;
+ }
+}
+
+static void rtcp_rx_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg,
+ const struct osmo_sockaddr *saddr)
+{
+ struct osmo_twrtp *endp = osmo_iofd_get_data(iofd);
+
+ if (!msg)
+ return;
+ if (!endp->remote_set) {
+ msgb_free(msg);
+ return;
+ }
+ if (osmo_sockaddr_cmp(saddr, &endp->rtcp_remote)) {
+ endp->stats.rx_rtcp_badsrc++;
+ msgb_free(msg);
+ return;
+ }
+ endp->stats.rx_rtcp_pkt++;
+ parse_rtcp(endp, msg);
+ msgb_free(msg);
+}
+
+/* RTCP Tx functions */
+
+static void fill_rr_block(struct osmo_twrtp *endp, struct rtcp_rr_block *rr)
+{
+ struct osmo_twjit *twjit = endp->twjit;
+ const struct osmo_twjit_rr_info *rri = osmo_twjit_get_rr_info(twjit);
+ const struct rtcp_rx_state *rxs = &endp->rtcp_rx;
+ struct rtcp_tx_state *txs = &endp->rtcp_tx;
+ uint32_t delta_expect, delta_rcvd;
+ int32_t cumulative_lost, newly_lost;
+ uint32_t lost_fract, lost_word;
+ struct timespec now, time_delta;
+
+ cumulative_lost = (int32_t)(rri->expected_pkt - rri->rx_packets);
+ if (cumulative_lost > 0x7FFFFF)
+ cumulative_lost = 0x7FFFFF;
+ else if (cumulative_lost < -0x800000)
+ cumulative_lost = -0x800000;
+ delta_expect = rri->expected_pkt - txs->last_expected;
+ txs->last_expected = rri->expected_pkt;
+ delta_rcvd = rri->rx_packets - txs->last_received;
+ txs->last_received = rri->rx_packets;
+ newly_lost = (int32_t)(delta_expect - delta_rcvd);
+ if (delta_expect == 0 || newly_lost <= 0)
+ lost_fract = 0;
+ else
+ lost_fract = (newly_lost << 8) / delta_expect;
+ lost_word = (lost_fract << 8) | (cumulative_lost & 0xFFFFFF);
+
+ rr->ssrc = htonl(rri->ssrc);
+ rr->lost_word = htonl(lost_word);
+ rr->max_seq_ext = htonl(rri->max_seq_ext);
+ rr->jitter = htonl(rri->jitter_accum >> 4);
+
+ if (rxs->got_sr && rxs->sr_ssrc == rri->ssrc) {
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ time_delta.tv_sec = now.tv_sec - rxs->sr_rx_time.tv_sec;
+ time_delta.tv_nsec = now.tv_nsec - rxs->sr_rx_time.tv_nsec;
+ if (time_delta.tv_nsec < 0) {
+ time_delta.tv_sec--;
+ time_delta.tv_nsec += 1000000000;
+ }
+ rr->lsr_sec = htons(rxs->sr_ntp_sec);
+ rr->lsr_fract = htons(rxs->sr_ntp_fract);
+ rr->dlsr_sec = htons(time_delta.tv_sec);
+ rr->dlsr_fract = htons(time_delta.tv_nsec / 1000000000.0f *
+ 65536.0f);
+ } else {
+ rr->lsr_sec = 0;
+ rr->lsr_fract = 0;
+ rr->dlsr_sec = 0;
+ rr->dlsr_fract = 0;
+ }
+}
+
+static int send_rtcp_sr_rr(struct osmo_twrtp *endp, bool send_sr,
+ const struct timespec *utc, uint32_t rtp_ts)
+{
+ bool send_rr = false;
+ struct msgb *msg;
+ struct rtcp_sr_rr_hdr *hdr;
+ struct rtcp_sr_block *sr;
+ struct rtcp_rr_block *rr;
+ uint8_t *sdes_out;
+ int rc;
+
+ if (!endp->iofd_rtcp || !endp->remote_set || !endp->sdes_buf)
+ return -EINVAL;
+ if (endp->twjit && osmo_twjit_rr_info_valid(endp->twjit))
+ send_rr = true;
+ if (!send_sr && !send_rr)
+ return -ENODATA; /* nothing to send, neither SR nor RR */
+ msg = msgb_alloc_c(endp, sizeof(struct rtcp_sr_rr_hdr) +
+ sizeof(struct rtcp_sr_block) +
+ sizeof(struct rtcp_rr_block) + endp->sdes_len,
+ "ThemWi-RTCP-Tx");
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = (struct rtcp_sr_rr_hdr *)
+ msgb_put(msg, sizeof(struct rtcp_sr_rr_hdr));
+ hdr->v_p_rc = send_rr ? 0x81 : 0x80;
+ if (send_sr) {
+ hdr->pt = RTCP_PT_SR;
+ hdr->len = htons(send_rr ? 12 : 6);
+ } else {
+ hdr->pt = RTCP_PT_RR;
+ hdr->len = htons(7);
+ }
+ hdr->ssrc = htonl(endp->tx.ssrc);
+ if (send_sr) {
+ sr = (struct rtcp_sr_block *)
+ msgb_put(msg, sizeof(struct rtcp_sr_block));
+ sr->ntp_sec = htonl(utc->tv_sec + NTP_UNIX_EPOCH_DIFF);
+ sr->ntp_fract = htonl(utc->tv_nsec / 1000000000.0 *
+ TWO_TO_32_DOUBLE);
+ sr->rtp_ts = htonl(rtp_ts);
+ sr->pkt_count = htonl(endp->stats.tx_rtp_pkt);
+ sr->octet_count = htonl(endp->stats.tx_rtp_bytes);
+ }
+ if (send_rr) {
+ rr = (struct rtcp_rr_block *)
+ msgb_put(msg, sizeof(struct rtcp_rr_block));
+ fill_rr_block(endp, rr);
+ }
+ sdes_out = msgb_put(msg, endp->sdes_len);
+ memcpy(sdes_out, endp->sdes_buf, endp->sdes_len);
+
+ rc = osmo_iofd_sendto_msgb(endp->iofd_rtcp, msg, 0, &endp->rtcp_remote);
+ if (rc < 0) {
+ msgb_free(msg);
+ return rc;
+ }
+ endp->stats.tx_rtcp_pkt++;
+ return 0;
+}
+
+/*! Emit RTCP RR packet
+ *
+ * \param[in] endp Endpoint to operate on
+ * \returns 0 if successful, negative on errors
+ *
+ * This function is safe to call on any twrtp endpoint, but it will actually
+ * result in an RTCP RR packet being emitted only if (1) the twrtp endpoint
+ * is equipped with twjit and (2) some RTP data packets have been successfully
+ * received and header-decoded. If these conditions aren't met, no RTCP
+ * packet will be emitted and the function will return -ENODATA.
+ *
+ * This API is rarely needed: in most RTCP-enabled RTP applications,
+ * it is more useful to enable automatic SR generation with
+ * osmo_twrtp_set_auto_rtcp_interval(), in which case the library will emit
+ * RTCP SR packets that also include the same reception report block as
+ * those standalone RR packets that are emitted by the present function.
+ * However, the present API is provided in case an application receives
+ * RTP traffic via twrtp+twjit, but does not emit any RTP traffic of its
+ * own - in this case only RTCP RR can be generated, not SR.
+ */
+int osmo_twrtp_send_rtcp_rr(struct osmo_twrtp *endp)
+{
+ return send_rtcp_sr_rr(endp, false, NULL, 0);
+}
+
+/*! Configure automatic emission of periodic RTCP SR packets
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] interval Automatically emit RTCP SR after this many RTP data
+ * packets, or 0 to turn off this mechanism.
+ */
+void osmo_twrtp_set_auto_rtcp_interval(struct osmo_twrtp *endp,
+ uint16_t interval)
+{
+ endp->auto_rtcp_interval = interval;
+}
+
+/*! Set SDES strings for RTCP SR and RR packet generation
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] cname Per RFC 3550 section 6.5.1
+ * \param[in] name Per RFC 3550 section 6.5.2
+ * \param[in] email Per RFC 3550 section 6.5.3
+ * \param[in] phone Per RFC 3550 section 6.5.4
+ * \param[in] loc Per RFC 3550 section 6.5.5
+ * \param[in] tool Per RFC 3550 section 6.5.6
+ * \param[in] note Per RFC 3550 section 6.5.7
+ * \returns 0 if successful, negative on errors
+ *
+ * RFC 3550 section 6.1 stipulates that every RTCP SR or RR packet also
+ * needs to include an SDES block, containing at least a CNAME string.
+ * The present function sets the full complement of SDES strings: the
+ * mandatory CNAME string and 6 optional ones per RFC 3550. This function
+ * must be called successfully before any RTCP SR or RR packets will be
+ * emitted.
+ */
+int osmo_twrtp_set_sdes(struct osmo_twrtp *endp, const char *cname,
+ const char *name, const char *email, const char *phone,
+ const char *loc, const char *tool, const char *note)
+{
+ uint16_t len_str, len_padded, len_with_hdr, len;
+ struct rtcp_sr_rr_hdr *hdr;
+ uint8_t *dp;
+
+ if (!cname)
+ return -EINVAL;
+ len_str = strlen(cname) + 2;
+ if (name)
+ len_str += strlen(name) + 2;
+ if (email)
+ len_str += strlen(email) + 2;
+ if (phone)
+ len_str += strlen(phone) + 2;
+ if (loc)
+ len_str += strlen(loc) + 2;
+ if (tool)
+ len_str += strlen(tool) + 2;
+ if (note)
+ len_str += strlen(note) + 2;
+ len_padded = (len_str + 4) & ~3;
+ len_with_hdr = len_padded + sizeof(struct rtcp_sr_rr_hdr);
+
+ if (endp->sdes_buf)
+ talloc_free(endp->sdes_buf);
+ endp->sdes_buf = talloc_size(endp, len_with_hdr);
+ if (!endp->sdes_buf)
+ return -ENOMEM;
+
+ hdr = (struct rtcp_sr_rr_hdr *) endp->sdes_buf;
+ hdr->v_p_rc = 0x81;
+ hdr->pt = RTCP_PT_SDES;
+ hdr->len = htons(len_with_hdr / 4 - 1);
+ hdr->ssrc = htonl(endp->tx.ssrc);
+ dp = endp->sdes_buf + sizeof(struct rtcp_sr_rr_hdr);
+ *dp++ = SDES_ITEM_CNAME;
+ *dp++ = len = strlen(cname);
+ memcpy(dp, cname, len);
+ dp += len;
+ if (name) {
+ *dp++ = SDES_ITEM_NAME;
+ *dp++ = len = strlen(name);
+ memcpy(dp, name, len);
+ dp += len;
+ }
+ if (email) {
+ *dp++ = SDES_ITEM_EMAIL;
+ *dp++ = len = strlen(email);
+ memcpy(dp, email, len);
+ dp += len;
+ }
+ if (phone) {
+ *dp++ = SDES_ITEM_PHONE;
+ *dp++ = len = strlen(phone);
+ memcpy(dp, phone, len);
+ dp += len;
+ }
+ if (loc) {
+ *dp++ = SDES_ITEM_LOC;
+ *dp++ = len = strlen(loc);
+ memcpy(dp, loc, len);
+ dp += len;
+ }
+ if (tool) {
+ *dp++ = SDES_ITEM_TOOL;
+ *dp++ = len = strlen(tool);
+ memcpy(dp, tool, len);
+ dp += len;
+ }
+ if (note) {
+ *dp++ = SDES_ITEM_NOTE;
+ *dp++ = len = strlen(note);
+ memcpy(dp, note, len);
+ dp += len;
+ }
+ memset(dp, 0, len_padded - len_str);
+
+ endp->sdes_len = len_with_hdr;
+ return 0;
+}
+
+/* retrieving info from reception reports we got from the peer */
+
+/*! Have we received any RTCP RR?
+ *
+ * \param[in] endp Endpoint to query
+ * \returns true if at least one reception report block has been received
+ * whose SSRC matches that of our locally generated RTP output,
+ * false otherwise.
+ */
+bool osmo_twrtp_got_rtcp_rr(struct osmo_twrtp *endp)
+{
+ return endp->rtcp_rx.got_rr;
+}
+
+/*! Info from received RTCP RR: lost packets word
+ *
+ * \param[in] endp Endpoint to query
+ * \returns lost packets word from the most recently received RR block.
+ *
+ * This API returns the 32-bit word from received RTCP RR in its raw form,
+ * exactly as it appears in RFC 3550 section 6.4.1: fraction lost in the
+ * upper 8 bits, cumulative number of packets lost in the lower 24 bits.
+ *
+ * If no RTCP RR has been received, this function returns 0.
+ */
+uint32_t osmo_twrtp_rr_lost_word(struct osmo_twrtp *endp)
+{
+ return endp->rtcp_rx.rr_lost_word;
+}
+
+/*! Info from received RTCP RR: cumulative number of packets lost
+ *
+ * \param[in] endp Endpoint to query
+ * \returns "cumulative number of packets lost" value from the most recently
+ * received RR block, extracted and converted to a proper signed type. This
+ * number can be negative because of the way it is defined in RFC 3550.
+ *
+ * If no RTCP RR has been received, this function returns 0.
+ */
+int32_t osmo_twrtp_rr_lost_cumulative(struct osmo_twrtp *endp)
+{
+ int32_t lost_count;
+
+ lost_count = endp->rtcp_rx.rr_lost_word & 0xFFFFFF;
+ if (lost_count & 0x800000)
+ lost_count |= 0xFF000000;
+ return lost_count;
+}
+
+/*! Info from received RTCP RR: interarrival jitter, most recent
+ *
+ * \param[in] endp Endpoint to query
+ * \returns "interarrival jitter" value from the most recently received
+ * RR block.
+ */
+uint32_t osmo_twrtp_rr_jitter_last(struct osmo_twrtp *endp)
+{
+ return endp->rtcp_rx.rr_jitter;
+}
+
+/*! Info from received RTCP RR: interarrival jitter, highest received
+ *
+ * \param[in] endp Endpoint to query
+ * \returns "interarrival jitter" value from received RR blocks, the highest
+ * value that was received in this session.
+ */
+uint32_t osmo_twrtp_rr_jitter_max(struct osmo_twrtp *endp)
+{
+ return endp->rtcp_rx.rr_jitter_max;
+}
+
+/* misc functions */
+
+/*! Get twjit from twrtp
+ *
+ * \param[in] endp Endpoint to query
+ * \returns pointer to twjit instance owned by this twrtp endpoint,
+ * or NULL if this twrtp endpoint has no associated twjit.
+ *
+ * The twjit instance made accessible via this function is still owned
+ * by the parent twrtp instance - calling osmo_twjit_destroy() on it
+ * would cause a crash the next time twrtp tries to use it! Safe twjit
+ * APIs are as follows, for dynamic reconfiguration and information
+ * retrieval:
+ *
+ * osmo_twjit_set_config()
+ * osmo_twjit_get_stats()
+ * osmo_twjit_get_rr_info()
+ * osmo_twjit_rr_info_valid()
+ */
+struct osmo_twjit *osmo_twrtp_get_twjit(struct osmo_twrtp *endp)
+{
+ return endp->twjit;
+}
+
+/*! Retrieve lifetime stats from twrtp instance
+ *
+ * \param[in] endp Endpoint to query
+ * \returns pointer to lifetime stats structure
+ *
+ * Note that a twrtp endpoint equipped with twjit has two levels of stats:
+ * there are stats at twrtp level and at twjit level. The present function
+ * retrieves twrtp stats; to get twjit stats, call osmo_twrtp_get_twjit()
+ * followed by osmo_twjit_get_stats().
+ */
+const struct osmo_twrtp_stats *osmo_twrtp_get_stats(struct osmo_twrtp *endp)
+{
+ return &endp->stats;
+}
+
+/*! Retrieve file descriptor for RTP UDP socket
+ *
+ * \param[in] endp Endpoint to query
+ * \returns OS-level file descriptor of the UDP socket used for RTP.
+ *
+ * The file descriptor made accessible via this function is still owned
+ * by the parent twrtp instance - closing it, or otherwise manipulating it
+ * too heavily (e.g., doing kernel-level connect on it) will break the library.
+ */
+int osmo_twrtp_get_rtp_fd(struct osmo_twrtp *endp)
+{
+ if (!endp->iofd_rtp)
+ return -1;
+ return osmo_iofd_get_fd(endp->iofd_rtp);
+}
+
+/*! Retrieve file descriptor for RTCP UDP socket
+ *
+ * \param[in] endp Endpoint to query
+ * \returns OS-level file descriptor of the UDP socket used for RTCP.
+ *
+ * The file descriptor made accessible via this function is still owned
+ * by the parent twrtp instance - closing it, or otherwise manipulating it
+ * too heavily (e.g., doing kernel-level connect on it) will break the library.
+ */
+int osmo_twrtp_get_rtcp_fd(struct osmo_twrtp *endp)
+{
+ if (!endp->iofd_rtcp)
+ return -1;
+ return osmo_iofd_get_fd(endp->iofd_rtcp);
+}
+
+/*! Set DSCP (Differentiated Services Code Point) for emitted RTP and RTCP
+ * packets
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] dscp DSCP value
+ * \returns 0 if successful, negative on errors
+ *
+ * This function exists for feature parity with osmo_ortp: when migrating
+ * from osmo_ortp to osmo_twrtp, use this function in the place of
+ * osmo_rtp_socket_set_dscp().
+ */
+int osmo_twrtp_set_dscp(struct osmo_twrtp *endp, uint8_t dscp)
+{
+ int rc;
+
+ OSMO_ASSERT(endp->iofd_rtp);
+ rc = osmo_sock_set_dscp(osmo_iofd_get_fd(endp->iofd_rtp), dscp);
+ if (rc < 0)
+ return rc;
+ if (!endp->iofd_rtcp)
+ return 0;
+ return osmo_sock_set_dscp(osmo_iofd_get_fd(endp->iofd_rtcp), dscp);
+}
+
+/*! Set socket priority for emitted RTP and RTCP packets
+ *
+ * \param[in] endp Endpoint to operate on
+ * \param[in] prio Socket priority
+ * \returns 0 if successful, negative on errors
+ *
+ * This function exists for feature parity with osmo_ortp: when migrating
+ * from osmo_ortp to osmo_twrtp, use this function in the place of
+ * osmo_rtp_socket_set_priority().
+ */
+int osmo_twrtp_set_socket_prio(struct osmo_twrtp *endp, int prio)
+{
+ int rc;
+
+ OSMO_ASSERT(endp->iofd_rtp);
+ rc = osmo_sock_set_priority(osmo_iofd_get_fd(endp->iofd_rtp), prio);
+ if (rc < 0)
+ return rc;
+ if (!endp->iofd_rtcp)
+ return 0;
+ return osmo_sock_set_priority(osmo_iofd_get_fd(endp->iofd_rtcp), prio);
+}
+
+/*! @} */
--
To view, visit https://gerrit.osmocom.org/c/libosmo-netif/+/39281?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: libosmo-netif
Gerrit-Branch: master
Gerrit-Change-Id: Ib63215aaf13ef8ab8f2e0c8d310164cd5c8824eb
Gerrit-Change-Number: 39281
Gerrit-PatchSet: 9
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
falconia has submitted this change. ( https://gerrit.osmocom.org/c/libosmo-netif/+/39291?usp=email )
Change subject: doc: add twrtp guide document
......................................................................
doc: add twrtp guide document
Change-Id: I90976edfd8c66c2d1c5bc7939e0a49f725a02f3f
---
M .gitignore
A doc/twrtp/README
A doc/twrtp/twrtp-guide.t
M include/osmocom/netif/twjit.h
M include/osmocom/netif/twrtp.h
5 files changed, 2,423 insertions(+), 24 deletions(-)
Approvals:
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
laforge: Looks good to me, approved
diff --git a/.gitignore b/.gitignore
index 74848f5..3526104 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,8 @@
tests/osmux/osmux_test
tests/testsuite.log
+doc/twrtp/twrtp-guide.pdf
+
examples/ipa-stream-client
examples/ipa-stream-server
examples/lapd-over-datagram-network
diff --git a/doc/twrtp/README b/doc/twrtp/README
new file mode 100644
index 0000000..a7dbc58
--- /dev/null
+++ b/doc/twrtp/README
@@ -0,0 +1,22 @@
+Themyscira Wireless RTP endpoint library is a software component that was
+originally developed externally to Osmocom prior to being integrated into
+libosmo-netif, hence its documentation has been done in a different manner
+than is usual in Osmocom.
+
+There is a detailed document titled _Guide to ThemWi RTP endpoint library_;
+its original/official ThemWi version resides here:
+
+https://www.freecalypso.org/TW-doc/twrtp-guide-latest.pdf
+(See TW-doc directory listing for other formats and previous versions.)
+
+There also exists a version of this document whose content has been updated
+to reflect changes to twrtp & twjit made as part of Osmocom integration,
+and which can be edited further in sync with code changes - however, the
+formatting language in which this document is written is still troff.
+twrtp-guide.t is the troff source for the Osmocom-adapted version of twrtp
+guide document; to compile it into readable PDF, run this command:
+
+groff -p -t -ms -Tpdf twrtp-guide.t > twrtp-guide.pdf
+
+groff is classic, old school GNU software that should be easily available
+in any GNU+Linux distribution.
diff --git a/doc/twrtp/twrtp-guide.t b/doc/twrtp/twrtp-guide.t
new file mode 100644
index 0000000..bbe5664
--- /dev/null
+++ b/doc/twrtp/twrtp-guide.t
@@ -0,0 +1,2387 @@
+.\" This document is written in troff language, using -ms macros
+.\" plus pic and tbl preprocessors. The original ThemWi version
+.\" of this document was produced on a MicroVAX 4.3BSD system
+.\" using Quasijarus troff; the present Osmocom-adapted version
+.\" has been modified to format with groff, an implementation of
+.\" troff that is available in most GNU+Linux distributions.
+.nr LL 6.5i
+.TL
+Guide to ThemWi RTP endpoint library (Osmocom version)
+.AU
+Mychaela N. Falconia
+.AI
+Themyscira Wireless
+.\" Any additional authors editing this document in the future,
+.\" please add another stanza of this form:
+.\"
+.\" .AU
+.\" Your name
+.\" .AI
+.\" Your "institution", e.g., sysmocom GmbH or Osmocom Community etc.
+.\"
+.hw hand-over
+.hw time-stamp
+.hw trans-port
+.hw wrap-around
+.NH 1
+Introduction
+.PP
+This document describes the version of Themyscira Wireless RTP endpoint
+library (\fBtwrtp\fP) that is integrated into \&\fClibosmo\-netif\fP.
+This version of \fBtwrtp\fP is a derivative work based on the original
+author's \&\fCtwrtp\-native\fP version, used by ThemWi network elements
+in Osmocom+ThemWi hybrid networks, and this version of the manual
+is a derivative work based on the original \fBtwrtp\fP guide document.
+.NH 2
+Principal function
+.PP
+ThemWi RTP endpoint library, consisting of \%\fC<osmocom/netif/twrtp.h>\fP
+and \%\fC<osmocom/netif/twjit.h>\fP blocks in the present version,
+is intended for interworking between an RTP stream and a fixed timing system
+such as GSM Um interface TCH or T1/E1 TDM. Such interworking consists of two
+fundamental elements:
+.IP \(bu
+In every fixed time quantum, the interworking element receives a unit of
+speech or CSData media from the fixed source and emits an RTP packet
+carrying that quantum of speech or CSData.
+.IP \(bu
+In the opposite direction, the fixed timing system requires a quantum of
+speech or CSData to be fed to it on every tick without fail, yet the
+interworking element has no control over when RTP packets may arrive
+from the IP network. This direction of interworking requires a rather
+complex element called a jitter buffer, an element whose design and
+configuration always involves some trade-offs and compromises.
+.NH 2
+Domain of application
+.PP
+The present library is \fBnot\fP intended to be an all-purpose implementation
+of IETF RFCs 3550 and 3551, supporting all possible RTP use cases as
+envisioned by IETF. Instead it is intended to support RTP \fIas it is used\fP
+in these two specific telecom network environments:
+.IP \(bu
+3GPP networks that use RTP according to TS\ 26.102 and TS\ 48.103;
+.IP \(bu
+The way RTP is used to transport G.711 PSTN traffic across the public
+Internet in what may be colloquially referred to as IP-PSTN.
+.LP
+The two 3GPP specs referenced above prescribe a fixed packetization time
+of 20\ ms for all codecs on AoIP interface. Furthermore, they stipulate
+that:
+.IP \(bu
+In the case of compressed speech transport, each RTP packet carries
+exactly one frame of the speech codec in use;
+.IP \(bu
+In the case of uncompressed G.711 speech or CSData transport, each RTP
+packet carries exactly 160 payload octets (20\ ms worth) of what would
+have been a 64\ kbit/s timeslot in T1/E1 transport.
+.LP
+This fixed-quantum property, namely the property that every RTP packet
+carries exactly one fixed quantum of speech or CSData, where the duration
+of this quantum is known at connection setup time and cannot suddenly
+change from one packet to the next, is required by the present ThemWi
+RTP endpoint library \(em this requirement constitutes a fundamental aspect
+of its architectural design.
+.PP
+An RTP endpoint implementation library that imposes the just-described
+requirement is sufficient for the purpose of building IP-based GSM networks
+that follow 3GPP TS\ 48.103 (the requirements of that spec are in agreement
+with the library constraint), and it is also sufficient for interfacing
+to IP-PSTN by way of common commercial PSTN-via-SIP connectivity providers.
+.PP
+In the case of IP-PSTN, the author of the present library has experience
+only with North American PSTN-via-SIP connectivity providers. In all
+of our operational experience so far, these IP-PSTN connectivity providers
+behave in ways that are fully compatible with the expectations of the
+present RTP library, as long as the following conditions are met:
+.IP \(bu
+No attempt is made to use any codecs other than PCMU or PCMA:
+don't include any other codecs in the SDP offer, and send only SDP answers
+that select either PCMU or PCMA out of the received multi-codec offer.
+.IP \(bu
+No attempt is made to use any other \&\fCptime\fP besides the most common
+industry standard of 20\ ms.
+.LP
+In all operational experience so far, incoming INVITE SDPs indicate either
+\&\fCa=ptime:20\fP or \&\fCa=maxptime:20\fP, and when we indicate
+\&\fCa=ptime:20\fP in all SDPs we send out, the IP-PSTN peer always sends us
+20\ ms RTP packets, as opposed to some other packetization interval which
+would break the fixed-quantum model assumed by the present RTP library.
+.PP
+However, it needs to be acknowledged that the present library is \fBnot\fP
+suitable for general-purpose, IETF-style applications outside of
+``walled garden'' 3GPP networks or the semi-walled environment of
+IP-PSTN with ``well-behaved'' entities: there are many behaviors that
+are perfectly legal per the RFCs, but are not supported by the present
+library. Having a peer send RTP with a packetization interval that is
+different from what we asked for via \&\fCptime\fP attribute is one of those
+behaviors that is allowed by IETF, but not supported by this library.
+.NH 3
+Expectation of continuous streaming
+.PP
+In addition to the just-described requirement for a fixed packetization
+interval, the domain of application for \fBtwrtp\fP is subject to one more
+constraint: our jitter buffer component (\fBtwjit\fP) is designed for
+environments that implement continuous streaming, and may perform suboptimally
+in those that do not.
+.PP
+Continuous streaming is an operational policy under which an RTP endpoint
+\fIalways\fP emits an RTP packet in \fIevery\fP 20\ ms (or whatever other
+packetization interval is used) time window, be it rain or shine, even if
+it has no data to send because nothing was received on the air interface
+(DTX pause on the radio link, reception errors, frame stealing) or because
+E1 TRAU frame decoding failed, etc.
+Continuous streaming may be implemented by sending an RTP packet with a
+zero-length payload when the endpoint has nothing else to send in a given
+quantum time window \(em this method allows any existing RTP payload format
+standard to be operationally modified for continuous streaming.
+There also exist Themyscira-defined enhanced RTP payload formats for GSM
+speech codecs that not only mandate continuous streaming, but additionally
+convey errored frame bits and the Time Alignment Flag in every 20\ ms frame
+position, exactly like TRAU-UL frames in the world of TDM-based GSM.
+.PP
+The opposite of continuous streaming is the practice of intentional gaps.
+Under this operational policy, an RTP endpoint may create intentional gaps
+in the RTP stream it emits, simply by sending no RTP packets at all when
+it deems that there are no useful data to be transmitted.
+An intentional gap is distinguished from packet loss in that the sequence
+number in the RTP header increments by one while the timestamp increments
+by a greater than normal amount.
+Unfortunately for \fBtwjit\fP, intentional gaps in RTP were the design intent
+of IETF.
+Even more unfortunately, this IETF-ism has been canonized
+by 3GPP in TS\ 26.102 and TS\ 48.103 \(em hence those operators who prefer a
+continuous streaming model now have to explicitly deviate from 3GPP
+specifications.
+.PP
+In an Osmocom GSM network, 3GPP-compliant operation with intentional gaps
+is the default \(em however, the operator can switch to continuous streaming
+model
+by setting \%\fCrtp\ continuous\-streaming\fP in OsmoBTS vty configuration.
+.PP
+Fortunately for \fBtwjit\fP, however, the situation is better in the world
+of IP-PSTN, the other RTP environment for which the present library was
+designed.
+At least on North American IP-PSTN and at least when uncompressed PCMU or
+PCMA codecs are used, all PSTN-via-SIP connectivity providers in our
+operational experience so far always emit perfectly continuous RTP streams,
+without any intentional gaps.
+.PP
+If an application uses \fBtwrtp\fP with \fBtwjit\fP to receive an RTP stream
+that incurs intentional gaps, the resulting performance may be acceptable
+or unacceptable depending on additional factors:
+.IP \(bu
+If RTP gaps are incurred only during frame erasure events (radio
+errors or FACCH stealing) without DTX, the resulting \fBtwjit\fP performance
+will most likely still be acceptable for speech applications.
+All transmitted speech frames will still be delivered to the receiver,
+but the frame erasure gap may lengthen or shorten depending on exact jitter
+buffer conditions at the time of the intentional gap in the Tx stream \(em
+in other words, a phase shift may be incurred.
+.IP \(bu
+If RTP stream gaps are enabled in conjunction with DTX, \fBtwjit\fP will not
+be able to receive such a stream according to common expectations.
+When RTP stream gaps are used together with DTX, the stream will typically
+feature occasional single packets of comfort noise update, sent every
+160, 240 or 480 ms depending on the codec, surrounded by gaps.
+When \fBtwjit\fP receives such a stream and the flow-starting fill level
+is set to 2 or greater (the default and usually necessary configuration),
+all of these ``isolated island'' comfort noise update packets will be dropped
+\(em a behavior counter to the way DTX is expected to work.
+.LP
+The take-away is that if an operator wishes to use DTX with \fBtwrtp\fP,
+they need to enable \%\fCrtp\ continuous\-streaming\fP.
+.NH 2
+Configurable quantum duration and time scale
+.LP
+For every RTP stream it handles, the library needs to know two key
+parameters:
+.IP \(bu
+The scale or ``clock rate'' used for RTP timestamps, i.e., how many
+timestamp units equal one millisecond of physical time;
+.IP \(bu
+The ``quantum'' duration in milliseconds.
+.PP
+\fBQuantum\fP is the term used in this RTP endpoint library for the unit
+of speech or CSData carried in one RTP packet. In Kantian philosophy terms,
+a quantum of speech or CSData is the thing-in-itself (a single codec frame,
+or a contiguous chunk of 160 PCM samples grabbed from an ISDN B channel),
+whereas the RTP packet that carries said quantum is one particular transport
+representation of that thing-in-itself.
+.PP
+In most applications of this library (all 3GPP codecs other than AMR-WB,
+and all IP-PSTN applications in our experience so far), the time scale
+is 8000 timestamp units per second (or 8 per millisecond, as it appears
+in the actual APIs) and the time duration of a single quantum is 20\ ms,
+hence one quantum equals 160 timestamp units.
+Both parameters (RTP timestamp clock rate in kHz and the number of ms
+per quantum) are configurable at the time of endpoint creation, allowing
+RTP endpoints for AMR-WB, or perhaps G.711 or CSData applications with
+different packetization times \(em but they cannot be changed later in
+the lifetime of an allocated endpoint.
+.PP
+For ease of exposition, the rest of this document will assume that
+one quantum equals 20\ ms in time or 160 RTP timestamp units. If these
+numbers are different in your application, substitute accordingly.
+.NH 1
+Jitter buffer model
+.PP
+In the interworking direction from incoming RTP to the fixed timing system,
+the latter will poll the RTP endpoint (or more precisely, the jitter buffer
+portion thereof) for a quantum of media every 20\ ms, whenever that quantum
+is required for transmission on GSM Um TCH or for TDM output etc.
+The job of the jitter buffer is to match previously received RTP packets
+to these fixed-timing output polls, while striving to meet these two
+conflicting goals:
+.IP \(bu
+Any time that elapses between an RTP packet being received and its
+payload being passed as a quantum to the fixed timing system constitutes
+added latency \(em which needs to be minimized.
+.IP \(bu
+IP-based transport always involves some jitter: the time delta between
+the receipt of one RTP packet and the arrival of its successor is very
+unlikely to be exactly equal to 20\ ms every time. This jitter may be
+already present in the RTP stream from its source if that source is
+an IP-native BTS that does not pass through E1 Abis and thus exposes
+the inherent jitter of GSM TDMA multiframe structure, but even if
+the source is perfectly timed, some jitter will still be seen on the
+receiving end. Depending on the actual amount of jitter seen in a
+given deployment, it may be necessary to introduce some latency-adding
+buffering in the receiving RTP endpoint \(em otherwise the function of
+interworking to the fixed timing system at the destination will perform
+poorly, as will be seen in the ensuing sections.
+.LP
+This chapter covers the design and operation of \fBtwjit\fP,
+the jitter buffer component of ThemWi RTP endpoint library.
+.NH 2
+Flows and handovers
+.LP
+In \fBtwjit\fP terminology, a single RTP flow is a portion (or the whole)
+of an RTP stream that exhibits the following two key properties:
+.IP \(bu
+All packets have the same SSRC;
+.IP \(bu
+The RTP timestamp increment from each packet to the next always equals
+the fixed quantum duration expressed in timestamp units, i.e., 160
+in most practical applications.
+.LP
+A handover in \fBtwjit\fP terminology is a point in the incoming RTP stream
+at which either of the following events occurs:
+.IP \(bu
+An SSRC change is seen;
+.IP \(bu
+The RTP timestamp advances by an increment that is not an integral multiple
+of the expected fixed quantum duration. In contrast, if an RTP timestamp
+increment is seen that \fIis\fP an integral multiple of the quantum, but that
+integral multiple is more than one, and there is enough buffering in the
+system such that this event is seen before the jitter buffer underruns,
+such events are \fBnot\fP treated as handovers: instead it is assumed to be
+an occurrence of either packet loss or reordering.
+.LP
+A handover in \fBtwjit\fP is thus a transition from one flow to the next.
+This term was adopted because such transitions are expected to occur
+when an RTP stream belonging to a single call switches from one BSS endpoint
+to another (in the same BSS or in a different one) upon radio handover events
+in GSM and other cellular networks, but handovers in \fBtwjit\fP sense can also
+occur in other applications that aren't GSM. For example, if an IP-PSTN peer
+we are conversing with suddenly decides, for its own reasons known only to
+itself, to change its SSRC or jump its RTP output timescale, our \fBtwjit\fP
+instance will treat that event as a handover.
+.NH 2
+Examples of flows
+.PP
+The following drawing depicts the best case scenario of a TDM-native speech or
+CSData stream being transported across an IP network in RTP:
+.KS
+.PS
+# each time line in these drawings is 4i long
+# let's represent each 20 ms interval as 0.4i in the drawing,
+# i.e., each 1 ms is 0.020i long
+
+Tx_line: line -> right 4i
+"RTP Tx:" at Tx_line.start - 0.1,0 rjust
+"time" at Tx_line.end + 0.1,0 ljust
+
+vdist = 0.8i
+move to Tx_line - 0,vdist
+Rx_line: line -> right 4i
+"RTP Rx:" at Rx_line.start - 0.1,0 rjust
+"time" at Rx_line.end + 0.1,0 ljust
+for x = 0.2i to 3.8i by 0.4i do {
+ line from Rx_line + x,0 down 0.1i
+}
+
+for x = 0.2i to 3.4i by 0.4i do {
+ arrow from Tx_line + x,0 down vdist right 0.3i
+}
+.PE
+.ce
+Figure 1: Ideal case
+.KE
+.PP
+In the above figure and other similar drawings that follow, each
+down-and-forward arrow represents an RTP packet: the beginning of each arrow
+on the RTP Tx line is the point in time when that RTP packet is emitted
+by the source endpoint, and the landing point of each arrow on the RTP Rx
+line is the time point when the same packet is received at the destination
+endpoint. The forward horizontal movement of each arrow in the figure is
+the flight time of the corresponding RTP packet through the IP network.
+Tick marks below the RTP Rx time axis represent fixed points in time
+when the destination application polls its \fBtwjit\fP buffer because
+the destination fixed timing system (GSM Um TCH, T1/E1 etc) requires a new
+quantum of media.
+.PP
+Figure\ 1 depicts the ideal scenario: the source endpoint emits RTP packets
+in a perfect 20\ ms cadence without built-in jitter, the flight time through
+the IP network also remains constant from each packet to the next (no jitter
+introduced by IP transport), and these packets arrive in a perfect cadence
+at the receiving endpoint, exactly one packet before each \fBtwjit\fP polling
+instant.
+Let us now consider a more realistic scenario:
+.KS
+.PS
+Tx_line: line -> right 4i
+"RTP Tx:" at Tx_line.start - 0.1,0 rjust
+"time" at Tx_line.end + 0.1,0 ljust
+
+vdist = 0.8i
+move to Tx_line - 0,vdist
+Rx_line: line -> right 4i
+"RTP Rx:" at Rx_line.start - 0.1,0 rjust
+"time" at Rx_line.end + 0.1,0 ljust
+for x = 0.2i to 3.8i by 0.4i do {
+ line from Rx_line + x,0 down 0.1i
+}
+
+Tx0: Tx_line + 0.04i,0
+
+Land0: Rx_line + (0.04i + 0.02i * 26.0),0
+Land1: Land0 + (0.02i * 19.992),0
+Land2: Land1 + (0.02i * 20.522),0
+Land3: Land2 + (0.02i * 19.509),0
+Land4: Land3 + (0.02i * 20.211),0
+Land5: Land4 + (0.02i * 19.741),0
+Land6: Land5 + (0.02i * 25.245),0
+Land7: Land6 + (0.02i * 14.776),0
+Land8: Land7 + (0.02i * 20.007),0
+
+arrow from Tx0 to Land0
+arrow from Tx0 + (0.02i * 20),0 to Land1
+arrow from Tx0 + (0.02i * 40),0 to Land2
+arrow from Tx0 + (0.02i * 60),0 to Land3
+arrow from Tx0 + (0.02i * 80),0 to Land4
+arrow from Tx0 + (0.02i * 100),0 to Land5
+arrow from Tx0 + (0.02i * 120),0 to Land6
+arrow from Tx0 + (0.02i * 140),0 to Land7
+arrow from Tx0 + (0.02i * 160),0 to Land8
+
+"seq # 0x0630" at 1/2 <Tx_line.start, Rx_line.start> + 0.23i,0 rjust
+"seq # 0x0638" at 1/2 <Tx_line.end, Rx_line.end> - 0.45i,0 ljust
+.PE
+.ce
+Figure 2: IP-PSTN realistic scenario, good Internet connection
+.KE
+.PP
+Figure\ 2 above is based on an actual IP-PSTN test call that was made on
+2024-05-14 from the author's interconnection point with BulkVS to a phone
+number on T-Mobile USA (2G). The figure was drawn using these time-of-arrival
+delta numbers from the pcap of that call:
+.TS
+box center;
+c|c|c
+n|n|n.
+From seq # To seq # ToA delta (ms)
+_
+0x0630 0x0631 19.992
+0x0631 0x0632 20.522
+0x0632 0x0633 19.509
+0x0633 0x0634 20.211
+0x0634 0x0635 19.741
+0x0635 0x0636 25.245
+0x0636 0x0637 14.776
+0x0637 0x0638 20.007
+.TE
+.PP
+This small excerpt from the pcap of one particular test call is a
+representative example of this author's general experience with North American
+IP-PSTN. Most of the time the \(*D between arrival times of two successive
+RTP packets is within a few microseconds of the ideal 20\ ms value;
+interarrival jitter spikes up to about 500\ \(*ms are fairly frequent
+(seen a few times every second), but larger spikes (in the range of several
+milliseconds) appear as rare outliers when I look at pcap files from
+short test calls.
+.PP
+In order to draw packet flight diagrams that are intuitively understandable
+by a human reader, we need to know the absolute flight time from the source
+for each depicted packet. In actual operation this absolute flight time
+is unknowable \(em the only available info is the observed cadence of
+time-of-arrival deltas. (The absolute reception time of each packet according
+to the local clock is known of course, but it is of no use without knowing
+the absolute time at which those packets were emitted by the sender.)
+These absolute flight times of packets are furthermore not needed for
+actual operation of jitter buffers \(em the available ToA \(*D information
+is sufficient for jitter buffer design and tuning \(em but they are needed
+for more intuitive understanding by humans.
+Therefore, when we draw packet flight diagrams, we have to factor in some
+arbitrary, made-up number for the ``baseline'' packet flight time under
+ideal conditions: the purely notional ``baseline'' number which, when added
+to the actually observed jitter, equals what we assume to be the true
+flight time of each individual packet.
+In drawing Figure\ 2 above, I set this ``baseline'' delay to 26\ ms:
+one half of the lowest round-trip time I observe now when I ping the
+IPv4 address of the IP-PSTN node I was conversing with in that test call.
+.PP
+In drawing this figure, I also exercised a degree of freedom in choosing
+the arbitrary (not known in advance in real operation) phase shift between
+the arrival time of RTP packets and the receiving entity's fixed time base,
+i.e., the position of fixed-time polling ticks below the RTP Rx time axis
+relative to the times of packet arrival on the same axis.
+The specific phase shift I chose in drawing this figure is one that
+illustrates the effect of this amount of real-world jitter on \fBtwjit\fP
+operation: RTP packet with sequence number 0x0636 arrives just after
+the receiver's polling time instead of just before.
+The significance of this effect will be seen when we examine \fBtwjit\fP
+operation and tuning.
+.PP
+At this point a reader of this paper, seeing that most packets depicted
+in Figure\ 2 exhibit interarrival jitter measured in \(*ms rather than ms,
+with a single occurrence of 5\ ms jitter as a rare outlier, may accuse
+this author of first-world privilege in terms of Internet connection quality.
+So let us consider what the figure would look like with more substantial
+jitter \(em but still below 20\ ms.
+(Why below 20\ ms, you may ask? The answer will be seen shortly.)
+Because such jitter does not occur in the wild where I live,
+I used a made-up dataset for the following figure:
+.KS
+.PS
+# Let's assume each packet flight time consists of a 24 ms fixed component
+# and a random jitter component between 0 and 13 ms: total flight time
+# thus ranges from 24 to 37 ms, random in this range.
+
+Tx_line: line -> right 4i
+"RTP Tx:" at Tx_line.start - 0.1,0 rjust
+"time" at Tx_line.end + 0.1,0 ljust
+
+vdist = 0.8i
+move to Tx_line - 0,vdist
+Rx_line: line -> right 4i
+"RTP Rx:" at Rx_line.start - 0.1,0 rjust
+"time" at Rx_line.end + 0.1,0 ljust
+for x = 0.2i to 3.8i by 0.4i do {
+ line from Rx_line + x,0 down 0.1i
+}
+
+Pkt1: arrow from Tx_line + 0.06,0 down vdist right 0.02i * 24.791
+Pkt2: arrow from Pkt1 + 0.4,0 down vdist right 0.02i * 34.899
+Pkt3: arrow from Pkt2 + 0.4,0 down vdist right 0.02i * 26.518
+Pkt4: arrow from Pkt3 + 0.4,0 down vdist right 0.02i * 29.739
+Pkt5: arrow from Pkt4 + 0.4,0 down vdist right 0.02i * 27.318
+Pkt6: arrow from Pkt5 + 0.4,0 down vdist right 0.02i * 31.820
+Pkt7: arrow from Pkt6 + 0.4,0 down vdist right 0.02i * 26.468
+Pkt8: arrow from Pkt7 + 0.4,0 down vdist right 0.02i * 36.795
+Pkt9: arrow from Pkt8 + 0.4,0 down vdist right 0.02i * 24.051
+.PE
+.ce
+Figure 3: 13\ ms of random jitter
+.KE
+.PP
+In the above figure, each packet flight time was arbitrarily picked in the
+range between 24 and 37 ms, i.e., a ``baseline'' delay of 24\ ms combined
+with 13\ ms of jitter. (The actual flight times for the figure were
+initially drawn from an RNG program, then slightly tweaked by hand to
+get closer to the extremes of the jitter range to be illustrated.)
+Unlike Figure\ 2 which represents a real life occurrence, Figure\ 3 is
+completely made up \(em yet as will be seen later in this paper,
+the same \fBtwjit\fP configuration that is optimal for Figure\ 2 will also
+handle the conditions of Figure\ 3 just as well \(em and the jitter
+is more visible here.
+.PP
+So far we've only considered cases of jitter below 20\ ms, i.e., jitter
+magnitude less than the periodic interval between successive RTP packets.
+A reader ought to ask now: what happens if the jitter exceeds 20\ ms? Before
+we can answer the question of what happens in such cases, let us first
+consider what it means for IP network-induced jitter to exceed the interval
+between successive packets.
+Assuming that successive RTP packets are emitted every 20\ ms by the sender,
+for the receiver to experience interarrival jitter that exceeds 20\ ms,
+the intervening IP network would have to ``bunch up'' packets: the receiving
+end suddenly stops receiving packets when they are expected, then a slew of
+massively delayed packets arrive all at once.
+The following figure is a real life example of such occurrence:
+.KS
+.PS
+Tx_line: line -> right 6i
+"RTP Tx:" at Tx_line.start - 0.1,0 rjust
+"time" at Tx_line.end + 0.1,0 ljust
+
+vdist = 0.8i
+move to Tx_line - 0,vdist
+Rx_line: line -> right 6i
+"RTP Rx:" at Rx_line.start - 0.1,0 rjust
+"time" at Rx_line.end + 0.1,0 ljust
+for x = 0.2i to 5.8i by 0.4i do {
+ line from Rx_line + x,0 down 0.1i
+}
+
+Tx0: Tx_line + 0.1i,0
+
+Land0: Rx_line + (0.1i + 0.02i * 26.0),0
+Land1: Land0 + (0.02i * 19.951),0
+Land2: Land1 + (0.02i * 22.235),0
+Land3: Land2 + (0.02i * 18.341),0
+Land4: Land3 + (0.02i * 19.429),0
+Land5: Land4 + (0.02i * 127.784),0
+Land6: Land5 + (0.02i * 1.542),0
+Land7: Land6 + (0.02i * 3.417),0
+Land8: Land7 + (0.02i * 0.273),0
+Land9: Land8 + (0.02i * 0.440),0
+Land10: Land9 + (0.02i * 0.115),0
+Land11: Land10 + (0.02i * 6.467),0
+Land12: Land11 + (0.02i * 20.013),0
+Land13: Land12 + (0.02i * 20.009),0
+
+arrow from Tx0 to Land0
+arrow from Tx0 + (0.02i * 20),0 to Land1
+arrow from Tx0 + (0.02i * 40),0 to Land2
+arrow from Tx0 + (0.02i * 60),0 to Land3
+arrow from Tx0 + (0.02i * 80),0 to Land4
+arrow from Tx0 + (0.02i * 100),0 to Land5
+arrow from Tx0 + (0.02i * 120),0 to Land6
+arrow from Tx0 + (0.02i * 140),0 to Land7
+arrow from Tx0 + (0.02i * 160),0 to Land8
+arrow from Tx0 + (0.02i * 180),0 to Land9
+arrow from Tx0 + (0.02i * 200),0 to Land10
+arrow from Tx0 + (0.02i * 220),0 to Land11
+arrow from Tx0 + (0.02i * 240),0 to Land12
+arrow from Tx0 + (0.02i * 260),0 to Land13
+
+"seq # 0x0082" at 1/2 <Tx_line.start, Rx_line.start> + 0.28i,0 rjust
+"seq # 0x008F" at 1/2 <Tx_line.end, Rx_line.end> - 0.40i,0 ljust
+.PE
+.ce
+Figure 4: 6 packets bunched together by IP network bottleneck
+.KE
+.PP
+The above figure is based on observed behavior during an experiment performed
+by this author on 2024-03-31, involving a mobile Internet connection (LTE)
+and a Hurricane Electric IPv6 tunnel.
+A WireGuard tunnel was established between the author's laptop and a server;
+the test laptop was connected to the Internet via T-Mobile LTE in this
+experiment, while the server was one that has native IPv4 plus an IPv6 address
+by way of HE 6-in-4 tunnel.
+The WireGuard tunnel was set up using only IPv6 addresses on the outside,
+i.e., the LTE leg saw only IPv6 in this experiment.
+A test stream of RTP packets,
+spaced 20\ ms apart on the sending end, was transmitted inside a WireGuard
+tunnel from the test laptop to the test server; the path of each packet
+was thus as follows:
+.IP \(bu
+WireGuard encapsulation in IPv6 on the sending end (laptop);
+.IP \(bu
+Transport across T-Mobile Internet service (LTE) in IPv6, going
+to the IPv6 address of the Hurricane Electric tunnel;
+.IP \(bu
+Hurricane Electric PoP received each packet on IPv6 and re-emitted it in IPv4
+wrapping, going to the IPv4 address of the author's server;
+.IP \(bu
+The receiving server decapsulated first 6-in-4, then WireGuard.
+.LP
+A similar experiment was performed addressing a different server, one that has
+native IPv6 connectivity in addition to IPv4; the behavior seen in Figure\ 4
+was not seen in that other experiment, leading to the conclusion that the IP
+network bottleneck that occasionally ``bunches together'' a series of
+consecutive RTP packets is an artifact of the HE 6-in-4 tunnel, rather than
+an artifact of mobile Internet access via LTE.
+.PP
+Irrespective of the cause though, Figure\ 4 is a good illustration of what
+happens when buffering delays at an IP network bottleneck significantly exceed
+the spacing interval between successive RTP packets.
+No packet loss occurred in this experiment, i.e., every packet emitted by
+the sending end was \fIeventually\fP received; likewise, no reordering appeared
+at the receiving end: packets were received in the same order in which they
+were emitted by the sender.
+However, if we look at the arrival times of these selected packets
+on the receiving end, we see the following picture \(em the dataset from which
+Figure\ 4 was drawn:
+.TS
+box center;
+c|c|c
+c|c|n.
+From seq # To seq # ToA delta (ms)
+_
+0x0082 0x0083 19.951
+0x0083 0x0084 22.235
+0x0084 0x0085 18.341
+0x0085 0x0086 19.429
+0x0086 0x0087 127.784
+0x0087 0x0088 1.542
+0x0088 0x0089 3.417
+0x0089 0x008A 0.273
+0x008A 0x008B 0.440
+0x008B 0x008C 0.115
+0x008C 0x008D 6.467
+0x008D 0x008E 20.013
+0x008E 0x008F 20.009
+.TE
+.PP
+Generally speaking, IP network behavior in this more adverse environment
+(passing through a leg of consumer mobile Internet) is not much worse than the
+``luxurious'' IP-PSTN environment (server to server, business-grade Internet
+connection on the non-datacenter end) of Figure\ 2: most of the time, ToA \(*D
+from one packet to the next is only a few \(*ms away from the true 20\ ms
+ideal, with occasional jitter spikes of a few ms.
+However, occasionally a more obstinent bottleneck occurs in the IP network path
+that blocks the flow for a much longer duration: in the example I presented
+here, for just over 120\ ms.
+During such suddenly induced pauses, RTP packets coming from the source every
+20\ ms accumulate at the bottleneck, and when that bottleneck clears,
+all queued-up packets are delivered directly back to back, arriving less than
+1\ ms apart, essentially all at once.
+.PP
+In all examples we have considered so far, there has been no packet reordering:
+despite variations in flight time that appear on the receiving end as jitter,
+all RTP packets were received in the same order in which they were emitted
+by the source endpoint.
+Now let us consider what kind of scenarios can result in RTP packets arriving
+out of order.
+So far this author has not observed even one actual occurrence of packet
+reordering \(em apparently it does not happen on IP networks that exist
+in this part of the world, even on consumer LTE \(em hence the following
+analysis will be strictly theoretical.
+Given that the source endpoint steadily emits one packet at a time,
+spaced every 20\ ms, how can these packets arrive in a reversed order? In
+order for packets to arrive out of order, a later-sent packet has to
+experience significantly shorter transit delay than an earlier-sent one,
+such that the later-sent packet ``overtakes'' the earlier-sent one.
+One situation where we can easily imagine such happening is the ``melee''
+shown in Figure\ 4, the spot on the RTP Rx time axis where 6 different arrows,
+representing 6 different RTP packets emitted 20\ ms apart, all arrive at
+essentially the same point in time.
+In our actual experience, such ``bunched together'' packets still arrive in
+the correct order, even if the \(*D in the time of arrival between them is
+only a few \(*ms \(em but we can easily imagine a different implementation
+of the offending IP network element (the one where the bottleneck occurs)
+that ``sprays'' buffered packets out of order when the congestion clears.
+The following drawing is a rework of Figure\ 4, showing what this hypothesized
+behavior would look like:
+.KS
+.PS
+Tx_line: line -> right 6i
+"RTP Tx:" at Tx_line.start - 0.1,0 rjust
+"time" at Tx_line.end + 0.1,0 ljust
+
+vdist = 0.8i
+move to Tx_line - 0,vdist
+Rx_line: line -> right 6i
+"RTP Rx:" at Rx_line.start - 0.1,0 rjust
+"time" at Rx_line.end + 0.1,0 ljust
+for x = 0.2i to 5.8i by 0.4i do {
+ line from Rx_line + x,0 down 0.1i
+}
+
+Tx0: Tx_line + 0.1i,0
+
+Land0: Rx_line + (0.1i + 0.02i * 26.0),0
+Land1: Land0 + (0.02i * 19.951),0
+Land2: Land1 + (0.02i * 22.235),0
+Land3: Land2 + (0.02i * 18.341),0
+Land4: Land3 + (0.02i * 19.429),0
+Land5: Land4 + (0.02i * 127.784),0
+Land6: Land5 + (0.02i * 1.542),0
+Land7: Land6 + (0.02i * 3.417),0
+Land8: Land7 + (0.02i * 0.273),0
+Land9: Land8 + (0.02i * 0.440),0
+Land10: Land9 + (0.02i * 0.115),0
+Land11: Land10 + (0.02i * 6.467),0
+Land12: Land11 + (0.02i * 20.013),0
+Land13: Land12 + (0.02i * 20.009),0
+
+arrow from Tx0 to Land0
+arrow from Tx0 + (0.02i * 20),0 to Land1
+arrow from Tx0 + (0.02i * 40),0 to Land2
+arrow from Tx0 + (0.02i * 60),0 to Land3
+arrow from Tx0 + (0.02i * 80),0 to Land4
+arrow from Tx0 + (0.02i * 100),0 to Land10
+arrow from Tx0 + (0.02i * 120),0 to Land9
+arrow from Tx0 + (0.02i * 140),0 to Land8
+arrow from Tx0 + (0.02i * 160),0 to Land7
+arrow from Tx0 + (0.02i * 180),0 to Land6
+arrow from Tx0 + (0.02i * 200),0 to Land5
+arrow from Tx0 + (0.02i * 220),0 to Land11
+arrow from Tx0 + (0.02i * 240),0 to Land12
+arrow from Tx0 + (0.02i * 260),0 to Land13
+
+"seq # 0x0082" at 1/2 <Tx_line.start, Rx_line.start> + 0.28i,0 rjust
+"seq # 0x008F" at 1/2 <Tx_line.end, Rx_line.end> - 0.40i,0 ljust
+.PE
+.ce
+Figure 5: Hypothetical reordering of the 6 ``bunched up'' packets of Figure 4
+.KE
+.PP
+Figure\ 5 above (hypothetical scenario) differs from Figure\ 4 (actual
+experience) in that the arrival times of 6 RTP packets 0x0087 through 0x008C
+(RTP sequence numbers from the dataset of Figure\ 4) have been reversed:
+0x008C is hypothesized to arrive when 0x0087 actually arrived, 0x0087 is
+hypothesized to arrive when 0x008C actually arrived, and the 4 packets in
+the middle are mirrored symmetrically.
+Because all 6 of these packets were stuck waiting behind the same bottleneck
+at the same time, the fictional scenario presented in Figure\ 5 is at least
+plausible.
+.PP
+For the sake of completeness, let us consider a more fantastical (less likely
+in reality) scenario of packet reordering.
+Figure\ 6 below depicts the way beginning students of IP networking likely
+imagine packet reordering:
+.KS
+.PS
+Tx_line: line -> right 4i
+"RTP Tx:" at Tx_line.start - 0.1,0 rjust
+"time" at Tx_line.end + 0.1,0 ljust
+
+vdist = 0.8i
+move to Tx_line - 0,vdist
+Rx_line: line -> right 4i
+"RTP Rx:" at Rx_line.start - 0.1,0 rjust
+"time" at Rx_line.end + 0.1,0 ljust
+for x = 0.2i to 3.8i by 0.4i do {
+ line from Rx_line + x,0 down 0.1i
+}
+
+arrow from Tx_line + 0.2i,0 down vdist right 0.3i
+arrow from Tx_line + 0.6i,0 down vdist right 1.3i
+arrow from Tx_line + 1.0i,0 down vdist right 0.3i
+arrow from Tx_line + 1.4i,0 down vdist right 0.3i
+arrow from Tx_line + 1.8i,0 down vdist right 1.0i
+arrow from Tx_line + 2.2i,0 down vdist right 0.3i
+arrow from Tx_line + 2.6i,0 down vdist right 0.8i
+arrow from Tx_line + 3.0i,0 down vdist right 0.3i
+arrow from Tx_line + 3.4i,0 down vdist right 0.3i
+.PE
+.ce
+Figure 6: Unlikely form of packet reordering
+.KE
+.PP
+In order for the fictional scenario of Figure\ 6 to occur, the IP network
+would have to behave in a way where some packets get stuck behind a
+delay-inducing bottleneck, yet other packets, including those that directly
+follow the unlucky ``stuck'' packet, are delivered without excessive delay,
+arriving ahead of those that got stuck.
+It is difficult to imagine what mechanisms could cause a real IP network
+to behave in such manner \(em but if some real IP network somewhere does
+indeed exhibit this theorized behavior, \fBtwjit\fP should be able to handle
+it with appropriate tuning.
+.PP
+None of the examples we've examined so far include packet loss, only delay
+and perhaps reordering. However, each of the presented figures can be
+trivially modified to reflect packet loss: instead of a packet arriving late
+or out of order (later than a subsequently-sent packet), it never arrives
+at all.
+Readers are invited to use their imagination: take any of the arrows that
+represent individual RTP packets, and erase it.
+.NH 2
+Design of twjit
+.PP
+Having seen various scenarios of RTP flows, let us now consider what work
+\fBtwjit\fP needs to do in order to convert a received RTP stream back to
+fixed timing.
+.PP
+Each \fBtwjit\fP instance consists of two sub-buffers (subbufs for short)
+and a global state variable that gives the overall state of the instance
+across both subbufs.
+Each subbuf holds a queue of received RTP packets that belong to a single
+RTP flow as defined in \(sc2.1; two subbufs are needed in order to handle
+handovers \(em if the incoming RTP stream does not exhibit any handover
+events, a single subbuf is sufficient.
+.NH 3
+The major states of twjit
+.LP
+Each \fBtwjit\fP instance as a whole, across both subbufs, is a finite state
+machine with 4 possible states:
+.sp .3
+.IP \fBEMPTY\fP 15
+The \fBtwjit\fP instance is completely empty, neither subbuf holds any
+packets or any meaningful state information.
+.IP \fBHUNT\fP 15
+Only one subbuf is active and valid in this state;
+this subbuf is non-empty \(em some received packets are held \(em but it hasn't
+started flowing out yet, as will be explained in the following section.
+.IP \fBFLOWING\fP 15
+Only one subbuf is active and valid in this state;
+this subbuf is both flowing out and accepting new packets.
+This state is the one that holds long-term during good reception of a
+steady RTP flow.
+.IP \fBHANDOVER\fP 15
+Both subbufs are active and valid:
+one is flowing out while the other receives new packets.
+As indicated in the name, this state is entered from the \fBFLOWING\fP state
+only when the received RTP stream exhibits a handover.
+.sp .3
+.LP
+Possible transitions between these 4 fundamental states are as follows:
+.PS
+circlerad = 0.5
+EMPTY: circle "\fBEMPTY\fP"
+move right 0.65i
+HUNT: circle "\fBHUNT\fP"
+move same
+FLOWING: circle "\fBFLOWING\fP"
+move same
+HANDOVER: circle "\fBHANDOVER\fP"
+arrow from EMPTY.e to HUNT.w
+arrow from HUNT.e to FLOWING.w
+arc cw -> from FLOWING.s to EMPTY.s rad 2i
+arc cw -> from FLOWING.e to HANDOVER.w rad 0.5i
+arc cw -> from HANDOVER.w to FLOWING.e rad 0.5i
+arc -> from HANDOVER.n to HUNT.n rad 2i
+.PE
+.PP
+In order to understand the workings of \fBtwjit\fP,
+let us first consider operation without handovers
+(SSRC never changes, and the timestamp increment from each source-emitted packet
+to the next always equals the samples-per-quantum constant) \(em in such
+sans-handover operation, only \fBEMPTY\fP, \fBHUNT\fP and \fBFLOWING\fP states
+are encountered \(em and then examine handover handling.
+.NH 3
+Structure and operation of one subbuf
+.PP
+Each subbuf of \fBtwjit\fP
+holds a queue of received RTP packets that belong to a single
+RTP flow as defined in \(sc2.1.
+In terms of memory allocation, the queue of each subbuf is implemented
+as a linked list of Osmocom message buffers (\fBmsgb\fPs) \(em but this
+implementation detail is really only a matter of memory allocation strategy,
+and must \fBnot\fP be misconstrued to infer what kinds of packet sequences
+are allowed to exist in one subbuf.
+In sharp contrast with a naive interpretation of what a linked list can
+presumably hold (any sequence of packets, without strict constraints on
+timestamp increment or any other aspect), the logical structure of a single
+\fBtwjit\fP subbuf is a chain of fixed slots, where each slot corresponds
+to a given RTP timestamp and may be either empty or filled with a received
+packet.
+.PP
+A good physical analogy for the logical structure of a \fBtwjit\fP subbuf
+can be found in carrier tapes that hold electronic components in tape-and-reel
+packaging.
+There is a long tape made of plastic or thick paper with regularly spaced
+wells, with each well intended to hold one piece of the reeled part.
+Whether each given well in the tape holds a component or is empty, the spacing
+between wells remains fixed, and a component cannot be inserted anywhere into
+the tape except into a designated well.
+.PP
+The following drawing depicts a subbuf with both filled and empty slots:
+.KS
+.PS
+boxwid = 0.5
+boxht = 0.3
+
+box; box; box; box
+
+move to 1st box.n; line <- up " head" ljust
+move to last box.n; line <- up " tail" ljust
+
+"0" at 1st box.s below
+"1" at 2nd box.s below
+"2" at 3rd box.s below
+"3" at last box.s below
+
+boxwid = 0.2
+boxht = 0.1
+
+box filled 1 with .center at 1st box
+box filled 1 with .center at 4th box
+
+.PE
+.ce
+Figure 7: Basic principle of twjit subbuf
+.KE
+.PP
+The subbuf depicted in Figure\ 7 has a total depth (to be defined shortly)
+of 4 quantum units (please recall the definition of a quantum in \(sc1.3),
+the tail slot is filled as always required for a non-empty subbuf,
+the head slot is also filled in this example, but the other two slots
+are empty.
+(Such subbuf state may result from packet loss, and may also occur in cases
+of packet reordering if the packets destined for empty slots 1 and 2 may yet
+arrive.)
+.PP
+Every non-empty \fBtwjit\fP subbuf has a head slot, a tail slot and a total
+depth.
+The head slot is defined by the 32-bit RTP timestamp stored in \&\fChead_ts\fP
+member of the subbuf structure, which is \&\fCstruct\ twjit_subbuf\fP inside
+\&\fCtwjit.c\fP in the present version.
+If the subbuf holds a received RTP packet whose timestamp equals
+\&\fChead_ts\fP, that packet resides in the head slot; if no such packet is
+held, then the head slot is empty.
+However, packets with RTP timestamps earlier than \&\fChead_ts\fP \fBcannot\fP
+exist in a subbuf!
+.PP
+Every received RTP packet held in a subbuf, as well as every empty slot that
+can potentially be filled by a late-arriving out-of-order packet, can be viewed
+as existing at a certain depth.
+The head slot shall be regarded as depth 0, the following slot shall be
+regarded as depth 1, and so forth.
+Recall that per the fundamental design of \fBtwjit\fP, each subbuf can only
+hold RTP packets belonging to a single flow as defined in \(sc2.1 \(em thus
+if one quantum equals 160 timestamp units,
+slot 1 can only hold an RTP packet whose timestamp equals
+(\fChead_ts\fP\ +\ 160),
+slot 2 can only hold an RTP packet whose timestamp equals
+(\fChead_ts\fP\ +\ 320),
+and so forth.
+.PP
+Of all received RTP packets held by the subbuf, whichever packet has the newest
+RTP timestamp is regarded as the current tail of the subbuf, and its depth
+is regarded as the current tail slot.
+The total depth of a subbuf is defined as the depth of the tail packet plus 1;
+in the example of Figure\ 7, the tail packet has depth 3 and the total depth
+of the subbuf is 4.
+The total depth of a subbuf is also called the fill level, by analogy with
+fill level of a water tank.
+.PP
+When a new RTP packet is received, and that packet is deemed to belong to the
+flow already being received (same SSRC, timestamp increment meets expectations),
+the newly received packet is added to the current write subbuf.
+The insertion depth of the new packet is calculated as the newly received
+timestamp minus \&\fChead_ts\fP, divided by the number of timestamp units
+per quantum.
+If this insertion depth exceeds the current tail depth, which is the normal
+case, the subbuf grows (the fill level increases) and the newly added packet
+becomes the new tail.
+As a result of this operation, the tail slot of a non-empty subbuf
+can never be empty!
+Alternatively, if the insertion depth falls somewhere before the current
+total depth of the subbuf, the fill level stays the same and the target slot
+\(em which is expected to be empty in this case \(em
+is filled with the newly received packet.
+If that target slot was already filled, the new packet is discarded and
+an error counter is incremented, indicating duplicate Rx packets.
+.PP
+When an active, flowing-out subbuf is polled for output at fixed times
+determined by TDM or GSM Um etc, the head slot is consumed, whether it is
+filled or empty.
+If the consumed head slot was filled, that buffered packet is delivered
+to the fixed timing system on the output of the jitter buffer.
+If that slot was empty, the application on the output of the jitter buffer
+receives a gap in the stream.
+Either way, when the previous head slot is consumed, \&\fChead_ts\fP is
+incremented by the samples-per-quantum constant, the following slot becomes
+the new head slot, and the total depth of the subbuf decreases by 1.
+.PP
+There also exists a special condition in which a subbuf is empty
+(does not hold any buffered packets, fill level equals 0),
+but is still considered active.
+This condition can occur only in \fBFLOWING\fP state, and is covered in
+the respective section.
+.NH 3
+EMPTY and HUNT states
+.PP
+Upon initialization or reset, each \fBtwjit\fP instance begins life in
+\fBEMPTY\fP state.
+As soon as the first valid RTP packet is received, one subbuf is initialized
+to hold this first packet; the total depth of this newly initialized subbuf
+is 1 and the slot occupied by the initial packet is both the head and the tail.
+The overall state of \fBtwjit\fP instance transitions to \fBHUNT\fP.
+.PP
+The purpose of \fBHUNT\fP state is to accumulate enough received packets,
+necessarily belonging to a single flow as defined in \(sc2.1, until a
+configured threshold is met for entry into \fBFLOWING\fP state.
+The critically important configuration parameter is the flow-starting
+fill level; it is the first number given on the \&\fCbuffer\-depth\fP vty
+configuration line.
+.PP
+The flow-starting fill level is the fill level (total depth of the sole
+active subbuf) required for transition from \fBHUNT\fP state into \fBFLOWING\fP
+state.
+This criterion is evaluated on every 20\ ms fixed timing tick when the
+application polls \fBtwjit\fP for a required quantum of media; as a result of
+this check, the \fBtwjit\fP instance either delivers its first output and
+transitions into \fBFLOWING\fP, or returns \fBNULL\fP (``sorry, I got nothing'')
+to the application and remains in \fBHUNT\fP state.
+.PP
+The significance of this threshold parameter, and guidelines for its tuning,
+are best understood by looking at examples of RTP flows shown in \(sc2.2.
+The minimum allowed setting for this parameter is 1; with this minimum setting,
+the first tick of the fixed timing system after reception of any RTP packet
+always causes transition into \%\fBFLOWING\fP state,
+and the packet received just prior to this transition-causing tick
+is delivered to the output on that tick.
+This setting produces the lowest possible buffer-added latency: this latency
+can be near-zero if the RTP packet arrived just prior to the fixed timing tick,
+or just under 20\ ms if it arrives just after the previous tick.
+.PP
+However, if we look at Figures 1 and 2 in \(sc2.2, we can see the one big
+problem with this lowest latency setting: the flow remains perfect under
+absolutely ideal conditions of Figure\ 1, but as soon as we enter real-world
+conditions as shown in Figure\ 2, we can easily encounter scenarios like
+the RTP packet with sequence number 0x0636 in that figure.
+If the RTP flow depicted in Figure\ 2 were to be received by a \fBtwjit\fP
+instance whose flow-starting fill level is set to 1, the buffer would
+experience an underrun on the tick of the fixed timing system that just barely
+missed the slightly delayed packet; the user would then experience an equivalent
+of packet loss (frame erasure) at a time when no actual packet loss occurred.
+.PP
+For this reason, the default and generally recommended setting for the
+flow-starting fill level parameter is 2.
+With this setting, two RTP packets with properly consecutive timestamps
+must be received in \fBHUNT\fP state before \fBtwjit\fP transitions into
+\fBFLOWING\fP state.
+The buffer-added latency will be anywhere between 20 and 40 ms,
+depending on the unpredictable phase alignment between arriving RTP packets
+and ticks of the fixed timing system on the output side of \fBtwjit\fP.
+As long as the jitter between flight times of different packets, or its
+observable manifestation as interarrival jitter, remains below 20\ ms
+(or below 16.9\ ms if the receiving element is OsmoBTS whose fixed time
+base includes the inherent jitter of GSM Um multiframe structure),
+there will not be an occurrence where the receiving system transforms jitter
+into an effective equivalent of packet loss.
+This amount of jitter tolerance is sufficient for most practical IP networks
+in this author's experience.
+.PP
+But what if the IP network regularly exhibits packet delay jitter that is
+significantly greater than 20\ ms?
+Suppose the network regularly exhibits conditions similar to the aberration
+depicted in Figure\ 4 \(em what then?
+In this case the administrator of jitter-buffer-equipped network elements
+has to make a trade-off between two different forms of degraded user experience:
+either increase the flow-starting fill level setting and thereby increase the
+experienced latency, or live with underruns (frame erasure effectively
+equivalent to packet loss) whenever the IP network stops flowing smoothly
+and decides to ``bunch up'' packets instead.
+As just one example, if the amount of ``bunching up'' exhibited by the IP
+network were exactly as depicted in Figure\ 4, and the desire were to eliminate
+packet-loss-equivalent effects at the expense of added latency,
+the required flow-starting fill level setting would be 7,
+producing added latency between 120 and 140 ms.
+.NH 4
+Reception of additional packets in HUNT state
+.PP
+Every time an additional RTP packet is received when the \fBtwjit\fP instance
+is already in \fBHUNT\fP state (after the first Rx packet that moved the
+state from \fBEMPTY\fP to \fBHUNT\fP),
+certain checks are made.
+The first fundamental requirement of \fBHUNT\fP state is that all queued
+packets belong to the same flow.
+If the newly received RTP packet has a different SSRC, or if it exhibits
+a timestamp increment that is numerically incompatible with being a member
+of the same flow (not an integral multiple of samples-per-quantum constant),
+all previously queued packets are discarded and the \fBHUNT\fP state is
+reinitialized anew with the just-received packet.
+This behavior is unavoidably necessary: the single subbuf of \fBHUNT\fP state
+holds packets belonging to \fIone\fP particular flow, and given the choice
+between a stale flow that appears to have just ended and the new flow that
+appears to have just begun, the new flow is clearly the correct choice.
+.PP
+Once the same-flow requirement is met and the newly received packet is not
+too old (packets whose RTP timestamps precede the current \&\fChead_ts\fP
+have to be discarded), the new packet is inserted into the sole active
+subbuf at its respective depth.
+Most of the time, this insertion will increase the total depth or fill level
+of this subbuf.
+At this point the new fill level is checked against the flow-starting
+fill level setting: if the flow-starting fill level has just been exceeded,
+packets are discarded from the head of the subbuf and \&\fChead_ts\fP advances
+forward until the remaining fill level is equal to or below the flow-starting
+threshold.
+.PP
+This trimming of the subbuf to the flow-starting fill level is necessary to
+ensure that the latency added by the jitter buffer will indeed be what the
+administrator intended to set via the tunable parameter, as opposed to
+potentially much higher added latency that could be caused by artifacts
+at flow starting time.
+Suppose that the IP network bunches up a significant number of packets
+when the sender begins transmitting them 20\ ms apart, then delivers that
+bunch all at once, and then begins to flow evenly:
+.KS
+.PS
+Tx_line: line -> right 6i
+"RTP Tx:" at Tx_line.start - 0.1,0 rjust
+"time" at Tx_line.end + 0.1,0 ljust
+
+vdist = 0.8i
+move to Tx_line - 0,vdist
+Rx_line: line -> right 6i
+"RTP Rx:" at Rx_line.start - 0.1,0 rjust
+"time" at Rx_line.end + 0.1,0 ljust
+for x = 0.2i to 5.8i by 0.4i do {
+ line from Rx_line + x,0 down 0.1i
+}
+
+Tx0: Tx_line + 0.1i,0
+
+# timing numbers are made up!
+Land0: Rx_line + (0.1i + 0.02i * 127.5),0
+Land1: Land0 + (0.02i * 1.542),0
+Land2: Land1 + (0.02i * 3.417),0
+Land3: Land2 + (0.02i * 0.273),0
+Land4: Land3 + (0.02i * 0.440),0
+Land5: Land4 + (0.02i * 0.115),0
+Land6: Land5 + (0.02i * 6.467),0
+Land7: Land6 + (0.02i * 20.013),0
+Land8: Land7 + (0.02i * 20.009),0
+Land9: Land8 + (0.02i * 19.992),0
+Land10: Land9 + (0.02i * 20.522),0
+Land11: Land10 + (0.02i * 19.509),0
+Land12: Land11 + (0.02i * 20.211),0
+Land13: Land12 + (0.02i * 19.741),0
+
+arrow from Tx0 to Land0
+arrow from Tx0 + (0.02i * 20),0 to Land1
+arrow from Tx0 + (0.02i * 40),0 to Land2
+arrow from Tx0 + (0.02i * 60),0 to Land3
+arrow from Tx0 + (0.02i * 80),0 to Land4
+arrow from Tx0 + (0.02i * 100),0 to Land5
+arrow from Tx0 + (0.02i * 120),0 to Land6
+arrow from Tx0 + (0.02i * 140),0 to Land7
+arrow from Tx0 + (0.02i * 160),0 to Land8
+arrow from Tx0 + (0.02i * 180),0 to Land9
+arrow from Tx0 + (0.02i * 200),0 to Land10
+arrow from Tx0 + (0.02i * 220),0 to Land11
+arrow from Tx0 + (0.02i * 240),0 to Land12
+arrow from Tx0 + (0.02i * 260),0 to Land13
+.PE
+.ce
+Figure 8: Packets bunched together at the beginning of flow
+.KE
+.PP
+If the step of trimming the subbuf in \fBHUNT\fP state to the flow-starting
+fill level were omitted, then in the scenario depicted in Figure\ 8,
+the fill level on entry into \fBFLOWING\fP state would be 7 instead of 2
+or whatever flow-starting fill level is configured by the administrator,
+significantly increasing the latency experienced by the user.
+.PP
+If the flow-starting fill level is set to 1, the total depth
+of the sole active subbuf in \fBHUNT\fP state will never equal anything
+other than 1; if the flow-starting fill level is set to 2 (the default),
+the total depth of the subbuf in \fBHUNT\fP state will always equal either
+1 or 2, with both head and tail slots always filled.
+Empty slots in this sole active subbuf cannot exist when the flow-starting
+fill level is set to 1 or 2.
+However, if the flow-starting fill level is set to 3 or greater, empty
+slots in the subbuf in \fBHUNT\fP state become possible: both head and tail
+slots are still always filled in \fBHUNT\fP state, but with higher settings
+of the flow-starting fill level configuration parameter, it becomes possible
+to have empty slots in the middle, produced by packet loss or reordering.
+.NH 4
+Additional time delta guards
+.PP
+Let us once again consider the scenario depicted in Figure\ 8.
+The step of trimming the subbuf to the flow-starting fill level prevents
+induction of significantly increased latency by initial floods arriving
+while the \fBtwjit\fP instance is still in \fBHUNT\fP state
+\(em but suppose the transition from \fBHUNT\fP into \fBFLOWING\fP occurs
+while an initial flood, similar to that depicted in Figure\ 8,
+is still ongoing.
+As covered in the following section, once the overall state of \fBtwjit\fP
+instance is \fBFLOWING\fP, no more simple head trimming can occur: there is
+a much slower-acting standing queue thinning mechanism, but any extra latency
+that was induced at the start of the flow can only be dissipated very slowly,
+and with an unavoidable side effect of phase shifts in the delivered flow.
+.PP
+In order to produce better performance in IP network environments where
+scenarios like Figure\ 8 are expected,
+there is an additional check, optionally enabled per configuration,
+gating the transition from \fBHUNT\fP into \fBFLOWING\fP state:
+it is \&\fCstart\-min\-delta\fP vty setting.
+When this optional parameter is set, it specifies the minimum required
+time-of-arrival delta in milliseconds between the most recently received
+RTP packet and the one received just prior; this minimum ToA delta must hold
+in order for transition from \fBHUNT\fP into \fB\%FLOWING\fP to be allowed.
+.PP
+For the sake of symmetry, there is also an optional \&\fCstart\-max\-delta\fP
+vty setting.
+When this optional parameter is set, it specifies the maximum allowed
+ToA delta in \fBHUNT\fP and \fBHANDOVER\fP states:
+if the ToA delta between successively received packets exceeds this
+threshold, previously queued packets are discarded
+(regarded as remnants of a stale previous flow)
+and the hunt process begins anew with the latest received packet.
+.NH 3
+FLOWING state
+.PP
+When the global state of a given \fBtwjit\fP instance is \fBFLOWING\fP,
+there is only one active subbuf, just like in \fBHUNT\fP state.
+Newly received RTP packets that belong to the same flow (same SSRC,
+timestamp increments meet expectations) are likewise added to this subbuf
+just as they were during \fBHUNT\fP.
+However, the same subbuf is also flowing out: on every tick of the fixed
+timing system on the output side of \fBtwjit\fP,
+the head slot of the subbuf is consumed and \&\fChead_ts\fP advances
+accordingly.
+.PP
+Unlike \fBHUNT\fP state, \fBFLOWING\fP state allows the head slot of
+the sole active subbuf to be empty.
+This situation will occur if the received flow experiences a gap
+(packet loss, reordering or an intentional gap emitted by the RTP stream
+source), the last received packet before the gap is consumed,
+but there are still more packets at greater depth,
+such that the subbuf is not entirely empty.
+If the head slot remains empty on the fixed timing tick that consumes it,
+the application on the output side of \fBtwjit\fP receives a gap
+in the stream,
+but this event is \fBnot\fP regarded as an underrun.
+.NH 4
+Handling of underruns
+.PP
+It is possible for the flowing-out subbuf to be empty, but not incur
+an underrun just yet.
+Suppose the total depth (see \(sc2.3.2) of the flowing-out subbuf
+equals 1 at the time of an output poll: the head slot is also the tail slot,
+and the previously received RTP packet consumed on this tick is the very last
+one received till this moment.
+After this output poll tick, the subbuf is empty (total depth equals 0),
+but it is still valid in the sense that the overall state remains \fBFLOWING\fP
+(does not transition to \fBEMPTY\fP)
+and \&\fChead_ts\fP is still regarded as valid, equal to the timestamp
+of the last delivered packet plus 160.
+If another RTP packet, belonging to the same flow, is received before the
+next output poll tick, the flow continues without underrun or any other
+undesirable interruptions.
+This situation occurs all the time in normal operation when the flow-starting
+fill level configuration parameter is optimally tuned, adding just enough
+buffering latency to handle the amount of jitter that actually occurs,
+but no more.
+.PP
+A true underrun occurs on the next output poll tick after the one that
+leaves the flowing-out subbuf empty while still valid.
+At this point the overall state of the \fBtwjit\fP instance transitions to
+\fBEMPTY\fP.
+Any subsequently received RTP packet, whatever its SSRC and timestamp may be,
+causes a transition from \fBEMPTY\fP into \fBHUNT\fP, and the process of
+latching onto a flow begins anew.
+The application on the output of the jitter buffer will keep receiving
+\fBNULL\fP (``sorry, I got nothing'')
+starting with the output poll tick on which the underrun occurs
+and continuing until the new flow after the underrun (if there is one)
+reaches the flow-starting fill level.
+.PP
+For the benefit of network operations staff looking at stats counters
+logged upon call completion (see Chapter\ 3 regarding stats and analytics),
+the \&\fCunderruns\fP counter is incremented not at the point where the
+actual underrun occurs, but upon receipt of the next RTP packet
+(if there is one) that makes the transition from \fBEMPTY\fP into \fBHUNT\fP.
+This implementation detail results in not counting the final underrun
+that often occurs upon call teardown, instead counting only those underrun
+events that are true indications of problematic network conditions
+or insufficient jitter buffering.
+.NH 4
+Standing queue thinning mechanism
+.PP
+Suppose that the beginning of an RTP flow (acquisition in \fBHUNT\fP state,
+then transition into \fBFLOWING\fP) happens when the IP network path
+experiences a spike in latency, then later that spike subsides and
+latency along the IP network path returns to a lower baseline.
+This scenario may look as follows:
+.KS
+.PS
+Tx_line: line -> right 5i
+"RTP Tx:" at Tx_line.start - 0.1,0 rjust
+"time" at Tx_line.end + 0.1,0 ljust
+
+vdist = 0.8i
+move to Tx_line - 0,vdist
+Rx_line: line -> right 5i
+"RTP Rx:" at Rx_line.start - 0.1,0 rjust
+"time" at Rx_line.end + 0.1,0 ljust
+for x = 0.2i to 4.8i by 0.4i do {
+ line from Rx_line + x,0 down 0.1i
+}
+
+Tx0: Tx_line + 0.1i,0
+
+# timing numbers are made up!
+Land0: Rx_line + (0.1i + 0.02i * 63),0
+Land1: Land0 + (0.02i * 19.992),0
+Land2: Land1 + (0.02i * 20.522),0
+Land3: Land2 + (0.02i * 19.509),0
+Land4: Land3 + (0.02i * 20.211),0
+Land5: Land4 + (0.02i * 6.467),0
+Land6: Land5 + (0.02i * 1.542),0
+Land7: Land6 + (0.02i * 3.417),0
+Land8: Land7 + (0.02i * 20.273),0
+Land9: Land8 + (0.02i * 20.440),0
+Land10: Land9 + (0.02i * 19.501),0
+Land11: Land10 + (0.02i * 19.701),0
+
+arrow from Tx0 to Land0
+arrow from Tx0 + (0.02i * 20),0 to Land1
+arrow from Tx0 + (0.02i * 40),0 to Land2
+arrow from Tx0 + (0.02i * 60),0 to Land3
+arrow from Tx0 + (0.02i * 80),0 to Land4
+arrow from Tx0 + (0.02i * 100),0 to Land5
+arrow from Tx0 + (0.02i * 120),0 to Land6
+arrow from Tx0 + (0.02i * 140),0 to Land7
+arrow from Tx0 + (0.02i * 160),0 to Land8
+arrow from Tx0 + (0.02i * 180),0 to Land9
+arrow from Tx0 + (0.02i * 200),0 to Land10
+arrow from Tx0 + (0.02i * 220),0 to Land11
+.PE
+.ce
+Figure 9: Period of high IP latency followed by lower latency
+.KE
+.PP
+If the flow-starting fill level is set to 2 (the default),
+the total depth of the active subbuf will alternate between 1 and 2
+during the initial high latency phase: increase to 2 on each RTP packet
+arrival, then go back down to 1 when the subsequent output poll tick
+consumes the packet in the head slot.
+However, following the transition from higher to lower IP network path
+latency while \fBtwjit\fP is in \fBFLOWING\fP state, if the arriving
+packets land where they do in Figure\ 9,
+the subsequent total depth of the same active subbuf will alternate
+between 3 and 4: rise to 4 when ``bunched up'' packets arrive,
+then go down to 3 on each output poll tick and go back up to 4
+as new RTP packets arrive in between those ticks.
+.PP
+The end result of such happenings is increased buffer-added latency
+\(em a standing queue \(em
+in the steady flow state after the latency of the IP network path went down.
+In the present example, the standing queue latency added as a lasting
+artifact of earlier network conditions at flow starting time is 40\ ms \(em
+but it can be greater or smaller, in 20\ ms increments, depending on how
+the IP network path changes between initial acquisition of a new RTP flow
+and its subsequent steady state.
+.PP
+The design of \fBtwjit\fP includes a mechanism for thinning these standing
+queues, gradually bringing buffer-added latency down to a maximum limit
+set by the administrator.
+The controlling parameter is the high water mark fill level;
+it is the second number given on the \&\fCbuffer\-depth\fP vty
+configuration line.
+This parameter must be greater than or equal to the flow-starting fill level,
+but it takes effect only in \fBFLOWING\fP state, not in \fBHUNT\fP.
+Any time the total depth of the flowing-out subbuf exceeds this high water
+mark on an output poll tick, tested just before consuming the head slot
+and any RTP packet contained therein,
+the standing queue thinning mechanism kicks in.
+This mechanism deletes every \fIN\^\fPth packet from the stream being thinned,
+where \fIN\fP is the \&\fCthinning\-interval\fP parameter set in vty config,
+for as long as the total depth of the flowing-out subbuf exceeds the
+high water mark fill level.
+More precisely, every \fIN\^\fPth output poll tick that happens in the state
+of high water mark being exceeded advances the head slot by two quantum
+units instead of one, with the first consumed head slot always discarded
+and the second consumed head slot passed to the output.
+This operation remains the same irrespective of whether each of the two
+thus consumed head slots holds a previously received RTP packet or is empty.
+.PP
+The act of deleting a quantum from the middle of an ongoing stream of
+speech or CSData is always disruptive: in the case of speech, a 20\ ms
+quantum, potentially in the middle of a speaker talking, suddenly disappears;
+in the case of CSData, the effect may be even worse with a potentially
+important data chunk likewise disappearing, plus a 20\ ms phase shift
+in the stream is always incurred.
+However, such disruptions are the only way to bring down a standing queue,
+whose added latency is another form of evil \(em thus as usual,
+engineering is all about trade-offs and compromises.
+.PP
+The default configuration for \fBtwjit\fP is
+\&\fCbuffer\-depth\|\|\|2\|\|\|4\fP and \&\fCthinning\-interval\|\|\|17\fP:
+the default high water mark fill level is 4,
+and the default thinning interval is to delete every 17th packet, i.e.,
+delete one 20\ ms quantum every 340\ ms.
+The exact scenario depicted in Figure\ 9 will not invoke the standing queue
+thinning mechanism with these default settings: in this depicted scenario,
+the total depth of the active subbuf rises to 4 at its highest,
+which is also the default high water mark fill level.
+However, if this high water mark setting is lowered or if the latency spike
+at the beginning of the flow is more substantial, then the standing queue
+thinning mechanism will kick in when the IP latency spike subsides.
+.PP
+The default thinning interval of deleting every 17th packet was chosen
+based on these considerations:
+.IP a)
+340\ ms is long enough to where 20\ ms quantum deletions spaced this far
+apart should be tolerable, yet short enough to where a standing queue
+would be reduced to the high water mark in reasonable time;
+.IP b)
+17 is a prime number, thereby reducing the probability that the thinning
+mechanism will interfere badly with intrinsic features of the stream
+being thinned.
+.PP
+The default high water mark fill level was chosen so as to provide some
+margin above the flow-starting fill level (allow IP network path latency
+variations without needless thinning followed by underrun and reacquisition),
+while still maintaining a constraint against unbounded growth of a
+standing queue.
+As always, the optimal engineering trade-off will depend very strongly
+on the actual characteristics of the IP network environment on top of which
+a GSM network or IP-PSTN system is being built, hence careful attention is
+required from the managing operator.
+.NH 4
+Guard against time traveler packets
+.PP
+Every time a new RTP packet is received in \fBFLOWING\fP state, a series
+of checks are made to answer this question: should the newly received packet
+be treated as a continuation of the current flow, or should it be
+treated as belonging to a new flow and thus a handover event per \(sc2.1?
+The first check is SSRC comparison: if the newly received RTP packet has a
+different SSRC than the currently active flow, it is a handover.
+.PP
+The RTP timestamp is checked next.
+In order to handle arbitrary starting 32-bit timestamps and wraparound of
+the absolute 32-bit timestamp value at any point, \fBtwjit\fP code computes
+the difference between current subbuf \&\fChead_ts\fP (subtrahend) and
+the newly received timestamp (minuend),
+and treats this difference as a signed 32-bit integer.
+If this difference is negative, the newly received packet is treated as a
+stale (too old) one, received with so much delay that it can no longer be
+accepted: \&\fCtoo_old\fP stats counter is incremented, and the packet in
+question is discarded without further processing.
+After this check the timestamp increment, now confirmed to be non-negative,
+is checked to see if it is an integral multiple of the samples-per-quantum
+constant, which is usually 160.
+If this integral multiple constraint is violated,
+the new packet cannot belong to the same flow as the currently active one,
+and it is necessary to invoke handover handling.
+.PP
+After these two checks, one final check is needed for robustness: a guard
+against time traveler packets.
+If the increment between current \&\fChead_ts\fP and the timestamp field
+in the newly received RTP packet is positive after wraparound handling,
+if it is an integral multiple of the samples-per-quantum constant,
+but it is excessively large (at 8000 timestamp units per second,
+the largest possible post-wraparound-handling RTP timestamp increment
+is just over 3 days into the future),
+such aberrant RTP packets are jocularly referred to as time travelers.
+.PP
+Assuming that actual time travel either does not exist at all
+or at least does not happen in the present context,
+we reason that when such ``time traveler'' RTP packets do arrive,
+we must be dealing with the effect of a software bug or misdesign
+or misconfiguration in whatever foreign network element is sending us RTP.
+In any case, irrespective of the cause, we must be prepared for the
+possibility of seeming ``time travel'' in the incoming RTP stream.
+We implement an arbitrary threshold: if the received RTP timestamp
+is too far into the future, we treat that packet as the
+beginning of a new flow, same as SSRC change or non-quantum
+timestamp increment, and invoke handover handling.
+.PP
+The threshold that guards against time traveler packets has 1\ s granularity,
+which is sufficient for its intended purpose of catching gross errors.
+It is set with \&\fCmax\-future\-sec\fP vty configuration line.
+The default value is 10\ s: very generous, perhaps overly so,
+to networks with really bad latency.
+.NH 3
+Handling of packet loss and gaps
+.PP
+The design of RTP makes it impossible to distinguish between packet loss
+and intentional gaps in real time:
+if a packet fails to arrive at the time when it is expected and needed
+on the receiving end,
+the receiver has no way of knowing \fIat that moment\fP
+whether the cause of this lack of packet arrival is an intentional gap
+emitted by the sender or packet loss along the way.
+This distinction can be made later, after the fact: when subsequent
+packets arrive, the receiver can examine the sequence number field
+in the RTP header and thereby determine which of the two events
+(intentional gap or packet loss) happened previously.
+However, because this knowledge is not available at the time when it would
+be needed, \fBtwjit\fP makes no distinction between these two possibilities
+outside of analytics.
+For the purpose of mapping received RTP packets to ticks of the fixed timing
+system on the output side of \fBtwjit\fP,
+any intentional gaps in the incoming RTP stream are treated indistinguishably
+from packet loss.
+(Please recall from \(sc1.2.1 that \fBtwjit\fP is designed for use with
+continuous streaming, not intentional gaps.)
+The operational (as opposed to analytics) part of \fBtwjit\fP looks only
+at SSRC and timestamp fields in the RTP header; it does not consider
+the sequence number at all.
+.PP
+The actual effect of a gap in the received RTP stream,
+whether intentional or caused by packet loss,
+depends on the size of the gap
+(how many consecutive packets are lost or omitted)
+and jitter buffer conditions at the time of its occurrence.
+The critical question is whether or not the gap in RTP reception
+incurs an underrun:
+.IP \(bu
+If the gap is small enough, or the running depth of the jitter buffer
+is high enough, to where no underrun occurs, then the gap is presented
+to the application on the output side of \fBtwjit\fP without distortion:
+the application will receive \fBNULL\fP (absence of received packet)
+in those quanta whose corresponding RTP packets (corresponding per RTP
+timestamp) were not received, while all packets which did arrive will
+be delivered correctly, each at its respective quantum point.
+No phase shift is incurred in the received stream of media.
+.IP \(bu
+If the gap results in an underrun,
+subsequent received packets after this gap will proceed as acquisition
+of a new flow, passing through \fBHUNT\fP state before entering \fBFLOWING\fP.
+Preservation of phase cannot be guaranteed under such conditions,
+i.e., the gap as perceived by the fixed timing application may be lengthened
+or shortened compared to the actual number of RTP packets lost or omitted.
+.IP \(bu
+If a gap incurs an underrun, then there is a single RTP packet following
+this gap, then another gap, the lone RTP packet between the two gaps
+will be dropped, assuming the default configuration with flow-starting fill
+level set to 2.
+This drop occurs because a single RTP packet following an underrun is not
+sufficient to establish a new flow when the flow-starting fill level
+is greater than 1,
+and when another RTP packet arrives after the second gap, the first
+between-gaps packet will be too stale.
+This failure scenario is the reason why the combination of \fBtwjit\fP,
+DTX and intentional gaps will not work.
+.LP
+If \fBtwjit\fP is deployed in an IP network environment where packet loss
+occurs frequently enough to be a concern,
+it may be necessary to increase the amount of buffering (by increasing
+the flow-starting fill level) so that packet loss events do not turn
+into underruns.
+However, intentional gaps are always bad in principle and should be avoided
+\(em enable continuous streaming instead.
+.NH 3
+Handling of packet reordering
+.PP
+As already covered in \(sc2.2, we (Themyscira Wireless) have no operational
+experience with packet reordering, as it is a behavior which is not exhibited
+by IP networks in our part of the world.
+Let us consider nonetheless how \fBtwjit\fP would handle theoretically
+envisioned cases of packet reordering that were presented in that section.
+.PP
+The hypothetical scenario depicted in Figure\ 5 calls for exactly the same
+\fBtwjit\fP configuration as the real world scenario of Figure\ 4.
+If the IP network frequently exhibits effects like those depicted in
+Figure\ 4 and Figure\ 5,
+the flow-starting fill level would need to be set to 7.
+As long as episodes of packets bunched together, with or without reordering,
+are separated by periods of smooth packet flow, \fBtwjit\fP would proceed
+through its acquisition stage (\fBHUNT\fP state) in one of these smooth flow
+periods, and then episodes of the form depicted in Figure\ 4 or Figure\ 5
+would be handled gracefully, without any loss or distortion of transported
+media.
+.PP
+The more fantastical scenario of Figure\ 6 would be handled well by
+setting the flow-starting fill level to 4.
+Visualizing the state of the sole active subbuf after each RTP packet arrival
+and after each output poll tick in that figure is left as an exercise
+for the reader \(em however, each media quantum carried by each of the packets
+shown in the figure will be delivered to the application on the output side
+of \fBtwjit\fP in the correct order, without any loss or distortion.
+.NH 3
+Handling of handovers
+.PP
+As covered in \(sc2.1, a handover in \fBtwjit\fP terminology is
+a transition from one RTP flow to the next, within the context of a single
+RTP stream.
+A handover occurs when the incoming RTP stream exhibits a change of SSRC,
+a timestamp increment that is not an integral multiple of the
+samples-per-quantum constant,
+or a ``time travel'' event as described in \(sc2.3.4.3.
+.PP
+In order to be treated as a handover by \fBtwjit\fP, the newly received RTP
+packet that breaks the previous flow in one of the just-listed ways must
+arrive while the previous flow (the one it breaks from) is still active,
+while the state of the \fBtwjit\fP instance is \fBFLOWING\fP.
+If a handover happens after the previous flow underruns, such that the
+\fBtwjit\fP instance is in \fBEMPTY\fP state when the first packet of the
+new flow arrives,
+acquisition of the new flow proceeds via \fBHUNT\fP state in the same way
+whether this new flow is continuous or discontinuous with the previous one.
+Similarly, if a flow discontinuity (the kind that would be treated as a
+handover if it occurred in \fB\%FLOWING\fP state) occurs in \fBHUNT\fP state,
+it is handled by reinitializing the \fBHUNT\fP state, without entering
+the special \fBHANDOVER\fP state, as detailed in \(sc2.3.3.1.
+.PP
+True handover handling happens when a flow-breaking RTP packet arrives
+in \fBFLOWING\fP state.
+This event causes a transition into the dedicated \fBHANDOVER\fP state,
+described here.
+In this state both subbufs of \fBtwjit\fP are active and valid:
+the subbuf that was active in \fBFLOWING\fP state continues to flow out,
+while the other subbuf is initialized for the new flow just like in
+\fBHUNT\fP state.
+Accounting for \fBHANDOVER\fP state, each \fBtwjit\fP instance has
+a potentially valid write subbuf and a potentially valid read subbuf,
+breaking down as follows:
+.IP \(bu
+In \fBEMPTY\fP state, there is neither a valid write subbuf nor a valid
+read subbuf;
+.IP \(bu
+In \fBHUNT\fP state, there is a valid write subbuf, but no valid read subbuf;
+.IP \(bu
+In \fBFLOWING\fP state, the sole active subbuf is both the write subbuf
+and the read subbuf;
+.IP \(bu
+In \fBHANDOVER\fP state, the read subbuf and the write subbuf are different,
+and each is valid.
+.LP
+Once \fBHANDOVER\fP state has been entered, the code path that handles
+incoming RTP packets operates like it does in \fBHUNT\fP state,
+while the output path that executes on ticks of the fixed timing system
+operates like it does in \%\fBFLOWING\fP, each operating on its
+respective subbuf.
+There are two possible exit conditions from this state:
+.IP 1)
+If the new write subbuf reaches ready state (the same criterion as applied
+for transition from \fBHUNT\fP to \fBFLOWING\fP, covered in \(sc2.3.3)
+before the old read subbuf underruns,
+the \fBtwjit\fP instance transitions from \fBHANDOVER\fP back into
+\fBFLOWING\fP state.
+Any packets that remain in the old read subbuf are discarded, and the new
+write subbuf becomes the sole active subbuf for both reading and writing.
+.IP 2)
+If the old read subbuf underruns before the new write subbuf is ready
+to start flowing out,
+a handover underrun occurs (same as a regular underrun, but increments
+a different stats counter) and \fBtwjit\fP state transitions to \fBHUNT\fP.
+This handover underrun will occur if the new write subbuf does not become
+ready quickly enough, as the old read subbuf no longer receives any
+new packets in \fBHANDOVER\fP state.
+.NH 3
+Handling of RTP marker bit
+.PP
+This feature of \fBosmo_twjit\fP does not originate from Themyscira
+and is not present in \fCtwrtp\-native\fP version,
+instead it was added to the Osmocom-integrated version of this library
+per request of Osmocom reviewers.
+Vty configuration parameter \fCmarker\-handling\fP controls how
+\fBosmo_twjit\fP should react to incoming RTP packets that have
+\fBM\fP bit set.
+The two possible settings are \fChandover\fP and \fCignore\fP:
+.IP \(bu
+If \fCmarker\-handling\fP is set to \fChandover\fP, received
+packets with \fBM\fP bit set are treated like SSRC changes:
+if the previous state was \%\fBFLOWING\fP, the state of \fBosmo_twjit\fP
+instance transitions to \%\fBHANDOVER\fP.
+.IP \(bu
+If \fCmarker\-handling\fP is set to \fCignore\fP, incoming marker bits
+are ignored just like in \fCtwrtp\-native\fP version used by ThemWi
+network elements.
+.NH 3
+Additional notes
+.LP
+Some additional notes about \fBtwjit\fP design that don't fit anywhere else:
+.IP \(bu
+Neither the payload type nor any of payload content are checked by \fBtwjit\fP:
+all payload handling is the responsibility of the application.
+.IP \(bu
+RTP packets with zero-length payloads are treated as no different from
+other valid packets; such packets may be needed to ensure continuous
+streaming, as covered in \(sc1.2.1.
+.IP \(bu
+Only SSRC and timestamp fields in the RTP header (and possibly the marker bit)
+are considered for the purpose of mapping received RTP packets to ticks
+of the fixed timing system on the output of \fBtwjit\fP.
+The sequence number field is examined only for analytics (see Chapter\ 3),
+but not for actual operation.
+.NH 2
+Summary of configuration parameters
+.PP
+Applications that use \fBtwjit\fP, usually as part of \fBtwrtp\fP,
+are expected to also use Osmocom vty system for configuration.
+All tunable configuration parameters for \fBtwjit\fP are gathered into
+a config structure, named \%\fCstruct\ osmo_twjit_config\fP in
+the present Osmocom-integrated version;
+this config structure must be provided every time a \fBtwjit\fP instance
+is created.
+Every application that uses \fBtwrtp\fP with \fBtwjit\fP is expected
+to maintain one or more of these config structures, accessible
+to tuning via vty.
+Multiple \fBtwjit\fP configuration parameter sets in one application
+may be needed if the application creates different kinds of RTP endpoints
+that may need different \fBtwjit\fP tunings:
+for example, \&\fCtw\-border\-mgw\fP has one \fBtwjit\fP configuration
+parameter set for GSM RAN side and another for IP-PSTN side.
+Vty configuration for \fBtwjit\fP looks like this
+(excerpt from \&\fCtw\-border\-mgw.cfg\fP):
+.DS
+.ft C
+.tr -\-
+twjit-gsm
+ buffer-depth 2 4
+ thinning-interval 17
+ max-future-sec 10
+twjit-pstn
+ buffer-depth 2 4
+ thinning-interval 17
+ max-future-sec 10
+.ft
+.DE
+.tr --
+.LP
+All numbers in the example above are defaults for the respective settings.
+Individual settings are as follows:
+.IP \(bu
+\&\fCbuffer\-depth\fP line controls the flow-starting fill level (first number)
+and the high water mark fill level (second number), parameters that affect
+the amount of latency added by \fBtwjit\fP in return for tolerance to jitter
+and longer-term variations in IP network path delay.
+The flow-starting fill level is described in detail in \(sc2.3.3;
+the high water mark fill level is described in \(sc2.3.4.2.
+.IP \(bu
+\&\fCthinning\-interval\fP setting controls the interval at which quantum
+units are deleted from the received stream when the standing queue
+thinning mechanism kicks in \(em see \(sc2.3.4.2 for the detailed description.
+.IP \(bu
+\&\fCmax\-future\-sec\fP setting adjusts the guard against time traveler
+packets, described in detail in \(sc2.3.4.3.
+.IP \(bu
+\&\fCstart\-min\-delta\fP and \&\fCstart\-max\-delta\fP optional settings
+(each can be set or unset)
+allow the managing operator to set additional timing constraints
+that need to be met in order to start a new flow: see \(sc2.3.3.2
+for full details.
+.IP \(bu
+\&\fCmarker\-handling\fP setting is described in \(sc2.3.8.
+.NH 1
+Stats and analytics
+.PP
+Every \fBtwjit\fP instance maintains a set of statistical counters,
+collected into \&\fCstruct\ osmo_twjit_stats\fP in the present
+Osmocom-integrated version.
+The purpose of these counters is to assist network operations staff:
+applications that use \fBtwrtp\fP with \fBtwjit\fP are expected
+to provide vty introspection commands that display these statistical
+counters in real time for ongoing calls or connections,
+and then log any non-zero counters at call completion.
+Implementors of new applications are encouraged to examine the source
+for \%\fCtw\-border\-mgw\fP or \%\fCtw\-e1abis\-mgw\fP,
+C modules named \%\fCend_stats.c\fP and \%\fCintrospect.c\fP,
+for an example of how these functions should be implemented.
+.PP
+The rest of this chapter provides a description of every counter
+in \fBtwjit\fP stats structure.
+.NH 2
+Normal operation counters
+.PP
+The following counters record events that are expected to occur in
+normal operation, in the absence of any errors or adverse conditions:
+.sp .3
+.de St
+.IP \fC\\$1\fP 22
+..
+.St rx_packets
+This counter increments for every packet that was fed to \fBtwjit\fP input
+and passed the basic RTP header validity check.
+.St delivered_pkt
+This counter increments for every packet that was pulled from the head of
+a read subbuf for delivery to the fixed timing application
+on the output side of \fBtwjit\fP.
+.St handovers_in
+This counter increments when \fBtwjit\fP state transitions from
+\fB\%FLOWING\fP into \fB\%HANDOVER\fP, as described in \(sc2.3.7.
+.St handovers_out
+This counter increments when \fBtwjit\fP state transitions from
+\fB\%HANDOVER\fP back to \fB\%FLOWING\fP \fIwithout\fP incurring a handover
+underrun first, i.e., when the new (post-handover) packet flow becomes ready
+to flow out before the old one underruns.
+.St marker_resets
+This counter increments when a packet is received with \fBM\fP bit set
+other than in \fB\%EMPTY\fP state,
+and this \fBosmo_twjit\fP instance is configured to treat it as a flow reset,
+causing a handover or a reset of flow acquisition process.
+.NH 2
+Adverse event counters
+.LP
+The following counters record events that are undesirable,
+but not totally unexpected:
+.sp .3
+.St too_old
+This counter increments when an RTP packet received in \fBFLOWING\fP state
+has a timestamp that precedes the active subbuf's \&\fChead_ts\fP.
+This event can only occur if some packet reordering took place, such that
+an earlier-sent packet arrived later than a later-sent one,
+or if the buffer is in ``pre-underrun'' state (see \(sc2.3.4.1)
+and the very last RTP packet that just flowed out is duplicated.
+.St underruns
+This counter increments when a \fBFLOWING\fP state underrun occurs,
+followed by reception of
+at least one post-underrun RTP packet that is then treated as the beginning
+of a new flow \(em see \(sc2.3.4.1.
+.St ho_underruns
+This counter increments when an underrun occurs in \fBHANDOVER\fP state,
+i.e., when the previous flow underruns before the new one is ready to
+start flowing out.
+.St output_gaps
+This counter increments for every gap in the output stream from \fBtwjit\fP
+that occurs in \fBFLOWING\fP or \fBHANDOVER\fP state \fIwithout\fP an underrun,
+i.e., with the flow still continuing and further queued packets present
+past the gap.
+.St thinning_drops
+This counter increments when the standing queue thinning mechanism
+described in \(sc2.3.4.2 deletes a quantum from the stream delivered
+to the application on the output of \fBtwjit\fP.
+Please note that \&\fCdelivered_pkt\fP or \&\fCoutput_gaps\fP
+is still incremented for the quantum that is pulled from the read subbuf,
+but then artificially deleted by the thinning mechanism.
+.NH 2
+Error counters
+.LP
+The following counters record truly unusual and unexpected error events:
+.sp .3
+.St bad_packets
+This counter increments when a packet that was fed to \fBtwjit\fP input
+is too short for RTP (shorter than the minimum RTP header length of 12 bytes)
+or has an unknown value in the RTP version field.
+.St duplicate_ts
+This counter increments when \fBtwjit\fP attempts to add a newly received
+RTP packet to the active-for-write subbuf, but a previous packet is already
+held with the same timestamp and thus at the same depth position.
+For the sake of implementation simplicity in this error case that should not
+occur in a correctly working system, \fBtwjit\fP drops the new packet
+and keeps the old one.
+.NH 2
+Independent analytics
+.PP
+In addition to its main function of mapping received RTP packets
+to ticks of the fixed timing system on its output,
+\fBtwjit\fP performs some ``raw'' analytics on the stream of RTP packet
+it receives.
+These analytic steps are independent of \fBtwjit\fP algorithm details,
+of any configuration settings summarized in \(sc2.4, and
+independent of what happens on the output (fixed timing) side of \fBtwjit\fP
+\(em thus they bear no direct relation to \fBtwjit\fP state transitions,
+subbuf conditions and so forth.
+Instead these analytics depend only on the shape of the incoming RTP stream
+itself, same as if an analyst were looking at a pcap file after the fact.
+Some of these analytic steps are performed in order to gather information
+for the purpose of generating RTCP reception report blocks
+(see Chapter\ 5),
+but some simple analytic steps are done solely to produce some additional
+stats counters that are expected to be valuable to network operations staff.
+The following counters are maintained as part of these independent analytics:
+.sp .3
+.St ssrc_changes
+This counter increments when the received RTP stream exhibits a change of SSRC,
+or more precisely, every time an RTP packet arrives whose SSRC differs from
+that of the packet received just prior.
+.LP
+All following counters record events that occur within a same-SSRC substream:
+.sp .3
+.St seq_skips
+This counter increments every time a packet is received whose sequence number
+increment (over the packet received just prior) is positive and greater than 1.
+Such occurrence indicates either packet loss or reordering in the IP network.
+.St seq_backwards
+This counter increments every time a packet is received whose sequence number
+goes backward, relative to the packet received just prior.
+Such occurrence indicates packet reordering in the IP network.
+.St seq_repeats
+This counter increments every time a packet is received whose sequence number
+is the same as the packet received just prior.
+Such occurrence indicates packet duplication somewhere.
+.St intentional_gaps
+This counter increments every time a packet is received whose sequence number
+increments by 1 over the packet received just prior,
+indicating no packet loss or reordering at the hands of the transited
+IP network,
+the timestamp increment is positive and an integral multiple of the
+samples-per-quantum constant,
+but this increment does not equal exactly one quantum.
+Such occurrence indicates that the RTP stream sender emitted
+an intentional gap.
+.St ts_resets
+This counter increments every time a packet is received whose sequence number
+increments by 1 over the packet received just prior,
+but the timestamp relation between this packet and the previous one
+is neither the expected single quantum increment
+nor an increment of multiple quanta consistent with an intentional gap.
+.St jitter_max
+This reporting variable is not a counter, but a quantitative measure.
+It reports the highest interarrival jitter that was encountered within
+the present same-SSRC substream, measured as prescribed by RFC\ 3550:
+the absolute value of the difference between the timestamp delta
+of two adjacently-received packets and the \(*D in time of arrival,
+converted from seconds and nanoseconds to RTP timestamp units.
+.NH 1
+RTP endpoint functionality
+.PP
+The previous two chapters covered \fBtwjit\fP, the jitter buffer component
+of ThemWi RTP endpoint implementation.
+However, this \fBtwjit\fP layer is not expected to be used directly
+by applications: an application that needs to implement an RTP endpoint
+will need an endpoint implementation that actually sends and receives
+RTP packets, and possibly RTCP as well.
+The top layer of ThemWi RTP endpoint library, named \fBtwrtp\fP,
+provides this functionality.
+.PP
+This chapter describes the API to \fBosmo_twrtp\fP, the version of
+\fBtwrtp\fP layer that has been integrated into \fClibosmo\-netif\fP.
+.NH 2
+Endpoint life cycle
+.PP
+Every \fBtwrtp\fP endpoint is represented by opaque \&\fCstruct\ osmo_twrtp\fP,
+which is a talloc context.
+These endpoints are created with \&\fCosmo_twrtp_create()\fP and freed
+with \&\fCosmo_twrtp_destroy()\fP.
+Every \fBtwrtp\fP instance (\&\fCstruct\ osmo_twrtp\fP) owns
+the two UDP sockets that are bound to RTP and RTCP ports,
+their corresponding \&\fCstruct\ osmo_io_fd\fP instances,
+the subordinate \fBtwjit\fP instance if one exists,
+and any buffered packets.
+All of these resources are released upon \&\fCosmo_twrtp_destroy()\fP.
+.NH 2
+Supplying UDP sockets for RTP and RTCP
+.PP
+For every \fBtwrtp\fP endpoint, there is one file descriptor referring
+to the UDP socket to be used for RTP, and another file descriptor
+referring to the UDP socket to be used for RTCP.
+How do these UDP sockets and file descriptors come into being?
+Two ways are supported:
+.IP 1)
+In self-contained Osmocom applications where \fBtwrtp\fP is to be made
+available as an alternative to Belledonne \fBortp\fP,
+as well as \%\fCtw\-e1abis\-mgw\fP fitting in the place of OsmoMGW-E1,
+\&\fCosmo_twrtp_bind_local()\fP (or its \fCtwrtp\-native\fP equivalent
+in the case of \%\fCtw\-e1abis\-mgw\fP) creates both sockets and binds them
+to a specified IP:port address, supporting both IPv4 and IPv6
+and automatically incrementing the port number by 1 for RTCP.
+.IP 2)
+In Themyscira Wireless CN environment, there is a separate daemon process
+that manages the pool of local UDP ports for RTP+RTCP pairs,
+and that daemon passes allocated sockets to its clients
+via UNIX domain socket file descriptor passing mechanism.
+A network element that uses this mechanism will receive a pair of
+file descriptors for already-bound UDP sockets from \&\fCthemwi\-rtp\-mgr\fP;
+these two already-allocated and already-bound UDP socket
+file descriptors are then passed to a dedicated \fBtwrtp\fP API function.
+In \fCtwrtp\-native\fP this API function is \&\fCtwna_twrtp_supply_fds()\fP;
+the present Osmocom-integrated version provides an equivalent in the form of
+\&\fCosmo_twrtp_supply_fds()\fP.
+.LP
+Either way, the two UDP sockets and their file descriptors
+are then owned by the containing \fBtwrtp\fP instance, and will be closed
+upon \&\fCosmo_twrtp_destroy()\fP.
+.NH 2
+RTP remote address
+.PP
+The two UDP sockets for RTP and RTCP always remain unconnected
+at the kernel level \(em instead the notion of the remote peer address
+is maintained by \fBtwrtp\fP library.
+This remote address needs to be set with \&\fCosmo_twrtp_set_remote()\fP;
+until it is set, no RTP or RTCP packets can be sent or received.
+Once the remote address is set, the library will send outgoing RTP and RTCP
+packets to the correct destination,
+and the same remote address is also used to filter incoming packets:
+incoming RTP and RTCP packets are accepted only if the UDP source address
+matches the currently set remote peer.
+This remote peer address can be changed as needed throughout
+the lifetime of the RTP endpoint.
+.NH 2
+RTP receive path
+.PP
+Applications using \fBtwrtp\fP can receive incoming RTP packets in two ways:
+with or without \fBtwjit\fP.
+Every application that uses \fBtwrtp\fP must decide, at the time of endpoint
+creation via \&\fCosmo_twrtp_create()\fP,
+whether or not this endpoint should be equipped with \fBtwjit\fP;
+if a \fBtwjit\fP instance is needed along with \fBtwrtp\fP,
+the application must provide a \&\fCstruct\ osmo_twjit_config\fP
+\(em see \(sc2.4.
+.PP
+API functions for receiving incoming RTP traffic via \fBtwjit\fP, namely
+\&\fCosmo_twrtp_twjit_rx_ctrl()\fP and \&\fCosmo_twrtp_twjit_rx_poll()\fP,
+can be used only on \fBtwrtp\fP endpoints that were created with \fBtwjit\fP
+included \(em
+however, the other RTP Rx API, namely \&\fCosmo_twrtp_set_raw_rx_cb()\fP
+for non-delayed unbuffered Rx path, is available with all \fBtwrtp\fP
+endpoints.
+Applications are allowed to mix \fBtwjit\fP and raw Rx paths:
+if a raw Rx callback is set, that callback function is called first
+for every received packet, and it can either consume the \fBmsgb\fP
+passed to it, or leave it alone.
+If the callback function returns \fBtrue\fP, indicating that it consumed
+the \fBmsgb\fP, \fBtwrtp\fP Rx processing ends there;
+if it returns \fBfalse\fP, or if there is no raw Rx callback installed,
+then the packet is passed to \fBtwjit\fP if present and enabled,
+otherwise it is discarded.
+.PP
+The ability to use both \fBtwjit\fP and the non-delayed unbuffered Rx path
+at the same time is particularly useful for speech transcoder implementations
+that support AMR codec on the RAN side:
+such TC will use \fBtwjit\fP to feed the incoming RTP stream
+to the speech decoder function that runs on fixed timing, but the
+non-delayed Rx path can also be used to ``peek'' at received RTP packets
+as they come in and extract the CMR field \(em to be fed to the speech
+encoder element, which is separate from the speech decoder fed via \fBtwjit\fP.
+.NH 2
+RTP Tx output
+.PP
+The primary purpose of \fBtwrtp\fP library is to facilitate
+implementation of bidirectional interfaces
+between an RTP stream and a fixed timing system such as
+GSM Um TCH or T1/E1 TDM.
+Most of the work is in receiving the incoming RTP stream
+and mapping incoming RTP packets to ticks of the fixed timing system,
+as covered in Chapter\ 2 \(em however, output from the fixed timing system
+to RTP also requires some consideration.
+.NH 3
+Choice of output SSRC
+.PP
+A random Tx SSRC is assigned to each \fBtwrtp\fP endpoint when it is
+created with \&\fCosmo_twrtp_create()\fP.
+No loop detection or SSRC collision logic is implemented:
+if it so happens that both ends of the RTP link pick the same SSRC,
+no adverse effects will occur for \fBtwrtp\fP.
+If the foreign RTP implementation on the other end does object
+to SSRC collisions and applies some logic along the lines of
+RFC\ 3550 \(sc8.2, it is welcome to change its SSRC:
+on \fBtwrtp\fP receiving end such incoming SSRC changes will be treated
+by \fBtwjit\fP like any other handover.
+However, the SSRC emitted by the local \fBtwrtp\fP end will remain
+the same throughout the lifetime of the endpoint.
+.NH 3
+Starting, stopping and restarting Tx flow
+.PP
+The initial Tx flow is established when the application calls
+\&\fCosmo_twrtp_tx_quantum()\fP for the first time on a given endpoint.
+At this point the initial RTP timestamp for this Tx flow is set,
+based on the current UTC time (CLOCK_REALTIME) reading plus an
+optional random addend.
+The current UTC reading at the moment of Tx flow start is used,
+rather than a purely random number, for consistency with timestamp
+computation in the case of restart, as we shall see momentarily.
+Once the flow is started in this manner,
+the application must commit to calling \&\fCosmo_twrtp_tx_quantum()\fP
+or \&\fCosmo_twrtp_tx_skip()\fP every 20\ ms without fail;
+each of those calls will increment the timestamp by the samples-per-quantum
+constant, usually 160.
+.PP
+If this Tx flow continues uninterrupted for the lifetime of the RTP
+endpoint, the receiving end will see a timestamp increment of one quantum
+in every successive packet, forming a perfectly continuous flow.
+In this case the starting absolute value of the RTP timestamp does not
+matter at all; the UTC-based starting timestamp derivation used by \fBtwrtp\fP
+is indistinguishable from a random number.
+But what if the sending endpoint needs to interrupt and then restart
+its output?
+.PP
+A dedicated mechanism is provided for such restarts after interruption.
+If an application stops emitting packets via \&\fCosmo_twrtp_tx_quantum()\fP
+but later restarts, it must call \&\fCosmo_twrtp_tx_restart()\fP any time
+between the last quantum Tx call of the old flow and the first such call
+of the new flow.
+When \&\fCosmo_twrtp_tx_quantum()\fP is called with the internal restart
+flag set, a timestamp reset is performed.
+The new timestamp is computed from the current UTC reading just like
+on initial Tx start, but then the resulting delta relative to timestamps
+of the previous flow is checked, and the new timestamp may be adjusted
+so that the timestamp increment seen by the remote peer is always positive
+per 32-bit timestamp wraparound rules, and is \fBnot\fP an integral multiple
+of the samples-per-quantum constant.
+The resulting effect is that the far end will see a discontinuity which
+\fBtwjit\fP would treat as a handover, yet the increment of the RTP timestamp
+over this discontinuity gap is a best effort approximation of the actual
+time difference.
+.NH 3
+Ability to emit intentional gaps
+.PP
+As already covered in other parts of this document, Themyscira Wireless
+philosophy is opposed to the practice of intentional gaps in an RTP stream,
+and \fBtwjit\fP receiver performs suboptimally in the presence of such.
+However, \fBtwrtp\fP must be able to function as a drop-in replacement
+for Belledonne \fBortp\fP library in the context of OsmoBTS application;
+OsmoBTS defaults to intentional gaps unless
+\&\fCrtp\ continuous\-streaming\fP vty option is set.
+Therefore, \fBtwrtp\fP library provides the necessary support for
+emitting intentional gaps: it is \&\fCosmo_twrtp_tx_skip()\fP function.
+.NH 3
+Setting RTP marker bit
+.PP
+In an environment that uses continuous streaming (no intentional gaps),
+Themyscira recommendation is to set \fBM\fP bit to 1 on the very first
+emitted RTP packet and on the first packet following a restart
+(induced discontinuity), and set it to 0 on all other packets.
+To produce this behavior with \fBtwrtp\fP, pass the first two Boolean
+arguments to \&\fCosmo_twrtp_tx_quantum()\fP as \fBfalse\fP and \fBtrue\fP.
+For other policies with respect to setting the \fBM\fP bit
+(for example, as would be needed when using \fBtwrtp\fP in the place of
+\fBortp\fP in OsmoBTS),
+see \&\fCosmo_twrtp_tx_quantum()\fP API documentation.
+.NH 2
+No-delay forwarding between RTP endpoints
+.PP
+The present library supports building applications that forward RTP
+packets from one \fBtwrtp\fP endpoint to another without passing through
+\fBtwjit\fP and thus without adding buffering delay.
+To establish such a shortcut path, register a raw (unbuffered) RTP
+receiver on one endpoint via \&\fCosmo_twrtp_set_raw_rx_cb()\fP,
+and in that callback function, pass the \fBmsgb\fP to
+\&\fCosmo_twrtp_tx_forward()\fP.
+Such cross-connect may be applied in one or both directions as needed.
+.PP
+Each endpoint that is involved in such cross-connection can switch
+at any time between forwarding packets as just described and
+emitting internally generated in-band tones or announcements;
+the latter should be emitted with \&\fCosmo_twrtp_tx_quantum()\fP,
+and be sure to also call \&\fCosmo_twrtp_tx_restart()\fP between
+separate episodes of locally generated output.
+The receiving RTP end will see handover events as SSRC switches between
+the one emitted by \fBtwrtp\fP and the one coming from the other remote party.
+Actual timing will also switch, as there is no realistic way that your own
+20\ ms timing for announcement playout will exactly match the timing of the
+RTP stream switched from the other remote party.
+.NH 1
+Support for RTCP
+.PP
+ThemWi RTP endpoint library includes a built-in receiver and parser
+for RTCP packets: it knows how to parse SR and RR packets, it extracts
+information from RTCP reception report blocks that may be useful
+to the application,
+and it saves information from the sender info portion of SR packets
+for use in generating its own reception report blocks.
+The library also includes a facility for generating its own RTCP packets,
+either SR or RR,
+using information from \fBtwjit\fP to fill out the reception report block.
+This chapter describes all library facilities related to RTCP,
+across both \fBtwrtp\fP and \fBtwjit\fP layers.
+.NH 2
+Collection of RR info in twjit
+.PP
+The analytic function of \fBtwjit\fP, described in \(sc3.4, collects
+not only the set of statistical counters described in that section,
+but also a set of info for the purpose of generating RTCP reception reports.
+In the present version of \fBtwrtp\fP and \fBtwjit\fP,
+these tidbits are collected into \&\fCstruct\ osmo_twjit_rr_info\fP;
+this structure is to be retrieved from a \fBtwjit\fP instance
+via \&\fCosmo_twjit_get_rr_info()\fP.
+RTCP sender function in the upper layer of \fBtwrtp\fP uses this RR info
+structure to fill out the reception report block.
+.NH 2
+RTCP receiver in twrtp
+.PP
+Every packet that arrives at a \fBtwrtp\fP endpoint's RTCP port,
+coming from the correct source address that matches the current remote peer,
+is parsed by the library.
+The parser captures SR and RR information; since these two groups of data
+are captured for different purposes, they are best studied separately.
+.NH 3
+Extraction of SR info from received RTCP packets
+.PP
+If the received RTCP packet is a correctly formed SR packet per
+RFC\ 3550 \(sc6.4.1, \fBtwrtp\fP notes that an SR packet was received,
+captures the local time (CLOCK_MONOTONIC) of its arrival,
+notes the SSRC of the SR sender,
+and saves the middle 32 bits of the 64-bit NTP timestamp in the SR.
+This saved information will be used later if and when this \fBtwrtp\fP
+instance generates its own reception report.
+.NH 3
+Extraction of RR info from received RTCP packets
+.PP
+If the received RTCP packet is either SR or RR (either packet type
+is allowed to carry anywhere from 0 to 31 reception report blocks),
+the RTCP receiver in the library checks every included RR block
+to see if it describes our Tx SSRC, i.e., the one that was assigned
+as described in \(sc4.5.1.
+If such SSRC-matching RR block is seen, \fBtwrtp\fP sets a flag noting so,
+and captures two words of useful info from the report: the word describing
+packet loss and the word that expresses interarrival jitter.
+Both words are described in RFC\ 3550 \(sc6.4.1.
+These words are captured for retrieval by the application,
+to be made accessible for vty introspection and logged upon call completion,
+along with locally collected stats as described in Chapter\ 3.
+.NH 2
+Emitting RTCP packets
+.LP
+The library is capable of generating 3 forms of RTCP packet:
+.IP \(bu
+SR packet containing a single RR block;
+.IP \(bu
+SR packet containing no RR block;
+.IP \(bu
+RR packet containing a single reception report block.
+.LP
+The following sections describe how these RTCP packets
+may be generated and emitted.
+.NH 3
+Setting SDES strings
+.PP
+RFC\ 3550 \(sc6.1 stipulates that every RTCP SR or RR packet also
+needs to include an SDES block, containing at least a CNAME string.
+These SDES strings (the mandatory CNAME and any optional ones)
+are set with \&\fCosmo_twrtp_set_sdes()\fP API function;
+the application must call this function before any SR or RR packets
+can be emitted.
+.NH 3
+Emitting SR packets
+.PP
+In this library implementation, SR packets can be emitted in only one path:
+together with locally generated (not forwarded) RTP data output,
+as a result of the application calling \&\fCosmo_twrtp_tx_quantum()\fP.
+There are two ways to cause \&\fCosmo_twrtp_tx_quantum()\fP to emit
+RTCP SR in addition to its regular RTP data packet carrying
+its normally emitted quantum of media:
+.IP \(bu
+The application can call \&\fCosmo_twrtp_set_auto_rtcp_interval()\fP
+and thus configure the library to automatically emit an RTCP SR packet
+after every so many regular RTP data packets sent via
+\&\fCosmo_twrtp_tx_quantum()\fP.
+.IP \(bu
+The application can control directly which calls to
+\&\fCosmo_twrtp_tx_quantum()\fP should emit RTCP SR via the last Boolean
+argument to this function.
+.LP
+Whichever condition is used to trigger emission of RTCP SR packet,
+the decision as to whether or not this SR packet will include an RR block
+in addition to the required sender info is made by the library.
+This RR block will be included if and only if:
+.IP a)
+this \fBtwrtp\fP instance is equipped with \fBtwjit\fP, and
+.IP b)
+at least one valid RTP packet has been received by this \fBtwjit\fP instance,
+producing the necessary SSRC-keyed RR info structure.
+.LP
+The first 3 words in the RR block (the packet loss word,
+extended highest sequence number received and interarrival jitter)
+are always filled based on the info provided by \fBtwjit\fP via
+\&\fCstruct\ osmo_twjit_rr_info\fP.
+However, the last 2 words (LSR and DLSR) are filled based on info
+captured by \fBtwrtp\fP layer's RTCP receiver, as described in \(sc5.2.1.
+If an SR was previously received by this \fBtwrtp\fP endpoint
+and the sender of that SR had the same SSRC as the one for which
+we are producing our reception report
+(the SSRC in \&\fCstruct\ osmo_twjit_rr_info\fP),
+then information from that received SR
+(its time of arrival and saved NTP timestamp bits)
+is used to fill LSR and DLSR words in the generated RR block.
+Otherwise, these two words are set to 0.
+.NH 3
+Emitting standalone RR packets
+.PP
+In most RTCP-enabled RTP applications, it is most useful to emit SR packets
+and convey reception report blocks as part of them.
+However, \fBtwrtp\fP library also provides a way to emit standalone RR
+packets, which can be useful for applications that receive RTP via \fBtwjit\fP
+but don't send out their own originated RTP traffic.
+To generate a standalone RR packet, call \&\fCosmo_twrtp_send_rtcp_rr()\fP.
+This operation will succeed only if SDES strings have been set,
+if this \fBtwrtp\fP instance is equipped with \fBtwjit\fP, if that \fBtwjit\fP
+instance was actually used to receive traffic, and if at least one RTP packet
+has been received.
+The content of the generated standalone RR packet is exactly the same
+as the RR block that is more commonly included in an SR packet,
+as described in the previous section.
+.NH 2
+RTCP support limitations
+.PP
+RTCP support in \fBtwrtp\fP is subject to the following limitations:
+.IP \(bu
+Sender reports (SR packets) emitted by \fBtwrtp\fP can only describe
+traffic that is generated locally via \&\fCosmo_twrtp_tx_quantum()\fP,
+not forwarded traffic that is emitted via \&\fCosmo_twrtp_tx_forward()\fP.
+There is no way to generate SR packets at all outside of
+\&\fCosmo_twrtp_tx_quantum()\fP.
+.IP \(bu
+The library can only generate reception reports (either standalone RR packets
+or as part of SR packets) for traffic that is received via \fBtwjit\fP,
+but not for traffic that is received via non-delayed unbuffered path
+\(em see \(sc4.4.
+.IP \(bu
+The built-in RTCP receiver and parser can only extract potentially useful
+RR info (reports of packet loss and interarrival jitter) from far end
+reception reports when those far end RRs describe our own Tx SSRC
+(see \(sc4.5.1), not some foreign SSRC we forward per \(sc4.6.
+.LP
+The summary of these limitations is that \fBtwrtp\fP has truly functional
+RTCP support only when \fBtwrtp\fP is used to implement a full endpoint,
+one that interfaces between RTP and a fixed timing system such as GSM Um TCH,
+T1/E1 TDM or a software transcoder that runs on its own CLOCK_MONOTONIC
+timerfd time base.
+``Light'' RTP endpoints that omit some components of this full endpoint
+ensemble will most likely be unable to support RTCP.
+.NH 2
+Usefulness of RTCP
+.PP
+In the opinion of \fBtwrtp\fP author, RTCP is most useful in IP-PSTN
+environment where RTP traffic is exchanged between peer entities under
+different ownership and different administrative control,
+traveling across public Internet.
+In that environment, proper implementation of RTCP can be seen as
+good netizenship: the administrator of one fiefdom can see \fBtwjit\fP stats
+(or full pcap when needed for deeper debugging)
+on RTP traffic \fIreceived\fP by her queendom, but she can only know if her
+outgoing traffic suffers from packet loss or jitter if administrators of
+other fiefdoms have configured \fItheir\fP systems to emit RTCP reception
+reports.
+For this reason, \&\fCtw\-border\-mgw\fP instances at Themyscira MSCs
+are configured to dutifully emit RTCP SR (which includes RR block)
+on IP-PSTN side
+every 5\ s, or after every 250 RTP data packets sent every 20\ ms.
+.PP
+On the other hand,
+RTCP is \fBnot\fP really useful in a single-administration GSM RAN,
+i.e., in environments where both ends of the RTP transport leg
+are controlled by the same administration.
+In Themyscira environment, each GSM-codec-carrying RTP transport leg
+runs between \&\fCtw\-border\-mgw\fP or other ThemWi CN components on one end,
+located at an MSC site, and either OsmoBTS or \&\fCtw\-e1abis\-mgw\fP
+on the other end, located at a cell site, carried across public Internet
+in a WireGuard tunnel.
+Because transport across public Internet is involved, RTP performance
+needs to be closely monitored with an eye out for packet loss, jitter or
+even reordering, and \fBtwjit\fP configuration needs to be carefully tuned.
+However, direct examination of \fBtwjit\fP stats on both CN and BSS ends
+will yield much more detailed information than the constrained data model
+of RTCP \(em hence RTCP is not really useful.
+.NH 2
+Non-RTCP operation
+.PP
+If \fBtwrtp\fP needs to be used in an environment where RTCP is not needed,
+or even one where use of RTCP is forbidden,
+nothing special needs to be done to achieve non-RTCP operation.
+No RTCP packets will be emitted if the application never calls
+\&\fCosmo_twrtp_set_sdes()\fP;
+any received RTCP packets will still be parsed as described in \(sc5.2,
+but the existence of saved bits from this parsing can be simply ignored.
+RR info from \fBtwjit\fP, collected as described in \(sc5.1, can be
+likewise ignored.
+.PP
+As an additional feature in \fBosmo_twrtp\fP, it is also possible to
+disable RTCP completely by not binding a UDP socket for the odd-numbered
+RTCP port and not having an active file descriptor or
+\%\fCstruct\ osmo_io_fd\fP for it.
+Referring to \(sc4.2 for socket binding or file descriptor initialization
+procedures, such non-RTCP operation can be achieved by passing \fBfalse\fP
+as the last Boolean argument to \%\fCosmo_twrtp_bind_local()\fP or by
+passing a negative RTCP file descriptor to \%\fCosmo_twrtp_supply_fds()\fP.
+.NH 1
+Stats at twrtp level
+.PP
+In addition to \fBtwjit\fP stats counters described in Chapter\ 3,
+\fBtwrtp\fP layer has its own stats structure with a few additional counters,
+dealing with both RTP and RTCP packets in both Rx and Tx directions.
+This stats structure is \%\fCstruct\ osmo_twrtp_stats\fP
+in the present Osmocom-integrated version.
+Here is a description of all counters in this set:
+.sp .3
+.de St
+.IP \fC\\$1\fP 24
+..
+.St rx_rtp_pkt
+This counter increments for every packet that is received on the RTP UDP
+socket \fBand\fP has a source address that matches the current remote peer
+set with \%\fCosmo_twrtp_set_remote()\fP.
+.St rx_rtp_badsrc
+This counter counts packets that were received on the RTP UDP socket,
+but then discarded because their source address was wrong.
+.St rx_rtcp_pkt
+This counter increments for every packet that is received on the RTCP UDP
+socket \fBand\fP has a source address that matches the current remote peer
+set with \%\fCosmo_twrtp_set_remote()\fP.
+.St rx_rtcp_badsrc
+This counter counts packets that were received on the RTCP UDP socket,
+but then discarded because their source address was wrong.
+.St rx_rtcp_invalid
+This counter counts packets that were received on the RTCP UDP socket,
+passed the source address check, but were deemed invalid in parsing.
+.St rx_rtcp_wrong_ssrc
+This counter increments for every parsed reception report block within
+a received RTCP SR or RR packet that describes an SSRC other than
+our Tx SSRC of \(sc4.5.1.
+.St tx_rtp_pkt
+This counter increments for every RTP data packet emitted via
+\%\fCosmo_twrtp_tx_quantum()\fP;
+it is also emitted in RTCP SR packets in the ``sender's packet count'' word.
+Packets transmitted via \%\fCosmo_twrtp_tx_forward()\fP are \fBnot\fP
+counted here; as explained in \(sc5.4, there is no RTCP support in \fBtwrtp\fP
+for this path.
+.St tx_rtp_bytes
+This counter counts payload bytes transmitted via
+\%\fCosmo_twrtp_tx_quantum()\fP;
+it is also emitted in RTCP SR packets in the ``sender's octet count'' word.
+Just like \%\fCtx_rtp_pkt\fP, this counter is not affected by
+\%\fCosmo_twrtp_tx_forward()\fP path.
+.St tx_rtcp_pkt
+This counter increments for every RTCP packet emitted by this
+\fBtwrtp\fP instance.
diff --git a/include/osmocom/netif/twjit.h b/include/osmocom/netif/twjit.h
index a12420f..8591d22 100644
--- a/include/osmocom/netif/twjit.h
+++ b/include/osmocom/netif/twjit.h
@@ -22,18 +22,12 @@
* RTP stream to an output application that has fixed timing requirements,
* e.g., the Tx side of GSM Um TCH or a T1/E1 TDM interface.
*
- * There also exists a detailed document titled _Guide to ThemWi RTP
- * endpoint library_, located here:
- * https://www.freecalypso.org/TW-doc/twrtp-guide-latest.pdf
- * (See TW-doc directory listing for other formats and previous versions.)
- * This document is required reading for anyone seeking to properly
- * understand the present jitter buffer facility, its domain of application
- * and how to use it. Specific section references to this document
- * will be made in subsequent comments.
- *
- * FIXME: create an Osmocom-controlled version of this document
- * that describes the version of twrtp+twjit modified for inclusion
- * in Osmocom.
+ * For a more detailed description, please consult the full twrtp guide
+ * document that can be found in doc/twrtp directory. This document is
+ * required reading for anyone seeking to properly understand the present
+ * jitter buffer facility, its domain of application and how to use it.
+ * Specific section references to this document will be made in subsequent
+ * comments.
*/
/*! Each instance of twjit in the present version exists as struct osmo_twjit.
diff --git a/include/osmocom/netif/twrtp.h b/include/osmocom/netif/twrtp.h
index f1852ec..40075e3 100644
--- a/include/osmocom/netif/twrtp.h
+++ b/include/osmocom/netif/twrtp.h
@@ -108,18 +108,12 @@
* existence of the built-in RTCP receiver. Any received RTCP packets will
* still be parsed, but you can ignore the data that result from this parsing.
*
- * There also exists a detailed document titled _Guide to ThemWi RTP
- * endpoint library_, located here:
- * https://www.freecalypso.org/TW-doc/twrtp-guide-latest.pdf
- * (See TW-doc directory listing for other formats and previous versions.)
- * This document is required reading for anyone seeking to properly
- * understand twrtp, its domain of application and all of its capabilities,
- * beyond the brief summary given above. Specific section references to
- * this document will be made in subsequent comments.
- *
- * FIXME: create an Osmocom-controlled version of this document
- * that describes the version of twrtp+twjit modified for inclusion
- * in Osmocom.
+ * For a more detailed description, please consult the full twrtp guide
+ * document that can be found in doc/twrtp directory. This document is
+ * required reading for anyone seeking to properly understand twrtp, its
+ * domain of application and all of its capabilities, beyond the brief
+ * summary given above. Specific section references to this document
+ * will be made in subsequent comments.
*/
/*! Each instance of twrtp in the present version exists as struct osmo_twrtp.
--
To view, visit https://gerrit.osmocom.org/c/libosmo-netif/+/39291?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: libosmo-netif
Gerrit-Branch: master
Gerrit-Change-Id: I90976edfd8c66c2d1c5bc7939e0a49f725a02f3f
Gerrit-Change-Number: 39291
Gerrit-PatchSet: 3
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: jolly <andreas(a)eversberg.eu>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
falconia has submitted this change. ( https://gerrit.osmocom.org/c/osmo-bts/+/41050?usp=email )
Change subject: HRv1 codec: add support for TW-TS-002
......................................................................
HRv1 codec: add support for TW-TS-002
OsmoBTS supports TW-TS-001 enhanced RTP format for FR and EFR codecs
since 2024, providing functional equivalent of GSM 08.60 TRAU-UL
output over IP physical transport. Now do the same with TW-TS-002,
IP equivalent of GSM 08.61 for HRv1 codec.
Only TCH UL path is affected; no changes are needed to TCH DL path
because existing RTP Rx handling for RFC 5993 also covers TW-TS-002.
Related: OS#6036
Change-Id: Icf11e43d4ce9df990d0e0a856d62d9ea11cb837c
---
M src/common/bts.c
M src/common/l1sap.c
2 files changed, 71 insertions(+), 7 deletions(-)
Approvals:
Jenkins Builder: Verified
fixeria: Looks good to me, approved
diff --git a/src/common/bts.c b/src/common/bts.c
index 282d730..73cb8a6 100644
--- a/src/common/bts.c
+++ b/src/common/bts.c
@@ -394,6 +394,7 @@
osmo_bts_set_feature(bts->features, BTS_FEAT_IPV6_NSVC);
osmo_bts_set_feature(bts->features, BTS_FEAT_PAGING_COORDINATION);
osmo_bts_set_feature(bts->features, BTS_FEAT_TWTS001);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_TWTS002);
/* Maximum TA supported by the PHY (can be overridden by PHY specific code) */
bts->support.max_ta = MAX_TA_DEF;
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
index ab787ae..e77f602 100644
--- a/src/common/l1sap.c
+++ b/src/common/l1sap.c
@@ -2229,12 +2229,63 @@
}
}
+/* See Section 5.2 of RFC5993 and TW-TS-002 */
+enum super5993_ft {
+ FT_GOOD_SPEECH = 0,
+ FT_INVALID_SID = 1,
+ FT_GOOD_SID = 2,
+ FT_BFI_WITH_DATA = 6,
+ FT_NO_DATA = 7,
+};
+
+/* a helper function for emitting GSM-HR UL in TW-TS-002 format */
+static void send_rtp_twts002(struct gsm_lchan *lchan, uint32_t fn,
+ struct msgb *msg)
+{
+ enum super5993_ft ft;
+ uint8_t toc;
+
+ if (msg->len == GSM_HR_BYTES) {
+ switch (tch_ul_msg_hr_sid(msg)) {
+ case OSMO_GSM631_SID_CLASS_SPEECH:
+ ft = tch_ul_msg_bfi(msg) ? FT_BFI_WITH_DATA
+ : FT_GOOD_SPEECH;
+ break;
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ ft = FT_INVALID_SID;
+ break;
+ case OSMO_GSM631_SID_CLASS_VALID:
+ ft = tch_ul_msg_bfi(msg) ? FT_INVALID_SID : FT_GOOD_SID;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ } else {
+ ft = FT_NO_DATA;
+ }
+ /* ToC octet of TW-TS-002 is an extension of RFC 5993 */
+ toc = ft << 4;
+ if (ft == FT_INVALID_SID)
+ toc |= 0x04; /* TW-TS-002 version 1.2.0 */
+ /* always set DTXd and TAF bits */
+ if (lchan->ts->trx->bts->dtxd)
+ toc |= 0x08;
+ if (fr_hr_efr_sid_position(lchan, fn))
+ toc |= 0x01;
+ if (ft != FT_NO_DATA) {
+ msgb_push_u8(msg, toc);
+ send_ul_rtp_packet(lchan, fn, msg->data, msg->len);
+ } else {
+ send_ul_rtp_packet(lchan, fn, &toc, 1);
+ }
+}
+
/* A helper function for l1sap_tch_ind(): handling BFI
*
* Please note that the msgb passed to this function is used only when
- * the CN asked the BSS to emit extended RTP formats (currently TW-TS-001,
- * later TW-TS-002 as well) that can indicate BFI along with deemed-bad
- * frame data bits, just like GSM 08.60 and 08.61 TRAU-UL frames.
+ * the CN asked the BSS to emit extended RTP formats of TW-TS-001 or
+ * TW-TS-002 that can indicate BFI along with deemed-bad frame data bits,
+ * just like GSM 08.60 and 08.61 TRAU-UL frames.
*/
static void tch_ul_bfi_handler(struct gsm_lchan *lchan,
const struct gsm_time *g_time, struct msgb *msg)
@@ -2257,6 +2308,14 @@
return;
}
+ /* Ditto for TCH/HS and TW-TS-002. */
+ if ((lchan->abis_ip.rtp_extensions & OSMO_RTP_EXT_TWTS002) &&
+ lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch_mode == GSM48_CMODE_SPEECH_V1) {
+ send_rtp_twts002(lchan, fn, msg);
+ return;
+ }
+
/* Are we applying an ECU to this uplink, and are we in a state
* (not DTX pause) where we emit ECU output? */
if (lchan->ecu_state && !osmo_ecu_is_dtx_pause(lchan->ecu_state)) {
@@ -2530,10 +2589,14 @@
send_ul_rtp_packet(lchan, fn, msg->data, msg->len);
} else if (lchan->type == GSM_LCHAN_TCH_H &&
lchan->tch_mode == GSM48_CMODE_SPEECH_V1) {
- /* HR codec: TS 101 318 or RFC 5993,
- * will also support TW-TS-002 in the future. */
- send_gsmhr_std_rtp(lchan, &g_time, msg,
- bts->emit_hr_rfc5993);
+ /* HR codec: TW-TS-002 in ThemWi environment,
+ * or TS 101 318 or RFC 5993 in traditional
+ * 3GPP or Osmocom environments. */
+ if (lchan->abis_ip.rtp_extensions & OSMO_RTP_EXT_TWTS002)
+ send_rtp_twts002(lchan, fn, msg);
+ else
+ send_gsmhr_std_rtp(lchan, &g_time, msg,
+ bts->emit_hr_rfc5993);
} else {
/* generic case, no RTP alterations */
send_ul_rtp_packet(lchan, fn, msg->data, msg->len);
--
To view, visit https://gerrit.osmocom.org/c/osmo-bts/+/41050?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: osmo-bts
Gerrit-Branch: master
Gerrit-Change-Id: Icf11e43d4ce9df990d0e0a856d62d9ea11cb837c
Gerrit-Change-Number: 41050
Gerrit-PatchSet: 3
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
falconia has submitted this change. ( https://gerrit.osmocom.org/c/osmo-bts/+/41049?usp=email )
Change subject: FR/HR/EFR: implement SID filter in TCH UL path
......................................................................
FR/HR/EFR: implement SID filter in TCH UL path
As a result of how FR/HR/EFR DTX interacts with block diagonal
interleaving, at the beginning and end of each DTX pause a
correctly functioning TCH receiver will always pick up an
artifact consisting of 4 received bursts (2 for TCH/HS)
and same number of omitted bursts. Standard channel decoding
of this Rx artifact will produce a "half-block" in which half
of the bits prior to convolutional decoding will come from
a SID repetition whose Tx was notionally suppressed, while
the other half will be garbage. As a result of convolutional
decoding, the result will often appear as valid SID per
classification rules - but passing it as such to the Rx DTX
handler is wrong. Classic E1 BTS and GSM MS implementations
include a kind of SID filter at this point, setting BFI on
these received half-blocks, so that the Rx DTX handler will
see an invalid SID condition. Invalid SID means that comfort
noise generation is to be continued, but no updated CN
parameters are available - which is the truth in half-block
Rx situations. Implement the same filter.
Additional background info can be found here:
https://osmocom.org/projects/retro-gsm/wiki/DTXu_half-blocks
Change-Id: I46c62312316b04567bcadf6050597673f071247d
---
M include/osmo-bts/lchan.h
M src/common/l1sap.c
2 files changed, 58 insertions(+), 8 deletions(-)
Approvals:
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
fixeria: Looks good to me, approved
diff --git a/include/osmo-bts/lchan.h b/include/osmo-bts/lchan.h
index 5c34015..42de2f4 100644
--- a/include/osmo-bts/lchan.h
+++ b/include/osmo-bts/lchan.h
@@ -292,6 +292,9 @@
bool dl_sid_transmitted;
/* The current frame in the DL is taken up by FACCH */
bool dl_facch_stealing;
+ /* UL SID filter to catch DTXu half-blocks,
+ * see tch_ul_fr_hr_efr() function. */
+ bool ul_sid_filter;
} dtx_fr_hr_efr;
uint8_t last_cmr;
uint32_t last_fn;
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
index 5d5c65f..ab787ae 100644
--- a/src/common/l1sap.c
+++ b/src/common/l1sap.c
@@ -2328,7 +2328,19 @@
{
enum osmo_gsm631_sid_class sidc;
- /* If we got no payload (BFI without data), there is nothing
+ /* No matter what else is happening, even if we are about to bail out
+ * early because we received FACCH and thus BFI-no-data, if we are
+ * at a mandatory-Tx position for SID, we need to clear UL SID filter
+ * state so that the next valid SID frame will be allowed through,
+ * whether it occurs right now or in a later frame position because
+ * of FACCH. See the note in GSM 06.31 section 5.1.2: if the
+ * SACCH-aligned SID update position is stolen by FACCH, the next
+ * frame position shall carry the SID update as soon as FACCH stealing
+ * is over. */
+ if (fr_hr_efr_sid_position(lchan, fn))
+ lchan->tch.dtx_fr_hr_efr.ul_sid_filter = false;
+
+ /* If we got no payload (BFI without data), there is nothing more
* for us to do here. */
if (msg->len == 0)
return;
@@ -2369,11 +2381,26 @@
* is an accepted SID frame in the definition of GSM 06.31
* and its HR & EFR counterparts.
*
- * 2) Next patch in the series will introduce logic that sets BFI
- * under certain conditions dependent on SID classification and
- * previous state, in order to suppress false indications of
- * "valid" SID to the Rx DTX handler on the RTP receiving end
- * during "half-block" conditions.
+ * 2) As a result of how FR/HR/EFR DTX interacts with block diagonal
+ * interleaving, at the beginning and end of each DTX pause a
+ * correctly functioning TCH receiver will always pick up an
+ * artifact consisting of 4 received bursts (2 for TCH/HS)
+ * and same number of omitted bursts. Standard channel decoding
+ * of this Rx artifact will produce a "half-block" in which half
+ * of the bits prior to convolutional decoding will come from
+ * a SID repetition whose Tx was notionally suppressed, while
+ * the other half will be garbage. As a result of convolutional
+ * decoding, the result will often appear as valid SID per
+ * classification rules - but passing it as such to the Rx DTX
+ * handler is wrong. Classic E1 BTS and GSM MS implementations
+ * include a kind of SID filter at this point, setting BFI on
+ * these received half-blocks, so that the Rx DTX handler will
+ * see an invalid SID condition. Invalid SID means that comfort
+ * noise generation is to be continued, but no updated CN
+ * parameters are available - which is the truth in half-block
+ * Rx situations. We implement the same filter. More info here:
+ *
+ * https://osmocom.org/projects/retro-gsm/wiki/DTXu_half-blocks
*
* 3) For HR codec only, RTP output format functions need to know
* both BFI flag and SID classification.
@@ -2382,12 +2409,32 @@
case OSMO_GSM631_SID_CLASS_SPEECH:
/* Only a good speech frame, not an unusable frame,
* using GSM 06.31 definitions, marks exit from a DTX pause. */
- if (!tch_ul_msg_bfi(msg))
+ if (!tch_ul_msg_bfi(msg)) {
lchan_set_marker(false, lchan);
+ lchan->tch.dtx_fr_hr_efr.ul_sid_filter = false;
+ }
break;
- case OSMO_GSM631_SID_CLASS_INVALID:
case OSMO_GSM631_SID_CLASS_VALID:
+ /* Here comes the just-described SID filter. The logic is
+ * thus: a valid SID is allowed through to the Rx DTX handler
+ * if it follows speech (end of talkspurt), if it appears
+ * in the expected mandatory-Tx position for SID updates,
+ * or if it happens after that mandatory-Tx position due to
+ * FACCH stealing. Otherwise, valid SID is converted to
+ * invalid by setting BFI.
+ */
+ if (lchan->tch.dtx_fr_hr_efr.ul_sid_filter)
+ tch_ul_msg_bfi(msg) = true;
+ /* fall through */
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ /* Whether we got valid or invalid SID, we know that the MS
+ * has entered or remains in a DTXu pause. (Invalid SID
+ * means that MS state is known to be DTXu, but no CN
+ * parameters are available.) Hence we indicate DTXu pause
+ * state for both RTP marker bit and SID filter mechanisms.
+ */
lchan_set_marker(true, lchan);
+ lchan->tch.dtx_fr_hr_efr.ul_sid_filter = true;
break;
default:
/* There are only 3 possible SID classifications per
--
To view, visit https://gerrit.osmocom.org/c/osmo-bts/+/41049?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: osmo-bts
Gerrit-Branch: master
Gerrit-Change-Id: I46c62312316b04567bcadf6050597673f071247d
Gerrit-Change-Number: 41049
Gerrit-PatchSet: 2
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
falconia has submitted this change. ( https://gerrit.osmocom.org/c/osmo-bts/+/41047?usp=email )
Change subject: TCH UL path: add out-of-band BFI flag
......................................................................
TCH UL path: add out-of-band BFI flag
In original OsmoBTS architecture prior to Themyscira patches,
BFI (Bad Frame Indication) condition was signaled internally by
zero-length payload passed from BTS model to l1sap, and externally
by absence of RTP output (intentional gap) or a zero-length RTP
payload emitted in 'rtp continuous-streaming' mode. However, this
paradigm is contrary to classic GSM BSS architecture in which BFI
is an out-of-band metadata flag that travels alongside with frame
payload bits, whether the latter are valid or invalid.
Since 2024 OsmoBTS supports the option of TW-TS-001 output for
FR and EFR codecs, which allows the possibility of BFI-with-data
in RTP. OsmoBTS can already emit such BFI-with-data packets when
the BTS model delivered a deemed-good traffic frame, but that frame
was subsequently deemed bad by the link quality check in l1sap -
but there is still no explicit out-of-band BFI flag inside OsmoBTS
TCH UL path.
By introducing an out-of-band BFI flag, we make it possible for BTS
model PHYs to deliver marked-bad traffic frames: when CRC fails
in GSM 05.03 channel decoding step, a PHY that permits modification
(libosmocoding or future FOSS DSP PHY) can be enhanced to preserve
channel-decoded bits while conveying BFI.
The link quality check in l1sap is reworked to use the new OOB BFI
flag. Follow-on patches will introduce further logic that can also
assert BFI at high level, above BTS model PHYs.
While reworking the link quality check in l1sap, restrict it to
speech modes only: per GSM specs, BFI does not exist in CSD.
Change-Id: I8097946429e83eae90f89e49d17ffb8eb0636fcb
---
M include/osmo-bts/msg_utils.h
M src/common/l1sap.c
2 files changed, 58 insertions(+), 21 deletions(-)
Approvals:
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
fixeria: Looks good to me, approved
diff --git a/include/osmo-bts/msg_utils.h b/include/osmo-bts/msg_utils.h
index db7142d..6cebce7 100644
--- a/include/osmo-bts/msg_utils.h
+++ b/include/osmo-bts/msg_utils.h
@@ -13,21 +13,38 @@
struct msgb;
-/* Access 1st part of msgb control buffer */
+/****************************************************************
+* Accessor macros for control buffer words in RTP input path (DL)
+*****************************************************************/
+
+/* Storing RTP header fields in the path from RTP and Osmux
+ * Rx callback functions to TCH-RTS.ind handling.
+ * FIXME: do we really need this RTP header info downstream
+ * of the jitter buffer mechanism in the RTP endpoint library?
+ */
#define rtpmsg_marker_bit(x) ((x)->cb[0])
+#define rtpmsg_seq(x) ((x)->cb[1])
+#define rtpmsg_ts(x) ((x)->cb[2])
-/* Access 2nd part of msgb control buffer */
-#define rtpmsg_seq(x) ((x)->cb[1])
-
-/* Access 3rd part of msgb control buffer */
-#define rtpmsg_ts(x) ((x)->cb[2])
-
-/* Access 4th part of msgb control buffer */
+/* l1sap_rtp_rx_cb() does some preening or preparsing on some
+ * RTP payloads, and in two cases (HR with RFC 5993 input and
+ * CSD NT modes) this preparsing step produces some metadata
+ * that need to be passed to TCH-RTS.ind handling.
+ */
#define rtpmsg_is_rfc5993_sid(x) ((x)->cb[3])
-
-/* Access 5th part of msgb control buffer */
#define rtpmsg_csd_align_bits(x) ((x)->cb[4])
+/********************************************************
+* Accessor macros for control buffer words in TCH UL path
+*********************************************************/
+
+/* We provide an ability for BTS models to indicate BFI along with payload
+ * bits just like in GSM 08.60 TRAU-UL frames, and the same BFI flag can
+ * then be set by model-independent functions for higher-level BFI
+ * conditions. This cb word shall act as a Boolean flag.
+ */
+#define tch_ul_msg_bfi(x) ((x)->cb[0])
+
/**
* Classification of OML message. ETSI for plain GSM 12.21
* messages and IPA/Osmo for manufacturer messages.
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
index 09f6a90..8f7745e 100644
--- a/src/common/l1sap.c
+++ b/src/common/l1sap.c
@@ -2220,16 +2220,15 @@
/* a helper function for emitting FR/EFR UL in TW-TS-001 format */
static void send_rtp_twts001(struct gsm_lchan *lchan, uint32_t fn,
- struct msgb *msg, bool good_frame)
+ struct msgb *msg)
{
uint8_t teh;
bool send_frame;
if (msg->len == GSM_FR_BYTES || msg->len == GSM_EFR_BYTES) {
- if (good_frame)
- teh = 0xE0;
- else
- teh = 0xE2;
+ teh = 0xE0;
+ if (tch_ul_msg_bfi(msg))
+ teh |= 0x02;
send_frame = true;
} else {
teh = 0xE6;
@@ -2272,7 +2271,7 @@
lchan->type == GSM_LCHAN_TCH_F &&
(lchan->tch_mode == GSM48_CMODE_SPEECH_V1 ||
lchan->tch_mode == GSM48_CMODE_SPEECH_EFR)) {
- send_rtp_twts001(lchan, fn, msg, false);
+ send_rtp_twts001(lchan, fn, msg);
return;
}
@@ -2345,10 +2344,31 @@
msgb_pull_to_l2(msg);
/* Low level layers always call us when TCH content is expected, even if
- * the content is not available due to decoding issues. Content not
- * available is expected as empty payload. We also check if quality is
- * good enough. */
- if (msg->len && tch_ind->lqual_cb >= bts->min_qual_norm) {
+ * the content is not available due to decoding issues. Content not
+ * available is indicated as empty payload, also termed BFI w/o data.
+ * Alternatively, a BTS model can supply channel-decoded payload bits,
+ * but also set BFI flag, just like in TRAU-UL frames on E1 Abis.
+ *
+ * If the channel mode is speech (not CSD), we also check if quality is
+ * good enough - if it isn't, we set BFI. This quality check is
+ * essential with FRv1 codec and DTXu, otherwise DTXu pauses will be
+ * filled with very unpleasant sounds as channel-decoded radio noise
+ * gets declared as good traffic frames with 1/8 probability given
+ * only a 3-bit CRC. However, this check is restricted to speech
+ * because per the specs, BFI does not exist in CSD: every channel-
+ * decoded frame is passed along, error handling either falls on RLP
+ * or is the responsibility of user applications in Transparent mode.
+ */
+ if ((lchan->rsl_cmode == RSL_CMOD_SPD_SPEECH) &&
+ (tch_ind->lqual_cb < bts->min_qual_norm))
+ tch_ul_msg_bfi(msg) = true;
+
+ /* FR/HR/EFR SID classification, with potential effects on BFI flag,
+ * will go here - further patches in the series. */
+
+ /* Good RTP output happens when we got some payload AND it is not
+ * marked as BFI. */
+ if (msg->len && !tch_ul_msg_bfi(msg)) {
/* feed the good frame to the ECU, if we are applying one */
if (lchan->ecu_state)
osmo_ecu_frame_in(lchan->ecu_state, false, msg->data, msg->len);
@@ -2361,7 +2381,7 @@
lchan->tch_mode == GSM48_CMODE_SPEECH_EFR)) {
/* FR and EFR codecs */
if (lchan->abis_ip.rtp_extensions & OSMO_RTP_EXT_TWTS001)
- send_rtp_twts001(lchan, fn, msg, true);
+ send_rtp_twts001(lchan, fn, msg);
else
send_ul_rtp_packet(lchan, fn, msg->data, msg->len);
} else if (lchan->type == GSM_LCHAN_TCH_H &&
--
To view, visit https://gerrit.osmocom.org/c/osmo-bts/+/41047?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: osmo-bts
Gerrit-Branch: master
Gerrit-Change-Id: I8097946429e83eae90f89e49d17ffb8eb0636fcb
Gerrit-Change-Number: 41047
Gerrit-PatchSet: 2
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
falconia has submitted this change. ( https://gerrit.osmocom.org/c/osmo-bts/+/41048?usp=email )
Change subject: FR/HR/EFR: centralize TCH UL SID classification
......................................................................
FR/HR/EFR: centralize TCH UL SID classification
In any environment where GSM MS may exercise DTXu on TCH/FS, TCH/HS or
TCH/EFS, the BTS receiving this TCH UL has to classify each received
traffic frame as valid SID, invalid SID or non-SID speech. For E1 BTS
this SID classification requirement is explicit as there are dedicated
bits in TRAU-UL frames carrying the SID code. For an IP BTS the need
for this classification is less obvious as most RTP payload formats
omit SID indicator bits - however:
* For HR codec, RTP output in RFC 5993 and TW-TS-002 formats does
include explicit SID classification;
* Also for HR output in both TS 101 318 and RFC 5993 formats
(but not TW-TS-002), SID classification must be considered in order
to turn valid SID with some bit errors into perfect SID codeword;
* OsmoBTS already had logic for all 3 of FR/HR/EFR whereby if a frame
is received that is an accepted SID frame in GSM 06.31/06.41/06.81
definition, a flag is set so that the next good speech frame will
be emitted in RTP with marker bit set. This logic implies SID
classification in TCH UL path.
Prior to this patch, OsmoBTS performed limited, non-consolidated
SID classification:
* For FR and EFR, the only SID classification in TCH UL path was done
for RTP marker purposes by way of osmo_{fr,efr}_is_any_sid() Boolean
result fed to lchan_set_marker();
* For the same RTP marker logic with HR codec, only perfect, error-free
SID frames were detected;
* The same limitation applied to SID classification for RFC 5993 output.
Centralize this SID classification by moving it from BTS model to common
l1sap code and unifying it across all 3 codecs. Immediate functional
effects from this change are:
* On TCH/HS we now detect imperfect (partially corrupted) SID frames
and classify them as valid or invalid SID as intended by ETSI,
like we already did for TCH/FS and TCH/EFS;
* When emitting TS 101 318 or RFC 5993, we apply the inherent limitations
of those RTP formats to valid and invalid SID;
* With all 3 codecs, the check for a good speech frame as exit criterion
from DTXu state now happens after the link quality check in l1sap,
rather than before.
AMR speech mode is not affected at all by these changes: AMR DTX model
is completely different from that of FR/HR/EFR.
Related: OS#6036
Change-Id: Id6c8c146962de2f173760889eb232693bb4229d3
---
M include/osmo-bts/msg_utils.h
M src/common/l1sap.c
M src/osmo-bts-lc15/tch.c
M src/osmo-bts-oc2g/tch.c
M src/osmo-bts-sysmo/tch.c
M src/osmo-bts-trx/sched_lchan_tchf.c
M src/osmo-bts-trx/sched_lchan_tchh.c
7 files changed, 131 insertions(+), 50 deletions(-)
Approvals:
pespin: Looks good to me, but someone else must approve
fixeria: Looks good to me, approved
Jenkins Builder: Verified
diff --git a/include/osmo-bts/msg_utils.h b/include/osmo-bts/msg_utils.h
index 6cebce7..7942a17 100644
--- a/include/osmo-bts/msg_utils.h
+++ b/include/osmo-bts/msg_utils.h
@@ -45,6 +45,18 @@
*/
#define tch_ul_msg_bfi(x) ((x)->cb[0])
+/* For HRv1 codec, we have to pass SID classification from the function
+ * that makes the initial determination to TS 101 318, RFC 5993 and
+ * TW-TS-002 output functions. Per classic GSM specs, common across
+ * FR/HR/EFR, SID classification code is an integer equal to 0, 1 or 2;
+ * in Osmocom it is enum osmo_gsm631_sid_class.
+ *
+ * NOTE: while the actual SID ternary classification exists in exactly
+ * the same form across all 3 of FR/HR/EFR, we store it in a cb word
+ * only for HR codec where we need it for RTP output functions.
+ */
+#define tch_ul_msg_hr_sid(x) ((x)->cb[1])
+
/**
* Classification of OML message. ETSI for plain GSM 12.21
* messages and IPA/Osmo for manufacturer messages.
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
index 8f7745e..5d5c65f 100644
--- a/src/common/l1sap.c
+++ b/src/common/l1sap.c
@@ -2200,24 +2200,6 @@
handle_tch_ind_csd_hr(lchan, tch_ind, data, data_len);
}
-/* a helper function for emitting HR1 UL in RFC 5993 format */
-static void send_rtp_rfc5993(struct gsm_lchan *lchan, uint32_t fn,
- struct msgb *msg)
-{
- uint8_t toc;
-
- OSMO_ASSERT(msg->len == GSM_HR_BYTES);
- /* FIXME: implement proper SID classification per GSM 06.41 section
- * 6.1.1; see OS#6036. Until then, detect error-free SID frames
- * using our existing osmo_hr_check_sid() function. */
- if (osmo_hr_check_sid(msg->data, msg->len))
- toc = 0x20;
- else
- toc = 0x00;
- msgb_push_u8(msg, toc);
- send_ul_rtp_packet(lchan, fn, msg->data, msg->len);
-}
-
/* a helper function for emitting FR/EFR UL in TW-TS-001 format */
static void send_rtp_twts001(struct gsm_lchan *lchan, uint32_t fn,
struct msgb *msg)
@@ -2305,6 +2287,117 @@
lchan->rtp_tx_marker = true;
}
+/* Helper function for l1sap_tch_ind(): RTP output for GSM-HR in either
+ * of the two standard, non-ThemWi-extended payload formats, with restrictions
+ * inherent to these non-TRAU-UL-like formats. */
+static void send_gsmhr_std_rtp(struct gsm_lchan *lchan,
+ const struct gsm_time *g_time, struct msgb *msg,
+ bool emit_rfc5993)
+{
+ uint32_t fn = g_time->fn;
+
+ OSMO_ASSERT(msg->len == GSM_HR_BYTES);
+
+ switch (tch_ul_msg_hr_sid(msg)) {
+ case OSMO_GSM631_SID_CLASS_SPEECH:
+ break;
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ /* neither of these RTP formats allows invalid SID */
+ tch_ul_bfi_handler(lchan, g_time, msg);
+ return;
+ case OSMO_GSM631_SID_CLASS_VALID:
+ /* both formats require perfect, error-free SID output */
+ osmo_hr_sid_reset(msg->data);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ /* Are we emitting "bare" TS 101 318 or "decorated" RFC 5993? */
+ if (emit_rfc5993) {
+ uint8_t toc = tch_ul_msg_hr_sid(msg) << 4;
+ msgb_push_u8(msg, toc);
+ }
+
+ send_ul_rtp_packet(lchan, fn, msg->data, msg->len);
+}
+
+/* Helper function for l1sap_tch_ind(): SID classification and related logic
+ * for FR, HR and EFR speech codecs. */
+static void tch_ul_fr_hr_efr(struct gsm_lchan *lchan, uint32_t fn, struct msgb *msg)
+{
+ enum osmo_gsm631_sid_class sidc;
+
+ /* If we got no payload (BFI without data), there is nothing
+ * for us to do here. */
+ if (msg->len == 0)
+ return;
+
+ /* GSM 06.31, 06.41 and 06.81 are DTX specs for FR, HR and EFR,
+ * respectively. Section 6.1.1 in each of these specs defines a
+ * ternary SID classification whereby each channel-decoded traffic
+ * frame is valid SID, invalid SID or non-SID speech. Perform
+ * this classification. */
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ sidc = osmo_fr_sid_classify(msg->data);
+ } else {
+ /* None of our current BTS models has UFI or BCI
+ * error flags for TCH/HS UL Rx, hence we have to
+ * perform SID classification without BCI. */
+ sidc = osmo_hr_sid_classify(msg->data, false, NULL);
+ /* Pass it to RTP output functions */
+ tch_ul_msg_hr_sid(msg) = sidc;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ sidc = osmo_efr_sid_classify(msg->data);
+ break;
+ default:
+ /* This static function should never be called except for
+ * V1 and EFR speech modes. */
+ OSMO_ASSERT(0);
+ }
+
+ /* We use this SID classification for three purposes:
+ *
+ * 1) For those users who desire to have RTP marker bit set in the
+ * output packet corresponding to the first speech frame after
+ * a DTX pause, the logic of lchan_set_marker() is driven by
+ * the determination of whether or not each received traffic frame
+ * is an accepted SID frame in the definition of GSM 06.31
+ * and its HR & EFR counterparts.
+ *
+ * 2) Next patch in the series will introduce logic that sets BFI
+ * under certain conditions dependent on SID classification and
+ * previous state, in order to suppress false indications of
+ * "valid" SID to the Rx DTX handler on the RTP receiving end
+ * during "half-block" conditions.
+ *
+ * 3) For HR codec only, RTP output format functions need to know
+ * both BFI flag and SID classification.
+ */
+ switch (sidc) {
+ case OSMO_GSM631_SID_CLASS_SPEECH:
+ /* Only a good speech frame, not an unusable frame,
+ * using GSM 06.31 definitions, marks exit from a DTX pause. */
+ if (!tch_ul_msg_bfi(msg))
+ lchan_set_marker(false, lchan);
+ break;
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ case OSMO_GSM631_SID_CLASS_VALID:
+ lchan_set_marker(true, lchan);
+ break;
+ default:
+ /* There are only 3 possible SID classifications per
+ * section 6.1.1 of each of the three DTX specs,
+ * and correspondingly only 3 possible output values
+ * from osmo_*_sid_classify() functions. */
+ OSMO_ASSERT(0);
+ }
+}
+
/* TCH received from bts model */
static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap,
struct ph_tch_param *tch_ind)
@@ -2363,8 +2456,12 @@
(tch_ind->lqual_cb < bts->min_qual_norm))
tch_ul_msg_bfi(msg) = true;
- /* FR/HR/EFR SID classification, with potential effects on BFI flag,
- * will go here - further patches in the series. */
+ /* For FR, HR or EFR speech we also have to perform SID classification
+ * and apply logic that results from such. This logic can also set
+ * BFI that wasn't set before! */
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1 ||
+ lchan->tch_mode == GSM48_CMODE_SPEECH_EFR)
+ tch_ul_fr_hr_efr(lchan, fn, msg);
/* Good RTP output happens when we got some payload AND it is not
* marked as BFI. */
@@ -2388,10 +2485,8 @@
lchan->tch_mode == GSM48_CMODE_SPEECH_V1) {
/* HR codec: TS 101 318 or RFC 5993,
* will also support TW-TS-002 in the future. */
- if (bts->emit_hr_rfc5993)
- send_rtp_rfc5993(lchan, fn, msg);
- else
- send_ul_rtp_packet(lchan, fn, msg->data, msg->len);
+ send_gsmhr_std_rtp(lchan, &g_time, msg,
+ bts->emit_hr_rfc5993);
} else {
/* generic case, no RTP alterations */
send_ul_rtp_packet(lchan, fn, msg->data, msg->len);
diff --git a/src/osmo-bts-lc15/tch.c b/src/osmo-bts-lc15/tch.c
index afd5b53..048b6d3 100644
--- a/src/osmo-bts-lc15/tch.c
+++ b/src/osmo-bts-lc15/tch.c
@@ -68,8 +68,6 @@
cur = msgb_put(msg, GSM_FR_BYTES);
memcpy(cur, l1_payload, GSM_FR_BYTES);
- lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan);
-
return msg;
}
@@ -102,8 +100,6 @@
cur = msgb_put(msg, GSM_EFR_BYTES);
memcpy(cur, l1_payload, GSM_EFR_BYTES);
- lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan);
-
return msg;
}
@@ -134,8 +130,6 @@
cur = msgb_put(msg, GSM_HR_BYTES);
memcpy(cur, l1_payload, GSM_HR_BYTES);
- lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan);
-
return msg;
}
diff --git a/src/osmo-bts-oc2g/tch.c b/src/osmo-bts-oc2g/tch.c
index 4ea1eb6..c3f0417 100644
--- a/src/osmo-bts-oc2g/tch.c
+++ b/src/osmo-bts-oc2g/tch.c
@@ -68,8 +68,6 @@
cur = msgb_put(msg, GSM_FR_BYTES);
memcpy(cur, l1_payload, GSM_FR_BYTES);
- lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan);
-
return msg;
}
@@ -102,8 +100,6 @@
cur = msgb_put(msg, GSM_EFR_BYTES);
memcpy(cur, l1_payload, GSM_EFR_BYTES);
- lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan);
-
return msg;
}
@@ -134,8 +130,6 @@
cur = msgb_put(msg, GSM_HR_BYTES);
memcpy(cur, l1_payload, GSM_HR_BYTES);
- lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan);
-
return msg;
}
diff --git a/src/osmo-bts-sysmo/tch.c b/src/osmo-bts-sysmo/tch.c
index 2cf784e..b5bf04b 100644
--- a/src/osmo-bts-sysmo/tch.c
+++ b/src/osmo-bts-sysmo/tch.c
@@ -77,8 +77,6 @@
cur[0] |= 0xD0;
#endif /* USE_L1_RTP_MODE */
- lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan);
-
return msg;
}
@@ -132,8 +130,6 @@
cur[0] |= 0xC0;
#endif /* USE_L1_RTP_MODE */
- lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan);
-
return msg;
}
@@ -218,8 +214,6 @@
osmo_revbytebits_buf(cur, GSM_HR_BYTES);
#endif /* USE_L1_RTP_MODE */
- lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan);
-
return msg;
}
diff --git a/src/osmo-bts-trx/sched_lchan_tchf.c b/src/osmo-bts-trx/sched_lchan_tchf.c
index 5a3e80a..2403392 100644
--- a/src/osmo-bts-trx/sched_lchan_tchf.c
+++ b/src/osmo-bts-trx/sched_lchan_tchf.c
@@ -166,14 +166,10 @@
case GSM48_CMODE_SPEECH_V1: /* FR */
rc = gsm0503_tch_fr_decode(tch_data, BUFTAIL8(bursts_p),
1, 0, &n_errors, &n_bits_total);
- if (rc == GSM_FR_BYTES) /* only for valid *speech* frames */
- lchan_set_marker(osmo_fr_is_any_sid(tch_data), lchan); /* DTXu */
break;
case GSM48_CMODE_SPEECH_EFR: /* EFR */
rc = gsm0503_tch_fr_decode(tch_data, BUFTAIL8(bursts_p),
1, 1, &n_errors, &n_bits_total);
- if (rc == GSM_EFR_BYTES) /* only for valid *speech* frames */
- lchan_set_marker(osmo_efr_is_any_sid(tch_data), lchan); /* DTXu */
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/* the first FN 0,8,17 defines that CMI is included in frame,
diff --git a/src/osmo-bts-trx/sched_lchan_tchh.c b/src/osmo-bts-trx/sched_lchan_tchh.c
index 01ac155..5d5f246 100644
--- a/src/osmo-bts-trx/sched_lchan_tchh.c
+++ b/src/osmo-bts-trx/sched_lchan_tchh.c
@@ -232,10 +232,6 @@
rc = gsm0503_tch_hr_decode2(tch_data, BUFTAIL8(bursts_p),
!sched_tchh_ul_facch_map[bi->fn % 26],
&n_errors, &n_bits_total);
- if (rc == GSM_HR_BYTES) { /* only for valid *speech* frames */
- bool is_sid = osmo_hr_check_sid(tch_data, GSM_HR_BYTES);
- lchan_set_marker(is_sid, lchan); /* DTXu */
- }
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/* the first FN 0,8,17 or 1,9,18 defines that CMI is included
--
To view, visit https://gerrit.osmocom.org/c/osmo-bts/+/41048?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: osmo-bts
Gerrit-Branch: master
Gerrit-Change-Id: Id6c8c146962de2f173760889eb232693bb4229d3
Gerrit-Change-Number: 41048
Gerrit-PatchSet: 2
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Attention is currently required from: fixeria, lynxis lazus, pespin.
Hello Jenkins Builder, fixeria, pespin,
I'd like you to reexamine a change. Please visit
https://gerrit.osmocom.org/c/erlang/osmo-epdg/+/41077?usp=email
to look at the new patch set (#3).
The following approvals got outdated and were removed:
Code-Review+1 by fixeria, Verified-1 by Jenkins Builder
Change subject: aaa_diameter_swx_tests: use MAR record to improve readability
......................................................................
aaa_diameter_swx_tests: use MAR record to improve readability
Change-Id: I0e77aae792fb9bc2e7277668a4fdda21a993aaa2
---
M src/aaa_diameter_swx_tests.erl
1 file changed, 21 insertions(+), 17 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/erlang/osmo-epdg refs/changes/77/41077/3
--
To view, visit https://gerrit.osmocom.org/c/erlang/osmo-epdg/+/41077?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: erlang/osmo-epdg
Gerrit-Branch: master
Gerrit-Change-Id: I0e77aae792fb9bc2e7277668a4fdda21a993aaa2
Gerrit-Change-Number: 41077
Gerrit-PatchSet: 3
Gerrit-Owner: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-Attention: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Attention: pespin <pespin(a)sysmocom.de>
Gerrit-Attention: lynxis lazus <lynxis(a)fe80.eu>