adding resampler to MM tests. still occasional fails on virtual card Pyaudio tests

This commit is contained in:
drowe67 2021-12-16 21:40:30 +10:30 committed by David Rowe
parent 03dccc4348
commit 2bf5c44cbb
6 changed files with 70 additions and 46 deletions

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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