From a371d369b25b23498346cc389208484fd74ff3b8 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Tue, 15 Oct 2024 16:15:38 -0400 Subject: [PATCH] MT#61263 add G.107.2 fullband option And add appropriate test Change-Id: Ib9fb7914a7f22ecbf29866a56b1d4c65921f15a0 --- daemon/main.c | 3 + daemon/ssrc.c | 35 + docs/rtpengine.md | 7 +- lib/codeclib.c | 5 +- lib/codeclib.h | 1 + t/Makefile | 8 +- t/auto-daemon-tests-mos-fullband.pl | 1008 +++++++++++++++++++++++++++ 7 files changed, 1063 insertions(+), 4 deletions(-) create mode 100755 t/auto-daemon-tests-mos-fullband.pl diff --git a/daemon/main.c b/daemon/main.c index c7da7a7fe..5948880fd 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -1041,6 +1041,9 @@ static void options(int *argc, char ***argv) { rtpe_config.common.mos_type = MOS_LEGACY; else if (!strcasecmp(mos, "g107") || !strcasecmp(mos, "g.107")) rtpe_config.common.mos_type = MOS_LEGACY; + else if (!strcasecmp(mos, "g1072") || !strcasecmp(mos, "g.1072") + || !strcasecmp(mos, "g.107.2") || !strcasecmp(mos, "g107.2")) + rtpe_config.common.mos_type = MOS_FB; #endif else die("Invalid --mos option ('%s')", mos); diff --git a/daemon/ssrc.c b/daemon/ssrc.c index 73fbebd73..8425ffa23 100644 --- a/daemon/ssrc.c +++ b/daemon/ssrc.c @@ -1,6 +1,7 @@ #include "ssrc.h" #include +#include #include "helpers.h" #include "call.h" @@ -13,9 +14,11 @@ static mos_calc_fn mos_calc_legacy; #ifdef WITH_TRANSCODING static mos_calc_fn mos_calc_nb; +static mos_calc_fn mos_calc_fb; static mos_calc_fn *mos_calcs[__MOS_TYPES] = { [MOS_NB] = mos_calc_nb, + [MOS_FB] = mos_calc_fb, [MOS_LEGACY] = mos_calc_legacy, }; #endif @@ -130,6 +133,38 @@ static void mos_calc_nb(struct ssrc_stats_block *ssb) { ssb->mos = mos_from_rx(Rx); } + +static void mos_calc_fb(struct ssrc_stats_block *ssb) { + double rtt; + if (rtpe_config.mos == MOS_CQ && !ssb->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 + else + rtt = ((double) ssb->rtt) / 1000. / 2.; + + // G.107.2 + rtt += ssb->jitter; + double Ppl = ssb->packetloss; + double Iee = 10.2 + (132. - 10.2) * (Ppl / (Ppl + 4.3)); + double Id; + if (rtt <= 100) + Id = 0; + else { + // x = (Math.log(Ta) - Math.log(100)) / Math.log(2) + // = Math.log2(Ta / 100) + // = Math.log2(Ta) - Math.log2(100) + double x = log2(rtt) - log2(100); + Id = 1.48 * 25 * (pow(1 + pow(x, 6), 1./6.) - 3 * pow(1 + pow(x / 3, 6), 1./6.) + 2); + } + + static const double Ro = 148; + static const double Is = 0; + static const double A = 0; + double Rx = Ro - Is - Id - Iee + A; + + ssb->mos = mos_from_rx(Rx / 1.48 * 100000); +} #endif // returned as mos * 10 (i.e. 10 - 50 for 1.0 to 5.0) diff --git a/docs/rtpengine.md b/docs/rtpengine.md index c7067104d..0652a2d8b 100644 --- a/docs/rtpengine.md +++ b/docs/rtpengine.md @@ -1300,7 +1300,7 @@ 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__\|__G.107__\|__legacy__ +- __\-\-mos=CQ__\|__LQ__\|__G.107__\|__G.107.2__\|__legacy__ Options influencing the MOS (Mean Opinion Score) calculation formula. Multiple options can be listed, using multiple __\-\-mos=...__ arguments at @@ -1318,6 +1318,11 @@ call to inject-DTMF won't be sent to __\-\-dtmf-log-dest=__ or __\-\-listen-tcp- The previous default (and only option) was __legacy__, which uses a custom formula which yields slightly higher MOS values than G.107. + The option __G.107.2__ uses G.107.2 for fullband audio codecs and the + simplified G.107 formula for all other audio codecs. The full G.107.2 + formula is somewhat math-heavy and yields higher MOS values for fullband + audio codecs compared to G.107. + - __\-\-measure-rtp__ Enable measuring RTP metrics even for plain RTP passthrough scenarios. Without diff --git a/lib/codeclib.c b/lib/codeclib.c index 666545ca8..24246b25e 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -1653,8 +1653,11 @@ void codeclib_init(int print) { if (def->supplemental) g_queue_push_tail(&__supplemental_codecs, def); - if (rtpe_common_config_ptr->mos_type) + if (rtpe_common_config_ptr->mos_type) { def->mos_type = rtpe_common_config_ptr->mos_type; + if (def->mos_type == MOS_FB && def->default_clockrate != 48000) + def->mos_type = MOS_NB; + } } } diff --git a/lib/codeclib.h b/lib/codeclib.h index 9e4543af3..00a4d367e 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -200,6 +200,7 @@ struct codec_def_s { const str silence_pattern; enum { MOS_NB = 0, // default + MOS_FB, MOS_LEGACY, __MOS_TYPES diff --git a/t/Makefile b/t/Makefile index cc8cf8e7c..cf15bd22c 100644 --- a/t/Makefile +++ b/t/Makefile @@ -101,7 +101,7 @@ include ../lib/common.Makefile daemon-tests-main daemon-tests-jb daemon-tests-dtx daemon-tests-dtx-cn daemon-tests-pubsub \ daemon-tests-intfs daemon-tests-stats daemon-tests-delay-buffer daemon-tests-delay-timing \ daemon-tests-evs daemon-tests-player-cache daemon-tests-redis daemon-tests-redis-json \ - daemon-tests-measure-rtp daemon-tests-mos-legacy + daemon-tests-measure-rtp daemon-tests-mos-legacy daemon-tests-mos-fullband TESTS= test-bitstr aes-crypt aead-aes-crypt test-const_str_hash.strhash ifeq ($(with_transcoding),yes) @@ -139,7 +139,8 @@ daemon-tests: daemon-tests-main daemon-tests-jb daemon-tests-pubsub daemon-tests daemon-tests-evs daemon-tests-async-tc \ daemon-tests-audio-player daemon-tests-audio-player-play-media \ daemon-tests-intfs daemon-tests-stats daemon-tests-player-cache daemon-tests-redis \ - daemon-tests-rtpp-flags daemon-tests-redis-json daemon-tests-measure-rtp daemon-tests-mos-legacy + daemon-tests-rtpp-flags daemon-tests-redis-json daemon-tests-measure-rtp daemon-tests-mos-legacy \ + daemon-tests-mos-fullband daemon-test-deps: tests-preload.so $(MAKE) -C ../daemon @@ -204,6 +205,9 @@ daemon-tests-measure-rtp: daemon-test-deps daemon-tests-mos-legacy: daemon-test-deps ./auto-test-helper "$@" perl -I../perl auto-daemon-tests-mos-legacy.pl +daemon-tests-mos-fullband: daemon-test-deps + ./auto-test-helper "$@" perl -I../perl auto-daemon-tests-mos-fullband.pl + test-bitstr: test-bitstr.o test-mix-buffer: test-mix-buffer.o $(COMMONOBJS) mix_buffer.o ssrc.o rtp.o crypto.o helpers.o \ diff --git a/t/auto-daemon-tests-mos-fullband.pl b/t/auto-daemon-tests-mos-fullband.pl new file mode 100755 index 000000000..a006dc1b5 --- /dev/null +++ b/t/auto-daemon-tests-mos-fullband.pl @@ -0,0 +1,1008 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use NGCP::Rtpengine::Test; +use NGCP::Rtpclient::SRTP; +use NGCP::Rtpengine::AutoTest; +use Test::More; +use NGCP::Rtpclient::ICE; +use POSIX; + + +autotest_start(qw(--config-file=none -t -1 -i 203.0.113.1 -i 2001:db8:4321::1 --mos=G.107.2 + -n 2223 -c 12345 -f -L 7 -E -u 2222 --silence-detect=1 --log-level-internals=7)) + or die; + + +my $extended_tests = $ENV{RTPENGINE_EXTENDED_TESTS}; + + + +my ($sock_a, $sock_b, $sock_c, $sock_d, $port_a, $port_b, $ssrc, $ssrc_b, $resp, + $sock_ax, $sock_bx, $port_ax, $port_bx, + $srtp_ctx_a, $srtp_ctx_b, $srtp_ctx_a_rev, $srtp_ctx_b_rev, $ufrag_a, $ufrag_b, + @ret1, @ret2, @ret3, @ret4, $srtp_key_a, $srtp_key_b, $ts, $seq, $has_recv); + + + +if ($extended_tests) { + +($sock_a, $sock_ax, $sock_b, $sock_bx) = new_call([qw(198.51.100.23 3000)], [qw(198.51.100.23 3001)], + [qw(198.51.100.23 3002)], [qw(198.51.100.23 3003)]); + +($port_a, $port_ax) = offer('MOS basic', { }, <{SSRC}{0x1234567}{'average MOS'}{samples}, 1, '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'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '<', $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 0, 'metric matches'; +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}, 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'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '<', $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '>=', 0, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '<', $processing_us, 'metric matches'; + + + + + +($sock_a, $sock_ax, $sock_b, $sock_bx) = new_call([qw(198.51.100.23 3004)], [qw(198.51.100.23 3005)], + [qw(198.51.100.23 3006)], [qw(198.51.100.23 3007)]); + +($port_a, $port_ax) = offer('MOS degraded', { }, <{SSRC}{0x1234567}{'average MOS'}{samples}, 1, 'metric matches'; +is $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'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '<', 250000 + $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 130000, 'metric matches'; +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'; +is $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'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '<', 250000 + $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '>=', 120000, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '<', 120000 + $processing_us, 'metric matches'; + + + + +($sock_a, $sock_ax, $sock_b, $sock_bx) = new_call([qw(198.51.100.23 3008)], [qw(198.51.100.23 3009)], + [qw(198.51.100.23 3010)], [qw(198.51.100.23 3011)]); + +($port_a, $port_ax) = offer('MOS very degraded', { }, <{SSRC}{0x1234567}{'average MOS'}{samples}, 1, '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'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '<', 400000 + $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 200000, 'metric matches'; +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}, 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'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '<', 400000 + $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '>=', 200000, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '<', 200000 + $processing_us, 'metric matches'; + + + + + +($sock_a, $sock_ax, $sock_b, $sock_bx) = new_call([qw(198.51.100.23 3016)], [qw(198.51.100.23 3017)], + [qw(198.51.100.23 3018)], [qw(198.51.100.23 3019)]); + +($port_a, $port_ax) = offer('Opus MOS basic', { }, <{SSRC}{0x1234567}{'average MOS'}{samples}, 1, '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'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '<', $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 0, 'metric matches'; +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}, 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'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '<', $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '>=', 0, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '<', $processing_us, 'metric matches'; + + + + + +($sock_a, $sock_ax, $sock_b, $sock_bx) = new_call([qw(198.51.100.23 3020)], [qw(198.51.100.23 3021)], + [qw(198.51.100.23 3022)], [qw(198.51.100.23 3023)]); + +($port_a, $port_ax) = offer('Opus MOS degraded', { }, <{SSRC}{0x1234567}{'average MOS'}{samples}, 1, 'metric matches'; +is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 30, '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'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '<', 250000 + $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 130000, 'metric matches'; +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'; +is $resp->{SSRC}{0x7654321}{'average MOS'}{MOS}, 30, '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'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '<', 250000 + $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '>=', 120000, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '<', 120000 + $processing_us, 'metric matches'; + + + + +($sock_a, $sock_ax, $sock_b, $sock_bx) = new_call([qw(198.51.100.23 3024)], [qw(198.51.100.23 3025)], + [qw(198.51.100.23 3026)], [qw(198.51.100.23 3027)]); + +($port_a, $port_ax) = offer('Opus MOS very degraded', { }, <{SSRC}{0x1234567}{'average MOS'}{samples}, 1, 'metric matches'; +is $resp->{SSRC}{0x1234567}{'average MOS'}{MOS}, 22, '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'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time'}, '<', 400000 + $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x1234567}{'average MOS'}{'round-trip time leg'}, '>=', 200000, 'metric matches'; +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}, 22, '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'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time'}, '<', 400000 + $processing_us * 2, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '>=', 200000, 'metric matches'; +cmp_ok $resp->{SSRC}{0x7654321}{'average MOS'}{'round-trip time leg'}, '<', 200000 + $processing_us, 'metric matches'; + + +} + + + + +#done_testing;NGCP::Rtpengine::AutoTest::terminate('f00');exit; +done_testing();