
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
598 lines
11 KiB
C
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;
|
|
}
|