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;
+}