Tristan Matthews | 0a329cc | 2013-07-17 13:20:14 -0400 | [diff] [blame] | 1 | /* $Id$ */ |
| 2 | /* |
| 3 | * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| 4 | * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU General Public License |
| 17 | * along with this program; if not, write to the Free Software |
| 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 19 | */ |
| 20 | |
| 21 | #include <pjmedia/rtcp_xr.h> |
| 22 | #include <pjmedia/errno.h> |
| 23 | #include <pjmedia/rtcp.h> |
| 24 | #include <pj/assert.h> |
| 25 | #include <pj/log.h> |
| 26 | #include <pj/os.h> |
| 27 | #include <pj/sock.h> |
| 28 | #include <pj/string.h> |
| 29 | |
| 30 | #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) |
| 31 | |
| 32 | #define THIS_FILE "rtcp_xr.c" |
| 33 | |
| 34 | |
| 35 | #if PJ_HAS_HIGH_RES_TIMER==0 |
| 36 | # error "High resolution timer needs to be enabled" |
| 37 | #endif |
| 38 | |
| 39 | |
| 40 | /* RTCP XR payload type */ |
| 41 | #define RTCP_XR 207 |
| 42 | |
| 43 | /* RTCP XR block types */ |
| 44 | #define BT_LOSS_RLE 1 |
| 45 | #define BT_DUP_RLE 2 |
| 46 | #define BT_RCPT_TIMES 3 |
| 47 | #define BT_RR_TIME 4 |
| 48 | #define BT_DLRR 5 |
| 49 | #define BT_STATS 6 |
| 50 | #define BT_VOIP_METRICS 7 |
| 51 | |
| 52 | |
| 53 | #define DEFAULT_GMIN 16 |
| 54 | |
| 55 | |
| 56 | #if 0 |
| 57 | # define TRACE_(x) PJ_LOG(3,x) |
| 58 | #else |
| 59 | # define TRACE_(x) ; |
| 60 | #endif |
| 61 | |
| 62 | void pjmedia_rtcp_xr_init( pjmedia_rtcp_xr_session *session, |
| 63 | struct pjmedia_rtcp_session *parent_session, |
| 64 | pj_uint8_t gmin, |
| 65 | unsigned frames_per_packet) |
| 66 | { |
| 67 | pj_bzero(session, sizeof(pjmedia_rtcp_xr_session)); |
| 68 | |
| 69 | session->name = parent_session->name; |
| 70 | session->rtcp_session = parent_session; |
| 71 | pj_memcpy(&session->pkt.common, &session->rtcp_session->rtcp_sr_pkt.common, |
| 72 | sizeof(pjmedia_rtcp_common)); |
| 73 | session->pkt.common.pt = RTCP_XR; |
| 74 | |
| 75 | /* Init config */ |
| 76 | session->stat.rx.voip_mtc.gmin = (pj_uint8_t)(gmin? gmin : DEFAULT_GMIN); |
| 77 | session->ptime = session->rtcp_session->pkt_size * 1000 / |
| 78 | session->rtcp_session->clock_rate; |
| 79 | session->frames_per_packet = frames_per_packet; |
| 80 | |
| 81 | /* Init Statistics Summary fields which have non-zero default */ |
| 82 | session->stat.rx.stat_sum.jitter.min = (unsigned) -1; |
| 83 | session->stat.rx.stat_sum.toh.min = (unsigned) -1; |
| 84 | |
| 85 | /* Init VoIP Metrics fields which have non-zero default */ |
| 86 | session->stat.rx.voip_mtc.signal_lvl = 127; |
| 87 | session->stat.rx.voip_mtc.noise_lvl = 127; |
| 88 | session->stat.rx.voip_mtc.rerl = 127; |
| 89 | session->stat.rx.voip_mtc.r_factor = 127; |
| 90 | session->stat.rx.voip_mtc.ext_r_factor = 127; |
| 91 | session->stat.rx.voip_mtc.mos_lq = 127; |
| 92 | session->stat.rx.voip_mtc.mos_cq = 127; |
| 93 | |
| 94 | session->stat.tx.voip_mtc.signal_lvl = 127; |
| 95 | session->stat.tx.voip_mtc.noise_lvl = 127; |
| 96 | session->stat.tx.voip_mtc.rerl = 127; |
| 97 | session->stat.tx.voip_mtc.r_factor = 127; |
| 98 | session->stat.tx.voip_mtc.ext_r_factor = 127; |
| 99 | session->stat.tx.voip_mtc.mos_lq = 127; |
| 100 | session->stat.tx.voip_mtc.mos_cq = 127; |
| 101 | } |
| 102 | |
| 103 | void pjmedia_rtcp_xr_fini(pjmedia_rtcp_xr_session *session) |
| 104 | { |
| 105 | PJ_UNUSED_ARG(session); |
| 106 | } |
| 107 | |
| 108 | PJ_DEF(void) pjmedia_rtcp_build_rtcp_xr( pjmedia_rtcp_xr_session *sess, |
| 109 | unsigned rpt_types, |
| 110 | void **rtcp_pkt, int *len) |
| 111 | { |
| 112 | pj_uint16_t size = 0; |
| 113 | |
| 114 | /* Receiver Reference Time Report Block */ |
| 115 | /* Build this block if we have received packets since last build */ |
| 116 | if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_RR_TIME)) && |
| 117 | sess->rx_last_rr != sess->rtcp_session->stat.rx.pkt) |
| 118 | { |
| 119 | pjmedia_rtcp_xr_rb_rr_time *r; |
| 120 | pjmedia_rtcp_ntp_rec ntp; |
| 121 | |
| 122 | r = (pjmedia_rtcp_xr_rb_rr_time*) &sess->pkt.buf[size]; |
| 123 | pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_rr_time)); |
| 124 | |
| 125 | /* Init block header */ |
| 126 | r->header.bt = BT_RR_TIME; |
| 127 | r->header.specific = 0; |
| 128 | r->header.length = pj_htons(2); |
| 129 | |
| 130 | /* Generate block contents */ |
| 131 | pjmedia_rtcp_get_ntp_time(sess->rtcp_session, &ntp); |
| 132 | r->ntp_sec = pj_htonl(ntp.hi); |
| 133 | r->ntp_frac = pj_htonl(ntp.lo); |
| 134 | |
| 135 | /* Finally */ |
| 136 | size += sizeof(pjmedia_rtcp_xr_rb_rr_time); |
| 137 | sess->rx_last_rr = sess->rtcp_session->stat.rx.pkt; |
| 138 | } |
| 139 | |
| 140 | /* DLRR Report Block */ |
| 141 | /* Build this block if we have received RR NTP (rx_lrr) before */ |
| 142 | if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_DLRR)) && |
| 143 | sess->rx_lrr) |
| 144 | { |
| 145 | pjmedia_rtcp_xr_rb_dlrr *r; |
| 146 | pjmedia_rtcp_xr_rb_dlrr_item *dlrr_item; |
| 147 | pj_timestamp ts; |
| 148 | |
| 149 | r = (pjmedia_rtcp_xr_rb_dlrr*) &sess->pkt.buf[size]; |
| 150 | pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_dlrr)); |
| 151 | |
| 152 | /* Init block header */ |
| 153 | r->header.bt = BT_DLRR; |
| 154 | r->header.specific = 0; |
| 155 | r->header.length = pj_htons(sizeof(pjmedia_rtcp_xr_rb_dlrr)/4 - 1); |
| 156 | |
| 157 | /* Generate block contents */ |
| 158 | dlrr_item = &r->item; |
| 159 | dlrr_item->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc); |
| 160 | dlrr_item->lrr = pj_htonl(sess->rx_lrr); |
| 161 | |
| 162 | /* Calculate DLRR */ |
| 163 | if (sess->rx_lrr != 0) { |
| 164 | pj_get_timestamp(&ts); |
| 165 | ts.u64 -= sess->rx_lrr_time.u64; |
| 166 | |
| 167 | /* Convert DLRR time to 1/65536 seconds resolution */ |
| 168 | ts.u64 = (ts.u64 << 16) / sess->rtcp_session->ts_freq.u64; |
| 169 | dlrr_item->dlrr = pj_htonl(ts.u32.lo); |
| 170 | } else { |
| 171 | dlrr_item->dlrr = 0; |
| 172 | } |
| 173 | |
| 174 | /* Finally */ |
| 175 | size += sizeof(pjmedia_rtcp_xr_rb_dlrr); |
| 176 | } |
| 177 | |
| 178 | /* Statistics Summary Block */ |
| 179 | /* Build this block if we have received packets since last build */ |
| 180 | if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_STATS)) && |
| 181 | sess->stat.rx.stat_sum.count > 0) |
| 182 | { |
| 183 | pjmedia_rtcp_xr_rb_stats *r; |
| 184 | pj_uint8_t specific = 0; |
| 185 | |
| 186 | r = (pjmedia_rtcp_xr_rb_stats*) &sess->pkt.buf[size]; |
| 187 | pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_stats)); |
| 188 | |
| 189 | /* Init block header */ |
| 190 | specific |= sess->stat.rx.stat_sum.l ? (1 << 7) : 0; |
| 191 | specific |= sess->stat.rx.stat_sum.d ? (1 << 6) : 0; |
| 192 | specific |= sess->stat.rx.stat_sum.j ? (1 << 5) : 0; |
| 193 | specific |= (sess->stat.rx.stat_sum.t & 3) << 3; |
| 194 | r->header.bt = BT_STATS; |
| 195 | r->header.specific = specific; |
| 196 | r->header.length = pj_htons(9); |
| 197 | |
| 198 | /* Generate block contents */ |
| 199 | r->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc); |
| 200 | r->begin_seq = pj_htons((pj_uint16_t) |
| 201 | (sess->stat.rx.stat_sum.begin_seq & 0xFFFF)); |
| 202 | r->end_seq = pj_htons((pj_uint16_t) |
| 203 | (sess->stat.rx.stat_sum.end_seq & 0xFFFF)); |
| 204 | if (sess->stat.rx.stat_sum.l) { |
| 205 | r->lost = pj_htonl(sess->stat.rx.stat_sum.lost); |
| 206 | } |
| 207 | if (sess->stat.rx.stat_sum.d) { |
| 208 | r->dup = pj_htonl(sess->stat.rx.stat_sum.dup); |
| 209 | } |
| 210 | if (sess->stat.rx.stat_sum.j) { |
| 211 | r->jitter_min = pj_htonl(sess->stat.rx.stat_sum.jitter.min); |
| 212 | r->jitter_max = pj_htonl(sess->stat.rx.stat_sum.jitter.max); |
| 213 | r->jitter_mean = |
| 214 | pj_htonl((unsigned)sess->stat.rx.stat_sum.jitter.mean); |
| 215 | r->jitter_dev = |
| 216 | pj_htonl(pj_math_stat_get_stddev(&sess->stat.rx.stat_sum.jitter)); |
| 217 | } |
| 218 | if (sess->stat.rx.stat_sum.t) { |
| 219 | r->toh_min = sess->stat.rx.stat_sum.toh.min; |
| 220 | r->toh_max = sess->stat.rx.stat_sum.toh.max; |
| 221 | r->toh_mean = (unsigned) sess->stat.rx.stat_sum.toh.mean; |
| 222 | r->toh_dev = pj_math_stat_get_stddev(&sess->stat.rx.stat_sum.toh); |
| 223 | } |
| 224 | |
| 225 | /* Reset TX statistics summary each time built */ |
| 226 | pj_bzero(&sess->stat.rx.stat_sum, sizeof(sess->stat.rx.stat_sum)); |
| 227 | sess->stat.rx.stat_sum.jitter.min = (unsigned) -1; |
| 228 | sess->stat.rx.stat_sum.toh.min = (unsigned) -1; |
| 229 | |
| 230 | /* Finally */ |
| 231 | size += sizeof(pjmedia_rtcp_xr_rb_stats); |
| 232 | pj_gettimeofday(&sess->stat.rx.stat_sum.update); |
| 233 | } |
| 234 | |
| 235 | /* Voip Metrics Block */ |
| 236 | /* Build this block if we have received packets */ |
| 237 | if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_VOIP_METRICS)) && |
| 238 | sess->rtcp_session->stat.rx.pkt) |
| 239 | { |
| 240 | pjmedia_rtcp_xr_rb_voip_mtc *r; |
| 241 | pj_uint32_t c11; |
| 242 | pj_uint32_t c13; |
| 243 | pj_uint32_t c14; |
| 244 | pj_uint32_t c22; |
| 245 | pj_uint32_t c23; |
| 246 | pj_uint32_t c31; |
| 247 | pj_uint32_t c32; |
| 248 | pj_uint32_t c33; |
| 249 | pj_uint32_t ctotal, m; |
| 250 | unsigned est_extra_delay; |
| 251 | |
| 252 | r = (pjmedia_rtcp_xr_rb_voip_mtc*) &sess->pkt.buf[size]; |
| 253 | pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_voip_mtc)); |
| 254 | |
| 255 | /* Init block header */ |
| 256 | r->header.bt = BT_VOIP_METRICS; |
| 257 | r->header.specific = 0; |
| 258 | r->header.length = pj_htons(8); |
| 259 | |
| 260 | /* Use temp vars for easiness. */ |
| 261 | c11 = sess->voip_mtc_stat.c11; |
| 262 | c13 = sess->voip_mtc_stat.c13; |
| 263 | c14 = sess->voip_mtc_stat.c14; |
| 264 | c22 = sess->voip_mtc_stat.c22; |
| 265 | c23 = sess->voip_mtc_stat.c23; |
| 266 | c33 = sess->voip_mtc_stat.c33; |
| 267 | m = sess->ptime * sess->frames_per_packet; |
| 268 | |
| 269 | /* Calculate additional transition counts. */ |
| 270 | c31 = c13; |
| 271 | c32 = c23; |
| 272 | ctotal = c11 + c14 + c13 + c22 + c23 + c31 + c32 + c33; |
| 273 | |
| 274 | if (ctotal) { |
| 275 | pj_uint32_t p32, p23; |
| 276 | |
| 277 | //original version: |
| 278 | //p32 = c32 / (c31 + c32 + c33); |
| 279 | if (c31 + c32 + c33 == 0) |
| 280 | p32 = 0; |
| 281 | else |
| 282 | p32 = (c32 << 16) / (c31 + c32 + c33); |
| 283 | |
| 284 | //original version: |
| 285 | //if ((c22 + c23) < 1) { |
| 286 | // p23 = 1; |
| 287 | //} else { |
| 288 | // p23 = 1 - c22 / (c22 + c23); |
| 289 | //} |
| 290 | if (c23 == 0) { |
| 291 | p23 = 0; |
| 292 | } else { |
| 293 | p23 = (c23 << 16) / (c22 + c23); |
| 294 | } |
| 295 | |
| 296 | /* Calculate loss/discard densities, scaled of 0-256 */ |
| 297 | if (c11 == 0) |
| 298 | sess->stat.rx.voip_mtc.gap_den = 0; |
| 299 | else |
| 300 | sess->stat.rx.voip_mtc.gap_den = (pj_uint8_t) |
| 301 | ((c14 << 8) / (c11 + c14)); |
| 302 | if (p23 == 0) |
| 303 | sess->stat.rx.voip_mtc.burst_den = 0; |
| 304 | else |
| 305 | sess->stat.rx.voip_mtc.burst_den = (pj_uint8_t) |
| 306 | ((p23 << 8) / (p23 + p32)); |
| 307 | |
| 308 | /* Calculate (average) durations, in ms */ |
| 309 | if (c13 == 0) { |
| 310 | c13 = 1; |
| 311 | ctotal += 1; |
| 312 | } |
| 313 | sess->stat.rx.voip_mtc.gap_dur = (pj_uint16_t) |
| 314 | ((c11+c14+c13) * m / c13); |
| 315 | sess->stat.rx.voip_mtc.burst_dur = (pj_uint16_t) |
| 316 | ((ctotal - (c11+c14+c13)) * m / c13); |
| 317 | |
| 318 | /* Callculate loss/discard rates, scaled 0-256 */ |
| 319 | sess->stat.rx.voip_mtc.loss_rate = (pj_uint8_t) |
| 320 | ((sess->voip_mtc_stat.loss_count << 8) / ctotal); |
| 321 | sess->stat.rx.voip_mtc.discard_rate = (pj_uint8_t) |
| 322 | ((sess->voip_mtc_stat.discard_count << 8) / ctotal); |
| 323 | } else { |
| 324 | /* No lost/discarded packet yet. */ |
| 325 | sess->stat.rx.voip_mtc.gap_den = 0; |
| 326 | sess->stat.rx.voip_mtc.burst_den = 0; |
| 327 | sess->stat.rx.voip_mtc.gap_dur = 0; |
| 328 | sess->stat.rx.voip_mtc.burst_dur = 0; |
| 329 | sess->stat.rx.voip_mtc.loss_rate = 0; |
| 330 | sess->stat.rx.voip_mtc.discard_rate = 0; |
| 331 | } |
| 332 | |
| 333 | /* Set round trip delay (in ms) to RTT calculated after receiving |
| 334 | * DLRR or DLSR. |
| 335 | */ |
| 336 | if (sess->stat.rtt.last) |
| 337 | sess->stat.rx.voip_mtc.rnd_trip_delay = (pj_uint16_t) |
| 338 | (sess->stat.rtt.last / 1000); |
| 339 | else if (sess->rtcp_session->stat.rtt.last) |
| 340 | sess->stat.rx.voip_mtc.rnd_trip_delay = (pj_uint16_t) |
| 341 | (sess->rtcp_session->stat.rtt.last / 1000); |
| 342 | |
| 343 | /* End system delay = RTT/2 + current jitter buffer size + |
| 344 | * EXTRA (estimated extra delay) |
| 345 | * EXTRA will cover additional delay introduced by other components of |
| 346 | * audio engine, e.g: sound device, codec, AEC, PLC, WSOLA. |
| 347 | * Since it is difficult to get the exact value of EXTRA, estimation |
| 348 | * is taken to be totally around 30ms + sound device latency. |
| 349 | */ |
| 350 | est_extra_delay = 30; |
| 351 | |
| 352 | #if PJMEDIA_SOUND_IMPLEMENTATION!=PJMEDIA_SOUND_NULL_SOUND |
| 353 | est_extra_delay += PJMEDIA_SND_DEFAULT_REC_LATENCY + |
| 354 | PJMEDIA_SND_DEFAULT_PLAY_LATENCY; |
| 355 | #endif |
| 356 | |
| 357 | sess->stat.rx.voip_mtc.end_sys_delay = (pj_uint16_t) |
| 358 | (sess->stat.rx.voip_mtc.rnd_trip_delay / 2 + |
| 359 | sess->stat.rx.voip_mtc.jb_nom + |
| 360 | est_extra_delay); |
| 361 | |
| 362 | /* Generate block contents */ |
| 363 | r->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc); |
| 364 | r->loss_rate = sess->stat.rx.voip_mtc.loss_rate; |
| 365 | r->discard_rate = sess->stat.rx.voip_mtc.discard_rate; |
| 366 | r->burst_den = sess->stat.rx.voip_mtc.burst_den; |
| 367 | r->gap_den = sess->stat.rx.voip_mtc.gap_den; |
| 368 | r->burst_dur = pj_htons(sess->stat.rx.voip_mtc.burst_dur); |
| 369 | r->gap_dur = pj_htons(sess->stat.rx.voip_mtc.gap_dur); |
| 370 | r->rnd_trip_delay = pj_htons(sess->stat.rx.voip_mtc.rnd_trip_delay); |
| 371 | r->end_sys_delay = pj_htons(sess->stat.rx.voip_mtc.end_sys_delay); |
| 372 | /* signal & noise level encoded in two's complement form */ |
| 373 | r->signal_lvl = (pj_uint8_t) |
| 374 | ((sess->stat.rx.voip_mtc.signal_lvl >= 0)? |
| 375 | sess->stat.rx.voip_mtc.signal_lvl : |
| 376 | (sess->stat.rx.voip_mtc.signal_lvl + 256)); |
| 377 | r->noise_lvl = (pj_uint8_t) |
| 378 | ((sess->stat.rx.voip_mtc.noise_lvl >= 0)? |
| 379 | sess->stat.rx.voip_mtc.noise_lvl : |
| 380 | (sess->stat.rx.voip_mtc.noise_lvl + 256)); |
| 381 | r->rerl = sess->stat.rx.voip_mtc.rerl; |
| 382 | r->gmin = sess->stat.rx.voip_mtc.gmin; |
| 383 | r->r_factor = sess->stat.rx.voip_mtc.r_factor; |
| 384 | r->ext_r_factor = sess->stat.rx.voip_mtc.ext_r_factor; |
| 385 | r->mos_lq = sess->stat.rx.voip_mtc.mos_lq; |
| 386 | r->mos_cq = sess->stat.rx.voip_mtc.mos_cq; |
| 387 | r->rx_config = sess->stat.rx.voip_mtc.rx_config; |
| 388 | r->jb_nom = pj_htons(sess->stat.rx.voip_mtc.jb_nom); |
| 389 | r->jb_max = pj_htons(sess->stat.rx.voip_mtc.jb_max); |
| 390 | r->jb_abs_max = pj_htons(sess->stat.rx.voip_mtc.jb_abs_max); |
| 391 | |
| 392 | /* Finally */ |
| 393 | size += sizeof(pjmedia_rtcp_xr_rb_voip_mtc); |
| 394 | pj_gettimeofday(&sess->stat.rx.voip_mtc.update); |
| 395 | } |
| 396 | |
| 397 | /* Add RTCP XR header size */ |
| 398 | size += sizeof(sess->pkt.common); |
| 399 | |
| 400 | /* Set RTCP XR header 'length' to packet size in 32-bit unit minus one */ |
| 401 | sess->pkt.common.length = pj_htons((pj_uint16_t)(size/4 - 1)); |
| 402 | |
| 403 | /* Set the return values */ |
| 404 | *rtcp_pkt = (void*) &sess->pkt; |
| 405 | *len = size; |
| 406 | } |
| 407 | |
| 408 | |
| 409 | void pjmedia_rtcp_xr_rx_rtcp_xr( pjmedia_rtcp_xr_session *sess, |
| 410 | const void *pkt, |
| 411 | pj_size_t size) |
| 412 | { |
| 413 | const pjmedia_rtcp_xr_pkt *rtcp_xr = (pjmedia_rtcp_xr_pkt*) pkt; |
| 414 | const pjmedia_rtcp_xr_rb_rr_time *rb_rr_time = NULL; |
| 415 | const pjmedia_rtcp_xr_rb_dlrr *rb_dlrr = NULL; |
| 416 | const pjmedia_rtcp_xr_rb_stats *rb_stats = NULL; |
| 417 | const pjmedia_rtcp_xr_rb_voip_mtc *rb_voip_mtc = NULL; |
| 418 | const pjmedia_rtcp_xr_rb_header *rb_hdr = (pjmedia_rtcp_xr_rb_header*) |
| 419 | rtcp_xr->buf; |
| 420 | unsigned pkt_len, rb_len; |
| 421 | |
| 422 | if (rtcp_xr->common.pt != RTCP_XR) |
| 423 | return; |
| 424 | |
| 425 | pkt_len = pj_ntohs((pj_uint16_t)rtcp_xr->common.length); |
| 426 | |
| 427 | if ((pkt_len + 1) > (size / 4)) |
| 428 | return; |
| 429 | |
| 430 | /* Parse report rpt_types */ |
| 431 | while ((pj_int32_t*)rb_hdr < (pj_int32_t*)pkt + pkt_len) |
| 432 | { |
| 433 | rb_len = pj_ntohs((pj_uint16_t)rb_hdr->length); |
| 434 | |
| 435 | /* Just skip any block with length == 0 (no report content) */ |
| 436 | if (rb_len) { |
| 437 | switch (rb_hdr->bt) { |
| 438 | case BT_RR_TIME: |
| 439 | rb_rr_time = (pjmedia_rtcp_xr_rb_rr_time*) rb_hdr; |
| 440 | break; |
| 441 | case BT_DLRR: |
| 442 | rb_dlrr = (pjmedia_rtcp_xr_rb_dlrr*) rb_hdr; |
| 443 | break; |
| 444 | case BT_STATS: |
| 445 | rb_stats = (pjmedia_rtcp_xr_rb_stats*) rb_hdr; |
| 446 | break; |
| 447 | case BT_VOIP_METRICS: |
| 448 | rb_voip_mtc = (pjmedia_rtcp_xr_rb_voip_mtc*) rb_hdr; |
| 449 | break; |
| 450 | default: |
| 451 | break; |
| 452 | } |
| 453 | } |
| 454 | rb_hdr = (pjmedia_rtcp_xr_rb_header*) |
| 455 | ((pj_int32_t*)rb_hdr + rb_len + 1); |
| 456 | } |
| 457 | |
| 458 | /* Receiving RR Time */ |
| 459 | if (rb_rr_time) { |
| 460 | /* Save LRR from NTP timestamp of the RR time block report */ |
| 461 | sess->rx_lrr = ((pj_ntohl(rb_rr_time->ntp_sec) & 0x0000FFFF) << 16) | |
| 462 | ((pj_ntohl(rb_rr_time->ntp_frac) >> 16) & 0xFFFF); |
| 463 | |
| 464 | /* Calculate RR arrival time for DLRR */ |
| 465 | pj_get_timestamp(&sess->rx_lrr_time); |
| 466 | |
| 467 | TRACE_((sess->name, "Rx RTCP SR: ntp_ts=%p", sess->rx_lrr, |
| 468 | (pj_uint32_t)(sess->rx_lrr_time.u64*65536/ |
| 469 | sess->rtcp_session->ts_freq.u64))); |
| 470 | } |
| 471 | |
| 472 | /* Receiving DLRR */ |
| 473 | if (rb_dlrr) { |
| 474 | pj_uint32_t lrr, now, dlrr; |
| 475 | pj_uint64_t eedelay; |
| 476 | pjmedia_rtcp_ntp_rec ntp; |
| 477 | |
| 478 | /* LRR is the middle 32bit of NTP. It has 1/65536 second |
| 479 | * resolution |
| 480 | */ |
| 481 | lrr = pj_ntohl(rb_dlrr->item.lrr); |
| 482 | |
| 483 | /* DLRR is delay since LRR, also in 1/65536 resolution */ |
| 484 | dlrr = pj_ntohl(rb_dlrr->item.dlrr); |
| 485 | |
| 486 | /* Get current time, and convert to 1/65536 resolution */ |
| 487 | pjmedia_rtcp_get_ntp_time(sess->rtcp_session, &ntp); |
| 488 | now = ((ntp.hi & 0xFFFF) << 16) + (ntp.lo >> 16); |
| 489 | |
| 490 | /* End-to-end delay is (now-lrr-dlrr) */ |
| 491 | eedelay = now - lrr - dlrr; |
| 492 | |
| 493 | /* Convert end to end delay to usec (keeping the calculation in |
| 494 | * 64bit space):: |
| 495 | * sess->ee_delay = (eedelay * 1000) / 65536; |
| 496 | */ |
| 497 | if (eedelay < 4294) { |
| 498 | eedelay = (eedelay * 1000000) >> 16; |
| 499 | } else { |
| 500 | eedelay = (eedelay * 1000) >> 16; |
| 501 | eedelay *= 1000; |
| 502 | } |
| 503 | |
| 504 | TRACE_((sess->name, "Rx RTCP XR DLRR: lrr=%p, dlrr=%p (%d:%03dms), " |
| 505 | "now=%p, rtt=%p", |
| 506 | lrr, dlrr, dlrr/65536, (dlrr%65536)*1000/65536, |
| 507 | now, (pj_uint32_t)eedelay)); |
| 508 | |
| 509 | /* Only save calculation if "now" is greater than lrr, or |
| 510 | * otherwise rtt will be invalid |
| 511 | */ |
| 512 | if (now-dlrr >= lrr) { |
| 513 | unsigned rtt = (pj_uint32_t)eedelay; |
| 514 | |
| 515 | /* Check that eedelay value really makes sense. |
| 516 | * We allow up to 30 seconds RTT! |
| 517 | */ |
| 518 | if (eedelay <= 30 * 1000 * 1000UL) { |
| 519 | /* "Normalize" rtt value that is exceptionally high. |
| 520 | * For such values, "normalize" the rtt to be three times |
| 521 | * the average value. |
| 522 | */ |
| 523 | if (rtt>((unsigned)sess->stat.rtt.mean*3) && sess->stat.rtt.n!=0) |
| 524 | { |
| 525 | unsigned orig_rtt = rtt; |
| 526 | rtt = (unsigned)sess->stat.rtt.mean*3; |
| 527 | PJ_LOG(5,(sess->name, |
| 528 | "RTT value %d usec is normalized to %d usec", |
| 529 | orig_rtt, rtt)); |
| 530 | } |
| 531 | |
| 532 | TRACE_((sess->name, "RTCP RTT is set to %d usec", rtt)); |
| 533 | pj_math_stat_update(&sess->stat.rtt, rtt); |
| 534 | } |
| 535 | } else { |
| 536 | PJ_LOG(5, (sess->name, "Internal RTCP NTP clock skew detected: " |
| 537 | "lrr=%p, now=%p, dlrr=%p (%d:%03dms), " |
| 538 | "diff=%d", |
| 539 | lrr, now, dlrr, dlrr/65536, |
| 540 | (dlrr%65536)*1000/65536, |
| 541 | dlrr-(now-lrr))); |
| 542 | } |
| 543 | } |
| 544 | |
| 545 | /* Receiving Statistics Summary */ |
| 546 | if (rb_stats) { |
| 547 | pj_uint8_t flags = rb_stats->header.specific; |
| 548 | |
| 549 | pj_bzero(&sess->stat.tx.stat_sum, sizeof(sess->stat.tx.stat_sum)); |
| 550 | |
| 551 | /* Range of packets sequence reported in this blocks */ |
| 552 | sess->stat.tx.stat_sum.begin_seq = pj_ntohs(rb_stats->begin_seq); |
| 553 | sess->stat.tx.stat_sum.end_seq = pj_ntohs(rb_stats->end_seq); |
| 554 | |
| 555 | /* Get flags of valid fields */ |
| 556 | sess->stat.tx.stat_sum.l = (flags & (1 << 7)) != 0; |
| 557 | sess->stat.tx.stat_sum.d = (flags & (1 << 6)) != 0; |
| 558 | sess->stat.tx.stat_sum.j = (flags & (1 << 5)) != 0; |
| 559 | sess->stat.tx.stat_sum.t = (flags & (3 << 3)) != 0; |
| 560 | |
| 561 | /* Fetch the reports info */ |
| 562 | if (sess->stat.tx.stat_sum.l) { |
| 563 | sess->stat.tx.stat_sum.lost = pj_ntohl(rb_stats->lost); |
| 564 | } |
| 565 | |
| 566 | if (sess->stat.tx.stat_sum.d) { |
| 567 | sess->stat.tx.stat_sum.dup = pj_ntohl(rb_stats->dup); |
| 568 | } |
| 569 | |
| 570 | if (sess->stat.tx.stat_sum.j) { |
| 571 | sess->stat.tx.stat_sum.jitter.min = pj_ntohl(rb_stats->jitter_min); |
| 572 | sess->stat.tx.stat_sum.jitter.max = pj_ntohl(rb_stats->jitter_max); |
| 573 | sess->stat.tx.stat_sum.jitter.mean= pj_ntohl(rb_stats->jitter_mean); |
| 574 | pj_math_stat_set_stddev(&sess->stat.tx.stat_sum.jitter, |
| 575 | pj_ntohl(rb_stats->jitter_dev)); |
| 576 | } |
| 577 | |
| 578 | if (sess->stat.tx.stat_sum.t) { |
| 579 | sess->stat.tx.stat_sum.toh.min = rb_stats->toh_min; |
| 580 | sess->stat.tx.stat_sum.toh.max = rb_stats->toh_max; |
| 581 | sess->stat.tx.stat_sum.toh.mean= rb_stats->toh_mean; |
| 582 | pj_math_stat_set_stddev(&sess->stat.tx.stat_sum.toh, |
| 583 | pj_ntohl(rb_stats->toh_dev)); |
| 584 | } |
| 585 | |
| 586 | pj_gettimeofday(&sess->stat.tx.stat_sum.update); |
| 587 | } |
| 588 | |
| 589 | /* Receiving VoIP Metrics */ |
| 590 | if (rb_voip_mtc) { |
| 591 | sess->stat.tx.voip_mtc.loss_rate = rb_voip_mtc->loss_rate; |
| 592 | sess->stat.tx.voip_mtc.discard_rate = rb_voip_mtc->discard_rate; |
| 593 | sess->stat.tx.voip_mtc.burst_den = rb_voip_mtc->burst_den; |
| 594 | sess->stat.tx.voip_mtc.gap_den = rb_voip_mtc->gap_den; |
| 595 | sess->stat.tx.voip_mtc.burst_dur = pj_ntohs(rb_voip_mtc->burst_dur); |
| 596 | sess->stat.tx.voip_mtc.gap_dur = pj_ntohs(rb_voip_mtc->gap_dur); |
| 597 | sess->stat.tx.voip_mtc.rnd_trip_delay = |
| 598 | pj_ntohs(rb_voip_mtc->rnd_trip_delay); |
| 599 | sess->stat.tx.voip_mtc.end_sys_delay = |
| 600 | pj_ntohs(rb_voip_mtc->end_sys_delay); |
| 601 | /* signal & noise level encoded in two's complement form */ |
| 602 | sess->stat.tx.voip_mtc.signal_lvl = (pj_int8_t) |
| 603 | ((rb_voip_mtc->signal_lvl > 127)? |
| 604 | ((int)rb_voip_mtc->signal_lvl - 256) : |
| 605 | rb_voip_mtc->signal_lvl); |
| 606 | sess->stat.tx.voip_mtc.noise_lvl = (pj_int8_t) |
| 607 | ((rb_voip_mtc->noise_lvl > 127)? |
| 608 | ((int)rb_voip_mtc->noise_lvl - 256) : |
| 609 | rb_voip_mtc->noise_lvl); |
| 610 | sess->stat.tx.voip_mtc.rerl = rb_voip_mtc->rerl; |
| 611 | sess->stat.tx.voip_mtc.gmin = rb_voip_mtc->gmin; |
| 612 | sess->stat.tx.voip_mtc.r_factor = rb_voip_mtc->r_factor; |
| 613 | sess->stat.tx.voip_mtc.ext_r_factor = rb_voip_mtc->ext_r_factor; |
| 614 | sess->stat.tx.voip_mtc.mos_lq = rb_voip_mtc->mos_lq; |
| 615 | sess->stat.tx.voip_mtc.mos_cq = rb_voip_mtc->mos_cq; |
| 616 | sess->stat.tx.voip_mtc.rx_config = rb_voip_mtc->rx_config; |
| 617 | sess->stat.tx.voip_mtc.jb_nom = pj_ntohs(rb_voip_mtc->jb_nom); |
| 618 | sess->stat.tx.voip_mtc.jb_max = pj_ntohs(rb_voip_mtc->jb_max); |
| 619 | sess->stat.tx.voip_mtc.jb_abs_max = pj_ntohs(rb_voip_mtc->jb_abs_max); |
| 620 | |
| 621 | pj_gettimeofday(&sess->stat.tx.voip_mtc.update); |
| 622 | } |
| 623 | } |
| 624 | |
| 625 | /* Place seq into a 32-bit sequence number space based upon a |
| 626 | * heuristic for its most likely location. |
| 627 | */ |
| 628 | static pj_uint32_t extend_seq(pjmedia_rtcp_xr_session *sess, |
| 629 | const pj_uint16_t seq) |
| 630 | { |
| 631 | |
| 632 | pj_uint32_t extended_seq, seq_a, seq_b, diff_a, diff_b; |
| 633 | if(sess->uninitialized_src_ref_seq) { |
| 634 | /* This is the first sequence number received. Place |
| 635 | * it in the middle of the extended sequence number |
| 636 | * space. |
| 637 | */ |
| 638 | sess->src_ref_seq = seq | 0x80000000u; |
| 639 | sess->uninitialized_src_ref_seq = PJ_FALSE; |
| 640 | extended_seq = sess->src_ref_seq; |
| 641 | } else { |
| 642 | /* Prior sequence numbers have been received. |
| 643 | * Propose two candidates for the extended sequence |
| 644 | * number: seq_a is without wraparound, seq_b with |
| 645 | * wraparound. |
| 646 | */ |
| 647 | seq_a = seq | (sess->src_ref_seq & 0xFFFF0000u); |
| 648 | if(sess->src_ref_seq < seq_a) { |
| 649 | seq_b = seq_a - 0x00010000u; |
| 650 | diff_a = seq_a - sess->src_ref_seq; |
| 651 | diff_b = sess->src_ref_seq - seq_b; |
| 652 | } else { |
| 653 | seq_b = seq_a + 0x00010000u; |
| 654 | diff_a = sess->src_ref_seq - seq_a; |
| 655 | diff_b = seq_b - sess->src_ref_seq; |
| 656 | } |
| 657 | |
| 658 | /* Choose the closer candidate. If they are equally |
| 659 | * close, the choice is somewhat arbitrary: we choose |
| 660 | * the candidate for which no rollover is necessary. |
| 661 | */ |
| 662 | if(diff_a < diff_b) { |
| 663 | extended_seq = seq_a; |
| 664 | } else { |
| 665 | extended_seq = seq_b; |
| 666 | } |
| 667 | |
| 668 | /* Set the reference sequence number to be this most |
| 669 | * recently-received sequence number. |
| 670 | */ |
| 671 | sess->src_ref_seq = extended_seq; |
| 672 | } |
| 673 | |
| 674 | /* Return our best guess for a 32-bit sequence number that |
| 675 | * corresponds to the 16-bit number we were given. |
| 676 | */ |
| 677 | return extended_seq; |
| 678 | } |
| 679 | |
| 680 | void pjmedia_rtcp_xr_rx_rtp( pjmedia_rtcp_xr_session *sess, |
| 681 | unsigned seq, |
| 682 | int lost, |
| 683 | int dup, |
| 684 | int discarded, |
| 685 | int jitter, |
| 686 | int toh, pj_bool_t toh_ipv4) |
| 687 | { |
| 688 | pj_uint32_t ext_seq; |
| 689 | |
| 690 | /* Get 32 bit version of sequence */ |
| 691 | ext_seq = extend_seq(sess, (pj_uint16_t)seq); |
| 692 | |
| 693 | /* Update statistics summary */ |
| 694 | sess->stat.rx.stat_sum.count++; |
| 695 | |
| 696 | if (sess->stat.rx.stat_sum.begin_seq == 0 || |
| 697 | sess->stat.rx.stat_sum.begin_seq > ext_seq) |
| 698 | { |
| 699 | sess->stat.rx.stat_sum.begin_seq = ext_seq; |
| 700 | } |
| 701 | |
| 702 | if (sess->stat.rx.stat_sum.end_seq == 0 || |
| 703 | sess->stat.rx.stat_sum.end_seq < ext_seq) |
| 704 | { |
| 705 | sess->stat.rx.stat_sum.end_seq = ext_seq; |
| 706 | } |
| 707 | |
| 708 | if (lost >= 0) { |
| 709 | sess->stat.rx.stat_sum.l = PJ_TRUE; |
| 710 | if (lost > 0) |
| 711 | sess->stat.rx.stat_sum.lost++; |
| 712 | } |
| 713 | |
| 714 | if (dup >= 0) { |
| 715 | sess->stat.rx.stat_sum.d = PJ_TRUE; |
| 716 | if (dup > 0) |
| 717 | sess->stat.rx.stat_sum.dup++; |
| 718 | } |
| 719 | |
| 720 | if (jitter >= 0) { |
| 721 | sess->stat.rx.stat_sum.j = PJ_TRUE; |
| 722 | pj_math_stat_update(&sess->stat.rx.stat_sum.jitter, jitter); |
| 723 | } |
| 724 | |
| 725 | if (toh >= 0) { |
| 726 | sess->stat.rx.stat_sum.t = toh_ipv4? 1 : 2; |
| 727 | pj_math_stat_update(&sess->stat.rx.stat_sum.toh, toh); |
| 728 | } |
| 729 | |
| 730 | /* Update burst metrics. |
| 731 | * There are two terms introduced in the RFC 3611: gap & burst. |
| 732 | * Gap represents good stream condition, lost+discard rate <= 1/Gmin. |
| 733 | * Burst represents the opposite, lost+discard rate > 1/Gmin. |
| 734 | */ |
| 735 | if (lost >= 0 && discarded >= 0) { |
| 736 | if(lost > 0) { |
| 737 | sess->voip_mtc_stat.loss_count++; |
| 738 | } |
| 739 | if(discarded > 0) { |
| 740 | sess->voip_mtc_stat.discard_count++; |
| 741 | } |
| 742 | if(!lost && !discarded) { |
| 743 | /* Number of good packets since last lost/discarded */ |
| 744 | sess->voip_mtc_stat.pkt++; |
| 745 | } |
| 746 | else { |
| 747 | if(sess->voip_mtc_stat.pkt >= sess->stat.rx.voip_mtc.gmin) { |
| 748 | /* Gap condition */ |
| 749 | if(sess->voip_mtc_stat.lost == 1) { |
| 750 | /* Gap -> Gap */ |
| 751 | sess->voip_mtc_stat.c14++; |
| 752 | } |
| 753 | else { |
| 754 | /* Burst -> Gap */ |
| 755 | sess->voip_mtc_stat.c13++; |
| 756 | } |
| 757 | sess->voip_mtc_stat.lost = 1; |
| 758 | sess->voip_mtc_stat.c11 += sess->voip_mtc_stat.pkt; |
| 759 | } |
| 760 | else { |
| 761 | /* Burst condition */ |
| 762 | sess->voip_mtc_stat.lost++; |
| 763 | if(sess->voip_mtc_stat.pkt == 0) { |
| 764 | /* Consecutive losts */ |
| 765 | sess->voip_mtc_stat.c33++; |
| 766 | } |
| 767 | else { |
| 768 | /* Any good packets, but still bursting */ |
| 769 | sess->voip_mtc_stat.c23++; |
| 770 | sess->voip_mtc_stat.c22 += (sess->voip_mtc_stat.pkt - 1); |
| 771 | } |
| 772 | } |
| 773 | |
| 774 | sess->voip_mtc_stat.pkt = 0; |
| 775 | } |
| 776 | } |
| 777 | } |
| 778 | |
| 779 | void pjmedia_rtcp_xr_tx_rtp( pjmedia_rtcp_xr_session *session, |
| 780 | unsigned ptsize ) |
| 781 | { |
| 782 | PJ_UNUSED_ARG(session); |
| 783 | PJ_UNUSED_ARG(ptsize); |
| 784 | } |
| 785 | |
| 786 | PJ_DEF(pj_status_t) pjmedia_rtcp_xr_update_info( |
| 787 | pjmedia_rtcp_xr_session *sess, |
| 788 | unsigned info, |
| 789 | pj_int32_t val) |
| 790 | { |
| 791 | int v = val; |
| 792 | |
| 793 | switch(info) { |
| 794 | case PJMEDIA_RTCP_XR_INFO_SIGNAL_LVL: |
| 795 | sess->stat.rx.voip_mtc.signal_lvl = (pj_int8_t) v; |
| 796 | break; |
| 797 | |
| 798 | case PJMEDIA_RTCP_XR_INFO_NOISE_LVL: |
| 799 | sess->stat.rx.voip_mtc.noise_lvl = (pj_int8_t) v; |
| 800 | break; |
| 801 | |
| 802 | case PJMEDIA_RTCP_XR_INFO_RERL: |
| 803 | sess->stat.rx.voip_mtc.rerl = (pj_uint8_t) v; |
| 804 | break; |
| 805 | |
| 806 | case PJMEDIA_RTCP_XR_INFO_R_FACTOR: |
| 807 | sess->stat.rx.voip_mtc.ext_r_factor = (pj_uint8_t) v; |
| 808 | break; |
| 809 | |
| 810 | case PJMEDIA_RTCP_XR_INFO_MOS_LQ: |
| 811 | sess->stat.rx.voip_mtc.mos_lq = (pj_uint8_t) v; |
| 812 | break; |
| 813 | |
| 814 | case PJMEDIA_RTCP_XR_INFO_MOS_CQ: |
| 815 | sess->stat.rx.voip_mtc.mos_cq = (pj_uint8_t) v; |
| 816 | break; |
| 817 | |
| 818 | case PJMEDIA_RTCP_XR_INFO_CONF_PLC: |
| 819 | if (v >= 0 && v <= 3) { |
| 820 | sess->stat.rx.voip_mtc.rx_config &= 0x3F; |
| 821 | sess->stat.rx.voip_mtc.rx_config |= (pj_uint8_t) (v << 6); |
| 822 | } |
| 823 | break; |
| 824 | |
| 825 | case PJMEDIA_RTCP_XR_INFO_CONF_JBA: |
| 826 | if (v >= 0 && v <= 3) { |
| 827 | sess->stat.rx.voip_mtc.rx_config &= 0xCF; |
| 828 | sess->stat.rx.voip_mtc.rx_config |= (pj_uint8_t) (v << 4); |
| 829 | } |
| 830 | break; |
| 831 | |
| 832 | case PJMEDIA_RTCP_XR_INFO_CONF_JBR: |
| 833 | if (v >= 0 && v <= 15) { |
| 834 | sess->stat.rx.voip_mtc.rx_config &= 0xF0; |
| 835 | sess->stat.rx.voip_mtc.rx_config |= (pj_uint8_t) v; |
| 836 | } |
| 837 | break; |
| 838 | |
| 839 | case PJMEDIA_RTCP_XR_INFO_JB_NOM: |
| 840 | sess->stat.rx.voip_mtc.jb_nom = (pj_uint16_t) v; |
| 841 | break; |
| 842 | |
| 843 | case PJMEDIA_RTCP_XR_INFO_JB_MAX: |
| 844 | sess->stat.rx.voip_mtc.jb_max = (pj_uint16_t) v; |
| 845 | break; |
| 846 | |
| 847 | case PJMEDIA_RTCP_XR_INFO_JB_ABS_MAX: |
| 848 | sess->stat.rx.voip_mtc.jb_abs_max = (pj_uint16_t) v; |
| 849 | break; |
| 850 | |
| 851 | default: |
| 852 | return PJ_EINVAL; |
| 853 | } |
| 854 | |
| 855 | return PJ_SUCCESS; |
| 856 | } |
| 857 | |
| 858 | #endif |