From b27563fe777d983dda97e9b80050070620908498 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Tue, 14 Dec 2021 23:07:01 +0100 Subject: [PATCH] redesign of buffer management --- CMakeLists.txt | 7 ++ .../test_multimode_rx.py | 76 +++++++++++++------ .../test_multimode_tx.py | 17 ++++- test/001_highsnr_stdio_audio/test_rx.py | 63 +++++++++------ test/001_highsnr_stdio_audio/test_tx.py | 16 +++- test/001_highsnr_stdio_audio/test_virtual2.sh | 4 +- .../test_virtual_mm.sh | 29 +++++++ 7 files changed, 158 insertions(+), 54 deletions(-) create mode 100755 test/001_highsnr_stdio_audio/test_virtual_mm.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a8a891b..9d19cba7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,5 +74,12 @@ add_test(NAME 001_highsnr_virtual2_P_P ./test_virtual2.sh") set_tests_properties(001_highsnr_virtual2_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") +# lets Python do a multimode test +add_test(NAME 001_highsnr_virtual3_P_P_MM + COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; + PATH=$PATH:${CODEC2_BUILD_DIR}/src; + cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; + ./test_virtual_mm.sh") + set_tests_properties(001_highsnr_virtual3_P_P_MM PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") endif() diff --git a/test/001_highsnr_stdio_audio/test_multimode_rx.py b/test/001_highsnr_stdio_audio/test_multimode_rx.py index 93d7da10..67be78cb 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 codec2 #--------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='Simons TEST TNC') parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) -parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=0, 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") parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") @@ -32,14 +32,17 @@ if args.LIST: N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE +DEBUGGING_MODE = args.DEBUGGING_MODE TIMEOUT = args.TIMEOUT # AUDIO PARAMETERS -AUDIO_FRAMES_PER_BUFFER = 2048 +AUDIO_FRAMES_PER_BUFFER = 4096 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_SAMPLE_RATE_RX = 48000 assert (AUDIO_SAMPLE_RATE_RX % MODEM_SAMPLE_RATE) == 0 +BUF_SIZE = 160 + # SET COUNTERS rx_total_frames_datac0 = 0 rx_frames_datac0 = 0 @@ -79,7 +82,19 @@ datac3_buffer = bytes() # check if we want to use an audio device then do an pyaudio init if AUDIO_INPUT_DEVICE != -1: p = pyaudio.PyAudio() - # --------------------------------------------OPEN AUDIO CHANNEL RX + # auto search for loopback devices + if AUDIO_INPUT_DEVICE == -2: + loopback_list = [] + for dev in range(0,p.get_device_count()): + if 'Loopback: PCM' in p.get_device_info_by_index(dev)["name"]: + loopback_list.append(dev) + if len(loopback_list) >= 2: + AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX + print(f"loopback_list rx: {loopback_list}", file=sys.stderr) + else: + quit() + + print(f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE} DEVICE: {p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['name']} AUDIO SAMPLE RATE: {AUDIO_SAMPLE_RATE_RX}", file=sys.stderr) stream_rx = p.open(format=pyaudio.paInt16, channels=1, rate=AUDIO_SAMPLE_RATE_RX, @@ -92,29 +107,30 @@ if AUDIO_INPUT_DEVICE != -1: timeout = time.time() + TIMEOUT receive = True -BUF_SIZE = 160 while receive and time.time() < timeout: if AUDIO_INPUT_DEVICE != -1: - buf_size_converted = int(BUF_SIZE*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) - data_in = stream_rx.read(buf_size_converted, exception_on_overflow=False) - data_in = audioop.ratecv(data_in,2,1,AUDIO_SAMPLE_RATE_RX, MODEM_SAMPLE_RATE, None) - data_in = data_in[0].rstrip(b'\x00') + + data_in = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) + data_in = audioop.ratecv(data_in,2,1,AUDIO_SAMPLE_RATE_RX, MODEM_SAMPLE_RATE, None) + + datac0_buffer += data_in[0] + datac1_buffer += data_in[0] + datac3_buffer += data_in[0] + + else: data_in = sys.stdin.buffer.read(BUF_SIZE) + + datac0_buffer += data_in + datac1_buffer += data_in + datac3_buffer += data_in + - - datac0_buffer += data_in - datac1_buffer += data_in - datac3_buffer += data_in - - - datac0_nin = codec2.api.freedv_nin(datac0_freedv) * 2 - + datac0_nin = codec2.api.freedv_nin(datac0_freedv) * 2 if len(datac0_buffer) >= (datac0_nin): - datac0_audio = datac0_buffer[:datac0_nin] + nbytes = codec2.api.freedv_rawdatarx(datac0_freedv, datac0_bytes_out, datac0_buffer[:datac0_nin]) # demodulate audio datac0_buffer = datac0_buffer[datac0_nin:] - nbytes = codec2.api.freedv_rawdatarx(datac0_freedv, datac0_bytes_out, datac0_audio) # demodulate audio if nbytes == datac0_bytes_per_frame: rx_total_frames_datac0 = rx_total_frames_datac0 + 1 rx_frames_datac0 = rx_frames_datac0 + 1 @@ -126,9 +142,8 @@ while receive and time.time() < timeout: datac1_nin = codec2.api.freedv_nin(datac1_freedv) * 2 if len(datac1_buffer) >= (datac1_nin): - datac1_audio = datac1_buffer[:datac1_nin] + nbytes = codec2.api.freedv_rawdatarx(datac1_freedv, datac1_bytes_out, datac1_buffer[:datac1_nin]) # demodulate audio datac1_buffer = datac1_buffer[datac1_nin:] - nbytes = codec2.api.freedv_rawdatarx(datac1_freedv, datac1_bytes_out, datac1_audio) # demodulate audio if nbytes == datac1_bytes_per_frame: rx_total_frames_datac1 = rx_total_frames_datac1 + 1 rx_frames_datac1 = rx_frames_datac1 + 1 @@ -139,9 +154,8 @@ while receive and time.time() < timeout: datac3_nin = codec2.api.freedv_nin(datac3_freedv) * 2 if len(datac3_buffer) >= (datac3_nin): - datac3_audio = datac3_buffer[:datac3_nin] + nbytes = codec2.api.freedv_rawdatarx(datac3_freedv, datac3_bytes_out, datac3_buffer[:datac3_nin]) # demodulate audio datac3_buffer = datac3_buffer[datac3_nin:] - nbytes = codec2.api.freedv_rawdatarx(datac3_freedv, datac3_bytes_out, datac3_audio) # demodulate audio if nbytes == datac3_bytes_per_frame: rx_total_frames_datac3 = rx_total_frames_datac3 + 1 rx_frames_datac3 = rx_frames_datac3 + 1 @@ -151,6 +165,23 @@ while receive and time.time() < timeout: rx_bursts_datac3 = rx_bursts_datac3 + 1 + if DEBUGGING_MODE: + sync0 = codec2.api.freedv_get_rx_status(datac0_freedv) + sync1 = codec2.api.freedv_get_rx_status(datac1_freedv) + sync3 = codec2.api.freedv_get_rx_status(datac3_freedv) + + datac0_rxstatus = codec2.api.freedv_get_rx_status(datac0_freedv) + datac0_state = codec2.api.rx_sync_flags_to_text[datac0_rxstatus] + + datac1_rxstatus = codec2.api.freedv_get_rx_status(datac1_freedv) + datac1_state = codec2.api.rx_sync_flags_to_text[datac1_rxstatus] + + datac3_rxstatus = codec2.api.freedv_get_rx_status(datac3_freedv) + datac3_state = codec2.api.rx_sync_flags_to_text[datac3_rxstatus] + + print(f"NIN0: {datac0_nin} STATE0: {datac3_state} | NIN1: {datac1_nin} STATE1: {datac1_state} | NIN3: {datac3_nin} STATE3: {datac3_state}", file=sys.stderr) + + if rx_bursts_datac0 == N_BURSTS and rx_bursts_datac1 == N_BURSTS and rx_bursts_datac3 == N_BURSTS: receive = False @@ -159,3 +190,4 @@ if time.time() > timeout: print(f"TIMEOUT REACHED", file=sys.stderr) print(f"DATAC0: {rx_bursts_datac0}/{rx_total_frames_datac0} DATAC1: {rx_bursts_datac1}/{rx_total_frames_datac1} DATAC3: {rx_bursts_datac3}/{rx_total_frames_datac3}", 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 69da149a..3aa0906c 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -46,8 +46,19 @@ assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 if AUDIO_OUTPUT_DEVICE != -1: - # pyaudio init p = pyaudio.PyAudio() + # auto search for loopback devices + if AUDIO_OUTPUT_DEVICE == -2: + loopback_list = [] + for dev in range(0,p.get_device_count()): + if 'Loopback: PCM' in p.get_device_info_by_index(dev)["name"]: + loopback_list.append(dev) + if len(loopback_list) >= 2: + AUDIO_OUTPUT_DEVICE = loopback_list[1] #0 = RX 1 = TX + print(f"loopback_list tx: {loopback_list}", file=sys.stderr) + else: + quit() + # pyaudio init stream_tx = p.open(format=pyaudio.paInt16, channels=1, rate=AUDIO_SAMPLE_RATE_TX, @@ -116,7 +127,7 @@ for m in modes: 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) + audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) stream_tx.write(audio[0]) else: @@ -134,7 +145,7 @@ for m in modes: # and at last check if we had an openend pyaudio instance and close it -if AUDIO_OUTPUT_DEVICE != 0: +if AUDIO_OUTPUT_DEVICE != -1: stream_tx.close() p.terminate() diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 5781da0f..42042427 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -27,6 +27,7 @@ parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=0, ty parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) 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") +parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends") parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") args = parser.parse_args() @@ -42,10 +43,10 @@ N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value DEBUGGING_MODE = args.DEBUGGING_MODE - +TIMEOUT = args.TIMEOUT # AUDIO PARAMETERS -AUDIO_FRAMES_PER_BUFFER = 2048 +AUDIO_FRAMES_PER_BUFFER = 2048 # seems to be best if >=1024 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_SAMPLE_RATE_RX = 48000 assert (AUDIO_SAMPLE_RATE_RX % MODEM_SAMPLE_RATE) == 0 @@ -53,6 +54,19 @@ assert (AUDIO_SAMPLE_RATE_RX % MODEM_SAMPLE_RATE) == 0 # check if we want to use an audio device then do an pyaudio init if AUDIO_INPUT_DEVICE != -1: p = pyaudio.PyAudio() + # auto search for loopback devices + if AUDIO_INPUT_DEVICE == -2: + loopback_list = [] + for dev in range(0,p.get_device_count()): + if 'Loopback: PCM' in p.get_device_info_by_index(dev)["name"]: + loopback_list.append(dev) + if len(loopback_list) >= 2: + AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX + print(f"loopback_list rx: {loopback_list}", file=sys.stderr) + else: + quit() + + print(f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE} DEVICE: {p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['name']} AUDIO SAMPLE RATE: {AUDIO_SAMPLE_RATE_RX}", file=sys.stderr) stream_rx = p.open(format=pyaudio.paInt16, channels=1, rate=AUDIO_SAMPLE_RATE_RX, @@ -83,37 +97,38 @@ rx_total_frames = 0 rx_frames = 0 rx_bursts = 0 rx_errors = 0 -timeout = time.time() + 10 +timeout = time.time() + TIMEOUT receive = True - +audio_buffer = bytes() +nbytes = 0 +BUF_SIZE = 160 while receive and time.time() < timeout: - data_in = b'' if AUDIO_INPUT_DEVICE != -1: - nin = codec2.api.freedv_nin(freedv) - nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) - if DEBUGGING_MODE == True: - print("nin: %5d nin_converted: %5d " % (nin,nin_converted), end='', file=sys.stderr) - - data_in = stream_rx.read(nin_converted, exception_on_overflow = False) + data_in = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) data_in = audioop.ratecv(data_in,2,1,AUDIO_SAMPLE_RATE_RX, MODEM_SAMPLE_RATE, None) - data_in = data_in[0].rstrip(b'\x00') + audio_buffer += data_in[0] else: - nin = codec2.api.freedv_nin(freedv) * 2 - if DEBUGGING_MODE == True: - print("nin: %5d " % (nin), end='', file=sys.stderr) - data_in = sys.stdin.buffer.read(nin) - nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio - total_n_bytes = total_n_bytes + nbytes + audio_buffer += sys.stdin.buffer.read(BUF_SIZE) + + nin = codec2.api.freedv_nin(freedv) * 2 - rx_status = codec2.api.freedv_get_rx_status(freedv) - if DEBUGGING_MODE == True: - rx_status_string = codec2.api.rx_sync_flags_to_text[rx_status] - print(f"rx_status: {rx_status_string}", file=sys.stderr) - + rx_status = codec2.api.freedv_get_rx_status(freedv) + if DEBUGGING_MODE: + state = codec2.api.rx_sync_flags_to_text[rx_status] + print(f"NIN: {nin} STATE: {state} BUFFER: {len(audio_buffer)}", file=sys.stderr) + + #decode as soon as we filled the audio buffer + if len(audio_buffer) >= (nin): + nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, audio_buffer[:nin]) # demodulate audio + audio_buffer = audio_buffer[nin:] + + + total_n_bytes = total_n_bytes + nbytes + if nbytes == bytes_per_frame: rx_total_frames = rx_total_frames + 1 rx_frames = rx_frames + 1 @@ -124,7 +139,7 @@ while receive and time.time() < timeout: if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: rx_errors = rx_errors + 1 - + if rx_bursts == N_BURSTS: receive = False diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index bd47a046..6a818e50 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -37,7 +37,6 @@ N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE - MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value @@ -49,8 +48,19 @@ assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 # check if we want to use an audio device then do an pyaudio init if AUDIO_OUTPUT_DEVICE != -1: - # pyaudio init p = pyaudio.PyAudio() + # auto search for loopback devices + if AUDIO_OUTPUT_DEVICE == -2: + loopback_list = [] + for dev in range(0,p.get_device_count()): + if 'Loopback: PCM' in p.get_device_info_by_index(dev)["name"]: + loopback_list.append(dev) + if len(loopback_list) >= 2: + AUDIO_OUTPUT_DEVICE = loopback_list[1] #0 = RX 1 = TX + print(f"loopback_list tx: {loopback_list}", file=sys.stderr) + else: + quit() + # pyaudio init stream_tx = p.open(format=pyaudio.paInt16, channels=1, rate=AUDIO_SAMPLE_RATE_TX, @@ -115,7 +125,7 @@ for i in range(1,N_BURSTS+1): txbuffer += bytes(mod_out) - print(f"BURST: {i}/{N_BURSTS} FRAME: {n}/{N_FRAMES_PER_BURST}", file=sys.stderr) + print(f"TX BURST: {i}/{N_BURSTS} FRAME: {n}/{N_FRAMES_PER_BURST}", file=sys.stderr) # append postamble to txbuffer codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) diff --git a/test/001_highsnr_stdio_audio/test_virtual2.sh b/test/001_highsnr_stdio_audio/test_virtual2.sh index 367fd628..504ae54a 100755 --- a/test/001_highsnr_stdio_audio/test_virtual2.sh +++ b/test/001_highsnr_stdio_audio/test_virtual2.sh @@ -18,8 +18,8 @@ RX_LOG=$(mktemp) # make sure all child processes are killed when we exit trap 'jobs -p | xargs -r kill' EXIT -python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --audiodev 4 --debug & +python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --audiodev -2 --debug & rx_pid=$! sleep 1 -python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 --audiodev 5 +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 --audiodev -2 wait ${rx_pid} diff --git a/test/001_highsnr_stdio_audio/test_virtual_mm.sh b/test/001_highsnr_stdio_audio/test_virtual_mm.sh new file mode 100755 index 00000000..186c334e --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_virtual_mm.sh @@ -0,0 +1,29 @@ +#!/bin/bash -x +# Run a test using the virtual sound cards + +function check_alsa_loopback { + lsmod | grep snd_aloop >> /dev/null + if [ $? -eq 1 ]; then + echo "ALSA loopback device not present. Please install with:" + echo + echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" + exit 1 + fi +} + +check_alsa_loopback + +RX_LOG=$(mktemp) + +# make sure all child processes are killed when we exit +trap 'jobs -p | xargs -r kill' EXIT + +python3 test_multimode_rx.py --timeout 30 --framesperburst 2 --bursts 1 --audiodev -2 --debug & +rx_pid=$! +sleep 1 +python3 test_multimode_tx.py --framesperburst 2 --bursts 1 --audiodev -2 + +#tail -f ${RX_LOG} | sed '/RECEIVED BURSTS/ q' + + +wait ${rx_pid}