Message ID | 20240615091612.9579-1-ju.o@free.fr |
---|---|
State | New |
Headers | show |
Series | [1/2] support/testing: test_audio_codec_base.py: new helper class | expand |
Julien, All, On 2024-06-15 11:16 +0200, Julien Olivain spake thusly: > This is a helper class providing a template for testing audio codec > programs such as lame mp3 encoder, flac tools, ogg vorbis-tools, ... > > Signed-off-by: Julien Olivain <ju.o@free.fr> > --- [--SNIP--] > diff --git a/support/testing/tests/package/test_audio_codec_base.py b/support/testing/tests/package/test_audio_codec_base.py > new file mode 100644 > index 00000000000..59c7efcad3c > --- /dev/null > +++ b/support/testing/tests/package/test_audio_codec_base.py > @@ -0,0 +1,86 @@ > +import math > +import os > + > +import infra.basetest > + > + > +class TestAudioCodecBase(infra.basetest.BRTest): > + """Common class to test an audio codec package. > + > + This base class builds a Buildroot system image containing the > + package enabled in its config, start the emulator, login to it. It > + prepares an input test WAV file containing a tone. This WAV file > + is encoded into a new output file in the native encoder format. It > + is then decoded to a new output WAV file. This final output WAV > + file is checked to contain the expected tone generated in the > + initial input WAV file. There is no specific check about file > + size, nor audio quality. The acceptance criteria is the > + recognition of the tone. Note: the tone generation is made with > + the Sox package, and the tone recognition is made with the Aubio > + package. > + > + Each test case that inherits from this class must have: > + __test__ = True - to let nose2 know that it is a test case If the class name does not stat with "Test", then nose2 will not consider it a test case, it would not be necxessary to set __test__ = False here, and rely on inheriters to set it. I see that there are many such cases already in the tree, but I don;t think we should continue in that direction. Maybe just name this base calss as such: class BaseAudioCodec(infra.basetest.BRTest): But see below: I think we should consider it a test by itself: it validates that the sox-aubio combo does work as expected. [--SNIP--] > + def encode_test(self, input_filename): > + msg = "method must be implemented in derived class" > + raise NotImplementedError(msg) I am not python expert, but I think that would be better handled by an "ABC", and Abstract Base Class (https://docs.python.org/3/library/abc.html), which I think would look like: import abc class BaseAudioCodec(abc.ABC): @abc.abstractmethod def encode_test(self, input_filename): ... Note that the ellipsis is really a real ellipsis, not a placeholder (see https://docs.python.org/3/reference/datamodel.html#ellipsis). But as I suggested above, we should just make this class a proper test, and have empty encode() and decode() functions. It would validate that the tone generated by sox can be decoded by aubio. Regards, Yann E. MORIN.
diff --git a/DEVELOPERS b/DEVELOPERS index 52c9b84a9d4..44ff8c01e03 100644 --- a/DEVELOPERS +++ b/DEVELOPERS @@ -1796,6 +1796,7 @@ F: support/testing/tests/package/test_acl.py F: support/testing/tests/package/test_acpica.py F: support/testing/tests/package/test_acpica/ F: support/testing/tests/package/test_apache.py +F: support/testing/tests/package/test_audio_codec_base.py F: support/testing/tests/package/test_bc.py F: support/testing/tests/package/test_bitcoin.py F: support/testing/tests/package/test_brotli.py diff --git a/support/testing/tests/package/test_audio_codec_base.py b/support/testing/tests/package/test_audio_codec_base.py new file mode 100644 index 00000000000..59c7efcad3c --- /dev/null +++ b/support/testing/tests/package/test_audio_codec_base.py @@ -0,0 +1,86 @@ +import math +import os + +import infra.basetest + + +class TestAudioCodecBase(infra.basetest.BRTest): + """Common class to test an audio codec package. + + This base class builds a Buildroot system image containing the + package enabled in its config, start the emulator, login to it. It + prepares an input test WAV file containing a tone. This WAV file + is encoded into a new output file in the native encoder format. It + is then decoded to a new output WAV file. This final output WAV + file is checked to contain the expected tone generated in the + initial input WAV file. There is no specific check about file + size, nor audio quality. The acceptance criteria is the + recognition of the tone. Note: the tone generation is made with + the Sox package, and the tone recognition is made with the Aubio + package. + + Each test case that inherits from this class must have: + __test__ = True - to let nose2 know that it is a test case + config - defconfig fragment with the packages to run the test + encode_test() - the function that runs the encoder and produces an + encoded file. + decode_test() - the function that runs the decoder and produces a + decode file. + """ + __test__ = False + config = infra.basetest.BASIC_TOOLCHAIN_CONFIG + \ + """ + BR2_PACKAGE_AUBIO=y + BR2_PACKAGE_SOX=y + BR2_TARGET_ROOTFS_CPIO=y + # BR2_TARGET_ROOTFS_TAR is not set + """ + input_file = "reference.wav" + decoded_file = "decoded.wav" + tone_freq = 440 # General Midi note A3 + + def login(self): + cpio_file = os.path.join(self.builddir, "images", "rootfs.cpio") + self.emulator.boot(arch="armv5", + kernel="builtin", + options=["-initrd", cpio_file]) + self.emulator.login() + + def test_run(self): + self.login() + self.prepare_data() + self.encode_test(self.input_file) + self.decode_test(self.decoded_file) + self.check_test() + + def prepare_data(self): + # Generate a sinusoidal tone. + cmd = "sox -V -r 48000 -n -b 16 -c 1 " + cmd += self.input_file + cmd += f" synth 3 sin {self.tone_freq} vol -10dB" + self.assertRunOk(cmd) + + def encode_test(self, input_filename): + msg = "method must be implemented in derived class" + raise NotImplementedError(msg) + + def decode_test(self, output_filename): + msg = "method must be implemented in derived class" + raise NotImplementedError(msg) + + def note_from_freq(self, freq): + """Return a note number from the input frequency in Hertz.""" + return round((12 * math.log(freq / 440) / math.log(2)) + 69) + + def check_test(self): + expected_note = self.note_from_freq(self.tone_freq) + out, ret = self.emulator.run(f"aubionotes {self.decoded_file}", timeout=20) + self.assertEqual(ret, 0) + note_found = False + for line in out: + values = line.split() + if len(values) == 3: + note = round(float(values[0])) + if note == expected_note: + note_found = True + self.assertTrue(note_found, "The expected note was not found")
This is a helper class providing a template for testing audio codec programs such as lame mp3 encoder, flac tools, ogg vorbis-tools, ... Signed-off-by: Julien Olivain <ju.o@free.fr> --- DEVELOPERS | 1 + .../tests/package/test_audio_codec_base.py | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 support/testing/tests/package/test_audio_codec_base.py