Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b
Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/rtp/fb.c b/src/rtp/fb.c
new file mode 100644
index 0000000..51b8d44
--- /dev/null
+++ b/src/rtp/fb.c
@@ -0,0 +1,169 @@
+/**
+ * @file fb.c Real-time Transport Control Protocol (RTCP)-Based Feedback
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtcp_pb"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ GNACK_SIZE = 4,
+ SLI_SIZE = 4
+};
+
+
+/* Encode functions */
+
+
+/**
+ * Encode an RTCP Generic NACK (GNACK) message
+ *
+ * @param mb Buffer to encode into
+ * @param pid Packet ID
+ * @param blp Bitmask of following lost packets (BLP)
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_rtpfb_gnack_encode(struct mbuf *mb, uint16_t pid, uint16_t blp)
+{
+ int err;
+ err = mbuf_write_u16(mb, htons(pid));
+ err |= mbuf_write_u16(mb, htons(blp));
+ return err;
+}
+
+
+/**
+ * Encode an RTCP Slice Loss Indication (SLI) message
+ *
+ * @param mb Buffer to encode into
+ * @param first Macroblock (MB) address of the first lost macroblock
+ * @param number Number of lost macroblocks
+ * @param picid Picture ID
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_psfb_sli_encode(struct mbuf *mb, uint16_t first, uint16_t number,
+ uint8_t picid)
+{
+ const uint32_t v = first<<19 | number<<6 | picid;
+ return mbuf_write_u32(mb, htonl(v));
+}
+
+
+/* Decode functions */
+
+
+/**
+ * Decode an RTCP Transport Layer Feedback Message
+ *
+ * @param mb Buffer to decode
+ * @param msg RTCP Message to decode into
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_rtpfb_decode(struct mbuf *mb, struct rtcp_msg *msg)
+{
+ size_t i, sz;
+
+ if (!msg)
+ return EINVAL;
+
+ switch (msg->hdr.count) {
+
+ case RTCP_RTPFB_GNACK:
+ sz = msg->r.fb.n * sizeof(*msg->r.fb.fci.gnackv);
+ msg->r.fb.fci.gnackv = mem_alloc(sz, NULL);
+ if (!msg->r.fb.fci.gnackv)
+ return ENOMEM;
+
+ if (mbuf_get_left(mb) < msg->r.fb.n * GNACK_SIZE)
+ return EBADMSG;
+ for (i=0; i<msg->r.fb.n; i++) {
+ msg->r.fb.fci.gnackv[i].pid = ntohs(mbuf_read_u16(mb));
+ msg->r.fb.fci.gnackv[i].blp = ntohs(mbuf_read_u16(mb));
+ }
+ break;
+
+ default:
+ DEBUG_NOTICE("unknown RTPFB fmt %d\n", msg->hdr.count);
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Decode an RTCP Payload-Specific Feedback Message
+ *
+ * @param mb Buffer to decode
+ * @param msg RTCP Message to decode into
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_psfb_decode(struct mbuf *mb, struct rtcp_msg *msg)
+{
+ size_t i, sz;
+
+ if (!msg)
+ return EINVAL;
+
+ switch (msg->hdr.count) {
+
+ case RTCP_PSFB_PLI:
+ /* no params */
+ break;
+
+ case RTCP_PSFB_SLI:
+ sz = msg->r.fb.n * sizeof(*msg->r.fb.fci.sliv);
+ msg->r.fb.fci.sliv = mem_alloc(sz, NULL);
+ if (!msg->r.fb.fci.sliv)
+ return ENOMEM;
+
+ if (mbuf_get_left(mb) < msg->r.fb.n * SLI_SIZE)
+ return EBADMSG;
+ for (i=0; i<msg->r.fb.n; i++) {
+ const uint32_t v = ntohl(mbuf_read_u32(mb));
+
+ msg->r.fb.fci.sliv[i].first = v>>19 & 0x1fff;
+ msg->r.fb.fci.sliv[i].number = v>> 6 & 0x1fff;
+ msg->r.fb.fci.sliv[i].picid = v>> 0 & 0x003f;
+ }
+ break;
+
+ case RTCP_PSFB_AFB:
+ sz = msg->r.fb.n * 4;
+
+ if (mbuf_get_left(mb) < sz)
+ return EBADMSG;
+
+ msg->r.fb.fci.afb = mbuf_alloc_ref(mb);
+ if (!msg->r.fb.fci.afb)
+ return ENOMEM;
+
+ msg->r.fb.fci.afb->end = msg->r.fb.fci.afb->pos + sz;
+ mbuf_advance(mb, sz);
+ break;
+
+ default:
+ DEBUG_NOTICE("unknown PSFB fmt %d\n", msg->hdr.count);
+ break;
+ }
+
+ return 0;
+}
diff --git a/src/rtp/member.c b/src/rtp/member.c
new file mode 100644
index 0000000..1a3bd78
--- /dev/null
+++ b/src/rtp/member.c
@@ -0,0 +1,53 @@
+/**
+ * @file member.c Real-time Transport Control Protocol member
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+static void destructor(void *data)
+{
+ struct rtp_member *mbr = data;
+
+ hash_unlink(&mbr->le);
+ mem_deref(mbr->s);
+}
+
+
+struct rtp_member *member_add(struct hash *ht, uint32_t src)
+{
+ struct rtp_member *mbr;
+
+ mbr = mem_zalloc(sizeof(*mbr), destructor);
+ if (!mbr)
+ return NULL;
+
+ hash_append(ht, src, &mbr->le, mbr);
+ mbr->src = src;
+
+ return mbr;
+}
+
+
+static bool hash_cmp_handler(struct le *le, void *arg)
+{
+ const struct rtp_member *mbr = le->data;
+
+ return mbr->src == *(uint32_t *)arg;
+}
+
+
+struct rtp_member *member_find(struct hash *ht, uint32_t src)
+{
+ return list_ledata(hash_lookup(ht, src, hash_cmp_handler, &src));
+}
diff --git a/src/rtp/mod.mk b/src/rtp/mod.mk
new file mode 100644
index 0000000..c7a7b63
--- /dev/null
+++ b/src/rtp/mod.mk
@@ -0,0 +1,16 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += rtp/fb.c
+SRCS += rtp/member.c
+SRCS += rtp/ntp.c
+SRCS += rtp/pkt.c
+SRCS += rtp/rr.c
+SRCS += rtp/rtcp.c
+SRCS += rtp/rtp.c
+SRCS += rtp/sdes.c
+SRCS += rtp/sess.c
+SRCS += rtp/source.c
diff --git a/src/rtp/ntp.c b/src/rtp/ntp.c
new file mode 100644
index 0000000..ab5494b
--- /dev/null
+++ b/src/rtp/ntp.c
@@ -0,0 +1,103 @@
+/**
+ * @file ntp.c NTP Routines
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+/*
+ * Unix time: seconds relative to 0h January 1, 1970
+ * NTP time: seconds relative to 0h UTC on 1 January 1900
+ */
+
+
+/* Number of seconds from 1900 to 1970 */
+#define UNIX_NTP_OFFSET 0x83aa7e80
+
+
+/**
+ * Convert from Unix time to NTP time
+ *
+ * @param ntp NTP time to convert to (output)
+ * @param tv Unix time to convert from (input)
+ */
+void unix2ntp(struct ntp_time *ntp, const struct timeval *tv)
+{
+ ntp->hi = (uint32_t)(tv->tv_sec + UNIX_NTP_OFFSET);
+ ntp->lo = (uint32_t)((double)tv->tv_usec*(double)(1LL<<32)*1.0e-6);
+}
+
+
+/**
+ * Convert from NTP time to Unix time
+ *
+ * @param tv Unix time to convert to (output)
+ * @param ntp NTP time to convert from (input)
+ */
+void ntp2unix(struct timeval *tv, const struct ntp_time *ntp)
+{
+ tv->tv_sec = ntp->hi - UNIX_NTP_OFFSET;
+ tv->tv_usec = (uint32_t)(1.0e6 * (double) ntp->lo / (1LL<<32));
+}
+
+
+int ntp_time_get(struct ntp_time *ntp)
+{
+ struct timeval tv;
+#ifdef WIN32
+ union {
+ long long ns100;
+ FILETIME ft;
+ } now;
+
+ GetSystemTimeAsFileTime(&now.ft);
+ tv.tv_usec = (long) ((now.ns100 / 10LL) % 1000000LL);
+ tv.tv_sec = (long) ((now.ns100 - 116444736000000000LL) / 10000000LL);
+#else
+ if (gettimeofday(&tv, NULL) != 0)
+ return errno;
+#endif
+ unix2ntp(ntp, &tv);
+
+ return 0;
+}
+
+
+/**
+ * Convert NTP time to middle 32-bits (compact representation)
+ *
+ * @param ntp NTP time
+ *
+ * @return NTP time in compact representation
+ */
+uint32_t ntp_compact(const struct ntp_time *ntp)
+{
+ return ntp ? ((ntp->hi & 0xffff) << 16 | (ntp->lo >> 16)) : 0;
+}
+
+
+/**
+ * Convert NTP compact representation to microseconds
+ *
+ * @param ntpc NTP time in compact representation
+ *
+ * @return NTP time in microseconds
+ */
+uint64_t ntp_compact2us(uint32_t ntpc)
+{
+ const uint32_t hi = (ntpc >> 16) & 0xffff;
+ const uint32_t lo = (ntpc & 0xffff) << 16;
+
+ return (1000000ULL * hi) + ((1000000ULL * lo) >> 32);
+}
diff --git a/src/rtp/pkt.c b/src/rtp/pkt.c
new file mode 100644
index 0000000..cd4880d
--- /dev/null
+++ b/src/rtp/pkt.c
@@ -0,0 +1,463 @@
+/**
+ * @file pkt.c RTCP Packet handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtcp_pkt"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static void rtcp_destructor(void *data)
+{
+ struct rtcp_msg *msg = data;
+ size_t i, j;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ mem_deref(msg->r.sr.rrv);
+ break;
+
+ case RTCP_RR:
+ mem_deref(msg->r.rr.rrv);
+ break;
+
+ case RTCP_SDES:
+ if (!msg->r.sdesv)
+ break;
+
+ for (i=0; i<msg->hdr.count; i++) {
+ struct rtcp_sdes *sdes = &msg->r.sdesv[i];
+
+ for (j=0; j<sdes->n; j++) {
+
+ mem_deref(sdes->itemv[j].data);
+ }
+ mem_deref(sdes->itemv);
+ }
+ mem_deref(msg->r.sdesv);
+ break;
+
+ case RTCP_BYE:
+ mem_deref(msg->r.bye.srcv);
+ mem_deref(msg->r.bye.reason);
+ break;
+
+ case RTCP_APP:
+ mem_deref(msg->r.app.data);
+ break;
+
+ case RTCP_RTPFB:
+ case RTCP_PSFB:
+ mem_deref(msg->r.fb.fci.p);
+ break;
+
+ default:
+ /* nothing allocated */
+ break;
+ }
+}
+
+
+/**
+ * Encode the RTCP Header
+ *
+ * @param mb Buffer to encode into
+ * @param count Number of sub-elemements
+ * @param type RTCP Packet type
+ * @param length Packet length in words
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_hdr_encode(struct mbuf *mb, uint8_t count, enum rtcp_type type,
+ uint16_t length)
+{
+ int err;
+
+ if (!mb)
+ return EINVAL;
+
+ err = mbuf_write_u8(mb, RTCP_VERSION<<6 | count);
+ err |= mbuf_write_u8(mb, type);
+ err |= mbuf_write_u16(mb, htons(length));
+
+ return err;
+}
+
+
+/**
+ * Decode the RTCP Header
+ *
+ * @param mb Buffer to decode from
+ * @param hdr RTCP Header to decode into
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_hdr_decode(struct mbuf *mb, struct rtcp_hdr *hdr)
+{
+ uint8_t b;
+
+ if (!hdr)
+ return EINVAL;
+ if (mbuf_get_left(mb) < RTCP_HDR_SIZE)
+ return EBADMSG;
+
+ b = mbuf_read_u8(mb);
+ hdr->pt = mbuf_read_u8(mb);
+ hdr->length = ntohs(mbuf_read_u16(mb));
+
+ hdr->version = (b >> 6) & 0x3;
+ hdr->p = (b >> 5) & 0x1;
+ hdr->count = (b >> 0) & 0x1f;
+
+ return 0;
+}
+
+
+int rtcp_vencode(struct mbuf *mb, enum rtcp_type type, uint32_t count,
+ va_list ap)
+{
+ size_t i, pos;
+ uint16_t len;
+ const uint8_t *data;
+ size_t data_len;
+ const uint32_t *srcv;
+ const char *reason;
+ rtcp_encode_h *ench;
+ void *arg;
+ int err = 0;
+
+ if (!mb)
+ return EINVAL;
+
+ pos = mb->pos;
+
+ /* Skip header - encoded last */
+ mb->pos = mb->end = (mb->pos + RTCP_HDR_SIZE);
+
+ switch (type) {
+
+ case RTCP_SR:
+ for (i=0; i<6; i++)
+ err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ ench = va_arg(ap, rtcp_encode_h *);
+ arg = va_arg(ap, void *);
+ if (ench)
+ err |= ench(mb, arg);
+ break;
+
+ case RTCP_RR:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ ench = va_arg(ap, rtcp_encode_h *);
+ arg = va_arg(ap, void *);
+ if (ench)
+ err |= ench(mb, arg);
+ break;
+
+ case RTCP_SDES:
+ ench = va_arg(ap, rtcp_encode_h *);
+ arg = va_arg(ap, void *);
+ if (ench)
+ err |= ench(mb, arg);
+ break;
+
+ case RTCP_BYE:
+ srcv = va_arg(ap, uint32_t *);
+ reason = va_arg(ap, char *);
+ for (i=0; i<count && !err; i++) {
+ err = mbuf_write_u32(mb, htonl(srcv[i]));
+ }
+ if (reason) {
+ err |= mbuf_write_u8(mb, strlen(reason));
+ err |= mbuf_write_str(mb, reason);
+ }
+ break;
+
+ case RTCP_APP:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ err |= mbuf_write_mem(mb, va_arg(ap, uint8_t *), 4);
+ data = va_arg(ap, const uint8_t *);
+ data_len = va_arg(ap, size_t);
+ if (data) {
+ if (data_len % 4) {
+ DEBUG_WARNING("not a multiple of 32bits\n");
+ return EBADMSG;
+ }
+ err |= mbuf_write_mem(mb, data, data_len);
+ }
+ break;
+
+ case RTCP_FIR:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ break;
+
+ case RTCP_NACK:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ err |= mbuf_write_u16(mb, htons(va_arg(ap, uint32_t)));
+ err |= mbuf_write_u16(mb, htons(va_arg(ap, uint32_t)));
+ break;
+
+ case RTCP_RTPFB:
+ case RTCP_PSFB:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ ench = va_arg(ap, rtcp_encode_h *);
+ arg = va_arg(ap, void *);
+ if (ench)
+ err |= ench(mb, arg);
+ break;
+
+ default:
+ return EINVAL;
+ }
+ if (err)
+ return err;
+
+ /* padding to 32 bits */
+ while ((mb->end - pos) & 0x3)
+ err |= mbuf_write_u8(mb, 0x00);
+ if (err)
+ return err;
+
+ /* Encode RTCP Header */
+ mb->pos = pos;
+ len = (mb->end - pos - RTCP_HDR_SIZE)/sizeof(uint32_t);
+ err = rtcp_hdr_encode(mb, count, type, len);
+ if (err)
+ return err;
+
+ mb->pos = mb->end;
+
+ return 0;
+}
+
+
+/**
+ * Encode an RTCP Packet into a buffer
+ *
+ * @param mb Buffer to encode into
+ * @param type RTCP Packet type
+ * @param count Packet-specific count
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_encode(struct mbuf *mb, enum rtcp_type type, uint32_t count, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, count);
+ err = rtcp_vencode(mb, type, count, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Decode one RTCP message from a buffer
+ *
+ * @param msgp Pointer to allocated RTCP Message
+ * @param mb Buffer to decode from
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_decode(struct rtcp_msg **msgp, struct mbuf *mb)
+{
+ struct rtcp_msg *msg = NULL;
+ size_t start, i, sz, count, rem;
+ int err;
+
+ if (!msgp)
+ return EINVAL;
+ if (mbuf_get_left(mb) < RTCP_HDR_SIZE)
+ return EBADMSG;
+
+ msg = mem_zalloc(sizeof(*msg), rtcp_destructor);
+ if (!msg)
+ return ENOMEM;
+
+ start = mb->pos;
+
+ /* decode and check header */
+ err = rtcp_hdr_decode(mb, &msg->hdr);
+ if (err)
+ goto out;
+
+ if (msg->hdr.version != RTCP_VERSION)
+ goto badmsg;
+
+ /* check length and remaining */
+ rem = msg->hdr.length * sizeof(uint32_t);
+ if (mbuf_get_left(mb) < rem)
+ goto badmsg;
+
+ count = msg->hdr.count;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ if (mbuf_get_left(mb) < (RTCP_SRC_SIZE + RTCP_SR_SIZE))
+ goto badmsg;
+ msg->r.sr.ssrc = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.ntp_sec = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.ntp_frac = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.rtp_ts = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.psent = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.osent = ntohl(mbuf_read_u32(mb));
+
+ err = rtcp_rr_alloc(&msg->r.sr.rrv, count);
+ if (err)
+ goto out;
+ for (i=0; i<count && !err; i++)
+ err = rtcp_rr_decode(mb, &msg->r.sr.rrv[i]);
+ break;
+
+ case RTCP_RR:
+ if (mbuf_get_left(mb) < RTCP_SRC_SIZE)
+ goto badmsg;
+ msg->r.rr.ssrc = ntohl(mbuf_read_u32(mb));
+
+ err = rtcp_rr_alloc(&msg->r.rr.rrv, count);
+ if (err)
+ goto out;
+ for (i=0; i<count && !err; i++)
+ err = rtcp_rr_decode(mb, &msg->r.rr.rrv[i]);
+ break;
+
+ case RTCP_SDES:
+ if (count == 0)
+ break;
+
+ sz = count * sizeof(*msg->r.sdesv);
+ msg->r.sdesv = mem_zalloc(sz, NULL);
+ if (!msg->r.sdesv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (i=0; i<msg->hdr.count && !err; i++)
+ err = rtcp_sdes_decode(mb, &msg->r.sdesv[i]);
+ break;
+
+ case RTCP_BYE:
+ sz = count * sizeof(*msg->r.bye.srcv);
+ msg->r.bye.srcv = mem_alloc(sz, NULL);
+ if (!msg->r.bye.srcv) {
+ err = ENOMEM;
+ goto out;
+ }
+ if (mbuf_get_left(mb) < sz)
+ goto badmsg;
+ for (i=0; i<count; i++)
+ msg->r.bye.srcv[i] = ntohl(mbuf_read_u32(mb));
+
+ /* decode reason (optional) */
+ if (rem > count*sizeof(uint32_t)) {
+ const size_t len = mbuf_read_u8(mb);
+ if (mbuf_get_left(mb) < len)
+ goto badmsg;
+
+ err = mbuf_strdup(mb, &msg->r.bye.reason, len);
+ }
+ break;
+
+ case RTCP_APP:
+ if (mbuf_get_left(mb) < RTCP_APP_SIZE)
+ goto badmsg;
+ msg->r.app.src = ntohl(mbuf_read_u32(mb));
+ (void)mbuf_read_mem(mb, (uint8_t *)msg->r.app.name,
+ sizeof(msg->r.app.name));
+ if (rem > RTCP_APP_SIZE) {
+ msg->r.app.data_len = rem - RTCP_APP_SIZE;
+ msg->r.app.data = mem_alloc(msg->r.app.data_len, NULL);
+ if (!msg->r.app.data) {
+ err = ENOMEM;
+ goto out;
+ }
+ if (mbuf_get_left(mb) < msg->r.app.data_len)
+ goto badmsg;
+ (void)mbuf_read_mem(mb, msg->r.app.data,
+ msg->r.app.data_len);
+ }
+ break;
+
+ case RTCP_FIR:
+ if (mbuf_get_left(mb) < RTCP_FIR_SIZE)
+ goto badmsg;
+ msg->r.fir.ssrc = ntohl(mbuf_read_u32(mb));
+ break;
+
+ case RTCP_NACK:
+ if (mbuf_get_left(mb) < RTCP_NACK_SIZE)
+ goto badmsg;
+ msg->r.nack.ssrc = ntohl(mbuf_read_u32(mb));
+ msg->r.nack.fsn = ntohs(mbuf_read_u16(mb));
+ msg->r.nack.blp = ntohs(mbuf_read_u16(mb));
+ break;
+
+ case RTCP_RTPFB:
+ if (mbuf_get_left(mb) < RTCP_FB_SIZE)
+ goto badmsg;
+
+ if (msg->hdr.length < 2)
+ goto badmsg;
+
+ msg->r.fb.ssrc_packet = ntohl(mbuf_read_u32(mb));
+ msg->r.fb.ssrc_media = ntohl(mbuf_read_u32(mb));
+ msg->r.fb.n = msg->hdr.length - 2;
+
+ err = rtcp_rtpfb_decode(mb, msg);
+ break;
+
+ case RTCP_PSFB:
+ if (mbuf_get_left(mb) < RTCP_FB_SIZE)
+ goto badmsg;
+
+ if (msg->hdr.length < 2)
+ goto badmsg;
+
+ msg->r.fb.ssrc_packet = ntohl(mbuf_read_u32(mb));
+ msg->r.fb.ssrc_media = ntohl(mbuf_read_u32(mb));
+ msg->r.fb.n = msg->hdr.length - 2;
+
+ err = rtcp_psfb_decode(mb, msg);
+ break;
+
+ default:
+ /* unknown message type */
+ mbuf_advance(mb, rem);
+ break;
+ }
+ if (err)
+ goto out;
+
+ /* slurp padding */
+ while ((mb->pos - start) & 0x3 && mbuf_get_left(mb))
+ ++mb->pos;
+
+ out:
+ if (err)
+ mem_deref(msg);
+ else
+ *msgp = msg;
+
+ return err;
+
+ badmsg:
+ mem_deref(msg);
+ return EBADMSG;
+}
diff --git a/src/rtp/rr.c b/src/rtp/rr.c
new file mode 100644
index 0000000..9d39de8
--- /dev/null
+++ b/src/rtp/rr.c
@@ -0,0 +1,72 @@
+/**
+ * @file rtp/rr.c RTCP Reception report
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sys.h>
+#include <re_net.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+int rtcp_rr_alloc(struct rtcp_rr **rrp, size_t count)
+{
+ struct rtcp_rr *rr;
+
+ if (!rrp)
+ return EINVAL;
+
+ rr = mem_alloc(count * sizeof(*rr), NULL);
+ if (!rr)
+ return ENOMEM;
+
+ *rrp = rr;
+ return 0;
+}
+
+
+int rtcp_rr_encode(struct mbuf *mb, const struct rtcp_rr *rr)
+{
+ int err;
+
+ if (!mb || !rr)
+ return EINVAL;
+
+ err = mbuf_write_u32(mb, htonl(rr->ssrc));
+ err |= mbuf_write_u32(mb, htonl(rr->fraction<<24 |
+ (rr->lost & 0x00ffffff)));
+ err |= mbuf_write_u32(mb, htonl(rr->last_seq));
+ err |= mbuf_write_u32(mb, htonl(rr->jitter));
+ err |= mbuf_write_u32(mb, htonl(rr->lsr));
+ err |= mbuf_write_u32(mb, htonl(rr->dlsr));
+
+ return err;
+}
+
+
+int rtcp_rr_decode(struct mbuf *mb, struct rtcp_rr *rr)
+{
+ uint32_t w;
+
+ if (!rr)
+ return EINVAL;
+ if (mbuf_get_left(mb) < RTCP_RR_SIZE)
+ return EBADMSG;
+
+ rr->ssrc = ntohl(mbuf_read_u32(mb));
+ w = ntohl(mbuf_read_u32(mb));
+ rr->fraction = w>>24; rr->lost = w & 0x00ffffffU;
+ rr->last_seq = ntohl(mbuf_read_u32(mb));
+ rr->jitter = ntohl(mbuf_read_u32(mb));
+ rr->lsr = ntohl(mbuf_read_u32(mb));
+ rr->dlsr = ntohl(mbuf_read_u32(mb));
+
+ return 0;
+}
diff --git a/src/rtp/rtcp.c b/src/rtp/rtcp.c
new file mode 100644
index 0000000..6e708de
--- /dev/null
+++ b/src/rtp/rtcp.c
@@ -0,0 +1,285 @@
+/**
+ * @file rtcp.c Real-time Transport Control Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+static int rtcp_quick_send(struct rtp_sock *rs, enum rtcp_type type,
+ uint32_t count, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ mb = mbuf_alloc(32);
+ if (!mb)
+ return ENOMEM;
+
+ mb->pos = RTCP_HEADROOM;
+
+ va_start(ap, count);
+ err = rtcp_vencode(mb, type, count, ap);
+ va_end(ap);
+
+ mb->pos = RTCP_HEADROOM;
+
+ if (!err)
+ err = rtcp_send(rs, mb);
+
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Send an RTCP Application-Defined (APP) packet
+ *
+ * @param rs RTP Socket
+ * @param name Ascii name (4 octets)
+ * @param data Application-dependent data
+ * @param len Number of bytes of data
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send_app(struct rtp_sock *rs, const char name[4],
+ const uint8_t *data, size_t len)
+{
+ return rtcp_quick_send(rs, RTCP_APP, 0, rtp_sess_ssrc(rs),
+ name, data, len);
+}
+
+
+/**
+ * Send a Full INTRA-frame Request (FIR) packet
+ *
+ * @param rs RTP Socket
+ * @param ssrc Synchronization source identifier for the sender of this packet
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send_fir(struct rtp_sock *rs, uint32_t ssrc)
+{
+ return rtcp_quick_send(rs, RTCP_FIR, 0, ssrc);
+}
+
+
+/**
+ * Send an RTCP NACK packet
+ *
+ * @param rs RTP Socket
+ * @param fsn First Sequence Number lost
+ * @param blp Bitmask of lost packets
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send_nack(struct rtp_sock *rs, uint16_t fsn, uint16_t blp)
+{
+ return rtcp_quick_send(rs, RTCP_NACK, 0, rtp_sess_ssrc(rs), fsn, blp);
+}
+
+
+/**
+ * Send an RTCP Picture Loss Indication (PLI) packet
+ *
+ * @param rs RTP Socket
+ * @param fb_ssrc Feedback SSRC
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send_pli(struct rtp_sock *rs, uint32_t fb_ssrc)
+{
+ return rtcp_quick_send(rs, RTCP_PSFB, RTCP_PSFB_PLI,
+ rtp_sess_ssrc(rs), fb_ssrc, NULL, NULL);
+}
+
+
+const char *rtcp_type_name(enum rtcp_type type)
+{
+ switch (type) {
+
+ case RTCP_FIR: return "FIR";
+ case RTCP_NACK: return "NACK";
+ case RTCP_SR: return "SR";
+ case RTCP_RR: return "RR";
+ case RTCP_SDES: return "SDES";
+ case RTCP_BYE: return "BYE";
+ case RTCP_APP: return "APP";
+ case RTCP_RTPFB: return "RTPFB";
+ case RTCP_PSFB: return "PSFB";
+ case RTCP_XR: return "XR";
+ case RTCP_AVB: return "AVB";
+ default: return "?";
+ }
+}
+
+
+const char *rtcp_sdes_name(enum rtcp_sdes_type sdes)
+{
+ switch (sdes) {
+
+ case RTCP_SDES_END: return "END";
+ case RTCP_SDES_CNAME: return "CNAME";
+ case RTCP_SDES_NAME: return "NAME";
+ case RTCP_SDES_EMAIL: return "EMAIL";
+ case RTCP_SDES_PHONE: return "PHONE";
+ case RTCP_SDES_LOC: return "LOC";
+ case RTCP_SDES_TOOL: return "TOOL";
+ case RTCP_SDES_NOTE: return "NOTE";
+ case RTCP_SDES_PRIV: return "PRIV";
+ default: return "?";
+ }
+}
+
+
+/**
+ * Print an RTCP Message
+ *
+ * @param pf Print handler for debug output
+ * @param msg RTCP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_msg_print(struct re_printf *pf, const struct rtcp_msg *msg)
+{
+ size_t i, j;
+ int err;
+
+ if (!msg)
+ return 0;
+
+ err = re_hprintf(pf, "%8s pad=%d count=%-2d pt=%-3d len=%u ",
+ rtcp_type_name((enum rtcp_type)msg->hdr.pt),
+ msg->hdr.p,
+ msg->hdr.count, msg->hdr.pt, msg->hdr.length);
+ if (err)
+ return err;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ err = re_hprintf(pf, "%08x %u %u %u %u %u",
+ msg->r.sr.ssrc,
+ msg->r.sr.ntp_sec,
+ msg->r.sr.ntp_frac,
+ msg->r.sr.rtp_ts,
+ msg->r.sr.psent,
+ msg->r.sr.osent);
+ for (i=0; i<msg->hdr.count && !err; i++) {
+ const struct rtcp_rr *rr = &msg->r.sr.rrv[i];
+ err = re_hprintf(pf, " {%08x %u %d %u %u %u %u}",
+ rr->ssrc, rr->fraction, rr->lost,
+ rr->last_seq, rr->jitter,
+ rr->lsr, rr->dlsr);
+ }
+ break;
+
+ case RTCP_RR:
+ err = re_hprintf(pf, "%08x", msg->r.rr.ssrc);
+ for (i=0; i<msg->hdr.count && !err; i++) {
+ const struct rtcp_rr *rr = &msg->r.rr.rrv[i];
+ err = re_hprintf(pf, " {0x%08x %u %d %u %u %u %u}",
+ rr->ssrc, rr->fraction, rr->lost,
+ rr->last_seq, rr->jitter,
+ rr->lsr, rr->dlsr);
+ }
+ break;
+
+ case RTCP_SDES:
+ for (i=0; i<msg->hdr.count; i++) {
+ const struct rtcp_sdes *sdes = &msg->r.sdesv[i];
+
+ err = re_hprintf(pf, " {0x%08x n=%u",
+ sdes->src, sdes->n);
+ for (j=0; j<sdes->n && !err; j++) {
+ const struct rtcp_sdes_item *item;
+ item = &sdes->itemv[j];
+ err = re_hprintf(pf, " <%s:%b>",
+ rtcp_sdes_name(item->type),
+ item->data,
+ (size_t)item->length);
+ }
+ err |= re_hprintf(pf, "}");
+ }
+ break;
+
+ case RTCP_BYE:
+ err = re_hprintf(pf, "%u srcs:", msg->hdr.count);
+ for (i=0; i<msg->hdr.count && !err; i++) {
+ err = re_hprintf(pf, " %08x",
+ msg->r.bye.srcv[i]);
+ }
+ err |= re_hprintf(pf, " '%s'", msg->r.bye.reason);
+ break;
+
+ case RTCP_APP:
+ err = re_hprintf(pf, "src=%08x '%b' data=%zu",
+ msg->r.app.src,
+ msg->r.app.name, sizeof(msg->r.app.name),
+ msg->r.app.data_len);
+ break;
+
+ case RTCP_FIR:
+ err = re_hprintf(pf, "ssrc=%08x", msg->r.fir.ssrc);
+ break;
+
+ case RTCP_NACK:
+ err = re_hprintf(pf, "ssrc=%08x fsn=%04x blp=%04x",
+ msg->r.nack.ssrc, msg->r.nack.fsn,
+ msg->r.nack.blp);
+ break;
+
+ case RTCP_RTPFB:
+ err = re_hprintf(pf, "pkt=%08x med=%08x n=%u",
+ msg->r.fb.ssrc_packet,
+ msg->r.fb.ssrc_media,
+ msg->r.fb.n);
+ if (msg->hdr.count == RTCP_RTPFB_GNACK) {
+ err |= re_hprintf(pf, " GNACK");
+ for (i=0; i<msg->r.fb.n; i++) {
+ err |= re_hprintf(pf, " {%04x %04x}",
+ msg->r.fb.fci.gnackv[i].pid,
+ msg->r.fb.fci.gnackv[i].blp);
+ }
+ }
+ break;
+
+ case RTCP_PSFB:
+ err = re_hprintf(pf, "pkt=%08x med=%08x n=%u",
+ msg->r.fb.ssrc_packet,
+ msg->r.fb.ssrc_media,
+ msg->r.fb.n);
+ if (msg->hdr.count == RTCP_PSFB_SLI) {
+ err |= re_hprintf(pf, " SLI");
+ for (i=0; i<msg->r.fb.n; i++) {
+ err |= re_hprintf(pf, " {%04x %04x %02x}",
+ msg->r.fb.fci.sliv[i].first,
+ msg->r.fb.fci.sliv[i].number,
+ msg->r.fb.fci.sliv[i].picid);
+ }
+ }
+ else if (msg->hdr.count == RTCP_PSFB_AFB) {
+ err |= re_hprintf(pf, " AFB %u bytes",
+ msg->r.fb.n * 4);
+ }
+ break;
+
+ default:
+ err = re_hprintf(pf, "<len=%u>", msg->hdr.length);
+ break;
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
diff --git a/src/rtp/rtcp.h b/src/rtp/rtcp.h
new file mode 100644
index 0000000..3c22c24
--- /dev/null
+++ b/src/rtp/rtcp.h
@@ -0,0 +1,120 @@
+/**
+ * @file rtcp.h Internal interface to RTCP
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** RTCP protocol values */
+enum {
+ RTCP_HDR_SIZE = 4, /**< Size of common RTCP header */
+ RTCP_SRC_SIZE = 4, /**< Size of Source field */
+ RTCP_SR_SIZE = 20, /**< Size of Sender Information */
+ RTCP_RR_SIZE = 24, /**< Size of Report Block */
+ RTCP_APP_SIZE = 8, /**< Size of Application packet */
+ RTCP_FIR_SIZE = 4, /**< Size of FIR packet */
+ RTCP_NACK_SIZE = 8, /**< Size of NACK packet */
+ RTCP_FB_SIZE = 8, /**< Size of Feedback packets */
+ RTCP_MAX_SDES = 255, /**< Maximum text length for SDES */
+ RTCP_HEADROOM = 4, /**< Headroom in RTCP packets */
+};
+
+/** NTP Time */
+struct ntp_time {
+ uint32_t hi; /**< Seconds since 0h UTC on 1 January 1900 */
+ uint32_t lo; /**< Fraction of seconds */
+};
+
+struct hash;
+
+/** Per-source state information */
+struct rtp_source {
+ struct sa rtp_peer; /**< IP-address of the RTP source */
+ uint16_t max_seq; /**< Highest seq. number seen */
+ uint32_t cycles; /**< Shifted count of seq. number cycles */
+ uint32_t base_seq; /**< Base seq number */
+ uint32_t bad_seq; /**< Last 'bad' seq number + 1 */
+ uint32_t probation; /**< Sequ. packets till source is valid */
+ uint32_t received; /**< Packets received */
+ uint32_t expected_prior; /**< Packet expected at last interval */
+ uint32_t received_prior; /**< Packet received at last interval */
+ int transit; /**< Relative trans time for prev pkt */
+ uint32_t jitter; /**< Estimated jitter */
+ size_t rtp_rx_bytes; /**< Number of RTP bytes received */
+ uint64_t sr_recv; /**< When the last SR was received */
+ struct ntp_time last_sr; /**< NTP Timestamp from last SR received */
+ uint32_t rtp_ts; /**< RTP timestamp */
+ uint32_t psent; /**< RTP packets sent */
+ uint32_t osent; /**< RTP octets sent */
+};
+
+/** RTP Member */
+struct rtp_member {
+ struct le le; /**< Hash-table element */
+ struct rtp_source *s; /**< RTP source state */
+ uint32_t src; /**< Source - used for hash-table lookup */
+ int cum_lost; /**< Cumulative number of packets lost */
+ uint32_t jit; /**< Jitter in [us] */
+ uint32_t rtt; /**< Round-trip time in [us] */
+};
+
+
+/* Member */
+struct rtp_member *member_add(struct hash *ht, uint32_t src);
+struct rtp_member *member_find(struct hash *ht, uint32_t src);
+
+/* Source */
+void source_init_seq(struct rtp_source *s, uint16_t seq);
+int source_update_seq(struct rtp_source *s, uint16_t seq);
+void source_calc_jitter(struct rtp_source *s, uint32_t rtp_ts,
+ uint32_t arrival);
+int source_calc_lost(const struct rtp_source *s);
+uint8_t source_calc_fraction_lost(struct rtp_source *s);
+
+/* RR (Reception report) */
+int rtcp_rr_alloc(struct rtcp_rr **rrp, size_t count);
+int rtcp_rr_encode(struct mbuf *mb, const struct rtcp_rr *rr);
+int rtcp_rr_decode(struct mbuf *mb, struct rtcp_rr *rr);
+
+/* SDES (Source Description) */
+int rtcp_sdes_decode(struct mbuf *mb, struct rtcp_sdes *sdes);
+
+/* RTCP Feedback */
+int rtcp_rtpfb_gnack_encode(struct mbuf *mb, uint16_t pid, uint16_t blp);
+int rtcp_psfb_sli_encode(struct mbuf *mb, uint16_t first, uint16_t number,
+ uint8_t picid);
+int rtcp_rtpfb_decode(struct mbuf *mb, struct rtcp_msg *msg);
+int rtcp_psfb_decode(struct mbuf *mb, struct rtcp_msg *msg);
+
+/** NTP Time */
+struct timeval;
+void unix2ntp(struct ntp_time *ntp, const struct timeval *tv);
+void ntp2unix(struct timeval *tv, const struct ntp_time *ntp);
+int ntp_time_get(struct ntp_time *ntp);
+uint32_t ntp_compact(const struct ntp_time *ntp);
+uint64_t ntp_compact2us(uint32_t ntpc);
+
+/* RTP Socket */
+struct rtcp_sess *rtp_rtcp_sess(const struct rtp_sock *rs);
+
+/* RTCP message */
+typedef int (rtcp_encode_h)(struct mbuf *mb, void *arg);
+
+int rtcp_hdr_encode(struct mbuf *mb, uint8_t count, enum rtcp_type type,
+ uint16_t length);
+int rtcp_hdr_decode(struct mbuf *mb, struct rtcp_hdr *hdr);
+int rtcp_vencode(struct mbuf *mb, enum rtcp_type type, uint32_t count,
+ va_list ap);
+
+/* RTCP Session */
+struct rtcp_sess;
+
+int rtcp_sess_alloc(struct rtcp_sess **sessp, struct rtp_sock *rs);
+int rtcp_enable(struct rtcp_sess *sess, bool enabled, const char *cname);
+int rtcp_send(struct rtp_sock *rs, struct mbuf *mb);
+void rtcp_handler(struct rtcp_sess *sess, struct rtcp_msg *msg);
+void rtcp_sess_tx_rtp(struct rtcp_sess *sess, uint32_t ts,
+ size_t payload_size);
+void rtcp_sess_rx_rtp(struct rtcp_sess *sess, uint16_t seq, uint32_t ts,
+ uint32_t src, size_t payload_size,
+ const struct sa *peer);
diff --git a/src/rtp/rtp.c b/src/rtp/rtp.c
new file mode 100644
index 0000000..066199d
--- /dev/null
+++ b/src/rtp/rtp.c
@@ -0,0 +1,600 @@
+/**
+ * @file rtp.c Real-time Transport Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sys.h>
+#include <re_net.h>
+#include <re_udp.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtp"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Defines an RTP Socket */
+struct rtp_sock {
+ /** Encode data */
+ struct {
+ uint16_t seq; /**< Sequence number */
+ uint32_t ssrc; /**< Synchronizing source */
+ } enc;
+ int proto; /**< Transport Protocol */
+ void *sock_rtp; /**< RTP Socket */
+ void *sock_rtcp; /**< RTCP Socket */
+ struct sa local; /**< Local RTP Address */
+ struct sa rtcp_peer; /**< RTCP address of Peer */
+ rtp_recv_h *recvh; /**< RTP Receive handler */
+ rtcp_recv_h *rtcph; /**< RTCP Receive handler */
+ void *arg; /**< Handler argument */
+ struct rtcp_sess *rtcp; /**< RTCP Session */
+ bool rtcp_mux; /**< RTP/RTCP multiplexing */
+};
+
+
+/**
+ * Encode the RTP header into a buffer
+ *
+ * @param mb Buffer to encode into
+ * @param hdr RTP Header to be encoded
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtp_hdr_encode(struct mbuf *mb, const struct rtp_header *hdr)
+{
+ uint8_t buf[2];
+ int err, i;
+
+ if (!mb || !hdr)
+ return EINVAL;
+
+ buf[0] = (hdr->ver & 0x02) << 6;
+ buf[0] |= (hdr->pad & 0x01) << 5;
+ buf[0] |= (hdr->ext & 0x01) << 4;
+ buf[0] |= (hdr->cc & 0x0f) << 0;
+ buf[1] = (hdr->m & 0x01) << 7;
+ buf[1] |= (hdr->pt & 0x7f) << 0;
+
+ err = mbuf_write_mem(mb, buf, sizeof(buf));
+ err |= mbuf_write_u16(mb, htons(hdr->seq));
+ err |= mbuf_write_u32(mb, htonl(hdr->ts));
+ err |= mbuf_write_u32(mb, htonl(hdr->ssrc));
+
+ for (i=0; i<hdr->cc; i++) {
+ err |= mbuf_write_u32(mb, htonl(hdr->csrc[i]));
+ }
+
+ return err;
+}
+
+
+/**
+ * Decode an RTP header from a buffer
+ *
+ * @param hdr RTP Header to decode into
+ * @param mb Buffer to decode from
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtp_hdr_decode(struct rtp_header *hdr, struct mbuf *mb)
+{
+ uint8_t buf[2];
+ int err, i;
+ size_t header_len;
+
+ if (!hdr || !mb)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < RTP_HEADER_SIZE)
+ return EBADMSG;
+
+ err = mbuf_read_mem(mb, buf, sizeof(buf));
+ if (err)
+ return err;
+
+ hdr->ver = (buf[0] >> 6) & 0x03;
+ hdr->pad = (buf[0] >> 5) & 0x01;
+ hdr->ext = (buf[0] >> 4) & 0x01;
+ hdr->cc = (buf[0] >> 0) & 0x0f;
+ hdr->m = (buf[1] >> 7) & 0x01;
+ hdr->pt = (buf[1] >> 0) & 0x7f;
+
+ hdr->seq = ntohs(mbuf_read_u16(mb));
+ hdr->ts = ntohl(mbuf_read_u32(mb));
+ hdr->ssrc = ntohl(mbuf_read_u32(mb));
+
+ header_len = hdr->cc*sizeof(uint32_t);
+ if (mbuf_get_left(mb) < header_len)
+ return EBADMSG;
+
+ for (i=0; i<hdr->cc; i++) {
+ hdr->csrc[i] = ntohl(mbuf_read_u32(mb));
+ }
+
+ if (hdr->ext) {
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ hdr->x.type = ntohs(mbuf_read_u16(mb));
+ hdr->x.len = ntohs(mbuf_read_u16(mb));
+
+ if (mbuf_get_left(mb) < hdr->x.len*sizeof(uint32_t))
+ return EBADMSG;
+
+ mb->pos += hdr->x.len*sizeof(uint32_t);
+ }
+
+ return 0;
+}
+
+
+static void destructor(void *data)
+{
+ struct rtp_sock *rs = data;
+
+ switch (rs->proto) {
+
+ case IPPROTO_UDP:
+ udp_handler_set(rs->sock_rtp, NULL, NULL);
+ udp_handler_set(rs->sock_rtcp, NULL, NULL);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Destroy RTCP Session now */
+ mem_deref(rs->rtcp);
+
+ mem_deref(rs->sock_rtp);
+ mem_deref(rs->sock_rtcp);
+}
+
+
+static void rtcp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct rtp_sock *rs = arg;
+ struct rtcp_msg *msg;
+
+ while (0 == rtcp_decode(&msg, mb)) {
+
+ /* handle internally first */
+ rtcp_handler(rs->rtcp, msg);
+
+ /* then relay to application */
+ if (rs->rtcph)
+ rs->rtcph(src, msg, rs->arg);
+
+ mem_deref(msg);
+ }
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct rtp_sock *rs = arg;
+ struct rtp_header hdr;
+ int err;
+
+ /* Handle RTCP multiplexed on RTP-port */
+ if (rs->rtcp_mux) {
+ uint8_t pt;
+
+ if (mbuf_get_left(mb) < 2)
+ return;
+
+ pt = mbuf_buf(mb)[1] & 0x7f;
+
+ if (64 <= pt && pt <= 95) {
+ rtcp_recv_handler(src, mb, arg);
+ return;
+ }
+ }
+
+ err = rtp_decode(rs, mb, &hdr);
+ if (err)
+ return;
+
+ if (rs->rtcp) {
+ rtcp_sess_rx_rtp(rs->rtcp, hdr.seq, hdr.ts,
+ hdr.ssrc, mbuf_get_left(mb), src);
+ }
+
+ if (rs->recvh)
+ rs->recvh(src, &hdr, mb, rs->arg);
+}
+
+
+static int udp_range_listen(struct rtp_sock *rs, const struct sa *ip,
+ uint16_t min_port, uint16_t max_port)
+{
+ struct sa rtcp;
+ int tries = 64;
+ int err = 0;
+
+ rs->local = rtcp = *ip;
+
+ /* try hard */
+ while (tries--) {
+ struct udp_sock *us_rtp, *us_rtcp;
+ uint16_t port;
+
+ port = (min_port + (rand_u16() % (max_port - min_port)));
+ port &= 0xfffe;
+
+ sa_set_port(&rs->local, port);
+ err = udp_listen(&us_rtp, &rs->local, udp_recv_handler, rs);
+ if (err)
+ continue;
+
+ sa_set_port(&rtcp, port + 1);
+ err = udp_listen(&us_rtcp, &rtcp, rtcp_recv_handler, rs);
+ if (err) {
+ mem_deref(us_rtp);
+ continue;
+ }
+
+ /* OK */
+ rs->sock_rtp = us_rtp;
+ rs->sock_rtcp = us_rtcp;
+ break;
+ }
+
+ return err;
+}
+
+
+/**
+ * Allocate a new RTP socket
+ *
+ * @param rsp Pointer to returned RTP socket
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtp_alloc(struct rtp_sock **rsp)
+{
+ struct rtp_sock *rs;
+
+ if (!rsp)
+ return EINVAL;
+
+ rs = mem_zalloc(sizeof(*rs), destructor);
+ if (!rs)
+ return ENOMEM;
+
+ sa_init(&rs->rtcp_peer, AF_UNSPEC);
+
+ rs->enc.seq = rand_u16() & 0x7fff;
+ rs->enc.ssrc = rand_u32();
+
+ *rsp = rs;
+
+ return 0;
+}
+
+
+/**
+ * Listen on an RTP/RTCP Socket
+ *
+ * @param rsp Pointer to returned RTP socket
+ * @param proto Transport protocol
+ * @param ip Local IP address
+ * @param min_port Minimum port range
+ * @param max_port Maximum port range
+ * @param enable_rtcp True to enable RTCP Session
+ * @param recvh RTP Receive handler
+ * @param rtcph RTCP Receive handler
+ * @param arg Handler argument
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtp_listen(struct rtp_sock **rsp, int proto, const struct sa *ip,
+ uint16_t min_port, uint16_t max_port, bool enable_rtcp,
+ rtp_recv_h *recvh, rtcp_recv_h *rtcph, void *arg)
+{
+ struct rtp_sock *rs;
+ int err;
+
+ if (!ip || min_port >= max_port || !recvh)
+ return EINVAL;
+
+ err = rtp_alloc(&rs);
+ if (err)
+ return err;
+
+ rs->proto = proto;
+ rs->recvh = recvh;
+ rs->rtcph = rtcph;
+ rs->arg = arg;
+
+ /* Optional RTCP */
+ if (enable_rtcp) {
+ err = rtcp_sess_alloc(&rs->rtcp, rs);
+ if (err)
+ goto out;
+ }
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_range_listen(rs, ip, min_port, max_port);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ out:
+ if (err)
+ mem_deref(rs);
+ else
+ *rsp = rs;
+
+ return err;
+}
+
+
+/**
+ * Encode a new RTP header into the beginning of the buffer
+ *
+ * @param rs RTP Socket
+ * @param ext Extension bit
+ * @param marker Marker bit
+ * @param pt Payload type
+ * @param ts Timestamp
+ * @param mb Memory buffer
+ *
+ * @return 0 for success, otherwise errorcode
+ *
+ * @note The buffer must have enough space for the RTP header
+ */
+int rtp_encode(struct rtp_sock *rs, bool ext, bool marker, uint8_t pt,
+ uint32_t ts, struct mbuf *mb)
+{
+ struct rtp_header hdr;
+
+ if (!rs || pt&~0x7f || !mb)
+ return EINVAL;
+
+ hdr.ver = RTP_VERSION;
+ hdr.pad = false;
+ hdr.ext = ext;
+ hdr.cc = 0;
+ hdr.m = marker ? 1 : 0;
+ hdr.pt = pt;
+ hdr.seq = rs->enc.seq++;
+ hdr.ts = ts;
+ hdr.ssrc = rs->enc.ssrc;
+
+ return rtp_hdr_encode(mb, &hdr);
+}
+
+
+/**
+ * Decode an RTP packet and return decoded RTP header and payload
+ *
+ * @param rs RTP Socket
+ * @param mb Memory buffer containing RTP packet
+ * @param hdr RTP header (set on return)
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtp_decode(struct rtp_sock *rs, struct mbuf *mb,
+ struct rtp_header *hdr)
+{
+ int err;
+
+ if (!rs || !mb || !hdr)
+ return EINVAL;
+
+ memset(hdr, 0, sizeof(*hdr));
+ err = rtp_hdr_decode(hdr, mb);
+ if (err)
+ return err;
+
+ if (RTP_VERSION != hdr->ver)
+ return EBADMSG;
+
+ return 0;
+}
+
+
+/**
+ * Send an RTP packet to a peer
+ *
+ * @param rs RTP Socket
+ * @param dst Destination address
+ * @param ext Extension bit
+ * @param marker Marker bit
+ * @param pt Payload type
+ * @param ts Timestamp
+ * @param mb Payload buffer
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtp_send(struct rtp_sock *rs, const struct sa *dst, bool ext,
+ bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb)
+{
+ size_t pos;
+ int err;
+
+ if (!rs || !mb)
+ return EINVAL;
+
+ if (mb->pos < RTP_HEADER_SIZE) {
+ DEBUG_WARNING("rtp_send: buffer must have space for"
+ " rtp header (pos=%u, end=%u)\n",
+ mb->pos, mb->end);
+ return EBADMSG;
+ }
+
+ mbuf_advance(mb, -RTP_HEADER_SIZE);
+
+ pos = mb->pos;
+
+ err = rtp_encode(rs, ext, marker, pt, ts, mb);
+ if (err)
+ return err;
+
+ if (rs->rtcp)
+ rtcp_sess_tx_rtp(rs->rtcp, ts, mbuf_get_left(mb));
+
+ mb->pos = pos;
+
+ return udp_send(rs->sock_rtp, dst, mb);
+}
+
+
+/**
+ * Get the RTP transport socket from an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return Transport socket for RTP
+ */
+void *rtp_sock(const struct rtp_sock *rs)
+{
+ return rs ? rs->sock_rtp : NULL;
+}
+
+
+/**
+ * Get the RTCP transport socket from an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return Transport socket for RTCP
+ */
+void *rtcp_sock(const struct rtp_sock *rs)
+{
+ return rs ? rs->sock_rtcp : NULL;
+}
+
+
+/**
+ * Get the local RTP address for an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return Local RTP address
+ */
+const struct sa *rtp_local(const struct rtp_sock *rs)
+{
+ return rs ? &rs->local : NULL;
+}
+
+
+/**
+ * Get the Synchronizing source for an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return Synchronizing source
+ */
+uint32_t rtp_sess_ssrc(const struct rtp_sock *rs)
+{
+ return rs ? rs->enc.ssrc : 0;
+}
+
+
+/**
+ * Get the RTCP-Session for an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return RTCP-Session
+ */
+struct rtcp_sess *rtp_rtcp_sess(const struct rtp_sock *rs)
+{
+ return rs ? rs->rtcp : NULL;
+}
+
+
+/**
+ * Start the RTCP Session
+ *
+ * @param rs RTP Socket
+ * @param cname Canonical Name
+ * @param peer IP-Address of RTCP Peer
+ */
+void rtcp_start(struct rtp_sock *rs, const char *cname,
+ const struct sa *peer)
+{
+ if (!rs)
+ return;
+
+ if (peer)
+ rs->rtcp_peer = *peer;
+
+ (void)rtcp_enable(rs->rtcp, peer != NULL, cname);
+}
+
+
+/**
+ * Enable RTCP-multiplexing on RTP-port
+ *
+ * @param rs RTP Socket
+ * @param enabled True to enable, false to disable
+ */
+void rtcp_enable_mux(struct rtp_sock *rs, bool enabled)
+{
+ if (!rs)
+ return;
+
+ rs->rtcp_mux = enabled;
+}
+
+
+/**
+ * Send RTCP packet(s) to the Peer
+ *
+ * @param rs RTP Socket
+ * @param mb Buffer containing the RTCP Packet(s)
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send(struct rtp_sock *rs, struct mbuf *mb)
+{
+ if (!rs || !rs->sock_rtcp || !sa_isset(&rs->rtcp_peer, SA_ALL))
+ return EINVAL;
+
+ return udp_send(rs->rtcp_mux ? rs->sock_rtp : rs->sock_rtcp,
+ &rs->rtcp_peer, mb);
+}
+
+
+/**
+ * RTP Debug handler, use with fmt %H
+ *
+ * @param pf Print function
+ * @param rs RTP Socket
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtp_debug(struct re_printf *pf, const struct rtp_sock *rs)
+{
+ int err;
+
+ if (!rs || !pf)
+ return EINVAL;
+
+ err = re_hprintf(pf, "RTP debug:\n");
+ err |= re_hprintf(pf, " Encode: seq=%u ssrc=0x%lx\n",
+ rs->enc.seq, rs->enc.ssrc);
+
+ if (rs->rtcp)
+ err |= rtcp_debug(pf, rs);
+
+ return err;
+}
diff --git a/src/rtp/sdes.c b/src/rtp/sdes.c
new file mode 100644
index 0000000..711d8a9
--- /dev/null
+++ b/src/rtp/sdes.c
@@ -0,0 +1,148 @@
+/**
+ * @file sdes.c RTCP Source Description
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtcp_sdes"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ RTCP_SDES_MIN_SIZE = 1,
+};
+
+
+/**
+ * Encode one SDES chunk into mbuffer
+ *
+ * @param mb Buffer to encode into
+ * @param src First SSRC/CSRC
+ * @param itemc Number of SDES items to encode
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_sdes_encode(struct mbuf *mb, uint32_t src, uint32_t itemc, ...)
+{
+ va_list ap;
+ size_t start;
+ int err = 0;
+
+ if (!mb || !itemc)
+ return EINVAL;
+
+ va_start(ap, itemc);
+
+ start = mb->pos;
+ err = mbuf_write_u32(mb, htonl(src));
+
+ /* add all SDES items */
+ while (itemc-- && !err) {
+ const uint8_t type = va_arg(ap, int);
+ const char *v = va_arg(ap, const char *);
+ size_t len;
+ if (!v)
+ continue;
+
+ len = strlen(v); /* note: max 255 chars */
+ if (len > 255) {
+ err = EINVAL;
+ goto out;
+ }
+
+ err = mbuf_write_u8(mb, type);
+ err |= mbuf_write_u8(mb, len & 0xff);
+ err |= mbuf_write_mem(mb, (uint8_t *)v, len);
+ }
+
+ /* END padding */
+ err |= mbuf_write_u8(mb, RTCP_SDES_END);
+ while ((mb->pos - start) & 0x3)
+ err |= mbuf_write_u8(mb, RTCP_SDES_END);
+
+ out:
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Decode SDES items from a buffer
+ *
+ * @param mb Buffer to decode from
+ * @param sdes RTCP SDES to decode into
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_sdes_decode(struct mbuf *mb, struct rtcp_sdes *sdes)
+{
+ size_t start;
+
+ if (!sdes)
+ return EINVAL;
+ if (mbuf_get_left(mb) < RTCP_SRC_SIZE)
+ return EBADMSG;
+
+ start = mb->pos;
+ sdes->src = ntohl(mbuf_read_u32(mb));
+
+ /* Decode all SDES items */
+ while (mbuf_get_left(mb) >= RTCP_SDES_MIN_SIZE) {
+ uint8_t type;
+ struct rtcp_sdes_item *item;
+
+ type = mbuf_read_u8(mb);
+ if (type == RTCP_SDES_END)
+ break;
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ if (!sdes->itemv) {
+ sdes->itemv = mem_alloc(sizeof(*sdes->itemv), NULL);
+ if (!sdes->itemv)
+ return ENOMEM;
+ }
+ else {
+ const size_t sz = (sdes->n + 1) * sizeof(*sdes->itemv);
+ struct rtcp_sdes_item *itemv;
+
+ itemv = mem_realloc(sdes->itemv, sz);
+ if (!itemv)
+ return ENOMEM;
+
+ sdes->itemv = itemv;
+ }
+
+ item = &sdes->itemv[sdes->n];
+
+ item->type = (enum rtcp_sdes_type)type;
+ item->length = mbuf_read_u8(mb);
+ if (mbuf_get_left(mb) < item->length)
+ return EBADMSG;
+ item->data = mem_alloc(item->length, NULL);
+ if (!item->data)
+ return ENOMEM;
+ (void)mbuf_read_mem(mb, (uint8_t *)item->data, item->length);
+
+ sdes->n++;
+ }
+
+ /* slurp padding */
+ while ((mb->pos - start) & 0x3 && mbuf_get_left(mb))
+ ++mb->pos;
+
+ return 0;
+}
diff --git a/src/rtp/sess.c b/src/rtp/sess.c
new file mode 100644
index 0000000..2115210
--- /dev/null
+++ b/src/rtp/sess.c
@@ -0,0 +1,675 @@
+/**
+ * @file rtp/sess.c Real-time Transport Control Protocol Session
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+#ifdef WIN32
+#include <winsock2.h>
+#endif
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_lock.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtcp_sess"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** RTP protocol values */
+enum {
+ RTCP_INTERVAL = 5000, /**< Interval in [ms] between sending reports */
+ MAX_MEMBERS = 8,
+};
+
+/** RTP Transmit stats */
+struct txstat {
+ uint32_t psent; /**< Total number of RTP packets sent */
+ uint32_t osent; /**< Total number of RTP octets sent */
+ uint64_t jfs_ref; /**< Timer ticks at RTP timestamp reference */
+ uint32_t ts_ref; /**< RTP timestamp reference (transmit) */
+ bool ts_synced; /**< RTP timestamp synchronization flag */
+};
+
+/** RTCP Session */
+struct rtcp_sess {
+ struct rtp_sock *rs; /**< RTP Socket */
+ struct hash *members; /**< Member table */
+ struct tmr tmr; /**< Event sender timer */
+ char *cname; /**< Canonical Name */
+ uint32_t memberc; /**< Number of members */
+ uint32_t senderc; /**< Number of senders */
+ uint32_t srate_tx; /**< Transmit sampling rate */
+ uint32_t srate_rx; /**< Receive sampling rate */
+
+ /* stats */
+ struct lock *lock; /**< Lock for txstat */
+ struct txstat txstat; /**< Local transmit statistics */
+};
+
+
+/* Prototypes */
+static void schedule(struct rtcp_sess *sess);
+static int send_bye_packet(struct rtcp_sess *sess);
+
+
+static void sess_destructor(void *data)
+{
+ struct rtcp_sess *sess = data;
+
+ if (sess->cname)
+ (void)send_bye_packet(sess);
+
+ tmr_cancel(&sess->tmr);
+
+ mem_deref(sess->cname);
+ hash_flush(sess->members);
+ mem_deref(sess->members);
+ mem_deref(sess->lock);
+}
+
+
+static struct rtp_member *get_member(struct rtcp_sess *sess, uint32_t src)
+{
+ struct rtp_member *mbr;
+
+ mbr = member_find(sess->members, src);
+ if (mbr)
+ return mbr;
+
+ if (sess->memberc >= MAX_MEMBERS)
+ return NULL;
+
+ mbr = member_add(sess->members, src);
+ if (!mbr)
+ return NULL;
+
+ ++sess->memberc;
+
+ return mbr;
+}
+
+
+/** Calculate Round-Trip Time in [microseconds] */
+static void calc_rtt(uint32_t *rtt, uint32_t lsr, uint32_t dlsr)
+{
+ struct ntp_time ntp_time;
+ uint64_t a_us, lsr_us, dlsr_us;
+ int err;
+
+ err = ntp_time_get(&ntp_time);
+ if (err)
+ return;
+
+ a_us = ntp_compact2us(ntp_compact(&ntp_time));
+ lsr_us = ntp_compact2us(lsr);
+ dlsr_us = 1000000ULL * dlsr / 65536;
+
+ /* RTT delay is (A - LSR - DLSR) */
+ *rtt = MAX((int)(a_us - lsr_us - dlsr_us), 0);
+}
+
+
+/** Decode Reception Report block */
+static void handle_rr_block(struct rtcp_sess *sess, struct rtp_member *mbr,
+ const struct rtcp_rr *rr)
+{
+ /* Lost */
+ mbr->cum_lost = rr->lost;
+
+ /* Interarrival jitter */
+ if (sess->srate_tx)
+ mbr->jit = 1000000 * rr->jitter / sess->srate_tx;
+
+ /* round-trip propagation delay as (A - LSR - DLSR) */
+ if (rr->lsr && rr->dlsr)
+ calc_rtt(&mbr->rtt, rr->lsr, rr->dlsr);
+}
+
+
+/** Handle incoming RR (Receiver Report) packet */
+static void handle_incoming_rr(struct rtcp_sess *sess,
+ const struct rtcp_msg *msg)
+{
+ struct rtp_member *mbr;
+ uint32_t i;
+
+ mbr = get_member(sess, msg->r.rr.ssrc);
+ if (!mbr)
+ return;
+
+ for (i=0; i<msg->hdr.count; i++)
+ handle_rr_block(sess, mbr, &msg->r.rr.rrv[i]);
+}
+
+
+/** Handle incoming SR (Sender Report) packet */
+static void handle_incoming_sr(struct rtcp_sess *sess,
+ const struct rtcp_msg *msg)
+{
+ struct rtp_member *mbr;
+ uint32_t i;
+
+ mbr = get_member(sess, msg->r.sr.ssrc);
+ if (!mbr) {
+ DEBUG_WARNING("0x%08x: could not add member\n",
+ msg->r.sr.ssrc);
+ return;
+ }
+
+ if (mbr->s) {
+ /* Save time when SR was received */
+ mbr->s->sr_recv = tmr_jiffies();
+
+ /* Save NTP timestamp from SR */
+ mbr->s->last_sr.hi = msg->r.sr.ntp_sec;
+ mbr->s->last_sr.lo = msg->r.sr.ntp_frac;
+ mbr->s->rtp_ts = msg->r.sr.rtp_ts;
+ mbr->s->psent = msg->r.sr.psent;
+ mbr->s->osent = msg->r.sr.osent;
+ }
+
+ for (i=0; i<msg->hdr.count; i++)
+ handle_rr_block(sess, mbr, &msg->r.sr.rrv[i]);
+}
+
+
+static void handle_incoming_bye(struct rtcp_sess *sess,
+ const struct rtcp_msg *msg)
+{
+ uint32_t i;
+
+ for (i=0; i<msg->hdr.count; i++) {
+
+ struct rtp_member *mbr;
+
+ mbr = member_find(sess->members, msg->r.bye.srcv[i]);
+ if (mbr) {
+ if (mbr->s)
+ --sess->senderc;
+
+ --sess->memberc;
+ mem_deref(mbr);
+ }
+ }
+}
+
+
+void rtcp_handler(struct rtcp_sess *sess, struct rtcp_msg *msg)
+{
+ if (!sess || !msg)
+ return;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ handle_incoming_sr(sess, msg);
+ break;
+
+ case RTCP_RR:
+ handle_incoming_rr(sess, msg);
+ break;
+
+ case RTCP_BYE:
+ handle_incoming_bye(sess, msg);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+int rtcp_sess_alloc(struct rtcp_sess **sessp, struct rtp_sock *rs)
+{
+ struct rtcp_sess *sess;
+ int err;
+
+ if (!sessp)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), sess_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->rs = rs;
+ tmr_init(&sess->tmr);
+
+ err = lock_alloc(&sess->lock);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sess->members, MAX_MEMBERS);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+/**
+ * Set the Sampling-rate on an RTCP Session
+ *
+ * @param rs RTP Socket
+ * @param srate_tx Transmit samplerate
+ * @param srate_rx Receive samplerate
+ */
+void rtcp_set_srate(struct rtp_sock *rs, uint32_t srate_tx, uint32_t srate_rx)
+{
+ struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ if (!sess)
+ return;
+
+ sess->srate_tx = srate_tx;
+ sess->srate_rx = srate_rx;
+}
+
+
+/**
+ * Set the transmit Sampling-rate on an RTCP Session
+ *
+ * @param rs RTP Socket
+ * @param srate_tx Transmit samplerate
+ */
+void rtcp_set_srate_tx(struct rtp_sock *rs, uint32_t srate_tx)
+{
+ struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ if (!sess)
+ return;
+
+ sess->srate_tx = srate_tx;
+}
+
+
+/**
+ * Set the receive Sampling-rate on an RTCP Session
+ *
+ * @param rs RTP Socket
+ * @param srate_rx Receive samplerate
+ */
+void rtcp_set_srate_rx(struct rtp_sock *rs, uint32_t srate_rx)
+{
+ struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ if (!sess)
+ return;
+
+ sess->srate_rx = srate_rx;
+}
+
+
+int rtcp_enable(struct rtcp_sess *sess, bool enabled, const char *cname)
+{
+ int err;
+
+ if (!sess)
+ return EINVAL;
+
+ sess->cname = mem_deref(sess->cname);
+ err = str_dup(&sess->cname, cname);
+ if (err)
+ return err;
+
+ if (enabled)
+ schedule(sess);
+ else
+ tmr_cancel(&sess->tmr);
+
+ return 0;
+}
+
+
+/** Calculate LSR (middle 32 bits out of 64 in the NTP timestamp) */
+static uint32_t calc_lsr(const struct ntp_time *last_sr)
+{
+ return last_sr->hi ? ntp_compact(last_sr) : 0;
+}
+
+
+static uint32_t calc_dlsr(uint64_t sr_recv)
+{
+ if (sr_recv) {
+ const uint64_t diff = tmr_jiffies() - sr_recv;
+ return (uint32_t)((65536 * diff) / 1000);
+ }
+ else {
+ return 0;
+ }
+}
+
+
+static bool sender_apply_handler(struct le *le, void *arg)
+{
+ struct rtp_member *mbr = le->data;
+ struct rtp_source *s = mbr->s;
+ struct mbuf *mb = arg;
+ struct rtcp_rr rr;
+
+ if (!s)
+ return false;
+
+ /* Initialise the members */
+ rr.ssrc = mbr->src;
+ rr.fraction = source_calc_fraction_lost(s);
+ rr.lost = source_calc_lost(s);
+ rr.last_seq = s->cycles | s->max_seq;
+ rr.jitter = s->jitter >> 4;
+ rr.lsr = calc_lsr(&s->last_sr);
+ rr.dlsr = calc_dlsr(s->sr_recv);
+
+ return 0 != rtcp_rr_encode(mb, &rr);
+}
+
+
+static int encode_handler(struct mbuf *mb, void *arg)
+{
+ struct hash *members = arg;
+
+ /* copy all report blocks */
+ if (hash_apply(members, sender_apply_handler, mb))
+ return ENOMEM;
+
+ return 0;
+}
+
+
+/** Create a Sender Report */
+static int mk_sr(struct rtcp_sess *sess, struct mbuf *mb)
+{
+ struct ntp_time ntp = {0, 0};
+ struct txstat txstat;
+ uint32_t dur, rtp_ts = 0;
+ int err;
+
+ err = ntp_time_get(&ntp);
+ if (err)
+ return err;
+
+ lock_write_get(sess->lock);
+ txstat = sess->txstat;
+ sess->txstat.ts_synced = false;
+ lock_rel(sess->lock);
+
+ if (txstat.jfs_ref) {
+ dur = (uint32_t)(tmr_jiffies() - txstat.jfs_ref);
+ rtp_ts = txstat.ts_ref + dur * sess->srate_tx / 1000;
+ }
+
+ err = rtcp_encode(mb, RTCP_SR, sess->senderc, rtp_sess_ssrc(sess->rs),
+ ntp.hi, ntp.lo, rtp_ts, txstat.psent, txstat.osent,
+ encode_handler, sess->members);
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+static int sdes_encode_handler(struct mbuf *mb, void *arg)
+{
+ struct rtcp_sess *sess = arg;
+
+ return rtcp_sdes_encode(mb, rtp_sess_ssrc(sess->rs), 1,
+ RTCP_SDES_CNAME, sess->cname);
+}
+
+
+static int mk_sdes(struct rtcp_sess *sess, struct mbuf *mb)
+{
+ return rtcp_encode(mb, RTCP_SDES, 1, sdes_encode_handler, sess);
+}
+
+
+static int send_rtcp_report(struct rtcp_sess *sess)
+{
+ struct mbuf *mb;
+ int err;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ mb->pos = RTCP_HEADROOM;
+
+ err = mk_sr(sess, mb);
+ err |= mk_sdes(sess, mb);
+ if (err)
+ goto out;
+
+ mb->pos = RTCP_HEADROOM;
+
+ err = rtcp_send(sess->rs, mb);
+
+ out:
+ mem_deref(mb);
+ return err;
+}
+
+
+static int send_bye_packet(struct rtcp_sess *sess)
+{
+ const uint32_t ssrc = rtp_sess_ssrc(sess->rs);
+ struct mbuf *mb;
+ int err;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ mb->pos = RTCP_HEADROOM;
+
+ err = rtcp_encode(mb, RTCP_BYE, 1, &ssrc, "Adjo");
+ err |= mk_sdes(sess, mb);
+ if (err)
+ goto out;
+
+ mb->pos = RTCP_HEADROOM;
+
+ err = rtcp_send(sess->rs, mb);
+
+ out:
+ mem_deref(mb);
+ return err;
+}
+
+
+static void timeout(void *arg)
+{
+ struct rtcp_sess *sess = arg;
+ int err;
+
+ err = send_rtcp_report(sess);
+ if (err) {
+ DEBUG_WARNING("Send RTCP report failed: %m\n", err);
+ }
+
+ schedule(sess);
+}
+
+
+static void schedule(struct rtcp_sess *sess)
+{
+ tmr_start(&sess->tmr, RTCP_INTERVAL, timeout, sess);
+}
+
+
+void rtcp_sess_tx_rtp(struct rtcp_sess *sess, uint32_t ts, size_t payload_size)
+{
+ if (!sess)
+ return;
+
+ lock_write_get(sess->lock);
+
+ sess->txstat.osent += (uint32_t)payload_size;
+ sess->txstat.psent += 1;
+
+ if (!sess->txstat.ts_synced) {
+ sess->txstat.jfs_ref = tmr_jiffies();
+ sess->txstat.ts_ref = ts;
+ sess->txstat.ts_synced = true;
+ }
+
+ lock_rel(sess->lock);
+}
+
+
+void rtcp_sess_rx_rtp(struct rtcp_sess *sess, uint16_t seq, uint32_t ts,
+ uint32_t ssrc, size_t payload_size,
+ const struct sa *peer)
+{
+ struct rtp_member *mbr;
+
+ if (!sess)
+ return;
+
+ mbr = get_member(sess, ssrc);
+ if (!mbr) {
+ DEBUG_NOTICE("could not add member: 0x%08x\n", ssrc);
+ return;
+ }
+
+ if (!mbr->s) {
+ mbr->s = mem_zalloc(sizeof(*mbr->s), NULL);
+ if (!mbr->s) {
+ DEBUG_NOTICE("could not add sender: 0x%08x\n", ssrc);
+ return;
+ }
+
+ /* first packet - init sequence number */
+ source_init_seq(mbr->s, seq);
+ /* probation not used */
+ sa_cpy(&mbr->s->rtp_peer, peer);
+ ++sess->senderc;
+ }
+
+ if (!source_update_seq(mbr->s, seq)) {
+ DEBUG_WARNING("rtp_update_seq() returned 0\n");
+ }
+
+ if (sess->srate_rx) {
+
+ uint64_t ts_arrive;
+
+ /* Convert from wall-clock time to timestamp units */
+ ts_arrive = tmr_jiffies() * sess->srate_rx / 1000;
+
+ source_calc_jitter(mbr->s, ts, (uint32_t)ts_arrive);
+ }
+
+ mbr->s->rtp_rx_bytes += payload_size;
+}
+
+
+/**
+ * Get the RTCP Statistics for a source
+ *
+ * @param rs RTP Socket
+ * @param ssrc Synchronization source
+ * @param stats RTCP Statistics, set on return
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_stats(struct rtp_sock *rs, uint32_t ssrc, struct rtcp_stats *stats)
+{
+ const struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ struct rtp_member *mbr;
+
+ if (!sess || !stats)
+ return EINVAL;
+
+ mbr = member_find(sess->members, ssrc);
+ if (!mbr)
+ return ENOENT;
+
+ lock_read_get(sess->lock);
+ stats->tx.sent = sess->txstat.psent;
+ lock_rel(sess->lock);
+
+ stats->tx.lost = mbr->cum_lost;
+ stats->tx.jit = mbr->jit;
+
+ stats->rtt = mbr->rtt;
+
+ if (!mbr->s) {
+ memset(&stats->rx, 0, sizeof(stats->rx));
+ return 0;
+ }
+
+ stats->rx.sent = mbr->s->received;
+ stats->rx.lost = source_calc_lost(mbr->s);
+ stats->rx.jit = sess->srate_rx ?
+ 1000000 * (mbr->s->jitter>>4) / sess->srate_rx : 0;
+
+ return 0;
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+ const struct rtp_member *mbr = le->data;
+ struct re_printf *pf = arg;
+ int err;
+
+ err = re_hprintf(pf, " member 0x%08x: lost=%d Jitter=%.1fms"
+ " RTT=%.1fms\n", mbr->src, mbr->cum_lost,
+ (double)mbr->jit/1000, (double)mbr->rtt/1000);
+ if (mbr->s) {
+ err |= re_hprintf(pf,
+ " IP=%J psent=%u rcvd=%u\n",
+ &mbr->s->rtp_peer, mbr->s->psent,
+ mbr->s->received);
+ }
+
+ return err != 0;
+}
+
+
+/**
+ * RTCP Debug handler, use with fmt %H
+ *
+ * @param pf Print function
+ * @param rs RTP Socket
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_debug(struct re_printf *pf, const struct rtp_sock *rs)
+{
+ const struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ int err = 0;
+
+ if (!sess)
+ return 0;
+
+ err |= re_hprintf(pf, "----- RTCP Session: -----\n");
+ err |= re_hprintf(pf, " cname=%s SSRC=0x%08x/%u rx=%uHz\n",
+ sess->cname,
+ rtp_sess_ssrc(sess->rs), rtp_sess_ssrc(sess->rs),
+ sess->srate_rx);
+
+ hash_apply(sess->members, debug_handler, pf);
+
+ lock_read_get(sess->lock);
+ err |= re_hprintf(pf, " TX: packets=%u, octets=%u\n",
+ sess->txstat.psent, sess->txstat.osent);
+ lock_rel(sess->lock);
+
+ return err;
+}
diff --git a/src/rtp/source.c b/src/rtp/source.c
new file mode 100644
index 0000000..c6a3242
--- /dev/null
+++ b/src/rtp/source.c
@@ -0,0 +1,177 @@
+/**
+ * @file source.c Real-time Transport Control Protocol source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+enum {
+ RTP_SEQ_MOD = 1<<16,
+};
+
+
+void source_init_seq(struct rtp_source *s, uint16_t seq)
+{
+ if (!s)
+ return;
+
+ s->base_seq = seq;
+ s->max_seq = seq;
+ s->bad_seq = RTP_SEQ_MOD + 1; /* so seq == bad_seq is false */
+ s->cycles = 0;
+ s->received = 0;
+ s->received_prior = 0;
+ s->expected_prior = 0;
+ /* other initialization */
+}
+
+
+/*
+ * See RFC 3550 - A.1 RTP Data Header Validity Checks
+ */
+int source_update_seq(struct rtp_source *s, uint16_t seq)
+{
+ uint16_t udelta = seq - s->max_seq;
+ const int MAX_DROPOUT = 3000;
+ const int MAX_MISORDER = 100;
+ const int MIN_SEQUENTIAL = 2;
+
+ /*
+ * Source is not valid until MIN_SEQUENTIAL packets with
+ * sequential sequence numbers have been received.
+ */
+ if (s->probation) {
+
+ /* packet is in sequence */
+ if (seq == s->max_seq + 1) {
+ s->probation--;
+ s->max_seq = seq;
+ if (s->probation == 0) {
+ source_init_seq(s, seq);
+ s->received++;
+ return 1;
+ }
+ }
+ else {
+ s->probation = MIN_SEQUENTIAL - 1;
+ s->max_seq = seq;
+ }
+ return 0;
+ }
+ else if (udelta < MAX_DROPOUT) {
+
+ /* in order, with permissible gap */
+ if (seq < s->max_seq) {
+ /*
+ * Sequence number wrapped - count another 64K cycle.
+ */
+ s->cycles += RTP_SEQ_MOD;
+ }
+ s->max_seq = seq;
+ }
+ else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) {
+
+ /* the sequence number made a very large jump */
+ if (seq == s->bad_seq) {
+ /*
+ * Two sequential packets -- assume that the other side
+ * restarted without telling us so just re-sync
+ * (i.e., pretend this was the first packet).
+ */
+ source_init_seq(s, seq);
+ }
+ else {
+ s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
+ return 0;
+ }
+ }
+ else {
+ /* duplicate or reordered packet */
+ }
+
+ s->received++;
+ return 1;
+}
+
+
+/* RFC 3550 A.8
+ *
+ * The inputs are:
+ *
+ * rtp_ts: the timestamp from the incoming RTP packet
+ * arrival: the current time in the same units.
+ */
+void source_calc_jitter(struct rtp_source *s, uint32_t rtp_ts,
+ uint32_t arrival)
+{
+ const int transit = arrival - rtp_ts;
+ int d = transit - s->transit;
+
+ if (!s->transit) {
+ s->transit = transit;
+ return;
+ }
+
+ s->transit = transit;
+
+ if (d < 0)
+ d = -d;
+
+ s->jitter += d - ((s->jitter + 8) >> 4);
+}
+
+
+/* A.3 */
+int source_calc_lost(const struct rtp_source *s)
+{
+ int extended_max = s->cycles + s->max_seq;
+ int expected = extended_max - s->base_seq + 1;
+ int lost;
+
+ lost = expected - s->received;
+
+ /* Clamp at 24 bits */
+ if (lost > 0x7fffff)
+ lost = 0x7fffff;
+ else if (lost < -0x7fffff)
+ lost = -0x7fffff;
+
+ return lost;
+}
+
+
+/* A.3 */
+uint8_t source_calc_fraction_lost(struct rtp_source *s)
+{
+ int extended_max = s->cycles + s->max_seq;
+ int expected = extended_max - s->base_seq + 1;
+ int expected_interval = expected - s->expected_prior;
+ int received_interval;
+ int lost_interval;
+ uint8_t fraction;
+
+ s->expected_prior = expected;
+
+ received_interval = s->received - s->received_prior;
+
+ s->received_prior = s->received;
+
+ lost_interval = expected_interval - received_interval;
+
+ if (expected_interval == 0 || lost_interval <= 0)
+ fraction = 0;
+ else
+ fraction = (lost_interval << 8) / expected_interval;
+
+ return fraction;
+}