From a7fac00b769f0de2e6be400976e0dd5cb20f5ff5 Mon Sep 17 00:00:00 2001 From: Stefan Westerfeld Date: Tue, 4 Jun 2024 08:58:26 +0200 Subject: [PATCH 1/7] Add helpers to audiowmark for better tests. Signed-off-by: Stefan Westerfeld --- src/audiowmark.cc | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/audiowmark.cc b/src/audiowmark.cc index 8f3f1a1..bb75378 100644 --- a/src/audiowmark.cc +++ b/src/audiowmark.cc @@ -373,10 +373,9 @@ test_speed (const Key& key, int seed) } int -test_gen_noise (const Key& key, const string& out_file, double seconds, int rate) +test_gen_noise (const Key& key, const string& out_file, double seconds, int rate, int bits) { const int channels = 2; - const int bits = 16; vector noise; Random rng (key, 0, /* there is no stream for this test */ Random::Stream::data_up_down); @@ -433,6 +432,30 @@ test_resample (const string& in_file, const string& out_file, int new_rate) return 0; } +int +test_info (const string& in_file, const string& property) +{ + WavData in_data; + Error err = in_data.load (in_file); + if (err) + { + error ("audiowmark: error loading %s: %s\n", in_file.c_str(), err.message()); + return 1; + } + if (property == "bit_depth") + { + printf ("%d\n", in_data.bit_depth()); + return 0; + } + if (property == "frames") + { + printf ("%zd\n", in_data.n_frames()); + return 0; + } + error ("audiowmark: unsupported property for test_info: %s\n", property.c_str()); + return 1; +} + static string escape_key_name (const string& name) { @@ -962,10 +985,12 @@ main (int argc, char **argv) else if (ap.parse_cmd ("test-gen-noise")) { parse_shared_options (ap); + int bits = 16; + ap.parse_opt ("--bits", bits); Key key = parse_key (ap); args = parse_positional (ap, "output_wav", "seconds", "sample_rate"); - return test_gen_noise (key, args[0], atof_or_die (args[1].c_str()), atoi_or_die (args[2].c_str())); + return test_gen_noise (key, args[0], atof_or_die (args[1].c_str()), atoi_or_die (args[2].c_str()), bits); } else if (ap.parse_cmd ("test-change-speed")) { @@ -981,6 +1006,13 @@ main (int argc, char **argv) args = parse_positional (ap, "input_wav", "output_wav", "new_rate"); return test_resample (args[0], args[1], atoi_or_die (args[2].c_str())); } + else if (ap.parse_cmd ("test-info")) + { + parse_shared_options (ap); + + args = parse_positional (ap, "input_wav", "property"); + return test_info (args[0], args[1]); + } else if (ap.remaining_args().size()) { string s = ap.remaining_args().front(); From 1b69d744b22c6068559c7b91b77ead9079e52d31 Mon Sep 17 00:00:00 2001 From: Stefan Westerfeld Date: Tue, 4 Jun 2024 09:04:09 +0200 Subject: [PATCH 2/7] TESTS: add test for wav-pipe format Signed-off-by: Stefan Westerfeld --- tests/Makefile.am | 7 +++++-- tests/test-common.sh.in | 9 +++++++++ tests/wav-pipe-test.sh | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100755 tests/wav-pipe-test.sh diff --git a/tests/Makefile.am b/tests/Makefile.am index 406b078..3310053 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,6 @@ CHECKS = detect-speed-test block-decoder-test clip-decoder-test \ pipe-test short-payload-test sync-test sample-rate-test \ - key-test + key-test wav-pipe-test if COND_WITH_FFMPEG CHECKS += hls-test @@ -8,7 +8,7 @@ endif EXTRA_DIST = detect-speed-test.sh block-decoder-test.sh clip-decoder-test.sh \ pipe-test.sh short-payload-test.sh sync-test.sh sample-rate-test.sh \ - key-test.sh hls-test.sh + key-test.sh hls-test.sh wav-pipe-test.sh check: $(CHECKS) @@ -24,6 +24,9 @@ clip-decoder-test: pipe-test: Q=1 $(top_srcdir)/tests/pipe-test.sh +wav-pipe-test: + Q=1 $(top_srcdir)/tests/wav-pipe-test.sh + short-payload-test: Q=1 $(top_srcdir)/tests/short-payload-test.sh diff --git a/tests/test-common.sh.in b/tests/test-common.sh.in index d97c6b6..0bacfb2 100644 --- a/tests/test-common.sh.in +++ b/tests/test-common.sh.in @@ -41,3 +41,12 @@ audiowmark_cmp() fi $AUDIOWMARK --strict cmp "$@" > $AUDIOWMARK_OUT || die "failed to detect watermark $@" } + +check_length() +{ + local in1=$(audiowmark test-info $1 frames) || die "error detecting length of $1" + local in2=$(audiowmark test-info $2 frames) || die "error detecting length of $2" + + [ "x$in1" != "x" ] || die "length of '$1' could not be detected" + [ "x$in1" == "x$in2" ] || die "length of '$1' ($in1) and '$2' ($in2) differs" +} diff --git a/tests/wav-pipe-test.sh b/tests/wav-pipe-test.sh new file mode 100755 index 0000000..f12c57a --- /dev/null +++ b/tests/wav-pipe-test.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +source test-common.sh + +IN_WAV=wav-pipe-test.wav +OUT1_WAV=wav-pipe-test-out1.wav +OUT2_WAV=wav-pipe-test-out2.wav +OUT3_WAV=wav-pipe-test-out3.wav + +for BITS in 16 24 +do + audiowmark test-gen-noise --bits $BITS $IN_WAV 200 44100 + + [ "x$BITS" == "x$(audiowmark test-info $IN_WAV bit_depth)" ] || die "generated input bit depth is not correct" + + cat $IN_WAV | audiowmark_add --test-key 1 --format wav-pipe - - $TEST_MSG > $OUT1_WAV || die "watermark from pipe failed" + cat $OUT1_WAV | audiowmark_add --test-key 2 --format wav-pipe - - $TEST_MSG > $OUT2_WAV || die "watermark from pipe failed" + cat $OUT2_WAV | audiowmark_add --test-key 3 --format wav-pipe - - $TEST_MSG > $OUT3_WAV || die "watermark from pipe failed" + + check_length $IN_WAV $OUT1_WAV + check_length $IN_WAV $OUT2_WAV + check_length $IN_WAV $OUT3_WAV + + audiowmark_cmp --expect-matches 0 $OUT3_WAV $TEST_MSG + audiowmark_cmp --expect-matches 5 --test-key 1 $OUT3_WAV $TEST_MSG + audiowmark_cmp --expect-matches 5 --test-key 2 $OUT3_WAV $TEST_MSG + audiowmark_cmp --expect-matches 5 --test-key 3 $OUT3_WAV $TEST_MSG + + # for wav-pipe format: 16 bit input should produce 16 bit output; 24 bit input should produce 32 bit output + BTEST=$BITS:$(audiowmark test-info $OUT3_WAV bit_depth) + [[ "$BTEST" =~ ^(16:16|24:32)$ ]] || die "unexpected input/output bit depth $BTEST" + + rm $IN_WAV $OUT1_WAV $OUT2_WAV $OUT3_WAV +done + +exit 0 From 0bf3f82db2bcaa2c570f4cc642d082960e3fef4b Mon Sep 17 00:00:00 2001 From: Stefan Westerfeld Date: Tue, 4 Jun 2024 10:12:25 +0200 Subject: [PATCH 3/7] TESTS: check snr in wav pipe test to catch conversion errors Signed-off-by: Stefan Westerfeld --- tests/test-common.sh.in | 13 +++++++++++-- tests/wav-pipe-test.sh | 9 ++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/test-common.sh.in b/tests/test-common.sh.in index 0bacfb2..fc14cc6 100644 --- a/tests/test-common.sh.in +++ b/tests/test-common.sh.in @@ -44,9 +44,18 @@ audiowmark_cmp() check_length() { - local in1=$(audiowmark test-info $1 frames) || die "error detecting length of $1" - local in2=$(audiowmark test-info $2 frames) || die "error detecting length of $2" + local in1="$($AUDIOWMARK test-info $1 frames)" + local in2="$($AUDIOWMARK test-info $2 frames)" [ "x$in1" != "x" ] || die "length of '$1' could not be detected" [ "x$in1" == "x$in2" ] || die "length of '$1' ($in1) and '$2' ($in2) differs" } + +check_snr() +{ + local snr="$($AUDIOWMARK test-snr $1 $2)" + echo >&2 "==== snr of $1 and $2 is $snr (expected $3) ====" + [ "x$snr" != "x" ] || die "snr of '$1' and '$2' could not be detected" + [ "x$3" != "x" ] || die "need snr bound" + awk "BEGIN {exit !($snr >= $3)}" || die "snr of '$1' and '$2' is worse than $3" +} diff --git a/tests/wav-pipe-test.sh b/tests/wav-pipe-test.sh index f12c57a..d8b18e2 100755 --- a/tests/wav-pipe-test.sh +++ b/tests/wav-pipe-test.sh @@ -13,13 +13,16 @@ do [ "x$BITS" == "x$(audiowmark test-info $IN_WAV bit_depth)" ] || die "generated input bit depth is not correct" - cat $IN_WAV | audiowmark_add --test-key 1 --format wav-pipe - - $TEST_MSG > $OUT1_WAV || die "watermark from pipe failed" - cat $OUT1_WAV | audiowmark_add --test-key 2 --format wav-pipe - - $TEST_MSG > $OUT2_WAV || die "watermark from pipe failed" - cat $OUT2_WAV | audiowmark_add --test-key 3 --format wav-pipe - - $TEST_MSG > $OUT3_WAV || die "watermark from pipe failed" + cat $IN_WAV | audiowmark_add --test-key 1 --test-no-limiter --format wav-pipe - - $TEST_MSG > $OUT1_WAV || die "watermark from pipe failed" + cat $OUT1_WAV | audiowmark_add --test-key 2 --test-no-limiter --format wav-pipe - - $TEST_MSG > $OUT2_WAV || die "watermark from pipe failed" + cat $OUT2_WAV | audiowmark_add --test-key 3 --test-no-limiter --format wav-pipe - - $TEST_MSG > $OUT3_WAV || die "watermark from pipe failed" check_length $IN_WAV $OUT1_WAV check_length $IN_WAV $OUT2_WAV check_length $IN_WAV $OUT3_WAV + check_snr $IN_WAV $OUT1_WAV 32 + check_snr $IN_WAV $OUT2_WAV 29 + check_snr $IN_WAV $OUT3_WAV 27 audiowmark_cmp --expect-matches 0 $OUT3_WAV $TEST_MSG audiowmark_cmp --expect-matches 5 --test-key 1 $OUT3_WAV $TEST_MSG From 78531767954fc4c8dfe9635ff6512e90287b7ce9 Mon Sep 17 00:00:00 2001 From: Stefan Westerfeld Date: Tue, 4 Jun 2024 10:19:28 +0200 Subject: [PATCH 4/7] TESTS: avoid snr output in quiet mode Signed-off-by: Stefan Westerfeld --- tests/test-common.sh.in | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test-common.sh.in b/tests/test-common.sh.in index fc14cc6..38f8321 100644 --- a/tests/test-common.sh.in +++ b/tests/test-common.sh.in @@ -54,7 +54,11 @@ check_length() check_snr() { local snr="$($AUDIOWMARK test-snr $1 $2)" - echo >&2 "==== snr of $1 and $2 is $snr (expected $3) ====" + if [ "x$Q" == "x1" ] && [ -z "$V" ]; then + : + else + echo >&2 "==== snr of $1 and $2 is $snr (expected $3) ====" + fi [ "x$snr" != "x" ] || die "snr of '$1' and '$2' could not be detected" [ "x$3" != "x" ] || die "need snr bound" awk "BEGIN {exit !($snr >= $3)}" || die "snr of '$1' and '$2' is worse than $3" From 868e6cd08156c64b83858af08abef78d97723c1a Mon Sep 17 00:00:00 2001 From: Stefan Westerfeld Date: Tue, 4 Jun 2024 12:56:29 +0200 Subject: [PATCH 5/7] Add test for RawConverter. Signed-off-by: Stefan Westerfeld --- src/Makefile.am | 6 ++- src/testrawconverter.cc | 104 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/testrawconverter.cc diff --git a/src/Makefile.am b/src/Makefile.am index aab6112..2bea6c0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,7 +15,8 @@ AM_CXXFLAGS = $(SNDFILE_CFLAGS) $(FFTW_CFLAGS) $(LIBGCRYPT_CFLAGS) $(LIBMPG123_C audiowmark_SOURCES = audiowmark.cc $(COMMON_SRC) audiowmark_LDFLAGS = $(COMMON_LIBS) -noinst_PROGRAMS = testconvcode testrandom testmp3 teststream testlimiter testshortcode testmpegts testthreadpool +noinst_PROGRAMS = testconvcode testrandom testmp3 teststream testlimiter testshortcode testmpegts testthreadpool \ + testrawconverter testconvcode_SOURCES = testconvcode.cc $(COMMON_SRC) testconvcode_LDFLAGS = $(COMMON_LIBS) @@ -41,6 +42,9 @@ testmpegts_LDFLAGS = $(COMMON_LIBS) testthreadpool_SOURCES = testthreadpool.cc $(COMMON_SRC) testthreadpool_LDFLAGS = $(COMMON_LIBS) +testrawconverter_SOURCES = testrawconverter.cc $(COMMON_SRC) +testrawconverter_LDFLAGS = $(COMMON_LIBS) + if COND_WITH_FFMPEG COMMON_SRC += hlsoutputstream.cc hlsoutputstream.hh diff --git a/src/testrawconverter.cc b/src/testrawconverter.cc new file mode 100644 index 0000000..cf8b83b --- /dev/null +++ b/src/testrawconverter.cc @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018-2024 Stefan Westerfeld + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include + +#include "rawconverter.hh" + +using std::vector; +using std::string; + +string +hash_bytes (vector& bytes) +{ + string result; + std::array hash; + gcry_md_hash_buffer (GCRY_MD_SHA1, hash.data(), bytes.data(), bytes.size()); + for (auto ch : hash) + result += string_printf ("%02x", ch); + return result; +} + +int +main (int argc, char **argv) +{ + std::set hashes; + Error error; + RawFormat format; + uint64_t K = 33452759; // prime + vector in_samples (K), out_samples (K); + vector bytes (K * 4); + for (uint64_t k = 0; k <= K; k++) + in_samples[k] = (-1 + double (2 * k) / K); + for (auto bit_depth : { 16, 24, 32 }) + { + for (auto encoding : { RawFormat::SIGNED, RawFormat::UNSIGNED }) + { + for (auto endian : { RawFormat::LITTLE, RawFormat::BIG }) + { + format.set_bit_depth (bit_depth); + format.set_encoding (encoding); + format.set_endian (endian); + + RawConverter *converter = RawConverter::create (format, error); + if (error) + { + printf ("error: %s\n", error.message()); + return 1; + } + + std::fill (bytes.begin(), bytes.end(), 0); + + double time1 = get_time(); + converter->to_raw (in_samples.data(), bytes.data(), in_samples.size()); + double time2 = get_time(); + converter->from_raw (bytes.data(), out_samples.data(), in_samples.size()); + double time3 = get_time(); + + double max_err = 0; + for (size_t i = 0; i < in_samples.size(); i++) + max_err = std::max (max_err, std::abs (double (in_samples[i]) - double (out_samples[i]))); + double ebits = log2 (max_err); + printf ("%s %d %s endian %f", + format.encoding() == RawFormat::SIGNED ? "signed" : "unsigned", + format.bit_depth(), + format.endian() == RawFormat::LITTLE ? "little" : "big", + ebits); + double min_ebits = -format.bit_depth() + 0.9; + double max_ebits = -format.bit_depth() + 1; + double ns_per_sample_to = (time2 - time1) * 1e9 / K; + double ns_per_sample_from = (time3 - time2) * 1e9 / K; + printf (" (should be in [%.2f,%.2f]) - to raw: %f ns/sample - from raw: %f ns/sample\n", + min_ebits, max_ebits, + ns_per_sample_to, ns_per_sample_from); + assert (ebits <= max_ebits && ebits >= min_ebits); + + /* every raw converted buffer should be different */ + string hash = hash_bytes (bytes); + assert (hashes.count (hash) == 0); + hashes.insert (hash); + } + } + printf ("\n"); + } +} From 2f5f67d4833df898697c43c07f15423579dd57e5 Mon Sep 17 00:00:00 2001 From: Stefan Westerfeld Date: Thu, 6 Jun 2024 15:49:25 +0200 Subject: [PATCH 6/7] TESTS: run testrawconverter in make check Signed-off-by: Stefan Westerfeld --- tests/Makefile.am | 7 +++++-- tests/test-common.sh.in | 1 + tests/test-programs.sh | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100755 tests/test-programs.sh diff --git a/tests/Makefile.am b/tests/Makefile.am index 3310053..f32ce4a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,6 @@ CHECKS = detect-speed-test block-decoder-test clip-decoder-test \ pipe-test short-payload-test sync-test sample-rate-test \ - key-test wav-pipe-test + key-test wav-pipe-test test-programs if COND_WITH_FFMPEG CHECKS += hls-test @@ -8,7 +8,7 @@ endif EXTRA_DIST = detect-speed-test.sh block-decoder-test.sh clip-decoder-test.sh \ pipe-test.sh short-payload-test.sh sync-test.sh sample-rate-test.sh \ - key-test.sh hls-test.sh wav-pipe-test.sh + key-test.sh hls-test.sh wav-pipe-test.sh test-programs.sh check: $(CHECKS) @@ -41,3 +41,6 @@ key-test: hls-test: Q=1 $(top_srcdir)/tests/hls-test.sh + +test-programs: + Q=1 $(top_srcdir)/tests/test-programs.sh diff --git a/tests/test-common.sh.in b/tests/test-common.sh.in index 38f8321..096cba0 100644 --- a/tests/test-common.sh.in +++ b/tests/test-common.sh.in @@ -2,6 +2,7 @@ AUDIOWMARK=@top_builddir@/src/audiowmark TEST_MSG=f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0 +TOP_BUILDDIR=@top_builddir@ # common shell functions diff --git a/tests/test-programs.sh b/tests/test-programs.sh new file mode 100755 index 0000000..74b6cad --- /dev/null +++ b/tests/test-programs.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +source test-common.sh + +for TEST in testrawconverter +do + if [ "x$Q" == "x1" ] && [ -z "$V" ]; then + $TOP_BUILDDIR/src/$TEST > /dev/null + else + $TOP_BUILDDIR/src/$TEST + fi +done + +exit 0 From 113815ae8cd6eeaa8753c2e215610fd15666a470 Mon Sep 17 00:00:00 2001 From: Stefan Westerfeld Date: Thu, 6 Jun 2024 16:01:01 +0200 Subject: [PATCH 7/7] TESTS: add a few more checks to existing tests Signed-off-by: Stefan Westerfeld --- tests/block-decoder-test.sh | 8 ++++++++ tests/pipe-test.sh | 2 ++ 2 files changed, 10 insertions(+) diff --git a/tests/block-decoder-test.sh b/tests/block-decoder-test.sh index b4b985e..0ec5304 100755 --- a/tests/block-decoder-test.sh +++ b/tests/block-decoder-test.sh @@ -9,5 +9,13 @@ audiowmark test-gen-noise $IN_WAV 200 44100 audiowmark_add $IN_WAV $OUT_WAV $TEST_MSG audiowmark_cmp --expect-matches 5 $OUT_WAV $TEST_MSG +check_length $IN_WAV $OUT_WAV + +audiowmark_add --test-no-limiter $IN_WAV $OUT_WAV $TEST_MSG +audiowmark_cmp --expect-matches 5 $OUT_WAV $TEST_MSG + +check_length $IN_WAV $OUT_WAV +check_snr $IN_WAV $OUT_WAV 32.4 + rm $IN_WAV $OUT_WAV exit 0 diff --git a/tests/pipe-test.sh b/tests/pipe-test.sh index c3bc6d4..25046c8 100755 --- a/tests/pipe-test.sh +++ b/tests/pipe-test.sh @@ -10,5 +10,7 @@ cat $IN_WAV | audiowmark_add - - $TEST_MSG > $OUT_WAV || die "watermark from pip audiowmark_cmp --expect-matches 5 $OUT_WAV $TEST_MSG cat $OUT_WAV | audiowmark_cmp --expect-matches 5 - $TEST_MSG || die "watermark detection from pipe failed" +check_length $IN_WAV $OUT_WAV + rm $IN_WAV $OUT_WAV exit 0