From 2bf5c44cbbf3f9d7c32b1820e2d0c2f57940f124 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 21:40:30 +1030 Subject: [PATCH] adding resampler to MM tests. still occasional fails on virtual card Pyaudio tests --- .../test_multimode_rx.py | 33 +++++++++--- .../test_multimode_tx.py | 20 +++---- test/001_highsnr_stdio_audio/test_rx.py | 8 +-- test/001_highsnr_stdio_audio/test_tx.py | 1 - .../test_virtual_mm.sh | 2 +- test/codec2.py | 52 +++++++++++-------- 6 files changed, 70 insertions(+), 46 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_rx.py b/test/001_highsnr_stdio_audio/test_multimode_rx.py index fbc22f34..da2728f9 100755 --- a/test/001_highsnr_stdio_audio/test_multimode_rx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_rx.py @@ -15,7 +15,7 @@ import numpy as np #--------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='Simons TEST TNC') -parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) +parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int) parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int) parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int, help="audio device number to use") parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") @@ -37,10 +37,11 @@ DEBUGGING_MODE = args.DEBUGGING_MODE TIMEOUT = args.TIMEOUT # AUDIO PARAMETERS -AUDIO_FRAMES_PER_BUFFER = 2048 +AUDIO_FRAMES_PER_BUFFER = 2400*2 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 -AUDIO_SAMPLE_RATE_RX = 8000 -assert (AUDIO_SAMPLE_RATE_RX % MODEM_SAMPLE_RATE) == 0 +AUDIO_SAMPLE_RATE_RX = 48000 +# make sure our resampler will work +assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 # SET COUNTERS rx_total_frames_datac0 = 0 @@ -78,6 +79,8 @@ datac3_bytes_out = create_string_buffer(datac3_bytes_per_frame * 2) codec2.api.freedv_set_frames_per_burst(datac3_freedv,N_FRAMES_PER_BURST) datac3_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER) +resampler = codec2.resampler() + # check if we want to use an audio device then do an pyaudio init if AUDIO_INPUT_DEVICE != -1: p = pyaudio.PyAudio() @@ -106,6 +109,7 @@ if AUDIO_INPUT_DEVICE != -1: timeout = time.time() + TIMEOUT print(time.time(),TIMEOUT, timeout) receive = True +nread_exceptions = 0 # initial nin values datac0_nin = codec2.api.freedv_nin(datac0_freedv) @@ -129,13 +133,23 @@ def print_stats(): while receive and time.time() < timeout: if AUDIO_INPUT_DEVICE != -1: - data_in = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) + try: + data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True) + except OSError as err: + print(err, file=sys.stderr) + if str(err).find("Input overflowed"): + nread_exceptions += 1 + if str(err).find("Stream closed"): + receive = False else: - data_in = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) + data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) - x = np.frombuffer(data_in, dtype=np.int16) - if len(x) == 0: + # insert samples in buffer + x = np.frombuffer(data_in48k, dtype=np.int16) + if len(x) != AUDIO_FRAMES_PER_BUFFER: receive = False + x = resampler.resample48_to_8(x) + datac0_buffer.push(x) datac1_buffer.push(x) datac3_buffer.push(x) @@ -186,6 +200,9 @@ while receive and time.time() < timeout: if rx_bursts_datac0 == N_BURSTS and rx_bursts_datac1 == N_BURSTS and rx_bursts_datac3 == N_BURSTS: receive = False +if nread_exceptions: + print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \ + nread_exceptions, file=sys.stderr) # INFO IF WE REACHED TIMEOUT if time.time() > timeout: print(f"TIMEOUT REACHED", file=sys.stderr) diff --git a/test/001_highsnr_stdio_audio/test_multimode_tx.py b/test/001_highsnr_stdio_audio/test_multimode_tx.py index 7318b77d..ae0308c7 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -13,7 +13,7 @@ import argparse import sys sys.path.insert(0,'..') import codec2 - +import numpy as np # GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='FreeDATA TEST') @@ -38,9 +38,9 @@ AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE # AUDIO PARAMETERS -AUDIO_FRAMES_PER_BUFFER = 2048 +AUDIO_FRAMES_PER_BUFFER = 2400 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 -AUDIO_SAMPLE_RATE_TX = 8000 +AUDIO_SAMPLE_RATE_TX = 48000 assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 if AUDIO_OUTPUT_DEVICE != -1: @@ -65,6 +65,7 @@ if AUDIO_OUTPUT_DEVICE != -1: output_device_index=AUDIO_OUTPUT_DEVICE, ) +resampler = codec2.resampler() modes = [codec2.api.FREEDV_MODE_DATAC0, codec2.api.FREEDV_MODE_DATAC1, codec2.api.FREEDV_MODE_DATAC3] for m in modes: @@ -120,20 +121,19 @@ for m in modes: mod_out_silence = create_string_buffer(samples_delay*2) txbuffer += bytes(mod_out_silence) + # resample up to 48k (resampler works on np.int16) + x = np.frombuffer(txbuffer, dtype=np.int16) + txbuffer_48k = resampler.resample8_to_48(x) + # check if we want to use an audio device or stdout if AUDIO_OUTPUT_DEVICE != -1: - - # sample rate conversion from 8000Hz to 48000Hz - #audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) - stream_tx.write(txbuffer) - + stream_tx.write(txbuffer_48k.tobytes()) else: - # this test needs a lot of time, so we are having a look at times... starttime = time.time() # print data to terminal for piping the output to other programs - sys.stdout.buffer.write(txbuffer) + sys.stdout.buffer.write(txbuffer_48k) sys.stdout.flush() # and at least print the needed time to see which time we needed diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index cc82f51a..b4f89233 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -120,14 +120,16 @@ while receive and time.time() < timeout: data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True) except OSError as err: print(err, file=sys.stderr) - nread_exceptions += 1 + if str(err).find("Input overflowed"): + nread_exceptions += 1 + if str(err).find("Stream closed"): + receive = False else: data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) # insert samples in buffer x = np.frombuffer(data_in48k, dtype=np.int16) - x.tofile(frx) - + x.tofile(frx) if len(x) != AUDIO_FRAMES_PER_BUFFER: receive = False x = resampler.resample48_to_8(x) diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 987f0fc1..1eeac6a7 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -72,7 +72,6 @@ if AUDIO_OUTPUT_DEVICE != -1: ) -AUDIO_FRAMES_PER_BUFFER_8K = int(AUDIO_FRAMES_PER_BUFFER/codec2.api.FDMDV_OS_48) resampler = codec2.resampler() # data binary string diff --git a/test/001_highsnr_stdio_audio/test_virtual_mm.sh b/test/001_highsnr_stdio_audio/test_virtual_mm.sh index 5460ba2a..d8dc365f 100755 --- a/test/001_highsnr_stdio_audio/test_virtual_mm.sh +++ b/test/001_highsnr_stdio_audio/test_virtual_mm.sh @@ -25,7 +25,7 @@ trap myInterruptHandler SIGINT # make sure all child processes are killed when we exit trap 'jobs -p | xargs -r kill' EXIT -python3 test_multimode_rx.py --timeout 60 --framesperburst 2 --bursts 2 --audiodev -2 & +python3 test_multimode_rx.py --timeout 60 --framesperburst 2 --bursts 2 --audiodev -2 --debug & rx_pid=$! sleep 1 python3 test_multimode_tx.py --framesperburst 2 --bursts 2 --audiodev -2 --delay 500 diff --git a/test/codec2.py b/test/codec2.py index 3c68fd71..6ce6e252 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -20,25 +20,6 @@ class FREEDV_MODE(Enum): def FREEDV_GET_MODE(mode): return FREEDV_MODE[mode].value -class audio_buffer: - # a buffer of int16 samples, using a fixed length numpy array self.buffer for storage - # self.nbuffer is the current number of samples in the buffer - def __init__(self, size): - print("create audio_buffer: ", size) - self.size = size - self.buffer = np.zeros(size, dtype=np.int16) - self.nbuffer = 0 - def push(self,samples): - # add samples at the end of the buffer - assert self.nbuffer+len(samples) <= self.size - self.buffer[self.nbuffer:self.nbuffer+len(samples)] = samples - self.nbuffer += len(samples) - def pop(self,size): - # remove samples from the start of the buffer - self.nbuffer -= size; - self.buffer[:self.nbuffer] = self.buffer[size:size+self.nbuffer] - assert self.nbuffer >= 0 - # LOAD FREEDV libname = "libcodec2.so" api = ctypes.CDLL(libname) @@ -120,7 +101,27 @@ api.rx_sync_flags_to_text = [ "EBS-", "EBST"] +# audio buffer --------------------------------------------------------- +class audio_buffer: + # a buffer of int16 samples, using a fixed length numpy array self.buffer for storage + # self.nbuffer is the current number of samples in the buffer + def __init__(self, size): + print("create audio_buffer: ", size) + self.size = size + self.buffer = np.zeros(size, dtype=np.int16) + self.nbuffer = 0 + def push(self,samples): + # add samples at the end of the buffer + assert self.nbuffer+len(samples) <= self.size + self.buffer[self.nbuffer:self.nbuffer+len(samples)] = samples + self.nbuffer += len(samples) + def pop(self,size): + # remove samples from the start of the buffer + self.nbuffer -= size; + self.buffer[:self.nbuffer] = self.buffer[size:size+self.nbuffer] + assert self.nbuffer >= 0 + # resampler --------------------------------------------------------- api.FDMDV_OS_48 = int(6) # oversampling rate @@ -130,8 +131,7 @@ api.fdmdv_8_to_48_short.argtype = [c_void_p, c_void_p, c_int] api.fdmdv_48_to_8_short.argtype = [c_void_p, c_void_p, c_int] class resampler: - # a buffer of int16 samples, using a fixed length numpy array self.buffer for storage - # self.nbuffer is the current number of samples in the buffer + # resample an array of variable length, we just store the filter memories here MEM8 = api.FDMDV_OS_TAPS_48_8K MEM48 = api.FDMDV_OS_TAPS_48K @@ -142,19 +142,22 @@ class resampler: def resample48_to_8(self,in48): assert in48.dtype == np.int16 - # length of input vector must be an interger multiple of api.FDMDV_OS_48 + # length of input vector must be an integer multiple of api.FDMDV_OS_48 assert(len(in48) % api.FDMDV_OS_48 == 0) # concat filter memory and input samples in48_mem = np.zeros(self.MEM48+len(in48), dtype=np.int16) in48_mem[:self.MEM48] = self.filter_mem48 in48_mem[self.MEM48:] = in48 - + + # In C: pin48=&in48[MEM48] pin48,flag = in48_mem.__array_interface__['data'] pin48 += 2*self.MEM48 n8 = int(len(in48) / api.FDMDV_OS_48) out8 = np.zeros(n8, dtype=np.int16) api.fdmdv_48_to_8_short(out8.ctypes, pin48, n8); + + # store memory for next time self.filter_mem48 = in48_mem[:self.MEM48] return out8 @@ -167,10 +170,13 @@ class resampler: in8_mem[:self.MEM8] = self.filter_mem8 in8_mem[self.MEM8:] = in8 + # In C: pin8=&in8[MEM8] pin8,flag = in8_mem.__array_interface__['data'] pin8 += 2*self.MEM8 out48 = np.zeros(api.FDMDV_OS_48*len(in8), dtype=np.int16) api.fdmdv_8_to_48_short(out48.ctypes, pin8, len(in8)); + + # store memory for next time self.filter_mem8 = in8_mem[:self.MEM8] return out48