blob: 6dc11fd7941b8fdd7454ba7e153e5aba39c1dcb7 [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia/rtp.h>
#include <pjmedia/errno.h>
#include <pj/log.h>
#include <pj/sock.h> /* pj_htonx, pj_htonx */
#include <pj/assert.h>
#include <pj/rand.h>
#include <pj/string.h>
#define THIS_FILE "rtp.c"
#define RTP_VERSION 2
#define RTP_SEQ_MOD (1 << 16)
#define MAX_DROPOUT ((pj_int16_t)3000)
#define MAX_MISORDER ((pj_int16_t)100)
#define MIN_SEQUENTIAL ((pj_int16_t)2)
static void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *seq_ctrl,
pj_uint16_t seq);
PJ_DEF(pj_status_t) pjmedia_rtp_session_init( pjmedia_rtp_session *ses,
int default_pt,
pj_uint32_t sender_ssrc )
{
PJ_LOG(5, (THIS_FILE,
"pjmedia_rtp_session_init: ses=%p, default_pt=%d, ssrc=0x%x",
ses, default_pt, sender_ssrc));
/* Check RTP header packing. */
if (sizeof(struct pjmedia_rtp_hdr) != 12) {
pj_assert(!"Wrong RTP header packing!");
return PJMEDIA_RTP_EINPACK;
}
/* If sender_ssrc is not specified, create from random value. */
if (sender_ssrc == 0 || sender_ssrc == (pj_uint32_t)-1) {
sender_ssrc = pj_htonl(pj_rand());
} else {
sender_ssrc = pj_htonl(sender_ssrc);
}
/* Initialize session. */
pj_bzero(ses, sizeof(*ses));
/* Initial sequence number SHOULD be random, according to RFC 3550. */
ses->out_extseq = pj_rand();
ses->peer_ssrc = 0;
/* Build default header for outgoing RTP packet. */
ses->out_hdr.v = RTP_VERSION;
ses->out_hdr.p = 0;
ses->out_hdr.x = 0;
ses->out_hdr.cc = 0;
ses->out_hdr.m = 0;
ses->out_hdr.pt = (pj_uint8_t) default_pt;
ses->out_hdr.seq = (pj_uint16_t) pj_htons( (pj_uint16_t)ses->out_extseq );
ses->out_hdr.ts = 0;
ses->out_hdr.ssrc = sender_ssrc;
/* Keep some arguments as session defaults. */
ses->out_pt = (pj_uint16_t) default_pt;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_rtp_encode_rtp( pjmedia_rtp_session *ses,
int pt, int m,
int payload_len, int ts_len,
const void **rtphdr, int *hdrlen )
{
PJ_UNUSED_ARG(payload_len);
/* Update timestamp */
ses->out_hdr.ts = pj_htonl(pj_ntohl(ses->out_hdr.ts)+ts_len);
/* If payload_len is zero, bail out.
* This is a clock frame; we're not really transmitting anything.
*/
if (payload_len == 0)
return PJ_SUCCESS;
/* Update session. */
ses->out_extseq++;
/* Create outgoing header. */
ses->out_hdr.pt = (pj_uint8_t) ((pt == -1) ? ses->out_pt : pt);
ses->out_hdr.m = (pj_uint16_t) m;
ses->out_hdr.seq = pj_htons( (pj_uint16_t) ses->out_extseq);
/* Return values */
*rtphdr = &ses->out_hdr;
*hdrlen = sizeof(pjmedia_rtp_hdr);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_rtp_decode_rtp( pjmedia_rtp_session *ses,
const void *pkt, int pkt_len,
const pjmedia_rtp_hdr **hdr,
const void **payload,
unsigned *payloadlen)
{
int offset;
PJ_UNUSED_ARG(ses);
/* Assume RTP header at the start of packet. We'll verify this later. */
*hdr = (pjmedia_rtp_hdr*)pkt;
/* Check RTP header sanity. */
if ((*hdr)->v != RTP_VERSION) {
return PJMEDIA_RTP_EINVER;
}
/* Payload is located right after header plus CSRC */
offset = sizeof(pjmedia_rtp_hdr) + ((*hdr)->cc * sizeof(pj_uint32_t));
/* Adjust offset if RTP extension is used. */
if ((*hdr)->x) {
pjmedia_rtp_ext_hdr *ext = (pjmedia_rtp_ext_hdr*)
(((pj_uint8_t*)pkt) + offset);
offset += (pj_ntohs(ext->length) * sizeof(pj_uint32_t));
}
/* Check that offset is less than packet size */
if (offset > pkt_len)
return PJMEDIA_RTP_EINLEN;
/* Find and set payload. */
*payload = ((pj_uint8_t*)pkt) + offset;
*payloadlen = pkt_len - offset;
return PJ_SUCCESS;
}
PJ_DEF(void) pjmedia_rtp_session_update( pjmedia_rtp_session *ses,
const pjmedia_rtp_hdr *hdr,
pjmedia_rtp_status *p_seq_st)
{
pjmedia_rtp_status seq_st;
/* Init status */
seq_st.status.value = 0;
seq_st.diff = 0;
/* Check SSRC. */
if (ses->peer_ssrc == 0) ses->peer_ssrc = pj_ntohl(hdr->ssrc);
if (pj_ntohl(hdr->ssrc) != ses->peer_ssrc) {
seq_st.status.flag.badssrc = 1;
ses->peer_ssrc = pj_ntohl(hdr->ssrc);
}
/* Check payload type. */
if (hdr->pt != ses->out_pt) {
if (p_seq_st) {
p_seq_st->status.value = seq_st.status.value;
p_seq_st->status.flag.bad = 1;
p_seq_st->status.flag.badpt = 1;
}
return;
}
/* Initialize sequence number on first packet received. */
if (ses->received == 0)
pjmedia_rtp_seq_init( &ses->seq_ctrl, pj_ntohs(hdr->seq) );
/* Check sequence number to see if remote session has been restarted. */
pjmedia_rtp_seq_update( &ses->seq_ctrl, pj_ntohs(hdr->seq), &seq_st);
if (seq_st.status.flag.restart) {
++ses->received;
} else if (!seq_st.status.flag.bad) {
++ses->received;
}
if (p_seq_st) {
p_seq_st->status.value = seq_st.status.value;
p_seq_st->diff = seq_st.diff;
}
}
void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *sess, pj_uint16_t seq)
{
sess->base_seq = seq;
sess->max_seq = seq;
sess->bad_seq = RTP_SEQ_MOD + 1;
sess->cycles = 0;
}
void pjmedia_rtp_seq_init(pjmedia_rtp_seq_session *sess, pj_uint16_t seq)
{
pjmedia_rtp_seq_restart(sess, seq);
sess->max_seq = (pj_uint16_t) (seq - 1);
sess->probation = MIN_SEQUENTIAL;
}
void pjmedia_rtp_seq_update( pjmedia_rtp_seq_session *sess,
pj_uint16_t seq,
pjmedia_rtp_status *seq_status)
{
pj_uint16_t udelta = (pj_uint16_t) (seq - sess->max_seq);
pjmedia_rtp_status st;
/* Init status */
st.status.value = 0;
st.diff = 0;
/*
* Source is not valid until MIN_SEQUENTIAL packets with
* sequential sequence numbers have been received.
*/
if (sess->probation) {
st.status.flag.probation = 1;
if (seq == sess->max_seq+ 1) {
/* packet is in sequence */
st.diff = 1;
sess->probation--;
sess->max_seq = seq;
if (sess->probation == 0) {
st.status.flag.probation = 0;
}
} else {
st.diff = 0;
st.status.flag.bad = 1;
if (seq == sess->max_seq)
st.status.flag.dup = 1;
else
st.status.flag.outorder = 1;
sess->probation = MIN_SEQUENTIAL - 1;
sess->max_seq = seq;
}
} else if (udelta == 0) {
st.status.flag.dup = 1;
} else if (udelta < MAX_DROPOUT) {
/* in order, with permissible gap */
if (seq < sess->max_seq) {
/* Sequence number wrapped - count another 64K cycle. */
sess->cycles += RTP_SEQ_MOD;
}
sess->max_seq = seq;
st.diff = udelta;
} else if (udelta <= (RTP_SEQ_MOD - MAX_MISORDER)) {
/* the sequence number made a very large jump */
if (seq == sess->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).
*/
pjmedia_rtp_seq_restart(sess, seq);
st.status.flag.restart = 1;
st.status.flag.probation = 1;
st.diff = 1;
}
else {
sess->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
st.status.flag.bad = 1;
st.status.flag.outorder = 1;
}
} else {
/* old duplicate or reordered packet.
* Not necessarily bad packet (?)
*/
st.status.flag.outorder = 1;
}
if (seq_status) {
seq_status->diff = st.diff;
seq_status->status.value = st.status.value;
}
}