summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia/rtcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia/rtcp.c')
-rw-r--r--pjmedia/src/pjmedia/rtcp.c1104
1 files changed, 1104 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/rtcp.c b/pjmedia/src/pjmedia/rtcp.c
new file mode 100644
index 0000000..f57797c
--- /dev/null
+++ b/pjmedia/src/pjmedia/rtcp.c
@@ -0,0 +1,1104 @@
+/* $Id: rtcp.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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/rtcp.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/sock.h>
+#include <pj/string.h>
+
+#define THIS_FILE "rtcp.c"
+
+#define RTCP_SR 200
+#define RTCP_RR 201
+#define RTCP_SDES 202
+#define RTCP_BYE 203
+#define RTCP_XR 207
+
+enum {
+ RTCP_SDES_NULL = 0,
+ RTCP_SDES_CNAME = 1,
+ RTCP_SDES_NAME = 2,
+ RTCP_SDES_EMAIL = 3,
+ RTCP_SDES_PHONE = 4,
+ RTCP_SDES_LOC = 5,
+ RTCP_SDES_TOOL = 6,
+ RTCP_SDES_NOTE = 7
+};
+
+#if PJ_HAS_HIGH_RES_TIMER==0
+# error "High resolution timer needs to be enabled"
+#endif
+
+
+
+#if 0
+# define TRACE_(x) PJ_LOG(3,x)
+#else
+# define TRACE_(x) ;
+#endif
+
+
+/*
+ * Get NTP time.
+ */
+PJ_DEF(pj_status_t) pjmedia_rtcp_get_ntp_time(const pjmedia_rtcp_session *sess,
+ pjmedia_rtcp_ntp_rec *ntp)
+{
+/* Seconds between 1900-01-01 to 1970-01-01 */
+#define JAN_1970 (2208988800UL)
+ pj_timestamp ts;
+ pj_status_t status;
+
+ status = pj_get_timestamp(&ts);
+
+ /* Fill up the high 32bit part */
+ ntp->hi = (pj_uint32_t)((ts.u64 - sess->ts_base.u64) / sess->ts_freq.u64)
+ + sess->tv_base.sec + JAN_1970;
+
+ /* Calculate seconds fractions */
+ ts.u64 = (ts.u64 - sess->ts_base.u64) % sess->ts_freq.u64;
+ pj_assert(ts.u64 < sess->ts_freq.u64);
+ ts.u64 = (ts.u64 << 32) / sess->ts_freq.u64;
+
+ /* Fill up the low 32bit part */
+ ntp->lo = ts.u32.lo;
+
+
+#if (defined(PJ_WIN32) && PJ_WIN32!=0) || \
+ (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0)
+
+ /* On Win32, since we use QueryPerformanceCounter() as the backend
+ * timestamp API, we need to protect against this bug:
+ * Performance counter value may unexpectedly leap forward
+ * http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323
+ */
+ {
+ /*
+ * Compare elapsed time reported by timestamp with actual elapsed
+ * time. If the difference is too excessive, then we use system
+ * time instead.
+ */
+
+ /* MIN_DIFF needs to be large enough so that "normal" diff caused
+ * by system activity or context switch doesn't trigger the time
+ * correction.
+ */
+ enum { MIN_DIFF = 400 };
+
+ pj_time_val ts_time, elapsed, diff;
+
+ pj_gettimeofday(&elapsed);
+
+ ts_time.sec = ntp->hi - sess->tv_base.sec - JAN_1970;
+ ts_time.msec = (long)(ntp->lo * 1000.0 / 0xFFFFFFFF);
+
+ PJ_TIME_VAL_SUB(elapsed, sess->tv_base);
+
+ if (PJ_TIME_VAL_LT(ts_time, elapsed)) {
+ diff = elapsed;
+ PJ_TIME_VAL_SUB(diff, ts_time);
+ } else {
+ diff = ts_time;
+ PJ_TIME_VAL_SUB(diff, elapsed);
+ }
+
+ if (PJ_TIME_VAL_MSEC(diff) >= MIN_DIFF) {
+
+ TRACE_((sess->name, "RTCP NTP timestamp corrected by %d ms",
+ PJ_TIME_VAL_MSEC(diff)));
+
+
+ ntp->hi = elapsed.sec + sess->tv_base.sec + JAN_1970;
+ ntp->lo = (elapsed.msec * 65536 / 1000) << 16;
+ }
+
+ }
+#endif
+
+ return status;
+}
+
+
+/*
+ * Initialize RTCP session setting.
+ */
+PJ_DEF(void) pjmedia_rtcp_session_setting_default(
+ pjmedia_rtcp_session_setting *settings)
+{
+ pj_bzero(settings, sizeof(*settings));
+}
+
+
+/*
+ * Initialize bidirectional RTCP statistics.
+ *
+ */
+PJ_DEF(void) pjmedia_rtcp_init_stat(pjmedia_rtcp_stat *stat)
+{
+ pj_time_val now;
+
+ pj_assert(stat);
+
+ pj_bzero(stat, sizeof(pjmedia_rtcp_stat));
+
+ pj_math_stat_init(&stat->rtt);
+ pj_math_stat_init(&stat->rx.loss_period);
+ pj_math_stat_init(&stat->rx.jitter);
+ pj_math_stat_init(&stat->tx.loss_period);
+ pj_math_stat_init(&stat->tx.jitter);
+
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+ pj_math_stat_init(&stat->rx_ipdv);
+#endif
+
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+ pj_math_stat_init(&stat->rx_raw_jitter);
+#endif
+
+ pj_gettimeofday(&now);
+ stat->start = now;
+}
+
+
+/*
+ * Initialize RTCP session.
+ */
+PJ_DEF(void) pjmedia_rtcp_init(pjmedia_rtcp_session *sess,
+ char *name,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ pj_uint32_t ssrc)
+{
+ pjmedia_rtcp_session_setting settings;
+
+ pjmedia_rtcp_session_setting_default(&settings);
+ settings.name = name;
+ settings.clock_rate = clock_rate;
+ settings.samples_per_frame = samples_per_frame;
+ settings.ssrc = ssrc;
+
+ pjmedia_rtcp_init2(sess, &settings);
+}
+
+
+/*
+ * Initialize RTCP session.
+ */
+PJ_DEF(void) pjmedia_rtcp_init2( pjmedia_rtcp_session *sess,
+ const pjmedia_rtcp_session_setting *settings)
+{
+ pjmedia_rtcp_sr_pkt *sr_pkt = &sess->rtcp_sr_pkt;
+ pj_time_val now;
+
+ /* Memset everything */
+ pj_bzero(sess, sizeof(pjmedia_rtcp_session));
+
+ /* Last RX timestamp in RTP packet */
+ sess->rtp_last_ts = (unsigned)-1;
+
+ /* Name */
+ sess->name = settings->name ? settings->name : (char*)THIS_FILE;
+
+ /* Set clock rate */
+ sess->clock_rate = settings->clock_rate;
+ sess->pkt_size = settings->samples_per_frame;
+
+ /* Init common RTCP SR header */
+ sr_pkt->common.version = 2;
+ sr_pkt->common.count = 1;
+ sr_pkt->common.pt = RTCP_SR;
+ sr_pkt->common.length = pj_htons(12);
+ sr_pkt->common.ssrc = pj_htonl(settings->ssrc);
+
+ /* Copy to RTCP RR header */
+ pj_memcpy(&sess->rtcp_rr_pkt.common, &sr_pkt->common,
+ sizeof(pjmedia_rtcp_common));
+ sess->rtcp_rr_pkt.common.pt = RTCP_RR;
+ sess->rtcp_rr_pkt.common.length = pj_htons(7);
+
+ /* Get time and timestamp base and frequency */
+ pj_gettimeofday(&now);
+ sess->tv_base = now;
+ pj_get_timestamp(&sess->ts_base);
+ pj_get_timestamp_freq(&sess->ts_freq);
+ sess->rtp_ts_base = settings->rtp_ts_base;
+
+ /* Initialize statistics states */
+ pjmedia_rtcp_init_stat(&sess->stat);
+
+ /* RR will be initialized on receipt of the first RTP packet. */
+}
+
+
+PJ_DEF(void) pjmedia_rtcp_fini(pjmedia_rtcp_session *sess)
+{
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ pjmedia_rtcp_xr_fini(&sess->xr_session);
+#else
+ /* Nothing to do. */
+ PJ_UNUSED_ARG(sess);
+#endif
+}
+
+static void rtcp_init_seq(pjmedia_rtcp_session *sess)
+{
+ sess->received = 0;
+ sess->exp_prior = 0;
+ sess->rx_prior = 0;
+ sess->transit = 0;
+ sess->jitter = 0;
+}
+
+PJ_DEF(void) pjmedia_rtcp_rx_rtp( pjmedia_rtcp_session *sess,
+ unsigned seq,
+ unsigned rtp_ts,
+ unsigned payload)
+{
+ pjmedia_rtcp_rx_rtp2(sess, seq, rtp_ts, payload, PJ_FALSE);
+}
+
+PJ_DEF(void) pjmedia_rtcp_rx_rtp2(pjmedia_rtcp_session *sess,
+ unsigned seq,
+ unsigned rtp_ts,
+ unsigned payload,
+ pj_bool_t discarded)
+{
+ pj_timestamp ts;
+ pj_uint32_t arrival;
+ pj_int32_t transit;
+ pjmedia_rtp_status seq_st;
+ unsigned last_seq;
+
+#if !defined(PJMEDIA_HAS_RTCP_XR) || (PJMEDIA_HAS_RTCP_XR == 0)
+ PJ_UNUSED_ARG(discarded);
+#endif
+
+ if (sess->stat.rx.pkt == 0) {
+ /* Init sequence for the first time. */
+ pjmedia_rtp_seq_init(&sess->seq_ctrl, (pj_uint16_t)seq);
+ }
+
+ sess->stat.rx.pkt++;
+ sess->stat.rx.bytes += payload;
+
+ /* Process the RTP packet. */
+ last_seq = sess->seq_ctrl.max_seq;
+ pjmedia_rtp_seq_update(&sess->seq_ctrl, (pj_uint16_t)seq, &seq_st);
+
+ if (seq_st.status.flag.restart) {
+ rtcp_init_seq(sess);
+ }
+
+ if (seq_st.status.flag.dup) {
+ sess->stat.rx.dup++;
+ TRACE_((sess->name, "Duplicate packet detected"));
+ }
+
+ if (seq_st.status.flag.outorder && !seq_st.status.flag.probation) {
+ sess->stat.rx.reorder++;
+ TRACE_((sess->name, "Out-of-order packet detected"));
+ }
+
+ if (seq_st.status.flag.bad) {
+ sess->stat.rx.discard++;
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq,
+ -1, /* lost */
+ (seq_st.status.flag.dup? 1:0), /* dup */
+ (!seq_st.status.flag.dup? 1:-1), /* discard */
+ -1, /* jitter */
+ -1, 0); /* toh */
+#endif
+
+ TRACE_((sess->name, "Bad packet discarded"));
+ return;
+ }
+
+ /* Only mark "good" packets */
+ ++sess->received;
+
+ /* Calculate loss periods. */
+ if (seq_st.diff > 1) {
+ unsigned count = seq_st.diff - 1;
+ unsigned period;
+
+ period = count * sess->pkt_size * 1000 / sess->clock_rate;
+ period *= 1000;
+
+ /* Update packet lost.
+ * The packet lost number will also be updated when we're sending
+ * outbound RTCP RR.
+ */
+ sess->stat.rx.loss += (seq_st.diff - 1);
+ TRACE_((sess->name, "%d packet(s) lost", seq_st.diff - 1));
+
+ /* Update loss period stat */
+ pj_math_stat_update(&sess->stat.rx.loss_period, period);
+ }
+
+
+ /*
+ * Calculate jitter only when sequence is good (see RFC 3550 section A.8),
+ * AND only when the timestamp is different than the last packet
+ * (see RTP FAQ).
+ */
+ if (seq_st.diff == 1 && rtp_ts != sess->rtp_last_ts) {
+ /* Get arrival time and convert timestamp to samples */
+ pj_get_timestamp(&ts);
+ ts.u64 = ts.u64 * sess->clock_rate / sess->ts_freq.u64;
+ arrival = ts.u32.lo;
+
+ transit = arrival - rtp_ts;
+
+ /* Ignore the first N packets as they normally have bad jitter
+ * due to other threads working to establish the call
+ */
+ if (sess->transit == 0 ||
+ sess->received < PJMEDIA_RTCP_IGNORE_FIRST_PACKETS)
+ {
+ sess->transit = transit;
+ sess->stat.rx.jitter.min = (unsigned)-1;
+ } else {
+ pj_int32_t d;
+ pj_uint32_t jitter;
+
+ d = transit - sess->transit;
+ if (d < 0)
+ d = -d;
+
+ sess->jitter += d - ((sess->jitter + 8) >> 4);
+
+ /* Update jitter stat */
+ jitter = sess->jitter >> 4;
+
+ /* Convert jitter unit from samples to usec */
+ if (jitter < 4294)
+ jitter = jitter * 1000000 / sess->clock_rate;
+ else {
+ jitter = jitter * 1000 / sess->clock_rate;
+ jitter *= 1000;
+ }
+ pj_math_stat_update(&sess->stat.rx.jitter, jitter);
+
+
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+ {
+ pj_uint32_t raw_jitter;
+
+ /* Convert raw jitter unit from samples to usec */
+ if (d < 4294)
+ raw_jitter = d * 1000000 / sess->clock_rate;
+ else {
+ raw_jitter = d * 1000 / sess->clock_rate;
+ raw_jitter *= 1000;
+ }
+
+ /* Update jitter stat */
+ pj_math_stat_update(&sess->stat.rx_raw_jitter, raw_jitter);
+ }
+#endif
+
+
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+ {
+ pj_int32_t ipdv;
+
+ ipdv = transit - sess->transit;
+ /* Convert IPDV unit from samples to usec */
+ if (ipdv > -2147 && ipdv < 2147)
+ ipdv = ipdv * 1000000 / (int)sess->clock_rate;
+ else {
+ ipdv = ipdv * 1000 / (int)sess->clock_rate;
+ ipdv *= 1000;
+ }
+
+ /* Update jitter stat */
+ pj_math_stat_update(&sess->stat.rx_ipdv, ipdv);
+ }
+#endif
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq,
+ 0, /* lost */
+ 0, /* dup */
+ discarded, /* discard */
+ (sess->jitter >> 4), /* jitter */
+ -1, 0); /* toh */
+#endif
+
+ /* Update session transit */
+ sess->transit = transit;
+ }
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ } else if (seq_st.diff > 1) {
+ int i;
+
+ /* Report RTCP XR about packet losses */
+ for (i=seq_st.diff-1; i>0; --i) {
+ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq - i,
+ 1, /* lost */
+ 0, /* dup */
+ 0, /* discard */
+ -1, /* jitter */
+ -1, 0); /* toh */
+ }
+
+ /* Report RTCP XR this packet */
+ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq,
+ 0, /* lost */
+ 0, /* dup */
+ discarded, /* discard */
+ -1, /* jitter */
+ -1, 0); /* toh */
+#endif
+ }
+
+ /* Update timestamp of last RX RTP packet */
+ sess->rtp_last_ts = rtp_ts;
+}
+
+PJ_DEF(void) pjmedia_rtcp_tx_rtp(pjmedia_rtcp_session *sess,
+ unsigned bytes_payload_size)
+{
+ /* Update statistics */
+ sess->stat.tx.pkt++;
+ sess->stat.tx.bytes += bytes_payload_size;
+}
+
+
+static void parse_rtcp_report( pjmedia_rtcp_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ pjmedia_rtcp_common *common = (pjmedia_rtcp_common*) pkt;
+ const pjmedia_rtcp_rr *rr = NULL;
+ const pjmedia_rtcp_sr *sr = NULL;
+ pj_uint32_t last_loss, jitter_samp, jitter;
+
+ /* Parse RTCP */
+ if (common->pt == RTCP_SR) {
+ sr = (pjmedia_rtcp_sr*) (((char*)pkt) + sizeof(pjmedia_rtcp_common));
+ if (common->count > 0 && size >= (sizeof(pjmedia_rtcp_sr_pkt))) {
+ rr = (pjmedia_rtcp_rr*)(((char*)pkt) + (sizeof(pjmedia_rtcp_common)
+ + sizeof(pjmedia_rtcp_sr)));
+ }
+ } else if (common->pt == RTCP_RR && common->count > 0) {
+ rr = (pjmedia_rtcp_rr*)(((char*)pkt) + sizeof(pjmedia_rtcp_common));
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ } else if (common->pt == RTCP_XR) {
+ if (sess->xr_enabled)
+ pjmedia_rtcp_xr_rx_rtcp_xr(&sess->xr_session, pkt, size);
+
+ return;
+#endif
+ }
+
+
+ if (sr) {
+ /* Save LSR from NTP timestamp of RTCP packet */
+ sess->rx_lsr = ((pj_ntohl(sr->ntp_sec) & 0x0000FFFF) << 16) |
+ ((pj_ntohl(sr->ntp_frac) >> 16) & 0xFFFF);
+
+ /* Calculate SR arrival time for DLSR */
+ pj_get_timestamp(&sess->rx_lsr_time);
+
+ TRACE_((sess->name, "Rx RTCP SR: ntp_ts=%p",
+ sess->rx_lsr,
+ (pj_uint32_t)(sess->rx_lsr_time.u64*65536/sess->ts_freq.u64)));
+ }
+
+
+ /* Nothing more to do if there's no RR packet */
+ if (rr == NULL)
+ return;
+
+
+ last_loss = sess->stat.tx.loss;
+
+ /* Get packet loss */
+ sess->stat.tx.loss = (rr->total_lost_2 << 16) +
+ (rr->total_lost_1 << 8) +
+ rr->total_lost_0;
+
+ TRACE_((sess->name, "Rx RTCP RR: total_lost_2=%x, 1=%x, 0=%x, lost=%d",
+ (int)rr->total_lost_2,
+ (int)rr->total_lost_1,
+ (int)rr->total_lost_0,
+ sess->stat.tx.loss));
+
+ /* We can't calculate the exact loss period for TX, so just give the
+ * best estimation.
+ */
+ if (sess->stat.tx.loss > last_loss) {
+ unsigned period;
+
+ /* Loss period in msec */
+ period = (sess->stat.tx.loss - last_loss) * sess->pkt_size *
+ 1000 / sess->clock_rate;
+
+ /* Loss period in usec */
+ period *= 1000;
+
+ /* Update loss period stat */
+ pj_math_stat_update(&sess->stat.tx.loss_period, period);
+ }
+
+ /* Get jitter value in usec */
+ jitter_samp = pj_ntohl(rr->jitter);
+ /* Calculate jitter in usec, avoiding overflows */
+ if (jitter_samp <= 4294)
+ jitter = jitter_samp * 1000000 / sess->clock_rate;
+ else {
+ jitter = jitter_samp * 1000 / sess->clock_rate;
+ jitter *= 1000;
+ }
+
+ /* Update jitter statistics */
+ pj_math_stat_update(&sess->stat.tx.jitter, jitter);
+
+ /* Can only calculate if LSR and DLSR is present in RR */
+ if (rr->lsr && rr->dlsr) {
+ pj_uint32_t lsr, now, dlsr;
+ pj_uint64_t eedelay;
+ pjmedia_rtcp_ntp_rec ntp;
+
+ /* LSR is the middle 32bit of NTP. It has 1/65536 second
+ * resolution
+ */
+ lsr = pj_ntohl(rr->lsr);
+
+ /* DLSR is delay since LSR, also in 1/65536 resolution */
+ dlsr = pj_ntohl(rr->dlsr);
+
+ /* Get current time, and convert to 1/65536 resolution */
+ pjmedia_rtcp_get_ntp_time(sess, &ntp);
+ now = ((ntp.hi & 0xFFFF) << 16) + (ntp.lo >> 16);
+
+ /* End-to-end delay is (now-lsr-dlsr) */
+ eedelay = now - lsr - dlsr;
+
+ /* Convert end to end delay to usec (keeping the calculation in
+ * 64bit space)::
+ * sess->ee_delay = (eedelay * 1000) / 65536;
+ */
+ if (eedelay < 4294) {
+ eedelay = (eedelay * 1000000) >> 16;
+ } else {
+ eedelay = (eedelay * 1000) >> 16;
+ eedelay *= 1000;
+ }
+
+ TRACE_((sess->name, "Rx RTCP RR: lsr=%p, dlsr=%p (%d:%03dms), "
+ "now=%p, rtt=%p",
+ lsr, dlsr, dlsr/65536, (dlsr%65536)*1000/65536,
+ now, (pj_uint32_t)eedelay));
+
+ /* Only save calculation if "now" is greater than lsr, or
+ * otherwise rtt will be invalid
+ */
+ if (now-dlsr >= lsr) {
+ unsigned rtt = (pj_uint32_t)eedelay;
+
+ /* Check that eedelay value really makes sense.
+ * We allow up to 30 seconds RTT!
+ */
+ if (eedelay > 30 * 1000 * 1000UL) {
+
+ TRACE_((sess->name, "RTT not making any sense, ignored.."));
+ goto end_rtt_calc;
+ }
+
+#if defined(PJMEDIA_RTCP_NORMALIZE_FACTOR) && PJMEDIA_RTCP_NORMALIZE_FACTOR!=0
+ /* "Normalize" rtt value that is exceptionally high. For such
+ * values, "normalize" the rtt to be PJMEDIA_RTCP_NORMALIZE_FACTOR
+ * times the average value.
+ */
+ if (rtt > ((unsigned)sess->stat.rtt.mean *
+ PJMEDIA_RTCP_NORMALIZE_FACTOR) && sess->stat.rtt.n!=0)
+ {
+ unsigned orig_rtt = rtt;
+ rtt = sess->stat.rtt.mean * PJMEDIA_RTCP_NORMALIZE_FACTOR;
+ PJ_LOG(5,(sess->name,
+ "RTT value %d usec is normalized to %d usec",
+ orig_rtt, rtt));
+ }
+#endif
+ TRACE_((sess->name, "RTCP RTT is set to %d usec", rtt));
+
+ /* Update RTT stat */
+ pj_math_stat_update(&sess->stat.rtt, rtt);
+
+ } else {
+ PJ_LOG(5, (sess->name, "Internal RTCP NTP clock skew detected: "
+ "lsr=%p, now=%p, dlsr=%p (%d:%03dms), "
+ "diff=%d",
+ lsr, now, dlsr, dlsr/65536,
+ (dlsr%65536)*1000/65536,
+ dlsr-(now-lsr)));
+ }
+ }
+
+end_rtt_calc:
+
+ pj_gettimeofday(&sess->stat.tx.update);
+ sess->stat.tx.update_cnt++;
+}
+
+
+static void parse_rtcp_sdes(pjmedia_rtcp_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ pjmedia_rtcp_sdes *sdes = &sess->stat.peer_sdes;
+ char *p, *p_end;
+ char *b, *b_end;
+
+ p = (char*)pkt + 8;
+ p_end = (char*)pkt + size;
+
+ pj_bzero(sdes, sizeof(*sdes));
+ b = sess->stat.peer_sdes_buf_;
+ b_end = b + sizeof(sess->stat.peer_sdes_buf_);
+
+ while (p < p_end) {
+ pj_uint8_t sdes_type, sdes_len;
+ pj_str_t sdes_value = {NULL, 0};
+
+ sdes_type = *p++;
+
+ /* Check for end of SDES item list */
+ if (sdes_type == RTCP_SDES_NULL || p == p_end)
+ break;
+
+ sdes_len = *p++;
+
+ /* Check for corrupted SDES packet */
+ if (p + sdes_len > p_end)
+ break;
+
+ /* Get SDES item */
+ if (b + sdes_len < b_end) {
+ pj_memcpy(b, p, sdes_len);
+ sdes_value.ptr = b;
+ sdes_value.slen = sdes_len;
+ b += sdes_len;
+ } else {
+ /* Insufficient SDES buffer */
+ PJ_LOG(5, (sess->name,
+ "Unsufficient buffer to save RTCP SDES type %d:%.*s",
+ sdes_type, sdes_len, p));
+ p += sdes_len;
+ continue;
+ }
+
+ switch (sdes_type) {
+ case RTCP_SDES_CNAME:
+ sdes->cname = sdes_value;
+ break;
+ case RTCP_SDES_NAME:
+ sdes->name = sdes_value;
+ break;
+ case RTCP_SDES_EMAIL:
+ sdes->email = sdes_value;
+ break;
+ case RTCP_SDES_PHONE:
+ sdes->phone = sdes_value;
+ break;
+ case RTCP_SDES_LOC:
+ sdes->loc = sdes_value;
+ break;
+ case RTCP_SDES_TOOL:
+ sdes->tool = sdes_value;
+ break;
+ case RTCP_SDES_NOTE:
+ sdes->note = sdes_value;
+ break;
+ default:
+ TRACE_((sess->name, "Received unknown RTCP SDES type %d:%.*s",
+ sdes_type, sdes_value.slen, sdes_value.ptr));
+ break;
+ }
+
+ p += sdes_len;
+ }
+}
+
+
+static void parse_rtcp_bye(pjmedia_rtcp_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ pj_str_t reason = {"-", 1};
+
+ /* Check and get BYE reason */
+ if (size > 8) {
+ reason.slen = *((pj_uint8_t*)pkt+8);
+ pj_memcpy(sess->stat.peer_sdes_buf_, ((pj_uint8_t*)pkt+9),
+ reason.slen);
+ reason.ptr = sess->stat.peer_sdes_buf_;
+ }
+
+ /* Just print RTCP BYE log */
+ PJ_LOG(5, (sess->name, "Received RTCP BYE, reason: %.*s",
+ reason.slen, reason.ptr));
+}
+
+
+PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ pj_uint8_t *p, *p_end;
+
+ p = (pj_uint8_t*)pkt;
+ p_end = p + size;
+ while (p < p_end) {
+ pjmedia_rtcp_common *common = (pjmedia_rtcp_common*)p;
+ unsigned len;
+
+ len = (pj_ntohs((pj_uint16_t)common->length)+1) * 4;
+ switch(common->pt) {
+ case RTCP_SR:
+ case RTCP_RR:
+ case RTCP_XR:
+ parse_rtcp_report(sess, p, len);
+ break;
+ case RTCP_SDES:
+ parse_rtcp_sdes(sess, p, len);
+ break;
+ case RTCP_BYE:
+ parse_rtcp_bye(sess, p, len);
+ break;
+ default:
+ /* Ignore unknown RTCP */
+ TRACE_((sess->name, "Received unknown RTCP packet type=%d",
+ common->pt));
+ break;
+ }
+
+ p += len;
+ }
+}
+
+
+PJ_DEF(void) pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *sess,
+ void **ret_p_pkt, int *len)
+{
+ pj_uint32_t expected, expected_interval, received_interval, lost_interval;
+ pjmedia_rtcp_common *common;
+ pjmedia_rtcp_sr *sr;
+ pjmedia_rtcp_rr *rr;
+ pj_timestamp ts_now;
+ pjmedia_rtcp_ntp_rec ntp;
+
+ /* Get current NTP time. */
+ pj_get_timestamp(&ts_now);
+ pjmedia_rtcp_get_ntp_time(sess, &ntp);
+
+
+ /* See if we have transmitted RTP packets since last time we
+ * sent RTCP SR.
+ */
+ if (sess->stat.tx.pkt != pj_ntohl(sess->rtcp_sr_pkt.sr.sender_pcount)) {
+ pj_time_val ts_time;
+ pj_uint32_t rtp_ts;
+
+ /* So we should send RTCP SR */
+ *ret_p_pkt = (void*) &sess->rtcp_sr_pkt;
+ *len = sizeof(pjmedia_rtcp_sr_pkt);
+ common = &sess->rtcp_sr_pkt.common;
+ rr = &sess->rtcp_sr_pkt.rr;
+ sr = &sess->rtcp_sr_pkt.sr;
+
+ /* Update packet count */
+ sr->sender_pcount = pj_htonl(sess->stat.tx.pkt);
+
+ /* Update octets count */
+ sr->sender_bcount = pj_htonl(sess->stat.tx.bytes);
+
+ /* Fill in NTP timestamp in SR. */
+ sr->ntp_sec = pj_htonl(ntp.hi);
+ sr->ntp_frac = pj_htonl(ntp.lo);
+
+ /* Fill in RTP timestamp (corresponds to NTP timestamp) in SR. */
+ ts_time.sec = ntp.hi - sess->tv_base.sec - JAN_1970;
+ ts_time.msec = (long)(ntp.lo * 1000.0 / 0xFFFFFFFF);
+ rtp_ts = sess->rtp_ts_base +
+ (pj_uint32_t)(sess->clock_rate*ts_time.sec) +
+ (pj_uint32_t)(sess->clock_rate*ts_time.msec/1000);
+ sr->rtp_ts = pj_htonl(rtp_ts);
+
+ TRACE_((sess->name, "TX RTCP SR: ntp_ts=%p",
+ ((ntp.hi & 0xFFFF) << 16) + ((ntp.lo & 0xFFFF0000)
+ >> 16)));
+
+
+ } else {
+ /* We should send RTCP RR then */
+ *ret_p_pkt = (void*) &sess->rtcp_rr_pkt;
+ *len = sizeof(pjmedia_rtcp_rr_pkt);
+ common = &sess->rtcp_rr_pkt.common;
+ rr = &sess->rtcp_rr_pkt.rr;
+ sr = NULL;
+ }
+
+ /* SSRC and last_seq */
+ rr->ssrc = pj_htonl(sess->peer_ssrc);
+ rr->last_seq = (sess->seq_ctrl.cycles & 0xFFFF0000L);
+ /* Since this is an "+=" operation, make sure we update last_seq on
+ * both RR and SR.
+ */
+ sess->rtcp_sr_pkt.rr.last_seq += sess->seq_ctrl.max_seq;
+ sess->rtcp_rr_pkt.rr.last_seq += sess->seq_ctrl.max_seq;
+ rr->last_seq = pj_htonl(rr->last_seq);
+
+
+ /* Jitter */
+ rr->jitter = pj_htonl(sess->jitter >> 4);
+
+
+ /* Total lost. */
+ expected = pj_ntohl(rr->last_seq) - sess->seq_ctrl.base_seq;
+
+ /* This is bug: total lost already calculated on each incoming RTP!
+ if (expected >= sess->received)
+ sess->stat.rx.loss = expected - sess->received;
+ else
+ sess->stat.rx.loss = 0;
+ */
+
+ rr->total_lost_2 = (sess->stat.rx.loss >> 16) & 0xFF;
+ rr->total_lost_1 = (sess->stat.rx.loss >> 8) & 0xFF;
+ rr->total_lost_0 = (sess->stat.rx.loss & 0xFF);
+
+ /* Fraction lost calculation */
+ expected_interval = expected - sess->exp_prior;
+ sess->exp_prior = expected;
+
+ received_interval = sess->received - sess->rx_prior;
+ sess->rx_prior = sess->received;
+
+ if (expected_interval >= received_interval)
+ lost_interval = expected_interval - received_interval;
+ else
+ lost_interval = 0;
+
+ if (expected_interval==0 || lost_interval == 0) {
+ rr->fract_lost = 0;
+ } else {
+ rr->fract_lost = (lost_interval << 8) / expected_interval;
+ }
+
+ if (sess->rx_lsr_time.u64 == 0 || sess->rx_lsr == 0) {
+ rr->lsr = 0;
+ rr->dlsr = 0;
+ } else {
+ pj_timestamp ts;
+ pj_uint32_t lsr = sess->rx_lsr;
+ pj_uint64_t lsr_time = sess->rx_lsr_time.u64;
+ pj_uint32_t dlsr;
+
+ /* Convert LSR time to 1/65536 seconds resolution */
+ lsr_time = (lsr_time << 16) / sess->ts_freq.u64;
+
+ /* Fill in LSR.
+ LSR is the middle 32bit of the last SR NTP time received.
+ */
+ rr->lsr = pj_htonl(lsr);
+
+ /* Fill in DLSR.
+ DLSR is Delay since Last SR, in 1/65536 seconds.
+ */
+ ts.u64 = ts_now.u64;
+
+ /* Convert interval to 1/65536 seconds value */
+ ts.u64 = (ts.u64 << 16) / sess->ts_freq.u64;
+
+ /* Get DLSR */
+ dlsr = (pj_uint32_t)(ts.u64 - lsr_time);
+ rr->dlsr = pj_htonl(dlsr);
+
+ TRACE_((sess->name,"Tx RTCP RR: lsr=%p, lsr_time=%p, now=%p, dlsr=%p"
+ "(%ds:%03dms)",
+ lsr,
+ (pj_uint32_t)lsr_time,
+ (pj_uint32_t)ts.u64,
+ dlsr,
+ dlsr/65536,
+ (dlsr%65536)*1000/65536 ));
+ }
+
+ /* Update counter */
+ pj_gettimeofday(&sess->stat.rx.update);
+ sess->stat.rx.update_cnt++;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_sdes(
+ pjmedia_rtcp_session *session,
+ void *buf,
+ pj_size_t *length,
+ const pjmedia_rtcp_sdes *sdes)
+{
+ pjmedia_rtcp_common *hdr;
+ pj_uint8_t *p;
+ unsigned len;
+
+ PJ_ASSERT_RETURN(session && buf && length && sdes, PJ_EINVAL);
+
+ /* Verify SDES item length */
+ if (sdes->cname.slen > 255 || sdes->name.slen > 255 ||
+ sdes->email.slen > 255 || sdes->phone.slen > 255 ||
+ sdes->loc.slen > 255 || sdes->tool.slen > 255 ||
+ sdes->note.slen > 255)
+ {
+ return PJ_EINVAL;
+ }
+
+ /* Verify buffer length */
+ len = sizeof(*hdr);
+ if (sdes->cname.slen) len += sdes->cname.slen + 2;
+ if (sdes->name.slen) len += sdes->name.slen + 2;
+ if (sdes->email.slen) len += sdes->email.slen + 2;
+ if (sdes->phone.slen) len += sdes->phone.slen + 2;
+ if (sdes->loc.slen) len += sdes->loc.slen + 2;
+ if (sdes->tool.slen) len += sdes->tool.slen + 2;
+ if (sdes->note.slen) len += sdes->note.slen + 2;
+ len++; /* null termination */
+ len = ((len+3)/4) * 4;
+ if (len > *length)
+ return PJ_ETOOSMALL;
+
+ /* Build RTCP SDES header */
+ hdr = (pjmedia_rtcp_common*)buf;
+ pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr));
+ hdr->pt = RTCP_SDES;
+ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));
+
+ /* Build RTCP SDES items */
+ p = (pj_uint8_t*)hdr + sizeof(*hdr);
+#define BUILD_SDES_ITEM(SDES_NAME, SDES_TYPE) \
+ if (sdes->SDES_NAME.slen) { \
+ *p++ = SDES_TYPE; \
+ *p++ = (pj_uint8_t)sdes->SDES_NAME.slen; \
+ pj_memcpy(p, sdes->SDES_NAME.ptr, sdes->SDES_NAME.slen); \
+ p += sdes->SDES_NAME.slen; \
+ }
+ BUILD_SDES_ITEM(cname, RTCP_SDES_CNAME);
+ BUILD_SDES_ITEM(name, RTCP_SDES_NAME);
+ BUILD_SDES_ITEM(email, RTCP_SDES_EMAIL);
+ BUILD_SDES_ITEM(phone, RTCP_SDES_PHONE);
+ BUILD_SDES_ITEM(loc, RTCP_SDES_LOC);
+ BUILD_SDES_ITEM(tool, RTCP_SDES_TOOL);
+ BUILD_SDES_ITEM(note, RTCP_SDES_NOTE);
+#undef BUILD_SDES_ITEM
+
+ /* Null termination */
+ *p++ = 0;
+
+ /* Pad to 32bit */
+ while ((p-(pj_uint8_t*)buf) % 4)
+ *p++ = 0;
+
+ /* Finally */
+ pj_assert((int)len == p-(pj_uint8_t*)buf);
+ *length = len;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_bye(pjmedia_rtcp_session *session,
+ void *buf,
+ pj_size_t *length,
+ const pj_str_t *reason)
+{
+ pjmedia_rtcp_common *hdr;
+ pj_uint8_t *p;
+ unsigned len;
+
+ PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL);
+
+ /* Verify BYE reason length */
+ if (reason && reason->slen > 255)
+ return PJ_EINVAL;
+
+ /* Verify buffer length */
+ len = sizeof(*hdr);
+ if (reason && reason->slen) len += reason->slen + 1;
+ len = ((len+3)/4) * 4;
+ if (len > *length)
+ return PJ_ETOOSMALL;
+
+ /* Build RTCP BYE header */
+ hdr = (pjmedia_rtcp_common*)buf;
+ pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr));
+ hdr->pt = RTCP_BYE;
+ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));
+
+ /* Write RTCP BYE reason */
+ p = (pj_uint8_t*)hdr + sizeof(*hdr);
+ if (reason && reason->slen) {
+ *p++ = (pj_uint8_t)reason->slen;
+ pj_memcpy(p, reason->ptr, reason->slen);
+ p += reason->slen;
+ }
+
+ /* Pad to 32bit */
+ while ((p-(pj_uint8_t*)buf) % 4)
+ *p++ = 0;
+
+ pj_assert((int)len == p-(pj_uint8_t*)buf);
+ *length = len;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_rtcp_enable_xr( pjmedia_rtcp_session *sess,
+ pj_bool_t enable)
+{
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+
+ /* Check if request won't change anything */
+ if (!(enable ^ sess->xr_enabled))
+ return PJ_SUCCESS;
+
+ if (!enable) {
+ sess->xr_enabled = PJ_FALSE;
+ return PJ_SUCCESS;
+ }
+
+ pjmedia_rtcp_xr_init(&sess->xr_session, sess, 0, 1);
+ sess->xr_enabled = PJ_TRUE;
+
+ return PJ_SUCCESS;
+
+#else
+
+ PJ_UNUSED_ARG(sess);
+ PJ_UNUSED_ARG(enable);
+ return PJ_ENOTSUP;
+
+#endif
+}