re/src/rtp/rtp.c
Alfred E. Heggestad b2f436deae patch: (s)rtp sequence fix
increase robustness during initial packetloss, ref:

    http://tools.ietf.org/html/rfc4568#section-6.4 (last paragraph)
    http://tools.ietf.org/html/rfc3711#section-3.3.1
2013-08-31 08:41:16 +00:00

598 lines
11 KiB
C

/**
* @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 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 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 = false;
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 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 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, 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;
}