diff options
author | Benny Prijono <bennylp@teluu.com> | 2008-04-28 18:05:49 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2008-04-28 18:05:49 +0000 |
commit | 97f581ac239ab2ef3689392d20ea705e5fde7a3a (patch) | |
tree | caa2f956030424ff54624fc666a9d00765d8d68f /pjmedia/src/pjmedia/rtcp_xr.c | |
parent | 1caf198b62ac211e7ed60b64dc9304e409f74a6e (diff) |
Ticket #513: Support for RTCP XR (initial patch)
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1942 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src/pjmedia/rtcp_xr.c')
-rw-r--r-- | pjmedia/src/pjmedia/rtcp_xr.c | 866 |
1 files changed, 866 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/rtcp_xr.c b/pjmedia/src/pjmedia/rtcp_xr.c new file mode 100644 index 00000000..f8d53896 --- /dev/null +++ b/pjmedia/src/pjmedia/rtcp_xr.c @@ -0,0 +1,866 @@ +/* $Id$ */ +/* + * 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_xr.h> +#include <pjmedia/errno.h> +#include <pjmedia/rtcp.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/sock.h> +#include <pj/string.h> + +#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) + +#define THIS_FILE "rtcp_xr.c" + + +#if PJ_HAS_HIGH_RES_TIMER==0 +# error "High resolution timer needs to be enabled" +#endif + + +/* RTCP XR payload type */ +#define RTCP_XR 207 + +/* RTCP XR block types */ +#define BT_LOSS_RLE 1 +#define BT_DUP_RLE 2 +#define BT_RCPT_TIMES 3 +#define BT_RR_TIME 4 +#define BT_DLRR 5 +#define BT_STATS 6 +#define BT_VOIP_METRICS 7 + + +#define DEFAULT_GMIN 16 + + +#if 0 +# define TRACE_(x) PJ_LOG(3,x) +#else +# define TRACE_(x) ; +#endif + +/* Integer square root for calculating standard deviation */ +static pj_uint32_t my_isqrt(pj_uint32_t i) +{ + pj_uint32_t res = 1, prev; + + /* Rough guess */ + prev = i >> 2; + while (prev) { + prev >>= 2; + res <<= 1; + } + + /* Babilonian method */ + do { + prev = res; + res = (prev + i/prev) >> 1; + } while ((prev+res)>>1 != res); + + return res; +} + +void pjmedia_rtcp_xr_init( pjmedia_rtcp_xr_session *session, + struct pjmedia_rtcp_session *parent_session, + pj_uint8_t gmin, + unsigned frames_per_packet) +{ + pj_bzero(session, sizeof(pjmedia_rtcp_xr_session)); + + session->rtcp_session = parent_session; + pj_memcpy(&session->pkt.common, &session->rtcp_session->rtcp_sr_pkt.common, + sizeof(pjmedia_rtcp_common)); + session->pkt.common.pt = RTCP_XR; + + /* Init config */ + session->stat.tx.voip_mtc.gmin = (pj_uint8_t)(gmin? gmin : DEFAULT_GMIN); + session->ptime = session->rtcp_session->pkt_size * 1000 / + session->rtcp_session->clock_rate; + session->frames_per_packet = frames_per_packet; + + /* Init Statistics Summary fields which have non-zero default */ + session->stat.tx.stat_sum.jitter.min = (unsigned) -1; + session->stat.tx.stat_sum.toh.min = (unsigned) -1; + + /* Init VoIP Metrics fields which have non-zero default */ + session->stat.tx.voip_mtc.signal_lvl = 127; + session->stat.tx.voip_mtc.noise_lvl = 127; + session->stat.tx.voip_mtc.rerl = 127; + session->stat.tx.voip_mtc.r_factor = 127; + session->stat.tx.voip_mtc.ext_r_factor = 127; + session->stat.tx.voip_mtc.mos_lq = 127; + session->stat.tx.voip_mtc.mos_cq = 127; +} + +void pjmedia_rtcp_xr_fini(pjmedia_rtcp_xr_session *session) +{ + PJ_UNUSED_ARG(session); +} + +PJ_DEF(void) pjmedia_rtcp_build_rtcp_xr( pjmedia_rtcp_xr_session *sess, + unsigned rpt_types, + void **rtcp_pkt, int *len) +{ + pj_uint16_t size = 0; + + /* Receiver Reference Time Report Block */ + /* Build this block if we have received packets since last build */ + if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_RR_TIME)) && + sess->rx_last_rr != sess->rtcp_session->stat.rx.pkt) + { + pjmedia_rtcp_xr_rb_rr_time *r; + pjmedia_rtcp_ntp_rec ntp; + + r = (pjmedia_rtcp_xr_rb_rr_time*) &sess->pkt.buf[size]; + pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_rr_time)); + + /* Init block header */ + r->header.bt = BT_RR_TIME; + r->header.specific = 0; + r->header.length = pj_htons(2); + + /* Generate block contents */ + pjmedia_rtcp_get_ntp_time(sess->rtcp_session, &ntp); + r->ntp_sec = pj_htonl(ntp.hi); + r->ntp_frac = pj_htonl(ntp.lo); + + /* Finally */ + size += sizeof(pjmedia_rtcp_xr_rb_rr_time); + sess->rx_last_rr = sess->rtcp_session->stat.rx.pkt; + } + + /* DLRR Report Block */ + /* Build this block if we have received RR NTP (rx_lrr) before */ + if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_DLRR)) && + sess->rx_lrr) + { + pjmedia_rtcp_xr_rb_dlrr *r; + pjmedia_rtcp_xr_rb_dlrr_item *dlrr_item; + pj_timestamp ts; + + r = (pjmedia_rtcp_xr_rb_dlrr*) &sess->pkt.buf[size]; + pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_dlrr)); + + /* Init block header */ + r->header.bt = BT_DLRR; + r->header.specific = 0; + r->header.length = pj_htons(sizeof(pjmedia_rtcp_xr_rb_dlrr)/4 - 1); + + /* Generate block contents */ + dlrr_item = &r->item; + dlrr_item->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc); + dlrr_item->lrr = pj_htonl(sess->rx_lrr); + + /* Calculate DLRR */ + if (sess->rx_lrr != 0) { + pj_get_timestamp(&ts); + ts.u64 -= sess->rx_lrr_time.u64; + + /* Convert DLRR time to 1/65536 seconds resolution */ + ts.u64 = (ts.u64 << 16) / sess->rtcp_session->ts_freq.u64; + dlrr_item->dlrr = pj_htonl(ts.u32.lo); + } else { + dlrr_item->dlrr = 0; + } + + /* Finally */ + size += sizeof(pjmedia_rtcp_xr_rb_dlrr); + } + + /* Statistics Summary Block */ + /* Build this block if we have received packets since last build */ + if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_STATS)) && + sess->stat.tx.stat_sum.count > 0) + { + pjmedia_rtcp_xr_rb_stats *r; + pj_uint8_t specific = 0; + + r = (pjmedia_rtcp_xr_rb_stats*) &sess->pkt.buf[size]; + pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_stats)); + + /* Init block header */ + specific |= sess->stat.tx.stat_sum.l ? (1 << 7) : 0; + specific |= sess->stat.tx.stat_sum.d ? (1 << 6) : 0; + specific |= sess->stat.tx.stat_sum.j ? (1 << 5) : 0; + specific |= (sess->stat.tx.stat_sum.t & 3) << 3; + r->header.bt = BT_STATS; + r->header.specific = specific; + r->header.length = pj_htons(9); + + /* Generate block contents */ + r->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc); + r->begin_seq = pj_htons((pj_uint16_t) + (sess->stat.tx.stat_sum.begin_seq & 0xFFFF)); + r->end_seq = pj_htons((pj_uint16_t) + (sess->stat.tx.stat_sum.end_seq & 0xFFFF)); + if (sess->stat.tx.stat_sum.l) { + r->lost = pj_htonl(sess->stat.tx.stat_sum.lost); + } + if (sess->stat.tx.stat_sum.d) { + r->dup = pj_htonl(sess->stat.tx.stat_sum.dup); + } + if (sess->stat.tx.stat_sum.j) { + r->jitter_min = pj_htonl(sess->stat.tx.stat_sum.jitter.min); + r->jitter_max = pj_htonl(sess->stat.tx.stat_sum.jitter.max); + r->jitter_mean = pj_htonl(sess->stat.tx.stat_sum.jitter.mean); + sess->stat.tx.stat_sum.jitter.dev = + my_isqrt(sess->stat.tx.stat_sum.jitter.dev); + r->jitter_dev = pj_htonl(sess->stat.tx.stat_sum.jitter.dev); + } + if (sess->stat.tx.stat_sum.t) { + r->toh_min = sess->stat.tx.stat_sum.toh.min; + r->toh_max = sess->stat.tx.stat_sum.toh.max; + r->toh_mean = sess->stat.tx.stat_sum.toh.mean; + sess->stat.tx.stat_sum.toh.dev = + my_isqrt(sess->stat.tx.stat_sum.toh.dev); + r->toh_dev = sess->stat.tx.stat_sum.toh.dev; + } + + /* Reset TX statistics summary each time built */ + pj_bzero(&sess->stat.tx.stat_sum, sizeof(sess->stat.tx.stat_sum)); + sess->stat.tx.stat_sum.jitter.min = (unsigned) -1; + sess->stat.tx.stat_sum.toh.min = (unsigned) -1; + + /* Finally */ + size += sizeof(pjmedia_rtcp_xr_rb_stats); + } + + /* Voip Metrics Block */ + /* Build this block if we have received packets */ + if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_VOIP_METRICS)) && + sess->rtcp_session->stat.rx.pkt) + { + pjmedia_rtcp_xr_rb_voip_mtc *r; + pj_uint32_t c11; + pj_uint32_t c13; + pj_uint32_t c14; + pj_uint32_t c22; + pj_uint32_t c23; + pj_uint32_t c31; + pj_uint32_t c32; + pj_uint32_t c33; + pj_uint32_t ctotal, p32, p23, m; + + r = (pjmedia_rtcp_xr_rb_voip_mtc*) &sess->pkt.buf[size]; + pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_voip_mtc)); + + /* Init block header */ + r->header.bt = BT_VOIP_METRICS; + r->header.specific = 0; + r->header.length = pj_htons(8); + + /* Calculate additional transition counts. */ + c11 = sess->voip_mtc_stat.c11; + c13 = sess->voip_mtc_stat.c13; + c14 = sess->voip_mtc_stat.c14; + c22 = sess->voip_mtc_stat.c22; + c23 = sess->voip_mtc_stat.c23; + c33 = sess->voip_mtc_stat.c33; + c31 = c13; + c32 = c23; + ctotal = c11 + c14 + c13 + c22 + c23 + c31 + c32 + c33; + m = sess->ptime * sess->frames_per_packet; + + /* Calculate burst and densities. */ + if (ctotal) { + p32 = c32 / (c31 + c32 + c33); + if((c22 + c23) < 1) { + p23 = 1; + } else { + p23 = 1 - c22/(c22 + c23); + } + sess->stat.tx.voip_mtc.burst_den = (pj_uint8_t)(256*p23/(p23 + p32)); + sess->stat.tx.voip_mtc.gap_den = (pj_uint8_t)(256*c14/(c11 + c14)); + + /* Calculate burst and gap durations in ms */ + sess->stat.tx.voip_mtc.gap_dur = (pj_uint16_t)((c11+c14+c13)*m/c13); + sess->stat.tx.voip_mtc.burst_dur = (pj_uint16_t)(ctotal*m/c13 - + sess->stat.tx.voip_mtc.gap_dur); + } else { + /* No burst occurred yet until this time? + * Just report full gap. + */ + ctotal = sess->rtcp_session->stat.rx.pkt; + + sess->stat.tx.voip_mtc.burst_den = 0; + sess->stat.tx.voip_mtc.gap_den = (pj_uint8_t)(256 * + (sess->voip_mtc_stat.loss_count + + sess->voip_mtc_stat.discard_count) / + ctotal); + + /* Calculate burst and gap durations in ms */ + sess->stat.tx.voip_mtc.gap_dur = (pj_uint16_t)((m*ctotal) < 0xFFFF? + (m*ctotal) : 0xFFFF); + sess->stat.tx.voip_mtc.burst_dur = 0; + } + + /* Calculate loss and discard rates */ + sess->stat.tx.voip_mtc.loss_rate = (pj_uint8_t) + (256 * sess->voip_mtc_stat.loss_count / ctotal); + sess->stat.tx.voip_mtc.discard_rate = (pj_uint8_t) + (256 * sess->voip_mtc_stat.discard_count / ctotal); + + /* Set round trip delay (in ms) to RTT calculated after receiving + * DLRR or DLSR. + */ + if (sess->stat.rtt.last) + sess->stat.tx.voip_mtc.rnd_trip_delay = (pj_uint16_t) + (sess->stat.rtt.last / 1000); + else if (sess->rtcp_session->stat.rtt.last) + sess->stat.tx.voip_mtc.rnd_trip_delay = (pj_uint16_t) + (sess->rtcp_session->stat.rtt.last / 1000); + + /* End system delay estimation = RTT/2 + current jitter buffer size + + * EXTRA + * EXTRA will cover additional delay introduced by other components of + * audio engine, e.g: sound device, codec, AEC, PLC, WSOLA. + * Since it is difficult to get the exact value of EXTRA, estimation + * is taken to be totally around 50 ms. + */ + sess->stat.tx.voip_mtc.end_sys_delay = (pj_uint16_t) + (sess->stat.tx.voip_mtc.rnd_trip_delay / 2 + + sess->stat.tx.voip_mtc.jb_nom + 50); + + /* Generate block contents */ + r->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc); + r->loss_rate = sess->stat.tx.voip_mtc.loss_rate; + r->discard_rate = sess->stat.tx.voip_mtc.discard_rate; + r->burst_den = sess->stat.tx.voip_mtc.burst_den; + r->gap_den = sess->stat.tx.voip_mtc.gap_den; + r->burst_dur = pj_htons(sess->stat.tx.voip_mtc.burst_dur); + r->gap_dur = pj_htons(sess->stat.tx.voip_mtc.gap_dur); + r->rnd_trip_delay = pj_htons(sess->stat.tx.voip_mtc.rnd_trip_delay); + r->end_sys_delay = pj_htons(sess->stat.tx.voip_mtc.end_sys_delay); + r->signal_lvl = sess->stat.tx.voip_mtc.signal_lvl; + r->noise_lvl = sess->stat.tx.voip_mtc.noise_lvl; + r->rerl = sess->stat.tx.voip_mtc.rerl; + r->gmin = sess->stat.tx.voip_mtc.gmin; + r->r_factor = sess->stat.tx.voip_mtc.r_factor; + r->ext_r_factor = sess->stat.tx.voip_mtc.ext_r_factor; + r->mos_lq = sess->stat.tx.voip_mtc.mos_lq; + r->mos_cq = sess->stat.tx.voip_mtc.mos_cq; + r->rx_config = sess->stat.tx.voip_mtc.rx_config; + r->jb_nom = pj_htons(sess->stat.tx.voip_mtc.jb_nom); + r->jb_max = pj_htons(sess->stat.tx.voip_mtc.jb_max); + r->jb_abs_max = pj_htons(sess->stat.tx.voip_mtc.jb_abs_max); + + /* Finally */ + size += sizeof(pjmedia_rtcp_xr_rb_voip_mtc); + } + + /* Add RTCP XR header size */ + size += sizeof(sess->pkt.common); + + /* Set RTCP XR header 'length' to packet size in 32-bit unit minus one */ + sess->pkt.common.length = pj_htons((pj_uint16_t)(size/4 - 1)); + + /* Set the return values */ + *rtcp_pkt = (void*) &sess->pkt; + *len = size; +} + + +void pjmedia_rtcp_xr_rx_rtcp_xr( pjmedia_rtcp_xr_session *sess, + const void *pkt, + pj_size_t size) +{ + const pjmedia_rtcp_xr_pkt *rtcp_xr = (pjmedia_rtcp_xr_pkt*) pkt; + const pjmedia_rtcp_xr_rb_rr_time *rb_rr_time = NULL; + const pjmedia_rtcp_xr_rb_dlrr *rb_dlrr = NULL; + const pjmedia_rtcp_xr_rb_stats *rb_stats = NULL; + const pjmedia_rtcp_xr_rb_voip_mtc *rb_voip_mtc = NULL; + const pjmedia_rtcp_xr_rb_header *rb_hdr = (pjmedia_rtcp_xr_rb_header*) + rtcp_xr->buf; + unsigned pkt_len, rb_len; + + if (rtcp_xr->common.pt != RTCP_XR) + return; + + pkt_len = pj_ntohs((pj_uint16_t)rtcp_xr->common.length); + + pj_assert((pkt_len + 1) <= (size / 4)); + + /* Parse report rpt_types */ + while ((pj_int32_t*)rb_hdr < (pj_int32_t*)pkt + pkt_len) + { + rb_len = pj_ntohs((pj_uint16_t)rb_hdr->length); + + /* Just skip any block with length == 0 (no report content) */ + if (rb_len) { + switch (rb_hdr->bt) { + case BT_RR_TIME: + rb_rr_time = (pjmedia_rtcp_xr_rb_rr_time*) rb_hdr; + break; + case BT_DLRR: + rb_dlrr = (pjmedia_rtcp_xr_rb_dlrr*) rb_hdr; + break; + case BT_STATS: + rb_stats = (pjmedia_rtcp_xr_rb_stats*) rb_hdr; + break; + case BT_VOIP_METRICS: + rb_voip_mtc = (pjmedia_rtcp_xr_rb_voip_mtc*) rb_hdr; + break; + default: + break; + } + } + rb_hdr = (pjmedia_rtcp_xr_rb_header*) + ((pj_int32_t*)rb_hdr + rb_len + 1); + } + + /* Receiving RR Time */ + if (rb_rr_time) { + /* Save LRR from NTP timestamp of the RR time block report */ + sess->rx_lrr = ((pj_ntohl(rb_rr_time->ntp_sec) & 0x0000FFFF) << 16) | + ((pj_ntohl(rb_rr_time->ntp_frac) >> 16) & 0xFFFF); + + /* Calculate RR arrival time for DLRR */ + pj_get_timestamp(&sess->rx_lrr_time); + + TRACE_((sess->name, "Rx RTCP SR: ntp_ts=%p", sess->rx_lrr, + (pj_uint32_t)(sess->rx_lrr_time.u64*65536/ + sess->rtcp_session->ts_freq.u64))); + } + + /* Receiving DLRR */ + if (rb_dlrr) { + pj_uint32_t lrr, now, dlrr; + pj_uint64_t eedelay; + pjmedia_rtcp_ntp_rec ntp; + + /* LRR is the middle 32bit of NTP. It has 1/65536 second + * resolution + */ + lrr = pj_ntohl(rb_dlrr->item.lrr); + + /* DLRR is delay since LRR, also in 1/65536 resolution */ + dlrr = pj_ntohl(rb_dlrr->item.dlrr); + + /* Get current time, and convert to 1/65536 resolution */ + pjmedia_rtcp_get_ntp_time(sess->rtcp_session, &ntp); + now = ((ntp.hi & 0xFFFF) << 16) + (ntp.lo >> 16); + + /* End-to-end delay is (now-lrr-dlrr) */ + eedelay = now - lrr - dlrr; + + /* 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 XR DLRR: lrr=%p, dlrr=%p (%d:%03dms), " + "now=%p, rtt=%p", + lrr, dlrr, dlrr/65536, (dlrr%65536)*1000/65536, + now, (pj_uint32_t)eedelay)); + + /* Only save calculation if "now" is greater than lrr, or + * otherwise rtt will be invalid + */ + if (now-dlrr >= lrr) { + 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) { + if (sess->stat.rtt.update_cnt == 0) + sess->stat.rtt.min = rtt; + + /* "Normalize" rtt value that is exceptionally high. + * For such values, "normalize" the rtt to be three times + * the average value. + */ + if (rtt>(sess->stat.rtt.avg*3) && sess->stat.rtt.update_cnt!=0) + { + unsigned orig_rtt = rtt; + rtt = sess->stat.rtt.avg*3; + PJ_LOG(5,(sess->name, + "RTT value %d usec is normalized to %d usec", + orig_rtt, rtt)); + } + + TRACE_((sess->name, "RTCP RTT is set to %d usec", rtt)); + + if (rtt < sess->stat.rtt.min && rtt) + sess->stat.rtt.min = rtt; + if (rtt > sess->stat.rtt.max) + sess->stat.rtt.max = rtt; + + sess->stat.rtt.avg = + (sess->stat.rtt.avg * sess->stat.rtt.update_cnt + rtt) / + (sess->stat.rtt.update_cnt + 1); + + sess->stat.rtt.last = rtt; + sess->stat.rtt.update_cnt++; + } + } else { + PJ_LOG(5, (sess->name, "Internal RTCP NTP clock skew detected: " + "lrr=%p, now=%p, dlrr=%p (%d:%03dms), " + "diff=%d", + lrr, now, dlrr, dlrr/65536, + (dlrr%65536)*1000/65536, + dlrr-(now-lrr))); + } + } + + /* Receiving Statistics Summary */ + if (rb_stats) { + pj_uint8_t flags = rb_stats->header.specific; + + pj_bzero(&sess->stat.rx.stat_sum, sizeof(sess->stat.rx.stat_sum)); + + /* Range of packets sequence reported in this blocks */ + sess->stat.rx.stat_sum.begin_seq = pj_ntohs(rb_stats->begin_seq); + sess->stat.rx.stat_sum.end_seq = pj_ntohs(rb_stats->end_seq); + + /* Get flags of valid fields */ + sess->stat.rx.stat_sum.l = (flags & (1 << 7)) != 0; + sess->stat.rx.stat_sum.d = (flags & (1 << 6)) != 0; + sess->stat.rx.stat_sum.j = (flags & (1 << 5)) != 0; + sess->stat.rx.stat_sum.t = (flags & (3 << 3)) != 0; + + /* Fetch the reports info */ + if (sess->stat.rx.stat_sum.l) { + sess->stat.rx.stat_sum.lost = pj_ntohl(rb_stats->lost); + } + + if (sess->stat.rx.stat_sum.d) { + sess->stat.rx.stat_sum.dup = pj_ntohl(rb_stats->dup); + } + + if (sess->stat.rx.stat_sum.j) { + sess->stat.rx.stat_sum.jitter.min = pj_ntohl(rb_stats->jitter_min); + sess->stat.rx.stat_sum.jitter.max = pj_ntohl(rb_stats->jitter_max); + sess->stat.rx.stat_sum.jitter.mean = pj_ntohl(rb_stats->jitter_mean); + sess->stat.rx.stat_sum.jitter.dev = pj_ntohl(rb_stats->jitter_dev); + } + + if (sess->stat.rx.stat_sum.t) { + sess->stat.rx.stat_sum.toh.min = rb_stats->toh_min; + sess->stat.rx.stat_sum.toh.max = rb_stats->toh_max; + sess->stat.rx.stat_sum.toh.mean = rb_stats->toh_mean; + sess->stat.rx.stat_sum.toh.dev = rb_stats->toh_dev; + } + } + + /* Receiving VoIP Metrics */ + if (rb_voip_mtc) { + sess->stat.rx.voip_mtc.loss_rate = rb_voip_mtc->loss_rate; + sess->stat.rx.voip_mtc.discard_rate = rb_voip_mtc->discard_rate; + sess->stat.rx.voip_mtc.burst_den = rb_voip_mtc->burst_den; + sess->stat.rx.voip_mtc.gap_den = rb_voip_mtc->gap_den; + sess->stat.rx.voip_mtc.burst_dur = pj_ntohs(rb_voip_mtc->burst_dur); + sess->stat.rx.voip_mtc.gap_dur = pj_ntohs(rb_voip_mtc->gap_dur); + sess->stat.rx.voip_mtc.rnd_trip_delay = + pj_ntohs(rb_voip_mtc->rnd_trip_delay); + sess->stat.rx.voip_mtc.end_sys_delay = + pj_ntohs(rb_voip_mtc->end_sys_delay); + sess->stat.rx.voip_mtc.signal_lvl = rb_voip_mtc->signal_lvl; + sess->stat.rx.voip_mtc.noise_lvl = rb_voip_mtc->noise_lvl; + sess->stat.rx.voip_mtc.rerl = rb_voip_mtc->rerl; + sess->stat.rx.voip_mtc.gmin = rb_voip_mtc->gmin; + sess->stat.rx.voip_mtc.r_factor = rb_voip_mtc->r_factor; + sess->stat.rx.voip_mtc.ext_r_factor = rb_voip_mtc->ext_r_factor; + sess->stat.rx.voip_mtc.mos_lq = rb_voip_mtc->mos_lq; + sess->stat.rx.voip_mtc.mos_cq = rb_voip_mtc->mos_cq; + sess->stat.rx.voip_mtc.rx_config = rb_voip_mtc->rx_config; + sess->stat.rx.voip_mtc.jb_nom = pj_ntohs(rb_voip_mtc->jb_nom); + sess->stat.rx.voip_mtc.jb_max = pj_ntohs(rb_voip_mtc->jb_max); + sess->stat.rx.voip_mtc.jb_abs_max = pj_ntohs(rb_voip_mtc->jb_abs_max); + } +} + +/* Place seq into a 32-bit sequence number space based upon a + * heuristic for its most likely location. + */ +static pj_uint32_t extend_seq(pjmedia_rtcp_xr_session *sess, + const pj_uint16_t seq) +{ + + pj_uint32_t extended_seq, seq_a, seq_b, diff_a, diff_b; + if(sess->uninitialized_src_ref_seq) { + /* This is the first sequence number received. Place + * it in the middle of the extended sequence number + * space. + */ + sess->src_ref_seq = seq | 0x80000000u; + sess->uninitialized_src_ref_seq = PJ_FALSE; + extended_seq = sess->src_ref_seq; + } else { + /* Prior sequence numbers have been received. + * Propose two candidates for the extended sequence + * number: seq_a is without wraparound, seq_b with + * wraparound. + */ + seq_a = seq | (sess->src_ref_seq & 0xFFFF0000u); + if(sess->src_ref_seq < seq_a) { + seq_b = seq_a - 0x00010000u; + diff_a = seq_a - sess->src_ref_seq; + diff_b = sess->src_ref_seq - seq_b; + } else { + seq_b = seq_a + 0x00010000u; + diff_a = sess->src_ref_seq - seq_a; + diff_b = seq_b - sess->src_ref_seq; + } + + /* Choose the closer candidate. If they are equally + * close, the choice is somewhat arbitrary: we choose + * the candidate for which no rollover is necessary. + */ + if(diff_a < diff_b) { + extended_seq = seq_a; + } else { + extended_seq = seq_b; + } + + /* Set the reference sequence number to be this most + * recently-received sequence number. + */ + sess->src_ref_seq = extended_seq; + } + + /* Return our best guess for a 32-bit sequence number that + * corresponds to the 16-bit number we were given. + */ + return extended_seq; +} + +void pjmedia_rtcp_xr_rx_rtp( pjmedia_rtcp_xr_session *sess, + unsigned seq, + int lost, + int dup, + int discarded, + int jitter, + int toh, pj_bool_t toh_ipv4) +{ + pj_uint32_t ext_seq; + + /* Get 32 bit version of sequence */ + ext_seq = extend_seq(sess, (pj_uint16_t)seq); + + /* Update statistics summary */ + sess->stat.tx.stat_sum.count++; + + if (sess->stat.tx.stat_sum.begin_seq == 0 || + sess->stat.tx.stat_sum.begin_seq > ext_seq) + { + sess->stat.tx.stat_sum.begin_seq = ext_seq; + } + + if (sess->stat.tx.stat_sum.end_seq == 0 || + sess->stat.tx.stat_sum.end_seq < ext_seq) + { + sess->stat.tx.stat_sum.end_seq = ext_seq; + } + + if (lost >= 0) { + sess->stat.tx.stat_sum.l = PJ_TRUE; + if (lost > 0) + sess->stat.tx.stat_sum.lost++; + } + + if (dup >= 0) { + sess->stat.tx.stat_sum.d = PJ_TRUE; + if (dup > 0) + sess->stat.tx.stat_sum.dup++; + } + + if (jitter >= 0) { + pj_int32_t diff; + + sess->stat.tx.stat_sum.j = PJ_TRUE; + if (sess->stat.tx.stat_sum.jitter.min > (pj_uint32_t)jitter) + sess->stat.tx.stat_sum.jitter.min = jitter; + if (sess->stat.tx.stat_sum.jitter.max < (pj_uint32_t)jitter) + sess->stat.tx.stat_sum.jitter.max = jitter; + sess->stat.tx.stat_sum.jitter.mean = + (jitter + sess->stat.tx.stat_sum.jitter.mean * + sess->stat.tx.stat_sum.jitter.count) / + (sess->stat.tx.stat_sum.jitter.count + 1); + + diff = sess->stat.tx.stat_sum.jitter.mean - jitter; + sess->stat.tx.stat_sum.jitter.dev = + (diff * diff + sess->stat.tx.stat_sum.jitter.dev * + sess->stat.tx.stat_sum.jitter.count) / + (sess->stat.tx.stat_sum.jitter.count + 1); + + ++sess->stat.tx.stat_sum.jitter.count; + } + + if (toh >= 0) { + pj_int32_t diff; + + sess->stat.tx.stat_sum.t = toh_ipv4? 1 : 2; + + if (sess->stat.tx.stat_sum.toh.min > (pj_uint32_t)toh) + sess->stat.tx.stat_sum.toh.min = toh; + if (sess->stat.tx.stat_sum.toh.max < (pj_uint32_t)toh) + sess->stat.tx.stat_sum.toh.max = toh; + sess->stat.tx.stat_sum.toh.mean = + (toh + sess->stat.tx.stat_sum.toh.mean * + sess->stat.tx.stat_sum.toh.count) / + (sess->stat.tx.stat_sum.toh.count + 1); + + diff = sess->stat.tx.stat_sum.toh.mean - toh; + sess->stat.tx.stat_sum.toh.dev = + (diff * diff + sess->stat.tx.stat_sum.toh.dev * + sess->stat.tx.stat_sum.toh.count) / + (sess->stat.tx.stat_sum.toh.count + 1); + + ++sess->stat.tx.stat_sum.toh.count; + } + + /* Update burst metrics. + * There are two terms introduced in the RFC 3611: gap & burst. + * Gap represents good stream condition, lost+discard rate <= 1/Gmin. + * Burst represents the opposite, lost+discard rate > 1/Gmin. + */ + if (lost >= 0 && discarded >= 0) { + if(lost > 0) { + sess->voip_mtc_stat.loss_count++; + } + if(discarded > 0) { + sess->voip_mtc_stat.discard_count++; + } + if(!lost && !discarded) { + /* Number of good packets since last lost/discarded */ + sess->voip_mtc_stat.pkt++; + } + else { + if(sess->voip_mtc_stat.pkt >= sess->stat.tx.voip_mtc.gmin) { + /* Gap condition */ + if(sess->voip_mtc_stat.lost == 1) { + /* Gap -> Gap */ + sess->voip_mtc_stat.c14++; + } + else { + /* Burst -> Gap */ + sess->voip_mtc_stat.c13++; + } + sess->voip_mtc_stat.lost = 1; + sess->voip_mtc_stat.c11 += sess->voip_mtc_stat.pkt; + } + else { + /* Burst condition */ + sess->voip_mtc_stat.lost++; + if(sess->voip_mtc_stat.pkt == 0) { + /* Consecutive losts */ + sess->voip_mtc_stat.c33++; + } + else { + /* Any good packets, but still bursting */ + sess->voip_mtc_stat.c23++; + sess->voip_mtc_stat.c22 += (sess->voip_mtc_stat.pkt - 1); + } + } + + sess->voip_mtc_stat.pkt = 0; + } + } +} + +void pjmedia_rtcp_xr_tx_rtp( pjmedia_rtcp_xr_session *session, + unsigned ptsize ) +{ + PJ_UNUSED_ARG(session); + PJ_UNUSED_ARG(ptsize); +} + +PJ_DEF(pj_status_t) pjmedia_rtcp_xr_update_info( + pjmedia_rtcp_xr_session *sess, + unsigned info, + pj_int32_t val) +{ + int v = val; + + switch(info) { + case PJMEDIA_RTCP_XR_INFO_SIGNAL_LVL: + sess->stat.tx.voip_mtc.signal_lvl = (pj_uint8_t) v; + break; + + case PJMEDIA_RTCP_XR_INFO_NOISE_LVL: + sess->stat.tx.voip_mtc.noise_lvl = (pj_uint8_t) v; + break; + + case PJMEDIA_RTCP_XR_INFO_RERL: + sess->stat.tx.voip_mtc.rerl = (pj_uint8_t) v; + break; + + case PJMEDIA_RTCP_XR_INFO_R_FACTOR: + sess->stat.tx.voip_mtc.ext_r_factor = (pj_uint8_t) v; + break; + + case PJMEDIA_RTCP_XR_INFO_MOS_LQ: + sess->stat.tx.voip_mtc.mos_lq = (pj_uint8_t) v; + break; + + case PJMEDIA_RTCP_XR_INFO_MOS_CQ: + sess->stat.tx.voip_mtc.mos_cq = (pj_uint8_t) v; + break; + + case PJMEDIA_RTCP_XR_INFO_CONF_PLC: + if (v >= 0 && v <= 3) { + sess->stat.tx.voip_mtc.rx_config &= 0x3F; + sess->stat.tx.voip_mtc.rx_config |= (pj_uint8_t) (v << 6); + } + break; + + case PJMEDIA_RTCP_XR_INFO_CONF_JBA: + if (v >= 0 && v <= 3) { + sess->stat.tx.voip_mtc.rx_config &= 0xCF; + sess->stat.tx.voip_mtc.rx_config |= (pj_uint8_t) (v << 4); + } + break; + + case PJMEDIA_RTCP_XR_INFO_CONF_JBR: + if (v >= 0 && v <= 15) { + sess->stat.tx.voip_mtc.rx_config &= 0xF0; + sess->stat.tx.voip_mtc.rx_config |= (pj_uint8_t) v; + } + break; + + case PJMEDIA_RTCP_XR_INFO_JB_NOM: + sess->stat.tx.voip_mtc.jb_nom = (pj_uint16_t) v; + break; + + case PJMEDIA_RTCP_XR_INFO_JB_MAX: + sess->stat.tx.voip_mtc.jb_max = (pj_uint16_t) v; + break; + + case PJMEDIA_RTCP_XR_INFO_JB_ABS_MAX: + sess->stat.tx.voip_mtc.jb_abs_max = (pj_uint16_t) v; + break; + + default: + return PJ_EINVAL; + } + + return PJ_SUCCESS; +} + +#endif |