From ef4e548dd8c7bff3f59c2ec79646e3be5c4fdaeb Mon Sep 17 00:00:00 2001 From: Richard Fuchs <rfuchs@sipwise.com> Date: Mon, 14 Oct 2024 12:55:02 -0400 Subject: [PATCH] MT#61263 update MOS formula Add support for simplified G.107 formula, with math changed to fixed-point. Retain legacy formula as an option for backwards compatibility. Change-Id: I999fc7de7be1407876c201c251538cea72b04008 --- daemon/main.c | 2 + daemon/ssrc.c | 62 +++++++++++++++++++++++++++++- docs/rtpengine.md | 23 ++++++++--- lib/codeclib.h | 3 +- t/auto-daemon-tests-measure-rtp.pl | 18 +++++---- t/auto-daemon-tests.pl | 16 ++++---- 6 files changed, 100 insertions(+), 24 deletions(-) diff --git a/daemon/main.c b/daemon/main.c index 5ef488fa8..c7da7a7fe 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -1039,6 +1039,8 @@ static void options(int *argc, char ***argv) { #ifdef WITH_TRANSCODING else if (!strcasecmp(mos, "legacy")) rtpe_config.common.mos_type = MOS_LEGACY; + else if (!strcasecmp(mos, "g107") || !strcasecmp(mos, "g.107")) + rtpe_config.common.mos_type = MOS_LEGACY; #endif else die("Invalid --mos option ('%s')", mos); diff --git a/daemon/ssrc.c b/daemon/ssrc.c index bd7234460..6a7d6ba2a 100644 --- a/daemon/ssrc.c +++ b/daemon/ssrc.c @@ -12,7 +12,10 @@ typedef void mos_calc_fn(struct ssrc_stats_block *ssb); static mos_calc_fn mos_calc_legacy; #ifdef WITH_TRANSCODING +static mos_calc_fn mos_calc_nb; + static mos_calc_fn *mos_calcs[__MOS_TYPES] = { + [MOS_NB] = mos_calc_nb, [MOS_LEGACY] = mos_calc_legacy, }; #endif @@ -75,6 +78,60 @@ static void ssrc_entry_put(void *ep) { obj_put(&e->h); } +#ifdef WITH_TRANSCODING +// returned as mos * 10 (i.e. 10 - 50 for 1.0 to 5.0) +static int64_t mos_from_rx(int64_t Rx) { + // Rx in e5 + + int64_t intmos; + if (Rx < 0) + intmos = 10; // e1 + else if (Rx > 10000000) // e5 + intmos = 45; // e1 + else { + Rx /= 100; // e5 -> e3 + intmos = 100; // e2 + intmos += 35 * Rx / 10000; // e2 + int64_t RxRx = (Rx - 60000) * (100000 - Rx); // e6 + RxRx /= 1000; // e6 -> e3 + RxRx = Rx * RxRx; // e6 + RxRx /= 1000; // e6 -> e3 + RxRx *= 7; // e9 + RxRx /= 10000000; // e9 -> e2 + intmos += RxRx; // e2 + intmos /= 10; // e2 -> e1 + if (intmos < 10) + intmos = 10; + } + return intmos; +} + +static void mos_calc_nb(struct ssrc_stats_block *ssb) { + uint64_t rtt = ssb->rtt; + if (rtpe_config.mos == MOS_CQ && !rtt) + return; // can not compute the MOS-CQ unless we have a valid RTT + else if (rtpe_config.mos == MOS_LQ) + rtt = 0; // ignore RTT + + // G.107 simplified, original formula in milliseconds (e0) + rtt /= 2; + rtt += ssb->jitter * 1000; // ms -> us, e0 -> e3 + uint64_t Id = (24 * rtt) / 1000; // e3 + if (rtt > 177300) + Id += ((rtt - 177300) * 11) / 100; // e3 + uint64_t r_factor = 0; + if (ssb->packetloss <= 93) + r_factor = 9320 - ssb->packetloss * 100; // e2 + int64_t Rx = 18 * r_factor * r_factor; // e6 + Rx /= 10; // e6 -> e5 + Rx -= 279 * r_factor * 100; // e5 + Rx += 112662000; // e5 + Rx -= Id * 100; // e5 + + ssb->mos = mos_from_rx(Rx); +} +#endif + // returned as mos * 10 (i.e. 10 - 50 for 1.0 to 5.0) static void mos_calc_legacy(struct ssrc_stats_block *ssb) { uint64_t rtt = ssb->rtt; @@ -431,10 +488,13 @@ void ssrc_receiver_report(struct call_media *m, stream_fd *sfd, const struct ssr RTPE_SAMPLE_SFD(rtt_dsct, rtt, sfd); RTPE_SAMPLE_SFD(packetloss, ssb->packetloss, sfd); - mos_calc_fn *mos_calc = mos_calc_legacy; + mos_calc_fn *mos_calc; #ifdef WITH_TRANSCODING + mos_calc = mos_calc_nb; if (rpt->codec_def) mos_calc = mos_calcs[rpt->codec_def->mos_type]; +#else + mos_calc = mos_calc_legacy; #endif other_e->packets_lost = rr->packets_lost; diff --git a/docs/rtpengine.md b/docs/rtpengine.md index 0be6c26c8..c7067104d 100644 --- a/docs/rtpengine.md +++ b/docs/rtpengine.md @@ -1300,12 +1300,23 @@ call to inject-DTMF won't be sent to __\-\-dtmf-log-dest=__ or __\-\-listen-tcp- with stats for that call media every *interval* milliseconds, plus one message every *interval* milliseconds with global stats. -- __\-\-mos=CQ__\|__LQ__ - - MOS (Mean Opinion Score) calculation formula. Defaults to __CQ__ (conversational - quality) which takes RTT into account and therefore requires peers to correctly - send RTCP. If set to __LQ__ (listening quality) RTT is ignored, allowing a MOS to - be calculated in the absence of RTCP. +- __\-\-mos=CQ__\|__LQ__\|__G.107__\|__legacy__ + + Options influencing the MOS (Mean Opinion Score) calculation formula. + Multiple options can be listed, using multiple __\-\-mos=...__ arguments at + the command line, or using a semicolon-separated list in a single + __mos=...__ line in the config file. + + __CQ__ and __LQ__ are mutually exclusive and only one of them can be in + effect. Defaults to __CQ__ (conversational quality) which takes RTT into + account and therefore requires peers to correctly send RTCP. If set to + __LQ__ (listening quality) RTT is ignored, allowing a MOS to be calculated + in the absence of RTCP. + + The remaining options select a MOS formula and are mutually exclusive. The + default is __G.107__, which uses a simplified version of the G.107 formula. + The previous default (and only option) was __legacy__, which uses a custom + formula which yields slightly higher MOS values than G.107. - __\-\-measure-rtp__ diff --git a/lib/codeclib.h b/lib/codeclib.h index 3b2b21054..9e4543af3 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -199,7 +199,8 @@ struct codec_def_s { const enum media_type media_type; const str silence_pattern; enum { - MOS_LEGACY = 0, // default + MOS_NB = 0, // default + MOS_LEGACY, __MOS_TYPES } mos_type; diff --git a/t/auto-daemon-tests-measure-rtp.pl b/t/auto-daemon-tests-measure-rtp.pl index 737e734f9..e9c6641a4 100755 --- a/t/auto-daemon-tests-measure-rtp.pl +++ b/t/auto-daemon-tests-measure-rtp.pl @@ -82,14 +82,14 @@ $resp = rtpe_req('delete', 'MOS basic', { }); cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, '>=', 1, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, '<=', 3, 'metric matches'; -is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 44, 'metric matches'; +is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 43, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, '>=', 0, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, '<=', 1, 'metric matches'; is $resp->{SSRC}{0x1234567}{'average MOS'}{'packet loss'}, 0, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, '>=', 1, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, '<=', 3, 'metric matches'; -is $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, 44, 'metric matches'; +is $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, 43, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, '>=', 0, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, '<=', 1, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{'packet loss'}, 0, 'metric matches'; @@ -154,7 +154,8 @@ $resp = rtpe_req('delete', 'MOS PL', { }); cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, '>=', 1, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, '<=', 3, 'metric matches'; -is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 41, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '>=', 35, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '<=', 36, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, '>=', 0, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, '<=', 1, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'packet loss'}, '>=', 3, 'metric matches'; @@ -162,7 +163,8 @@ cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'packet loss'}, '<=', 4, 'metric cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, '>=', 1, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, '<=', 3, 'metric matches'; -is $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, 41, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '>=', 35, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '<=', 36, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, '>=', 0, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, '<=', 1, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'packet loss'}, '>=', 3, 'metric matches'; @@ -229,8 +231,8 @@ $resp = rtpe_req('delete', 'MOS very degraded', { }); cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, '>=', 1, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, '<=', 4, 'metric matches'; -cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '>=', 35, 'metric matches'; -cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '<=', 36, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '>=', 27, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '<=', 28, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, '>=', 4, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, '<=', 12, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'packet loss'}, '>=', 8, 'metric matches'; @@ -238,8 +240,8 @@ cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'packet loss'}, '<=', 9, 'metric cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, '>=', 1, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, '<=', 4, 'metric matches'; -cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '>=', 35, 'metric matches'; -cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '<=', 36, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '>=', 27, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '<=', 28, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, '>=', 4, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, '<=', 12, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'packet loss'}, '>=', 8, 'metric matches'; diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 963622c95..7f62b29f0 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -228,7 +228,7 @@ my $processing_us = 10000; # allow for 10 ms processing time is $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, 1, 'metric matches'; -is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 44, 'metric matches'; +is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 43, 'metric matches'; is $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, 0, 'metric matches'; is $resp->{SSRC}{0x1234567}{'average MOS'}{'packet loss'}, 0, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '>=', 0, 'metric matches'; @@ -237,7 +237,7 @@ cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 0, cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '<', $processing_us, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, 1, 'metric matches'; -is $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, 44, 'metric matches'; +is $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, 43, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, 0, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{'packet loss'}, 0, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '>=', 0, 'metric matches'; @@ -389,8 +389,8 @@ snd($sock_bx, $port_ax, pack("CC n N NN N N N N N N N N N", $resp = rtpe_req('delete', 'MOS degraded', { }); is $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, 1, 'metric matches'; -cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '>=', 34, 'metric matches'; -cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '<=', 35, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '>=', 35, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, '<=', 36, 'metric matches'; is $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, 15, 'metric matches'; is $resp->{SSRC}{0x1234567}{'average MOS'}{'packet loss'}, 3, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '>=', 250000, 'metric matches'; @@ -399,8 +399,8 @@ cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 130 cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '<', 130000 + $processing_us, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, 1, 'metric matches'; -cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '>=', 34, 'metric matches'; -cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '<=', 35, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '>=', 35, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, '<=', 36, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, 15, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{'packet loss'}, 3, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '>=', 250000, 'metric matches'; @@ -551,7 +551,7 @@ snd($sock_bx, $port_ax, pack("CC n N NN N N N N N N N N N", $resp = rtpe_req('delete', 'MOS very degraded', { }); is $resp->{SSRC}{0x1234567}{'average MOS'}{samples}, 1, 'metric matches'; -is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 24, 'metric matches'; +is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 29, 'metric matches'; is $resp->{SSRC}{0x1234567}{'average MOS'}{jitter}, 20, 'metric matches'; is $resp->{SSRC}{0x1234567}{'average MOS'}{'packet loss'}, 5, 'metric matches'; cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '>=', 400000, 'metric matches'; @@ -560,7 +560,7 @@ cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 200 cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '<', 200000 + $processing_us, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{samples}, 1, 'metric matches'; -is $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, 24, 'metric matches'; +is $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, 29, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{jitter}, 20, 'metric matches'; is $resp->{SSRC}{0x7654321}{'average MOS'}{'packet loss'}, 5, 'metric matches'; cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '>=', 400000, 'metric matches';