diff --git a/daemon/media_socket.c b/daemon/media_socket.c index e54c3cf73..42242a1e9 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -1232,6 +1232,8 @@ loop_ok: if (media->protocol && media->protocol->rtp && !rtcp && !rtp_payload(&rtp_h, NULL, s)) { i = (rtp_h->m_pt & 0x7f); + // XXX two hash table lookups for each packet, not ideal + // XXX limit size of hash tables rtp_s = g_hash_table_lookup(stream->rtp_stats, &i); if (!rtp_s) { ilog(LOG_WARNING | LOG_FLAG_LIMIT, @@ -1243,6 +1245,9 @@ loop_ok: else { atomic64_inc(&rtp_s->packets); atomic64_add(&rtp_s->bytes, s->len); + + struct ssrc_entry *se = get_ssrc(ntohl(rtp_h->ssrc), call->ssrc_hash); + se->payload_type = i; } } diff --git a/daemon/rtcp.c b/daemon/rtcp.c index 243595163..da0c377d9 100644 --- a/daemon/rtcp.c +++ b/daemon/rtcp.c @@ -227,6 +227,7 @@ struct rtcp_chain_element { struct rtcp_process_ctx { // input struct call *call; + struct call_media *media; const struct timeval *received; // handler vars @@ -616,6 +617,7 @@ static int __rtcp_parse(GQueue *q, const str *_s, struct stream_fd *sfd, const e rtcp_handler_func func; str s = *_s; struct call *c = sfd->call; + struct call_media *m = sfd->stream->media; struct rtcp_process_ctx log_ctx_s, *log_ctx; unsigned int len; @@ -624,6 +626,7 @@ static int __rtcp_parse(GQueue *q, const str *_s, struct stream_fd *sfd, const e ZERO(log_ctx_s); log_ctx_s.call = c; + log_ctx_s.media = m; log_ctx_s.received = tv; log_ctx = &log_ctx_s; @@ -1162,10 +1165,10 @@ static void logging_destroy(struct rtcp_process_ctx *ctx) { static void mos_sr(struct rtcp_process_ctx *ctx, const struct sender_report_packet *sr) { - ssrc_sender_report(ctx->call, &ctx->scratch.sr, ctx->received); + ssrc_sender_report(ctx->media, &ctx->scratch.sr, ctx->received); } static void mos_rr(struct rtcp_process_ctx *ctx, const struct report_block *rr) { - ssrc_receiver_report(ctx->call, &ctx->scratch.rr, ctx->received); + ssrc_receiver_report(ctx->media, &ctx->scratch.rr, ctx->received); } diff --git a/daemon/ssrc.c b/daemon/ssrc.c index 4a7cc4be3..82f84ecdb 100644 --- a/daemon/ssrc.c +++ b/daemon/ssrc.c @@ -2,6 +2,7 @@ #include #include "aux.h" #include "call.h" +#include "rtplib.h" @@ -10,6 +11,7 @@ static struct ssrc_entry *create_ssrc_entry(u_int32_t ssrc) { ent = g_slice_alloc0(sizeof(struct ssrc_entry)); ent->ssrc = ssrc; mutex_init(&ent->lock); + ent->payload_type = -1; return ent; } static void add_ssrc_entry(struct ssrc_entry *ent, struct ssrc_hash *ht) { @@ -19,12 +21,30 @@ static void free_sender_report(void *p) { struct ssrc_sender_report_item *i = p; g_slice_free1(sizeof(*i), i); } +static void free_stats_block(void *p) { + struct ssrc_stats_block *ssb = p; + g_slice_free1(sizeof(*ssb), ssb); +} static void free_ssrc_entry(void *p) { struct ssrc_entry *e = p; g_queue_clear_full(&e->sender_reports, free_sender_report); + g_queue_clear_full(&e->stats_blocks, free_stats_block); g_slice_free1(sizeof(*e), e); } +// returned as mos * 10 (i.e. 10 - 50 for 1.0 to 5.0) +static void mos_calc(struct ssrc_stats_block *ssb) { + // as per https://www.pingman.com/kb/article/how-is-mos-calculated-in-pingplotter-pro-50.html + int eff_rtt = ssb->rtt / 1000 + ssb->jitter * 2 + 10; + double r; + if (eff_rtt < 160) + r = 93.2 - eff_rtt / 40.0; + else + r = 93.2 - (eff_rtt - 120) / 40.0; + r = r - (ssb->packetloss * 2.5); + double mos = 1.0 + (0.035) * r + (.000007) * r * (r-60) * (100-r); + ssb->mos = mos * 10; +} struct ssrc_entry *find_ssrc(u_int32_t ssrc, struct ssrc_hash *ht) { rwlock_lock_r(&ht->lock); @@ -82,9 +102,10 @@ struct ssrc_ctx *get_ssrc_ctx(u_int32_t ssrc, struct ssrc_hash *ht, enum ssrc_di -void ssrc_sender_report(struct call *c, const struct ssrc_sender_report *sr, +void ssrc_sender_report(struct call_media *m, const struct ssrc_sender_report *sr, const struct timeval *tv) { + struct call *c = m->call; struct ssrc_entry *e; struct ssrc_sender_report_item *seri; @@ -102,12 +123,16 @@ void ssrc_sender_report(struct call *c, const struct ssrc_sender_report *sr, 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)); mutex_unlock(&e->lock); } -void ssrc_receiver_report(struct call *c, const struct ssrc_receiver_report *rr, +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); @@ -116,18 +141,77 @@ void ssrc_receiver_report(struct call *c, const struct ssrc_receiver_report *rr, return; // no delay to be known struct ssrc_entry *e = get_ssrc(rr->ssrc, c->ssrc_hash); + struct ssrc_sender_report_item *seri; mutex_lock(&e->lock); - // go through the list backwards until we find the SR referenced, up to 10 steps - int i = 0; - for (GList *l = e->sender_reports.tail; - l && i < 10; - l = l->prev, i++) - { - struct ssrc_sender_report_item *seri = l->data; + // 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) continue; - ilog(LOG_DEBUG, "RR from %u reports delay %u from %u", rr->from, rr->dlsr, rr->ssrc); - break; + goto found; + } + + // not found + goto out; + +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); + + 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); + + if (rtt <= 0 || rtt > 10000000) { + ilog(LOG_DEBUG, "Invalid RTT - discarding"); + goto out_nl; } + + e->last_rtt = rtt; + + struct ssrc_entry *other_e = get_ssrc(rr->from, c->ssrc_hash); + + // determine the clock rate for jitter values + int pt = e->payload_type; + if (pt < 0) { + pt = other_e->payload_type; + if (pt < 0) { + ilog(LOG_DEBUG, "No payload type known for RTCP RR, discarding"); + goto out_nl; + } + } + + const struct rtp_payload_type *rpt = rtp_payload_type(pt, m->rtp_payload_types); + if (!rpt) { + ilog(LOG_INFO, "Invalid RTP payload type %i, discarding RTCP RR", pt); + goto out_nl; + } + unsigned int jitter = rpt->clock_rate ? (rr->jitter * 1000 / rpt->clock_rate) : rr->jitter; + ilog(LOG_DEBUG, "Calculated jitter for %u is %u ms", rr->ssrc, jitter); + + ilog(LOG_DEBUG, "Adding opposide side RTT of %u us", other_e->last_rtt); + + struct ssrc_stats_block *ssb = g_slice_alloc(sizeof(*ssb)); + *ssb = (struct ssrc_stats_block) { + .jitter = jitter, + .rtt = rtt + other_e->last_rtt, + .reported = *tv, + .packetloss = (unsigned int) rr->fraction_lost * 100 / 256, + }; + + mos_calc(ssb); + ilog(LOG_DEBUG, "Calculated MOS from RR for %u is %.1f", rr->from, (double) ssb->mos / 10.0); + + // got a new stats block, add it to reporting ssrc + e = get_ssrc(rr->from, c->ssrc_hash); + mutex_lock(&e->lock); + g_queue_push_tail(&e->stats_blocks, ssb); + goto out; + +out: mutex_unlock(&e->lock); +out_nl: + ; } diff --git a/daemon/ssrc.h b/daemon/ssrc.h index c6707fd25..0f29506ae 100644 --- a/daemon/ssrc.h +++ b/daemon/ssrc.h @@ -11,7 +11,9 @@ struct call; +struct call_media; struct timeval; +struct rtp_payload_type; @@ -29,13 +31,24 @@ struct ssrc_entry { u_int32_t ssrc; struct ssrc_ctx input_ctx, output_ctx; - GQueue sender_reports; + GQueue sender_reports; // as received via RTCP + GQueue stats_blocks; // calculated + int payload_type; // to determine the clock rate for jitter calculations + unsigned int last_rtt; // last calculated raw rtt without rtt from opposide side }; enum ssrc_dir { SSRC_DIR_INPUT = G_STRUCT_OFFSET(struct ssrc_entry, input_ctx), SSRC_DIR_OUTPUT = G_STRUCT_OFFSET(struct ssrc_entry, output_ctx), }; +struct ssrc_stats_block { + struct timeval reported; + unsigned int jitter; // ms + unsigned int rtt; // us - combined from both sides + unsigned int packetloss; // percent + int mos; // nominal range of 10 - 50 for MOS values 1.0 to 5.0 +}; + struct ssrc_sender_report { u_int32_t ssrc; u_int32_t ntp_msw; @@ -102,8 +115,8 @@ struct ssrc_entry *get_ssrc(u_int32_t, struct ssrc_hash * /* , int *created */); struct ssrc_ctx *get_ssrc_ctx(u_int32_t, struct ssrc_hash *, enum ssrc_dir); // creates new entry if not found -void ssrc_sender_report(struct call *, const struct ssrc_sender_report *, const struct timeval *); -void ssrc_receiver_report(struct call *, const struct ssrc_receiver_report *, +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 *);