From 23bc99ed814bf6ca20e4d578d4154f9daf4c7520 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 21 Apr 2017 14:10:42 -0400 Subject: [PATCH] TT#12800 consider RTCP XR reports for MOS calculations and stats Change-Id: I18f6ff92bab432caf446c59395ab253a305378f4 --- daemon/rtcp.c | 53 ++++++++++++--- daemon/ssrc.c | 177 +++++++++++++++++++++++++++++++++++++------------- daemon/ssrc.h | 39 +++++++++-- 3 files changed, 210 insertions(+), 59 deletions(-) diff --git a/daemon/rtcp.c b/daemon/rtcp.c index 64b47e25f..0a477e4c9 100644 --- a/daemon/rtcp.c +++ b/daemon/rtcp.c @@ -136,8 +136,8 @@ struct xr_packet { struct xr_rb_rr_time { struct xr_report_block header; - u_int32_t ntp_sec; /**< NTP time, seconds part. */ - u_int32_t ntp_frac; /**< NTP time, fractions part. */ + u_int32_t ntp_msw; /**< NTP time, seconds part. */ + u_int32_t ntp_lsw; /**< NTP time, fractions part. */ } __attribute__ ((packed)); struct xr_rb_dlrr_item { @@ -235,6 +235,8 @@ struct rtcp_process_ctx { struct ssrc_receiver_report rr; struct ssrc_sender_report sr; struct ssrc_xr_voip_metrics xr_vm; + struct ssrc_xr_rr_time xr_rr; + struct ssrc_xr_dlrr xr_dlrr; } scratch; u_int32_t scratch_common_ssrc; @@ -285,11 +287,16 @@ static void dummy_handler(); static void scratch_common(struct rtcp_process_ctx *, const struct rtcp_packet *); static void scratch_sr(struct rtcp_process_ctx *, const struct sender_report_packet *); static void scratch_rr(struct rtcp_process_ctx *, const struct report_block *); +static void scratch_xr_rr_time(struct rtcp_process_ctx *, const struct xr_rb_rr_time *); +static void scratch_xr_dlrr(struct rtcp_process_ctx *, const struct xr_rb_dlrr *); static void scratch_xr_voip_metrics(struct rtcp_process_ctx *, const struct xr_rb_voip_metrics *); // MOS calculation / stats static void mos_sr(struct rtcp_process_ctx *, const struct sender_report_packet *); static void mos_rr(struct rtcp_process_ctx *, const struct report_block *); +static void mos_xr_rr_time(struct rtcp_process_ctx *, const struct xr_rb_rr_time *); +static void mos_xr_dlrr(struct rtcp_process_ctx *, const struct xr_rb_dlrr *); +static void mos_xr_voip_metrics(struct rtcp_process_ctx *, const struct xr_rb_voip_metrics *); // homer functions static void homer_init(struct rtcp_process_ctx *); @@ -326,11 +333,16 @@ static struct rtcp_handler scratch_handlers = { .common = scratch_common, .rr = scratch_rr, .sr = scratch_sr, + .xr_rr_time = scratch_xr_rr_time, + .xr_dlrr = scratch_xr_dlrr, .xr_voip_metrics = scratch_xr_voip_metrics, }; static struct rtcp_handler mos_handlers = { .rr = mos_rr, .sr = mos_sr, + .xr_rr_time = mos_xr_rr_time, + .xr_dlrr = mos_xr_dlrr, + .xr_voip_metrics = mos_xr_voip_metrics, }; static struct rtcp_handler log_handlers = { .init = logging_init, @@ -556,6 +568,7 @@ static void xr_rr_time(struct xr_rb_rr_time *rb, struct rtcp_process_ctx *log_ct CAH(xr_rr_time, rb); } static void xr_dlrr(struct xr_rb_dlrr *rb, struct rtcp_process_ctx *log_ctx) { + // XXX support multiple report blocks CAH(xr_rb, &rb->header); CAH(xr_dlrr, rb); } @@ -918,10 +931,25 @@ static void scratch_sr(struct rtcp_process_ctx *ctx, const struct sender_report_ .timestamp = ntohl(sr->timestamp), .packet_count = ntohl(sr->packet_count), }; - ctx->scratch.sr.ntp_ts = ntp_ts_to_double(ctx->scratch.sr.ntp_msw, ctx->scratch.sr.ntp_lsw); +} +static void scratch_xr_rr_time(struct rtcp_process_ctx *ctx, const struct xr_rb_rr_time *rr) { + ctx->scratch.xr_rr = (struct ssrc_xr_rr_time) { + .ssrc = ctx->scratch_common_ssrc, + .ntp_msw = ntohl(rr->ntp_msw), + .ntp_lsw = ntohl(rr->ntp_lsw), + }; +} +static void scratch_xr_dlrr(struct rtcp_process_ctx *ctx, const struct xr_rb_dlrr *dlrr) { + ctx->scratch.xr_dlrr = (struct ssrc_xr_dlrr) { + .from = ctx->scratch_common_ssrc, + .ssrc = htonl(dlrr->item.ssrc), + .lrr = ntohl(dlrr->item.lrr), + .dlrr = ntohl(dlrr->item.dlrr), + }; } static void scratch_xr_voip_metrics(struct rtcp_process_ctx *ctx, const struct xr_rb_voip_metrics *vm) { ctx->scratch.xr_vm = (struct ssrc_xr_voip_metrics) { + .from = ctx->scratch_common_ssrc, .ssrc = ntohl(vm->ssrc), .loss_rate = vm->loss_rate, .discard_rate = vm->discard_rate, @@ -1099,14 +1127,14 @@ static void logging_xr_rb(struct rtcp_process_ctx *ctx, const struct xr_report_b } static void logging_xr_rr_time(struct rtcp_process_ctx *ctx, const struct xr_rb_rr_time *rb_rr_time) { g_string_append_printf(ctx->log, "rb_rr_time_ntp_sec=%u, rb_rr_time_ntp_frac=%u, ", - ntohl(rb_rr_time->ntp_sec), - ntohl(rb_rr_time->ntp_frac)); + ntohl(ctx->scratch.xr_rr.ntp_msw), + ntohl(ctx->scratch.xr_rr.ntp_lsw)); } static void logging_xr_dlrr(struct rtcp_process_ctx *ctx, const struct xr_rb_dlrr *rb_dlrr) { g_string_append_printf(ctx->log, "rb_dlrr_ssrc=%u, rb_dlrr_lrr=%u, rb_dlrr_dlrr=%u, ", - ntohl(rb_dlrr->item.ssrc), - ntohl(rb_dlrr->item.lrr), - ntohl(rb_dlrr->item.dlrr)); + ntohl(ctx->scratch.xr_dlrr.ssrc), + ntohl(ctx->scratch.xr_dlrr.lrr), + ntohl(ctx->scratch.xr_dlrr.dlrr)); } static void logging_xr_stats(struct rtcp_process_ctx *ctx, const struct xr_rb_stats *rb_stats) { g_string_append_printf(ctx->log, "rb_stats_ssrc=%u, rb_stats_begin_seq=%u, rb_stats_end_seq=%u, rb_stats_lost_packets=%u, rb_stats_duplicate_packets=%u," @@ -1180,6 +1208,15 @@ static void mos_sr(struct rtcp_process_ctx *ctx, const struct sender_report_pack static void mos_rr(struct rtcp_process_ctx *ctx, const struct report_block *rr) { ssrc_receiver_report(ctx->media, &ctx->scratch.rr, ctx->received); } +static void mos_xr_rr_time(struct rtcp_process_ctx *ctx, const struct xr_rb_rr_time *rr) { + ssrc_receiver_rr_time(ctx->media, &ctx->scratch.xr_rr, ctx->received); +} +static void mos_xr_dlrr(struct rtcp_process_ctx *ctx, const struct xr_rb_dlrr *dlrr) { + ssrc_receiver_dlrr(ctx->media, &ctx->scratch.xr_dlrr, ctx->received); +} +static void mos_xr_voip_metrics(struct rtcp_process_ctx *ctx, const struct xr_rb_voip_metrics *rb_voip_mtc) { + ssrc_voip_metrics(ctx->media, &ctx->scratch.xr_vm, ctx->received); +} diff --git a/daemon/ssrc.c b/daemon/ssrc.c index 5a0b5d199..74c734008 100644 --- a/daemon/ssrc.c +++ b/daemon/ssrc.c @@ -25,11 +25,15 @@ static void add_ssrc_entry(struct ssrc_entry *ent, struct ssrc_hash *ht) { static void free_sender_report(struct ssrc_sender_report_item *i) { g_slice_free1(sizeof(*i), i); } +static void free_rr_time(struct ssrc_rr_time_item *i) { + g_slice_free1(sizeof(*i), i); +} static void free_stats_block(struct ssrc_stats_block *ssb) { g_slice_free1(sizeof(*ssb), ssb); } static void free_ssrc_entry(struct ssrc_entry *e) { g_queue_clear_full(&e->sender_reports, (GDestroyNotify) free_sender_report); + g_queue_clear_full(&e->rr_time_reports, (GDestroyNotify) free_rr_time); g_queue_clear_full(&e->stats_blocks, (GDestroyNotify) free_stats_block); g_slice_free1(sizeof(*e), e); } @@ -44,6 +48,8 @@ static void mos_calc(struct ssrc_stats_block *ssb) { else r = 93.2 - (eff_rtt - 120) / 40.0; r = r - (ssb->packetloss * 2.5); + if (r < 0) + r = 0; double mos = 1.0 + (0.035) * r + (.000007) * r * (r-60) * (100-r); int64_t intmos = mos * 10.0; if (intmos < 0) @@ -117,88 +123,124 @@ struct ssrc_ctx *get_ssrc_ctx(u_int32_t ssrc, struct ssrc_hash *ht, enum ssrc_di -void ssrc_sender_report(struct call_media *m, const struct ssrc_sender_report *sr, - const struct timeval *tv) +static void *__do_time_report_item(struct call_media *m, size_t struct_size, size_t reports_queue_offset, + const struct timeval *tv, u_int32_t ssrc, u_int32_t ntp_msw, u_int32_t ntp_lsw, + GDestroyNotify free_func, struct ssrc_entry **e_p) { struct call *c = m->call; struct ssrc_entry *e; - struct ssrc_sender_report_item *seri; - - seri = g_slice_alloc(sizeof(*seri)); - seri->received = *tv; - seri->report = *sr; - seri->ntp_middle_bits = sr->ntp_msw << 16 | sr->ntp_lsw >> 16; + struct ssrc_time_item *sti; - ilog(LOG_DEBUG, "SR from %u: RTP TS %u PC %u OC %u NTP TS %u/%u=%f", - sr->ssrc, sr->timestamp, sr->packet_count, sr->octet_count, - sr->ntp_msw, sr->ntp_lsw, sr->ntp_ts); + sti = g_slice_alloc0(struct_size); + sti->received = *tv; + sti->ntp_middle_bits = ntp_msw << 16 | ntp_lsw >> 16; + sti->ntp_ts = ntp_ts_to_double(ntp_msw, ntp_lsw); - e = get_ssrc(sr->ssrc, c->ssrc_hash); + e = get_ssrc(ssrc, c->ssrc_hash); if (G_UNLIKELY(!e)) { - free_sender_report(seri); - return; + free_func(sti); + return NULL; } mutex_lock(&e->lock); - g_queue_push_tail(&e->sender_reports, seri); - while (e->sender_reports.length > 10) - free_sender_report(g_queue_pop_head(&e->sender_reports)); + GQueue *q = (((void *) e) + reports_queue_offset); - mutex_unlock(&e->lock); + g_queue_push_tail(q, sti); + while (q->length > 10) + free_func(g_queue_pop_head(q)); + + *e_p = e; + return sti; } -void ssrc_receiver_report(struct call_media *m, const struct ssrc_receiver_report *rr, - const struct timeval *tv) -{ - struct call *c = m->call; - ilog(LOG_DEBUG, "RR from %u about %u: FL %u TL %u HSR %u J %u LSR %u DLSR %u", - rr->from, rr->ssrc, rr->fraction_lost, rr->packets_lost, - rr->high_seq_received, rr->jitter, rr->lsr, rr->dlsr); +static long long __calc_rtt(struct call *c, u_int32_t ssrc, u_int32_t ntp_middle_bits, + u_int32_t delay, size_t reports_queue_offset, const struct timeval *tv, int *pt_p) +{ + if (pt_p) + *pt_p = -1; - if (!rr->lsr || !rr->dlsr) - return; // no delay to be known + if (!ntp_middle_bits || !delay) + return 0; - struct ssrc_entry *e = get_ssrc(rr->ssrc, c->ssrc_hash); + struct ssrc_entry *e = get_ssrc(ssrc, c->ssrc_hash); if (G_UNLIKELY(!e)) - return; + return 0; + + if (pt_p) + *pt_p = e->payload_type; - struct ssrc_sender_report_item *seri; + struct ssrc_time_item *sti; + GQueue *q = (((void *) e) + reports_queue_offset); mutex_lock(&e->lock); // go through the list backwards until we find the SR referenced - for (GList *l = e->sender_reports.tail; l; l = l->prev) { - seri = l->data; - if (seri->ntp_middle_bits != rr->lsr) + for (GList *l = q->tail; l; l = l->prev) { + sti = l->data; + if (sti->ntp_middle_bits != ntp_middle_bits) continue; goto found; } // not found - goto out_ul_e; + mutex_unlock(&e->lock); + return 0; -found: - // `e` remains locked for access to `seri` - ilog(LOG_DEBUG, "RR from %u reports delay %u from %u", rr->from, rr->dlsr, rr->ssrc); - long long rtt = timeval_diff(tv, &seri->received); +found:; + // `e` remains locked for access to `sti` + long long rtt = timeval_diff(tv, &sti->received); mutex_unlock(&e->lock); - rtt -= (long long) rr->dlsr * 1000000LL / 65536LL; - ilog(LOG_DEBUG, "Calculated round-trip time for %u is %lli us", rr->ssrc, rtt); + rtt -= (long long) delay * 1000000LL / 65536LL; + ilog(LOG_DEBUG, "Calculated round-trip time for %u is %lli us", ssrc, rtt); if (rtt <= 0 || rtt > 10000000) { ilog(LOG_DEBUG, "Invalid RTT - discarding"); - goto out_nl; + return 0; } e->last_rtt = rtt; + return rtt; +} + +void ssrc_sender_report(struct call_media *m, const struct ssrc_sender_report *sr, + const struct timeval *tv) +{ + struct ssrc_entry *e; + struct ssrc_sender_report_item *seri = __do_time_report_item(m, sizeof(*seri), + G_STRUCT_OFFSET(struct ssrc_entry, sender_reports), tv, sr->ssrc, + sr->ntp_msw, sr->ntp_lsw, (GDestroyNotify) free_sender_report, &e); + if (!seri) + return; + + seri->report = *sr; + + ilog(LOG_DEBUG, "SR from %u: RTP TS %u PC %u OC %u NTP TS %u/%u=%f", + sr->ssrc, sr->timestamp, sr->packet_count, sr->octet_count, + sr->ntp_msw, sr->ntp_lsw, seri->time_item.ntp_ts); + + mutex_unlock(&e->lock); +} +void ssrc_receiver_report(struct call_media *m, const struct ssrc_receiver_report *rr, + const struct timeval *tv) +{ + struct call *c = m->call; + + ilog(LOG_DEBUG, "RR from %u about %u: FL %u TL %u HSR %u J %u LSR %u DLSR %u", + rr->from, rr->ssrc, rr->fraction_lost, rr->packets_lost, + rr->high_seq_received, rr->jitter, rr->lsr, rr->dlsr); + + int pt; + + long long rtt = __calc_rtt(c, rr->ssrc, rr->lsr, rr->dlsr, + G_STRUCT_OFFSET(struct ssrc_entry, sender_reports), tv, &pt); + struct ssrc_entry *other_e = get_ssrc(rr->from, c->ssrc_hash); if (G_UNLIKELY(!other_e)) goto out_nl; // determine the clock rate for jitter values - int pt = e->payload_type; if (pt < 0) { pt = other_e->payload_type; if (pt < 0) { @@ -255,12 +297,57 @@ found: goto out_ul_oe; -out_ul_e: - mutex_unlock(&e->lock); - goto out_nl; out_ul_oe: mutex_unlock(&other_e->lock); goto out_nl; out_nl: ; } + +void ssrc_receiver_rr_time(struct call_media *m, const struct ssrc_xr_rr_time *rr, + const struct timeval *tv) +{ + struct ssrc_entry *e; + struct ssrc_rr_time_item *srti = __do_time_report_item(m, sizeof(*srti), + G_STRUCT_OFFSET(struct ssrc_entry, rr_time_reports), tv, rr->ssrc, + rr->ntp_msw, rr->ntp_lsw, (GDestroyNotify) free_rr_time, &e); + if (!srti) + return; + + ilog(LOG_DEBUG, "XR RR TIME from %u: NTP TS %u/%u=%f", + rr->ssrc, + rr->ntp_msw, rr->ntp_lsw, srti->time_item.ntp_ts); + + mutex_unlock(&e->lock); +} + +void ssrc_receiver_dlrr(struct call_media *m, const struct ssrc_xr_dlrr *dlrr, + const struct timeval *tv) +{ + ilog(LOG_DEBUG, "XR DLRR from %u about %u: LRR %u DLRR %u", + dlrr->from, dlrr->ssrc, + dlrr->lrr, dlrr->dlrr); + + __calc_rtt(m->call, dlrr->ssrc, dlrr->lrr, dlrr->dlrr, + G_STRUCT_OFFSET(struct ssrc_entry, rr_time_reports), tv, NULL); +} + +void ssrc_voip_metrics(struct call_media *m, const struct ssrc_xr_voip_metrics *vm, + const struct timeval *tv) +{ + ilog(LOG_DEBUG, "XR VM from %u about %u: LR %u DR %u BD %u GD %u BDu %u GDu %u RTD %u " + "ESD %u SL %u NL %u RERL %u GMin %u R %u eR %u MOSL %u MOSC %u RX %u " + "JBn %u JBm %u JBam %u", + vm->from, vm->ssrc, + vm->loss_rate, vm->discard_rate, vm->burst_den, vm->gap_den, + vm->burst_dur, vm->gap_dur, vm->rnd_trip_delay, vm->end_sys_delay, + vm->signal_lvl, vm->noise_lvl, vm->rerl, vm->gmin, vm->r_factor, + vm->ext_r_factor, vm->mos_lq, vm->mos_cq, vm->rx_config, vm->jb_nom, + vm->jb_max, vm->jb_abs_max); + + struct call *c = m->call; + struct ssrc_entry *e = get_ssrc(vm->ssrc, c->ssrc_hash); + if (!e) + return; + e->last_rtt = vm->rnd_trip_delay; +} diff --git a/daemon/ssrc.h b/daemon/ssrc.h index 4969c9fbe..93755edf3 100644 --- a/daemon/ssrc.h +++ b/daemon/ssrc.h @@ -45,6 +45,7 @@ struct ssrc_entry { struct ssrc_ctx input_ctx, output_ctx; GQueue sender_reports; // as received via RTCP + GQueue rr_time_reports; // as received via RTCP GQueue stats_blocks; // calculated struct ssrc_stats_block *lowest_mos, *highest_mos, @@ -57,6 +58,11 @@ enum ssrc_dir { // these values must not be used externally SSRC_DIR_OUTPUT = G_STRUCT_OFFSET(struct ssrc_entry, output_ctx), }; +struct ssrc_time_item { + struct timeval received; + u_int32_t ntp_middle_bits; // to match up with lsr/dlrr + double ntp_ts; // XXX convert to int? +}; struct ssrc_sender_report { u_int32_t ssrc; u_int32_t ntp_msw; @@ -64,11 +70,9 @@ struct ssrc_sender_report { u_int32_t timestamp; u_int32_t packet_count; u_int32_t octet_count; - double ntp_ts; }; struct ssrc_sender_report_item { - struct timeval received; - u_int32_t ntp_middle_bits; // to match up with rr->lsr + struct ssrc_time_item time_item; // must be first; struct ssrc_sender_report report; }; @@ -82,12 +86,29 @@ struct ssrc_receiver_report { u_int32_t lsr; u_int32_t dlsr; }; -struct ssrc_receiver_report_item { - struct timeval received; - struct ssrc_receiver_report report; +//struct ssrc_receiver_report_item { +// struct timeval received; +// struct ssrc_receiver_report report; +//}; + +struct ssrc_xr_rr_time { + u_int32_t ssrc; + u_int32_t ntp_msw; + u_int32_t ntp_lsw; +}; +struct ssrc_rr_time_item { + struct ssrc_time_item time_item; // must be first; +}; + +struct ssrc_xr_dlrr { + u_int32_t from; + u_int32_t ssrc; + u_int32_t lrr; + u_int32_t dlrr; }; struct ssrc_xr_voip_metrics { + u_int32_t from; u_int32_t ssrc; u_int8_t loss_rate; u_int8_t discard_rate; @@ -126,6 +147,12 @@ struct ssrc_ctx *get_ssrc_ctx(u_int32_t, struct ssrc_hash *, enum ssrc_dir); // void ssrc_sender_report(struct call_media *, const struct ssrc_sender_report *, const struct timeval *); void ssrc_receiver_report(struct call_media *, const struct ssrc_receiver_report *, const struct timeval *); +void ssrc_receiver_rr_time(struct call_media *m, const struct ssrc_xr_rr_time *rr, + const struct timeval *); +void ssrc_receiver_dlrr(struct call_media *m, const struct ssrc_xr_dlrr *dlrr, + const struct timeval *); +void ssrc_voip_metrics(struct call_media *m, const struct ssrc_xr_voip_metrics *vm, + const struct timeval *);