From 06d88a5ca1c6fc6e9fd11d7c309281fc42553a88 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sun, 12 Dec 2021 08:09:31 +1030 Subject: [PATCH 01/70] skeleton cmake test framework, not doing any significant etsting yet --- CMakeLists.txt | 26 ++++ test/000_audio_tests/README.md | 14 ++ test/000_audio_tests/sinustest.py | 50 ++++++ test/001_highsnr_stdio_audio/README.md | 90 +++++++++++ test/001_highsnr_stdio_audio/test_rx.py | 138 ++++++++++++++++ test/001_highsnr_stdio_audio/test_tx.py | 129 +++++++++++++++ test/002_highsnr_ping_pong/README.md | 96 ++++++++++++ test/002_highsnr_ping_pong/ping.py | 199 ++++++++++++++++++++++++ test/002_highsnr_ping_pong/pong.py | 166 ++++++++++++++++++++ 9 files changed, 908 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 test/000_audio_tests/README.md create mode 100644 test/000_audio_tests/sinustest.py create mode 100644 test/001_highsnr_stdio_audio/README.md create mode 100644 test/001_highsnr_stdio_audio/test_rx.py create mode 100644 test/001_highsnr_stdio_audio/test_tx.py create mode 100644 test/002_highsnr_ping_pong/README.md create mode 100644 test/002_highsnr_ping_pong/ping.py create mode 100644 test/002_highsnr_ping_pong/pong.py diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..de44e385 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.0) +project (FreeDATA) +include(CTest) +enable_testing() + +# Find codec2 +if(CODEC2_BUILD_DIR) + find_package(codec2 REQUIRED + PATHS ${CODEC2_BUILD_DIR} + NO_DEFAULT_PATH + CONFIGS codec2.cmake + ) + if(codec2_FOUND) + message(STATUS "Codec2 library found in build tree.") + endif() +else() + find_package(codec2 REQUIRED) +endif() + +add_test(NAME 000_audio_tests + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/test/000_audio_tests; + python3 sinustest.py") +add_test(NAME 001_highsnr_stdio_audio + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; + python3 test_tx.py --mode 12 --delay 500 --frames 2 --bursts 1 > t.s16") + diff --git a/test/000_audio_tests/README.md b/test/000_audio_tests/README.md new file mode 100644 index 00000000..b62cfa04 --- /dev/null +++ b/test/000_audio_tests/README.md @@ -0,0 +1,14 @@ +# FreeDV-JATE +## Just Another TNC Experiment + +Audio tests! + +## Frame rate conversion +### 48000Hz down to 8000Hz with different frame rate converters + +#### Install +pip3 install miniaudio + + +#### Run +python3 sinustest.py diff --git a/test/000_audio_tests/sinustest.py b/test/000_audio_tests/sinustest.py new file mode 100644 index 00000000..8b74d1eb --- /dev/null +++ b/test/000_audio_tests/sinustest.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import pyaudio +import numpy as np +import audioop +import miniaudio + +volume = 0.5 +fshigh = 48000 +fslow = 8000 +duration = 50.0 +f = 440.0 + +samples_48000 = (np.sin(2*np.pi*np.arange(fshigh*duration)*f/fshigh)).astype(np.float32) +samples_8000 = (np.sin(2*np.pi*np.arange(fslow*duration)*f/fslow)).astype(np.float32) +samples_converted = audioop.ratecv(samples_48000,2,1,fshigh, fslow , None) +samples_converted = bytes(samples_converted[0]) + +converted_frames = miniaudio.convert_frames(miniaudio.SampleFormat.FLOAT32, 1, 48000, bytes(samples_48000), miniaudio.SampleFormat.FLOAT32, 1, 8000) +#converted_frames = bytes(converted_frames) + +print(type(samples_8000)) +print(type(samples_converted)) +print(type(converted_frames)) + +# TODO - write ouputs to .int16 files so we can compare them + +''' +p = pyaudio.PyAudio() +stream = p.open(format=pyaudio.paFloat32, + channels=2, + rate=fshigh, + output=True, + output_device_index=0 #static.AUDIO_OUTPUT_DEVICE + ) +print("original 48000Hz sample") +stream.write(samples_48000) +print("original 8000Hz sample") +stream.write(samples_8000) +print("48000Hz to 8000Hz with audioop") +stream.write(samples_converted) +print("48000Hz to 8000Hz with miniaudio") +stream.write(converted_frames) + + +stream.stop_stream() +stream.close() +p.terminate() +''' diff --git a/test/001_highsnr_stdio_audio/README.md b/test/001_highsnr_stdio_audio/README.md new file mode 100644 index 00000000..b944f8c8 --- /dev/null +++ b/test/001_highsnr_stdio_audio/README.md @@ -0,0 +1,90 @@ + +# FreeDV-JATE [Just Another TNC Experiment] + +## 001_HIGHSNR_STDIO_AUDIO TEST SUITE + +### INSTALL TEST SUITE +#### Install prerequierements +``` +sudo apt update +sudo apt upgrade +sudo apt install git cmake build-essential python3-pip portaudio19-dev python3-pyaudio +pip3 install crcengine +pip3 install threading +``` + +Change into a directory of your choice +Run the following commands --> They will download and compile the latest codec2 ( dr-packet ) files and LPCNet as well into the directory of your choice +``` +wget https://raw.githubusercontent.com/DJ2LS/FreeDV-JATE/001_HIGHSNR_STDIO_AUDIO/install_test_suite.sh +chmod +x install_test_suite.sh +./install_test_suite.sh +``` + + + +### PARAMETERS +| parameter | description | side +|--|--|--| +| - -mode 12 | set the mode for FreeDV ( 10,11,12 ) | TX & RX +| - -delay 1 | set the delay between burst | TX +| - -frames 1 | set the number of frames per burst | TX & RX +| - -bursts 1 | set the number of bursts | TX & RX +| - -input "audio" | if set, program switches to audio instead of stdin | RX +| - -audioinput 2 | set the audio device | RX +| - -output "audio" | if set, program switches to audio instead of stdout | TX +| - -audiooutput 1 | set the audio device | TX +| - -debug | if used, print additional debugging output | RX + + + +### STDIO TESTS FOR TERMINAL USAGE ONLY + + python3 test_tx.py --mode 12 --delay 500 --frames 2 --bursts 1 | python3 test_rx.py --mode 12 --frames 2 --bursts 1 + + + +### AUDIO TESTS VIA VIRTUAL AUDIO DEVICE + + #### Create audio sinkhole and subdevices + Note: This command needs to be run again after every reboot + ``` +sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2 +``` +check if devices have been created + + + + aplay -l +Output should be like this: + + + Karte 0: Intel [HDA Intel], Gerät 0: Generic Analog [Generic Analog] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + Karte 1: CHAT1 [Loopback], Gerät 0: Loopback PCM [Loopback PCM] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + Karte 1: CHAT1 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + Karte 2: CHAT2 [Loopback], Gerät 0: Loopback PCM [Loopback PCM] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + Karte 2: CHAT2 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + +#### Run tests: +Its important, to run TEST_RX at first to reduce the chance that we get some system side audio errors. Tests are showing, that its important to start with audio device "2" at first and then go to the lower virtual devices "1". +Audio device "0" is the default sound card. + +##### RX side + + python3 test_rx.py --mode 12 --frames 2 --bursts 1 --input "audio" --audioinput 2 --debug + +##### TX side + + python3 test_tx.py --mode 12 --delay 500 --frames 2 --bursts 1 --output "audio" --audiooutput 1 + + diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py new file mode 100644 index 00000000..298f11ce --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 23 07:04:24 2020 + +@author: DJ2LS +""" + +import ctypes +from ctypes import * +import pathlib +import pyaudio +import audioop +import sys +import logging +import time +import threading +import sys +import argparse + +#--------------------------------------------GET PARAMETER INPUTS +parser = argparse.ArgumentParser(description='Simons TEST TNC') +parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) +parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", default=0, type=int) +parser.add_argument('--mode', dest="FREEDV_MODE", default=0, type=int) +parser.add_argument('--input', dest="DATA_INPUT", type=str) +parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int) +parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") + +args = parser.parse_args() + +N_BURSTS = args.N_BURSTS +N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST +DATA_INPUT = args.DATA_INPUT +AUDIO_INPUT_DEVICE = args.AUDIO_INPUT +FREEDV_MODE = args.FREEDV_MODE +DEBUGGING_MODE = args.DEBUGGING_MODE + +# 1024 good for mode 6 +AUDIO_FRAMES_PER_BUFFER = 2048 +MODEM_SAMPLE_RATE = 8000 + + + + + + + + + + #-------------------------------------------- LOAD FREEDV + +libname = "libcodec2.so" +c_lib = ctypes.CDLL(libname) + + #--------------------------------------------CREATE PYAUDIO INSTANCE + + + #--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE + + + #--------------------------------------------OPEN AUDIO CHANNEL RX + +if DATA_INPUT == "audio": + p = pyaudio.PyAudio() + AUDIO_SAMPLE_RATE_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate']) + stream_rx = p.open(format=pyaudio.paInt16, + channels=1, + rate=AUDIO_SAMPLE_RATE_RX, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, + input=True, + input_device_index=AUDIO_INPUT_DEVICE, + ) + + + # GENERAL PARAMETERS +c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) + + + # DATA CHANNEL INITIALISATION + +freedv = c_lib.freedv_open(FREEDV_MODE) +bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) +n_max_modem_samples = c_lib.freedv_get_n_max_modem_samples(freedv) +bytes_out = (ctypes.c_ubyte * bytes_per_frame) #bytes_per_frame +bytes_out = bytes_out() #get pointer from bytes_out + +c_lib.freedv_set_frames_per_burst(freedv,N_FRAMES_PER_BURST) + + +total_n_bytes = 0 +rx_total_frames = 0 +rx_frames = 0 +rx_bursts = 0 +receive = True +while receive == True: + time.sleep(0.01) + + data_in = b'' + if DATA_INPUT == "audio": + + nin = c_lib.freedv_nin(freedv) + nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) + if DEBUGGING_MODE == True: + print("-----------------------------") + print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]") + + data_in = stream_rx.read(nin_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') + + else: + nin = c_lib.freedv_nin(freedv)*2 + data_in = sys.stdin.buffer.read(nin) + + c_lib.freedv_rawdatarx.argtype = [ctypes.POINTER(ctypes.c_ubyte), bytes_out, data_in] # check if really neccessary + nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio + total_n_bytes = total_n_bytes + nbytes + if DEBUGGING_MODE == True: + print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv))) + + if nbytes == bytes_per_frame: + rx_total_frames = rx_total_frames + 1 + rx_frames = rx_frames + 1 + + if rx_frames == N_FRAMES_PER_BURST: + rx_frames = 0 + rx_bursts = rx_bursts + 1 + #c_lib.freedv_set_sync(freedv,0) #this should be automatically done by c_lib.freedv_set_frames_per_burst(freedv,N_FRAMES_PER_BURST) + + if rx_bursts == N_BURSTS: + receive = False + +print("------------------------------") +print("BURSTS: " + str(rx_bursts)) +print("TOTAL RECEIVED BYTES: " + str(total_n_bytes)) +print("RECEIVED FRAMES: " + str(rx_total_frames)) + diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py new file mode 100644 index 00000000..11ca8ba5 --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -0,0 +1,129 @@ + #!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +import ctypes +from ctypes import * +import pathlib +import pyaudio +import time +import threading +import audioop +import argparse +import sys + +#--------------------------------------------GET PARAMETER INPUTS +parser = argparse.ArgumentParser(description='Simons TEST TNC') +parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) +parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", default=0, type=int) +parser.add_argument('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) +parser.add_argument('--mode', dest="FREEDV_MODE", default=0, type=int) +parser.add_argument('--output', dest="DATA_OUTPUT", type=str) +parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) + +args = parser.parse_args() + + + + +N_BURSTS = args.N_BURSTS +N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST +DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 +DATA_OUTPUT = args.DATA_OUTPUT + + +AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT + + +# 1024 good for mode 6 +AUDIO_FRAMES_PER_BUFFER = 2048 +MODEM_SAMPLE_RATE = 8000 + +mode = args.FREEDV_MODE +data_out = b'HELLO WORLD!' + + + +print(N_BURSTS) +print(N_FRAMES_PER_BURST) + + + + + + #-------------------------------------------- LOAD FREEDV + +libname = "libcodec2.so" +c_lib = ctypes.CDLL(libname) + + #--------------------------------------------CREATE PYAUDIO INSTANCE + + + #--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE + + + #--------------------------------------------OPEN AUDIO CHANNEL TX + +if DATA_OUTPUT == "audio": + p = pyaudio.PyAudio() + stream_tx = p.open(format=pyaudio.paInt16, + channels=1, + rate=AUDIO_SAMPLE_RATE_TX, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples + output=True, + output_device_index=AUDIO_OUTPUT_DEVICE, + ) + AUDIO_SAMPLE_RATE_TX = int(p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['defaultSampleRate']) + + + +c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) +freedv = c_lib.freedv_open(mode) +bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) +payload_per_frame = bytes_per_frame -2 +n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) +n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) #get n_tx_modem_samples which defines the size of the modulation object # --> *2 + +mod_out = ctypes.c_short * n_tx_modem_samples +mod_out = mod_out() +mod_out_preamble = ctypes.c_short * (1760*2) #1760 for mode 10,11,12 #4000 for mode 9 +mod_out_preamble = mod_out_preamble() + +buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) +buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send + +crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16 +crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string +buffer += crc # append crc16 to buffer + + +print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST) ) + +for i in range(0,N_BURSTS): + + c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble); + + txbuffer = bytearray() + txbuffer += bytes(mod_out_preamble) + + for n in range(0,N_FRAMES_PER_BURST): + + data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) + c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer + + txbuffer += bytes(mod_out) + + if DATA_OUTPUT == "audio": + audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) + stream_tx.write(audio[0]) + txbuffer = bytearray() + else: + sys.stdout.buffer.write(txbuffer) # print data to terminal for piping the output to other programs + sys.stdout.flush() + txbuffer = bytearray() + + time.sleep(DELAY_BETWEEN_BURSTS) + +if DATA_OUTPUT == "audio": + stream_tx.close() + p.terminate() diff --git a/test/002_highsnr_ping_pong/README.md b/test/002_highsnr_ping_pong/README.md new file mode 100644 index 00000000..cd7b7bad --- /dev/null +++ b/test/002_highsnr_ping_pong/README.md @@ -0,0 +1,96 @@ + +# FreeDV-JATE [Just Another TNC Experiment] + +## 002_HIGHSNR_PING_PONG + +### INSTALL TEST SUITE +#### Install prerequierements +``` +sudo apt update +sudo apt upgrade +sudo apt install git cmake build-essential python3-pip portaudio19-dev python3-pyaudio +pip3 install crcengine +pip3 install threading +``` + +Go into a directory of your choice +Run the following commands --> They will download and compile the latest codec2 ( dr-packet ) files and LPCNet as well into the directory of your choice +``` +wget https://raw.githubusercontent.com/DJ2LS/FreeDV-JATE/002_HIGHSNR_PING_PONG/install_test_suite.sh +chmod +x install_test_suite.sh +./install_test_suite.sh +``` + + + +### PARAMETERS +| parameter | description | side +|--|--|--| +| - -txmode 12 | set the mode for FreeDV ( 10,11,12,14 ) | Terminal 1 & Terminal 2 +| - -rxmode 14 | set the mode for FreeDV ( 10,11,12,14 ) | Terminal 1 & Terminal 2 +| - -frames 1 | set the number of frames per burst | Terminal 1 +| - -bursts 1 | set the number of bursts | Terminal 1 +| - -audioinput 2 | set the audio device | Terminal 1 & Terminal 2 +| - -audiooutput 1 | set the audio device | Terminal 1 & Terminal 2 +| - -debug | if used, print additional debugging output | Terminal 1 & Terminal 2 + + + + +### AUDIO TESTS VIA VIRTUAL AUDIO DEVICE + + #### Create audio sinkhole and subdevices + Note: This command needs to be run again after every reboot + ``` +sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2 +``` +check if devices have been created + + + + aplay -l +Output should be like this: +``` + Karte 0: Intel [HDA Intel], Gerät 0: Generic Analog [Generic Analog] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + Karte 1: CHAT1 [Loopback], Gerät 0: Loopback PCM [Loopback PCM] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + Karte 1: CHAT1 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + Karte 2: CHAT2 [Loopback], Gerät 0: Loopback PCM [Loopback PCM] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 + Karte 2: CHAT2 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] + Sub-Geräte: 1/1 + Sub-Gerät #0: subdevice #0 +``` + +### Run tests: + +#### Terminal 1: Ping +``` +python3 PING.py --txmode 12 --rxmode 14 --audioinput 2 --audiooutput 2 --frames 1 --bursts 2 +``` +Output +``` +BURSTS: 2 FRAMES: 1 +----------------------------------------------------------------- +TX | PING | BURST [1/2] FRAME [1/1] +RX | PONG | BURST [1/2] FRAME [1/1] +----------------------------------------------------------------- +TX | PING | BURST [2/2] FRAME [1/1] +RX | PONG | BURST [2/2] FRAME [1/1] +``` + +#### Terminal 2: Pong +``` +python3 PONG.py --txmode 14 --rxmode 12 --audioinput 2 --audiooutput 2 +``` +Output +``` +RX | BURST [1/2] FRAME [1/1] >>> SENDING PONG +RX | BURST [2/2] FRAME [1/1] >>> SENDING PONG +``` diff --git a/test/002_highsnr_ping_pong/ping.py b/test/002_highsnr_ping_pong/ping.py new file mode 100644 index 00000000..1c8f4ef7 --- /dev/null +++ b/test/002_highsnr_ping_pong/ping.py @@ -0,0 +1,199 @@ + #!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +import ctypes +from ctypes import * +import pathlib +import pyaudio +import time +import threading +import argparse +import sys + +#--------------------------------------------GET PARAMETER INPUTS +parser = argparse.ArgumentParser(description='Simons TEST TNC') +parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) +parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", default=0, type=int) +parser.add_argument('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) +parser.add_argument('--txmode', dest="FREEDV_TX_MODE", default=0, type=int) +parser.add_argument('--rxmode', dest="FREEDV_RX_MODE", default=0, type=int) +parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) +parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int) +parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") + +args = parser.parse_args() + + +N_BURSTS = args.N_BURSTS +N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST +DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 + +AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT +AUDIO_INPUT_DEVICE = args.AUDIO_INPUT + +# 1024 good for mode 6 +AUDIO_FRAMES_PER_BUFFER = 2048 +MODEM_SAMPLE_RATE = 8000 + +FREEDV_TX_MODE = args.FREEDV_TX_MODE +FREEDV_RX_MODE = args.FREEDV_RX_MODE + +DEBUGGING_MODE = args.DEBUGGING_MODE + #-------------------------------------------- LOAD FREEDV +libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so" +c_lib = ctypes.CDLL(libname) + + #--------------------------------------------CREATE PYAUDIO INSTANCE +p = pyaudio.PyAudio() + #--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE +#AUDIO_SAMPLE_RATE_TX = int(p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['defaultSampleRate']) +#AUDIO_SAMPLE_RATE_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate']) +AUDIO_SAMPLE_RATE_TX = 8000 +AUDIO_SAMPLE_RATE_RX = 8000 + #--------------------------------------------OPEN AUDIO CHANNEL TX + +stream_tx = p.open(format=pyaudio.paInt16, + channels=1, + rate=AUDIO_SAMPLE_RATE_TX, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples + output=True, + output_device_index=AUDIO_OUTPUT_DEVICE, + ) + +stream_rx = p.open(format=pyaudio.paInt16, + channels=1, + rate=AUDIO_SAMPLE_RATE_RX, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, + input=True, + input_device_index=AUDIO_INPUT_DEVICE, + ) + + + + + +def receive(): + + c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) + freedv = c_lib.freedv_open(FREEDV_RX_MODE) + bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) + payload_per_frame = bytes_per_frame -2 + n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) + n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) #get n_tx_modem_samples which defines the size of the modulation object # --> *2 + + bytes_out = (ctypes.c_ubyte * bytes_per_frame) #bytes_per_frame + bytes_out = bytes_out() #get pointer from bytes_out + + total_n_bytes = 0 + rx_total_frames = 0 + rx_frames = 0 + rx_bursts = 0 + receive = True + while receive == True: + time.sleep(0.01) + + nin = c_lib.freedv_nin(freedv) + nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) + if DEBUGGING_MODE == True: + print("-----------------------------") + print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]") + + data_in = stream_rx.read(nin_converted, exception_on_overflow = False) + data_in = data_in.rstrip(b'\x00') + + c_lib.freedv_rawdatarx.argtype = [ctypes.POINTER(ctypes.c_ubyte), bytes_out, data_in] # check if really neccessary + nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio + total_n_bytes = total_n_bytes + nbytes + if DEBUGGING_MODE == True: + print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv))) + + if nbytes == bytes_per_frame: + rx_total_frames = rx_total_frames + 1 + rx_frames = rx_frames + 1 + + if rx_frames == N_FRAMES_PER_BURST: + rx_frames = 0 + rx_bursts = rx_bursts + 1 + c_lib.freedv_set_sync(freedv,0) + + + burst = bytes_out[0] + n_total_burst = bytes_out[1] + frame = bytes_out[2] + n_total_frame = bytes_out[3] + + + print("RX | PONG | BURST [" + str(burst) + "/" + str(n_total_burst) + "] FRAME [" + str(frame) + "/" + str(n_total_frame) + "]") + print("-----------------------------------------------------------------") + c_lib.freedv_set_sync(freedv,0) + + + if rx_bursts == N_BURSTS: + receive = False + + + +RECEIVE = threading.Thread(target=receive, name="RECEIVE THREAD") +RECEIVE.start() + + +c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) +freedv = c_lib.freedv_open(FREEDV_TX_MODE) +bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) +payload_per_frame = bytes_per_frame -2 +n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) +n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) #get n_tx_modem_samples which defines the size of the modulation object # --> *2 + +mod_out = ctypes.c_short * n_tx_modem_samples +mod_out = mod_out() +mod_out_preamble = ctypes.c_short * (1760*2) #1760 for mode 10,11,12 #4000 for mode 9 +mod_out_preamble = mod_out_preamble() + + + +print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST) ) +print("-----------------------------------------------------------------") + +for i in range(0,N_BURSTS): + + c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble); + + txbuffer = bytearray() + txbuffer += bytes(mod_out_preamble) + + for n in range(0,N_FRAMES_PER_BURST): + + data_out = bytearray() + data_out += bytes([i+1]) + data_out += bytes([N_BURSTS]) + data_out += bytes([n+1]) + data_out += bytes([N_FRAMES_PER_BURST]) + + buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) + buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send + + crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16 + crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string + buffer += crc # append crc16 to buffer + + data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) + c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer + + txbuffer += bytes(mod_out) + + print("TX | PING | BURST [" + str(i+1) + "/" + str(N_BURSTS) + "] FRAME [" + str(n+1) + "/" + str(N_FRAMES_PER_BURST) + "]") + stream_tx.write(bytes(txbuffer)) + ACK_TIMEOUT = time.time() + 3 + txbuffer = bytearray() + + #time.sleep(DELAY_BETWEEN_BURSTS) + + # WAIT UNTIL WE RECEIVD AN ACK/DATAC0 FRAME + while ACK_TIMEOUT >= time.time(): + time.sleep(0.01) + + +time.sleep(1) +stream_tx.close() +p.terminate() diff --git a/test/002_highsnr_ping_pong/pong.py b/test/002_highsnr_ping_pong/pong.py new file mode 100644 index 00000000..04033b2a --- /dev/null +++ b/test/002_highsnr_ping_pong/pong.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 23 07:04:24 2020 + +@author: DJ2LS +""" + +import ctypes +from ctypes import * +import pathlib +import pyaudio +import sys +import logging +import time +import threading +import sys +import argparse + +#--------------------------------------------GET PARAMETER INPUTS +parser = argparse.ArgumentParser(description='Simons TEST TNC') +parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) +parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", default=0, type=int) +parser.add_argument('--txmode', dest="FREEDV_TX_MODE", default=0, type=int) +parser.add_argument('--rxmode', dest="FREEDV_RX_MODE", default=0, type=int) +parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int) +parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) +parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") + +args = parser.parse_args() + +N_BURSTS = args.N_BURSTS +N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST + +AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT +AUDIO_INPUT_DEVICE = args.AUDIO_INPUT + +FREEDV_TX_MODE = args.FREEDV_TX_MODE +FREEDV_RX_MODE = args.FREEDV_RX_MODE + +DEBUGGING_MODE = args.DEBUGGING_MODE + +# 1024 good for mode 6 +AUDIO_FRAMES_PER_BUFFER = 2048 +MODEM_SAMPLE_RATE = 8000 + + #-------------------------------------------- LOAD FREEDV +libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so" +c_lib = ctypes.CDLL(libname) + #--------------------------------------------CREATE PYAUDIO INSTANCE +p = pyaudio.PyAudio() + #--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE + +#AUDIO_SAMPLE_RATE_TX = int(p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['defaultSampleRate']) +#AUDIO_SAMPLE_RATE_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate']) +AUDIO_SAMPLE_RATE_TX = 8000 +AUDIO_SAMPLE_RATE_RX = 8000 + #--------------------------------------------OPEN AUDIO CHANNEL RX + +stream_tx = p.open(format=pyaudio.paInt16, + channels=1, + rate=AUDIO_SAMPLE_RATE_TX, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples + output=True, + output_device_index=AUDIO_OUTPUT_DEVICE, + ) + +stream_rx = p.open(format=pyaudio.paInt16, + channels=1, + rate=AUDIO_SAMPLE_RATE_RX, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, + input=True, + input_device_index=AUDIO_INPUT_DEVICE, + ) + + + # GENERAL PARAMETERS +c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) + + +def send_pong(burst,n_total_burst,frame,n_total_frame): + + data_out = bytearray() + data_out[0:1] = bytes([burst]) + data_out[1:2] = bytes([n_total_burst]) + data_out[2:3] = bytes([frame]) + data_out[4:5] = bytes([n_total_frame]) + + + + c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) + freedv = c_lib.freedv_open(FREEDV_TX_MODE) + bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) + payload_per_frame = bytes_per_frame -2 + n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) + n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) #get n_tx_modem_samples which defines the size of the modulation object # --> *2 + + mod_out = ctypes.c_short * n_tx_modem_samples + mod_out = mod_out() + mod_out_preamble = ctypes.c_short * (1760*2) #1760 for mode 10,11,12 #4000 for mode 9 + mod_out_preamble = mod_out_preamble() + + buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) + buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send + + crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16 + crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string + buffer += crc # append crc16 to buffer + + c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble); + txbuffer = bytearray() + txbuffer += bytes(mod_out_preamble) + + data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) + c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer + + txbuffer += bytes(mod_out) + stream_tx.write(bytes(txbuffer)) + + txbuffer = bytearray() + + + + # DATA CHANNEL INITIALISATION + +freedv = c_lib.freedv_open(FREEDV_RX_MODE) +bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) +n_max_modem_samples = c_lib.freedv_get_n_max_modem_samples(freedv) +bytes_out = (ctypes.c_ubyte * bytes_per_frame) #bytes_per_frame +bytes_out = bytes_out() #get pointer from bytes_out + + + +receive = True +while receive == True: + time.sleep(0.01) + + data_in = b'' + + nin = c_lib.freedv_nin(freedv) + nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) + if DEBUGGING_MODE == True: + print("-----------------------------") + print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]") + + data_in = stream_rx.read(nin_converted, exception_on_overflow = False) + data_in = data_in.rstrip(b'\x00') + + c_lib.freedv_rawdatarx.argtype = [ctypes.POINTER(ctypes.c_ubyte), bytes_out, data_in] # check if really neccessary + nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio + + if DEBUGGING_MODE == True: + print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv))) + + if nbytes == bytes_per_frame: + + burst = bytes_out[0] + n_total_burst = bytes_out[1] + frame = bytes_out[2] + n_total_frame = bytes_out[3] + print("RX | BURST [" + str(burst) + "/" + str(n_total_burst) + "] FRAME [" + str(frame) + "/" + str(n_total_frame) + "] >>> SENDING PONG") + + TRANSMIT_PONG = threading.Thread(target=send_pong, args=[burst,n_total_burst,frame,n_total_frame], name="SEND PONG") + TRANSMIT_PONG.start() + + c_lib.freedv_set_sync(freedv,0) From beb5327a78f64e2ab613be7d063ed01934cdfafc Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sun, 12 Dec 2021 08:35:06 +1030 Subject: [PATCH 02/70] correct LD_LIBRARY_PATH --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de44e385..ca7249e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ add_test(NAME 000_audio_tests COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/test/000_audio_tests; python3 sinustest.py") add_test(NAME 001_highsnr_stdio_audio - COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; + COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; + cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; python3 test_tx.py --mode 12 --delay 500 --frames 2 --bursts 1 > t.s16") From d132357d9c06e56b62c40b3d4df37edad76db448 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sun, 12 Dec 2021 09:20:53 +1030 Subject: [PATCH 03/70] 001 test_tx.py working, tested against freedv_data_raw_rx --- CMakeLists.txt | 7 ++-- test/001_highsnr_stdio_audio/test_tx.py | 46 ++++++++++++++++--------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca7249e4..1482e366 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,11 @@ endif() add_test(NAME 000_audio_tests COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/test/000_audio_tests; python3 sinustest.py") -add_test(NAME 001_highsnr_stdio_audio +add_test(NAME 001_highsnr_stdio 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; - python3 test_tx.py --mode 12 --delay 500 --frames 2 --bursts 1 > t.s16") + python3 test_tx.py --mode 14 --delay 500 --frames 2 --bursts 1 | + freedv_data_raw_rx datac0 - - | hexdump -C") + set_tests_properties(001_highsnr_stdio PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 11ca8ba5..95947722 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -44,8 +44,8 @@ data_out = b'HELLO WORLD!' -print(N_BURSTS) -print(N_FRAMES_PER_BURST) +print(N_BURSTS, file=sys.stderr) +print(N_FRAMES_PER_BURST, file=sys.stderr) @@ -80,39 +80,53 @@ if DATA_OUTPUT == "audio": c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) freedv = c_lib.freedv_open(mode) bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) -payload_per_frame = bytes_per_frame -2 -n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) -n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) #get n_tx_modem_samples which defines the size of the modulation object # --> *2 - -mod_out = ctypes.c_short * n_tx_modem_samples +payload_bytes_per_frame = bytes_per_frame -2 + +n_mod_out = int(c_lib.freedv_get_n_tx_modem_samples(freedv)) +mod_out = ctypes.c_short * n_mod_out mod_out = mod_out() -mod_out_preamble = ctypes.c_short * (1760*2) #1760 for mode 10,11,12 #4000 for mode 9 + +n_preamble = int(c_lib.freedv_get_n_tx_preamble_modem_samples(freedv)) +mod_out_preamble = ctypes.c_short * n_preamble mod_out_preamble = mod_out_preamble() - -buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) + +n_postamble = int(c_lib.freedv_get_n_tx_postamble_modem_samples(freedv)) +mod_out_postamble = ctypes.c_short * n_postamble +mod_out_postamble = mod_out_postamble() + +buffer = bytearray(payload_bytes_per_frame) # use this if CRC16 checksum is required ( DATA1-3) buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send -crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16 +crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # generate CRC16 crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string buffer += crc # append crc16 to buffer -print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST) ) +print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST) , file=sys.stderr) for i in range(0,N_BURSTS): - c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble); - txbuffer = bytearray() + + c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble) txbuffer += bytes(mod_out_preamble) for n in range(0,N_FRAMES_PER_BURST): data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer + c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer txbuffer += bytes(mod_out) + c_lib.freedv_rawdatapostambletx(freedv, mod_out_postamble) + txbuffer += bytes(mod_out_postamble) + + inter_burst_delay_ms = 200 + samples_delay = int(MODEM_SAMPLE_RATE*inter_burst_delay_ms/1000) + mod_out_silence = ctypes.c_short * samples_delay + mod_out_silence = mod_out_silence() + txbuffer += bytes(mod_out_silence) + if DATA_OUTPUT == "audio": audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) stream_tx.write(audio[0]) @@ -122,8 +136,6 @@ for i in range(0,N_BURSTS): sys.stdout.flush() txbuffer = bytearray() - time.sleep(DELAY_BETWEEN_BURSTS) - if DATA_OUTPUT == "audio": stream_tx.close() p.terminate() From fde0c1432c470be1bc22faf4c6404f2572903c21 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sun, 12 Dec 2021 09:45:23 +1030 Subject: [PATCH 04/70] testing multiple frames per burst --- CMakeLists.txt | 4 ++-- test/001_highsnr_stdio_audio/test_tx.py | 19 +++++-------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1482e366..ad48347e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ add_test(NAME 001_highsnr_stdio 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; - python3 test_tx.py --mode 14 --delay 500 --frames 2 --bursts 1 | - freedv_data_raw_rx datac0 - - | hexdump -C") + python3 test_tx.py --mode 14 --delay 500 --frames 3 --bursts 1 | + freedv_data_raw_rx datac0 - - --framesperburst 3 | hexdump -C") set_tests_properties(001_highsnr_stdio PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 95947722..d08cc09e 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -42,15 +42,6 @@ MODEM_SAMPLE_RATE = 8000 mode = args.FREEDV_MODE data_out = b'HELLO WORLD!' - - -print(N_BURSTS, file=sys.stderr) -print(N_FRAMES_PER_BURST, file=sys.stderr) - - - - - #-------------------------------------------- LOAD FREEDV libname = "libcodec2.so" @@ -102,7 +93,7 @@ crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string buffer += crc # append crc16 to buffer -print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST) , file=sys.stderr) +print("BURSTS: " + str(N_BURSTS) + " FRAMES_PER_BURST: " + str(N_FRAMES_PER_BURST) , file=sys.stderr) for i in range(0,N_BURSTS): @@ -117,13 +108,14 @@ for i in range(0,N_BURSTS): c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer txbuffer += bytes(mod_out) - + print("frame",n, file=sys.stderr) + c_lib.freedv_rawdatapostambletx(freedv, mod_out_postamble) txbuffer += bytes(mod_out_postamble) - inter_burst_delay_ms = 200 - samples_delay = int(MODEM_SAMPLE_RATE*inter_burst_delay_ms/1000) + samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) mod_out_silence = ctypes.c_short * samples_delay + print("samples_delay", samples_delay, "DELAY_BETWEEN_BURSTS", DELAY_BETWEEN_BURSTS, file=sys.stderr) mod_out_silence = mod_out_silence() txbuffer += bytes(mod_out_silence) @@ -134,7 +126,6 @@ for i in range(0,N_BURSTS): else: sys.stdout.buffer.write(txbuffer) # print data to terminal for piping the output to other programs sys.stdout.flush() - txbuffer = bytearray() if DATA_OUTPUT == "audio": stream_tx.close() From d657fce1b0c97b9ec739c88ad90ed4f65beaada4 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sun, 12 Dec 2021 09:51:22 +1030 Subject: [PATCH 05/70] checking correct number of frames received --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad48347e..324f131a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ add_test(NAME 001_highsnr_stdio PATH=$PATH:${CODEC2_BUILD_DIR}/src; cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; python3 test_tx.py --mode 14 --delay 500 --frames 3 --bursts 1 | - freedv_data_raw_rx datac0 - - --framesperburst 3 | hexdump -C") + freedv_data_raw_rx datac0 - - --framesperburst 3 -v | hexdump -C") set_tests_properties(001_highsnr_stdio PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") + set_tests_properties(001_highsnr_stdio PROPERTIES PASS_REGULAR_EXPRESSION "Frms.: 3") From 1aa976c395e2956cb01336e5eb944d7673eb34cf Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 12 Dec 2021 11:00:47 +0100 Subject: [PATCH 06/70] gitignore for test artifacts --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3d3aa25e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# possible installation of codec2 within tnc +tnc/codec2 + +# temporary test artifacts +**/build +**/Testing From 305bb3b160ad5deec0bf752aa7c939212eb57eed Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 12 Dec 2021 12:09:13 +0100 Subject: [PATCH 07/70] changed cli output and usage of ctypes --- test/001_highsnr_stdio_audio/test_tx.py | 137 ++++++++++++++---------- 1 file changed, 83 insertions(+), 54 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index d08cc09e..30794dd8 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -12,10 +12,10 @@ import audioop import argparse import sys -#--------------------------------------------GET PARAMETER INPUTS +# GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='Simons TEST TNC') parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) -parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", default=0, type=int) +parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=0, type=int) parser.add_argument('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) parser.add_argument('--mode', dest="FREEDV_MODE", default=0, type=int) parser.add_argument('--output', dest="DATA_OUTPUT", type=str) @@ -23,39 +23,21 @@ parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) args = parser.parse_args() - - - N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 DATA_OUTPUT = args.DATA_OUTPUT - - AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT +MODE = args.FREEDV_MODE - -# 1024 good for mode 6 +# AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = 8000 +AUDIO_SAMPLE_RATE_TX = 48000 -mode = args.FREEDV_MODE -data_out = b'HELLO WORLD!' - - #-------------------------------------------- LOAD FREEDV - -libname = "libcodec2.so" -c_lib = ctypes.CDLL(libname) - - #--------------------------------------------CREATE PYAUDIO INSTANCE - - - #--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE - - - #--------------------------------------------OPEN AUDIO CHANNEL TX - -if DATA_OUTPUT == "audio": +# check if we want to use an audio device then do an pyaudio init +if DATA_OUTPUT == "audio": + # pyaudio init p = pyaudio.PyAudio() stream_tx = p.open(format=pyaudio.paInt16, channels=1, @@ -63,70 +45,117 @@ if DATA_OUTPUT == "audio": frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples output=True, output_device_index=AUDIO_OUTPUT_DEVICE, - ) - AUDIO_SAMPLE_RATE_TX = int(p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['defaultSampleRate']) - + ) + + +# data binary string +data_out = b'HELLO WORLD!' + +# LOAD FREEDV +libname = "libcodec2.so" +c_lib = ctypes.CDLL(libname) + +# ctypes function init + +c_lib.freedv_open.argype = [c_int] +c_lib.freedv_open.restype = c_void_p + +c_lib.freedv_get_bits_per_modem_frame.argtype = [c_void_p] +c_lib.freedv_get_bits_per_modem_frame.restype = c_int + +c_lib.freedv_get_n_tx_preamble_modem_samples.argtype = [c_void_p] +c_lib.freedv_get_n_tx_preamble_modem_samples.restype = c_int + +c_lib.freedv_get_n_tx_postamble_modem_samples.argtype = [c_void_p] +c_lib.freedv_get_n_tx_postamble_modem_samples.restype = c_int + +c_lib.freedv_gen_crc16.argtype = [c_void_p, c_int] +c_lib.freedv_gen_crc16.restype = c_void_p + +c_lib.freedv_nin.argtype = [c_void_p] +c_lib.freedv_nin.restype = c_int + +c_lib.freedv_rawdatatx.argtype = [c_void_p, c_char_p, c_char_p] +c_lib.freedv_rawdatatx.restype = c_int + + +# ---------------------------------------------------------------- -c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) -freedv = c_lib.freedv_open(mode) + +# open codec2 instance +freedv = cast(c_lib.freedv_open(MODE), c_void_p) + +# get number of bytes per frame for mode bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) payload_bytes_per_frame = bytes_per_frame -2 -n_mod_out = int(c_lib.freedv_get_n_tx_modem_samples(freedv)) -mod_out = ctypes.c_short * n_mod_out -mod_out = mod_out() +# init buffer for data +n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) +mod_out = create_string_buffer(n_tx_modem_samples * 2) -n_preamble = int(c_lib.freedv_get_n_tx_preamble_modem_samples(freedv)) -mod_out_preamble = ctypes.c_short * n_preamble -mod_out_preamble = mod_out_preamble() +# init buffer for preample +n_tx_preamble_modem_samples = c_lib.freedv_get_n_tx_preamble_modem_samples(freedv) +mod_out_preamble = create_string_buffer(n_tx_preamble_modem_samples * 2) -n_postamble = int(c_lib.freedv_get_n_tx_postamble_modem_samples(freedv)) -mod_out_postamble = ctypes.c_short * n_postamble -mod_out_postamble = mod_out_postamble() +# init buffer for postamble +n_tx_postamble_modem_samples = c_lib.freedv_get_n_tx_postamble_modem_samples(freedv) +mod_out_postamble = create_string_buffer(n_tx_postamble_modem_samples * 2) + +# create buffer for data buffer = bytearray(payload_bytes_per_frame) # use this if CRC16 checksum is required ( DATA1-3) buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send +# create crc for data frame - we are using the crc function shipped with codec2 to avoid +# crc algorithm incompatibilities crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # generate CRC16 crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string buffer += crc # append crc16 to buffer -print("BURSTS: " + str(N_BURSTS) + " FRAMES_PER_BURST: " + str(N_FRAMES_PER_BURST) , file=sys.stderr) +print(f"TOTAL BURSTS: {N_BURSTS} TOTAL FRAMES_PER_BURST: {N_FRAMES_PER_BURST}", file=sys.stderr) -for i in range(0,N_BURSTS): - - txbuffer = bytearray() +for i in range(1,N_BURSTS+1): + # write preamble to txbuffer c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble) - txbuffer += bytes(mod_out_preamble) + txbuffer = bytes(mod_out_preamble) - for n in range(0,N_FRAMES_PER_BURST): + # create modulaton for N = FRAMESPERBURST and append it to txbuffer + for n in range(1,N_FRAMES_PER_BURST+1): data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer txbuffer += bytes(mod_out) - print("frame",n, file=sys.stderr) - + + print(f"BURST: {i}/{N_BURSTS} FRAME: {n}/{N_FRAMES_PER_BURST}", file=sys.stderr) + + # append postamble to txbuffer c_lib.freedv_rawdatapostambletx(freedv, mod_out_postamble) txbuffer += bytes(mod_out_postamble) + # append a delay between bursts as audio silence samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) - mod_out_silence = ctypes.c_short * samples_delay - print("samples_delay", samples_delay, "DELAY_BETWEEN_BURSTS", DELAY_BETWEEN_BURSTS, file=sys.stderr) - mod_out_silence = mod_out_silence() + mod_out_silence = create_string_buffer(samples_delay) txbuffer += bytes(mod_out_silence) + print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr) - if DATA_OUTPUT == "audio": + # check if we want to use an audio device or stdout + if DATA_OUTPUT == "audio": + + # sample rate conversion from 8000Hz to 48000Hz audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) stream_tx.write(audio[0]) - txbuffer = bytearray() + else: - sys.stdout.buffer.write(txbuffer) # print data to terminal for piping the output to other programs + # print data to terminal for piping the output to other programs + sys.stdout.buffer.write(txbuffer) sys.stdout.flush() + +# and at last check if we had an openend pyaudio instance and close it if DATA_OUTPUT == "audio": stream_tx.close() p.terminate() From 8fe215be3e9037954bb1ff912172d0ed12e6d024 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 12 Dec 2021 12:12:05 +0100 Subject: [PATCH 08/70] changed frames to framesperburst parameters should be homogenous as well --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad48347e..285fa40d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ add_test(NAME 001_highsnr_stdio 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; - python3 test_tx.py --mode 14 --delay 500 --frames 3 --bursts 1 | + python3 test_tx.py --mode 14 --delay 500 --framesperburst 3 --bursts 1 | freedv_data_raw_rx datac0 - - --framesperburst 3 | hexdump -C") set_tests_properties(001_highsnr_stdio PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") From f70be11d95542f6c15c9503e4f1e11cd8d07b820 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 12 Dec 2021 13:54:23 +0100 Subject: [PATCH 09/70] added more tests --- CMakeLists.txt | 29 ++++++- test/001_highsnr_stdio_audio/test_rx.py | 107 +++++++++++++----------- test/001_highsnr_stdio_audio/test_tx.py | 11 ++- 3 files changed, 89 insertions(+), 58 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 285fa40d..9abc5b18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,14 +17,35 @@ else() find_package(codec2 REQUIRED) endif() +# test variables +set(FRAMESPERBURST 3) +set(BURSTS 1) +set(TESTFRAMES 3) + add_test(NAME 000_audio_tests COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/test/000_audio_tests; python3 sinustest.py") -add_test(NAME 001_highsnr_stdio + +add_test(NAME 001_highsnr_stdio_P<>C 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; - python3 test_tx.py --mode 14 --delay 500 --framesperburst 3 --bursts 1 | - freedv_data_raw_rx datac0 - - --framesperburst 3 | hexdump -C") - set_tests_properties(001_highsnr_stdio PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") + python3 test_tx.py --mode 14 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | + freedv_data_raw_rx datac0 - - --framesperburst ${FRAMESPERBURST} | hexdump -C") + set_tests_properties(001_highsnr_stdio_P<>C PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") +add_test(NAME 001_highsnr_stdio_C<>P + 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; + freedv_data_raw_tx --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} datac0 /dev/zero - | + python3 test_rx.py --mode 14 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") + set_tests_properties(001_highsnr_stdio_C<>P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") + +add_test(NAME 001_highsnr_stdio_P<>P + 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; + python3 test_tx.py --mode 14 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | + python3 test_rx.py --mode 14 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") + set_tests_properties(001_highsnr_stdio_P<>P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 298f11ce..a854dbc5 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -21,9 +21,8 @@ import argparse #--------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='Simons TEST TNC') parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) -parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", default=0, type=int) -parser.add_argument('--mode', dest="FREEDV_MODE", default=0, type=int) -parser.add_argument('--input', dest="DATA_INPUT", type=str) +parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=0, type=int) +parser.add_argument('--mode', dest="FREEDV_MODE", default=14, type=int) parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int) parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") @@ -31,39 +30,19 @@ args = parser.parse_args() N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST -DATA_INPUT = args.DATA_INPUT AUDIO_INPUT_DEVICE = args.AUDIO_INPUT -FREEDV_MODE = args.FREEDV_MODE +MODE = args.FREEDV_MODE DEBUGGING_MODE = args.DEBUGGING_MODE -# 1024 good for mode 6 + +# AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = 8000 +AUDIO_SAMPLE_RATE_TX = 48000 - - - - - - - - - #-------------------------------------------- LOAD FREEDV - -libname = "libcodec2.so" -c_lib = ctypes.CDLL(libname) - - #--------------------------------------------CREATE PYAUDIO INSTANCE - - - #--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE - - - #--------------------------------------------OPEN AUDIO CHANNEL RX - -if DATA_INPUT == "audio": +# check if we want to use an audio device then do an pyaudio init +if AUDIO_INPUT_DEVICE != 0: p = pyaudio.PyAudio() - AUDIO_SAMPLE_RATE_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate']) stream_rx = p.open(format=pyaudio.paInt16, channels=1, rate=AUDIO_SAMPLE_RATE_RX, @@ -72,18 +51,48 @@ if DATA_INPUT == "audio": input_device_index=AUDIO_INPUT_DEVICE, ) +# LOAD FREEDV +libname = "libcodec2.so" +c_lib = ctypes.CDLL(libname) - # GENERAL PARAMETERS -c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) - +# ctypes function init + +c_lib.freedv_open.argype = [c_int] +c_lib.freedv_open.restype = c_void_p + +c_lib.freedv_get_bits_per_modem_frame.argtype = [c_void_p] +c_lib.freedv_get_bits_per_modem_frame.restype = c_int + +c_lib.freedv_nin.argtype = [c_void_p] +c_lib.freedv_nin.restype = c_int + +c_lib.freedv_rawdatarx.argtype = [c_void_p, c_char_p, c_char_p] +c_lib.freedv_rawdatarx.restype = c_int + +c_lib.freedv_get_n_max_modem_samples.argtype = [c_void_p] +c_lib.freedv_get_n_max_modem_samples.restype = c_int + +c_lib.freedv_set_frames_per_burst.argtype = [c_void_p, c_int] +c_lib.freedv_set_frames_per_burst.restype = c_void_p + +c_lib.freedv_get_rx_status.argtype = [c_void_p] +c_lib.freedv_get_rx_status.restype = c_int + + +# ---------------------------------------------------------------- + # DATA CHANNEL INITIALISATION -freedv = c_lib.freedv_open(FREEDV_MODE) +# open codec2 instance +freedv = cast(c_lib.freedv_open(MODE), c_void_p) + +# get number of bytes per frame for mode bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) +payload_bytes_per_frame = bytes_per_frame -2 + n_max_modem_samples = c_lib.freedv_get_n_max_modem_samples(freedv) -bytes_out = (ctypes.c_ubyte * bytes_per_frame) #bytes_per_frame -bytes_out = bytes_out() #get pointer from bytes_out +bytes_out = create_string_buffer(bytes_per_frame * 2) c_lib.freedv_set_frames_per_burst(freedv,N_FRAMES_PER_BURST) @@ -92,32 +101,32 @@ total_n_bytes = 0 rx_total_frames = 0 rx_frames = 0 rx_bursts = 0 +timeout = time.time() + 10 receive = True -while receive == True: - time.sleep(0.01) + +while receive and time.time() < timeout: data_in = b'' - if DATA_INPUT == "audio": + if AUDIO_INPUT_DEVICE != 0: nin = c_lib.freedv_nin(freedv) nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) if DEBUGGING_MODE == True: - print("-----------------------------") - print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]") + print(f"NIN: {nin} [{nin_converted}]", file=sys.stderr) data_in = stream_rx.read(nin_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') else: - nin = c_lib.freedv_nin(freedv)*2 + nin = c_lib.freedv_nin(freedv) * 2 data_in = sys.stdin.buffer.read(nin) - c_lib.freedv_rawdatarx.argtype = [ctypes.POINTER(ctypes.c_ubyte), bytes_out, data_in] # check if really neccessary nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio total_n_bytes = total_n_bytes + nbytes + if DEBUGGING_MODE == True: - print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv))) + print(f"SYNC: {c_lib.freedv_get_rx_status(freedv)}", file=sys.stderr) if nbytes == bytes_per_frame: rx_total_frames = rx_total_frames + 1 @@ -126,13 +135,15 @@ while receive == True: if rx_frames == N_FRAMES_PER_BURST: rx_frames = 0 rx_bursts = rx_bursts + 1 - #c_lib.freedv_set_sync(freedv,0) #this should be automatically done by c_lib.freedv_set_frames_per_burst(freedv,N_FRAMES_PER_BURST) + if rx_bursts == N_BURSTS: receive = False -print("------------------------------") -print("BURSTS: " + str(rx_bursts)) -print("TOTAL RECEIVED BYTES: " + str(total_n_bytes)) -print("RECEIVED FRAMES: " + str(rx_total_frames)) +print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames}", file=sys.stderr) + +# and at last check if we had an openend pyaudio instance and close it +if AUDIO_INPUT_DEVICE != 0: + stream_tx.close() + p.terminate() diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 30794dd8..9efe7c26 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -17,8 +17,7 @@ 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('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) -parser.add_argument('--mode', dest="FREEDV_MODE", default=0, type=int) -parser.add_argument('--output', dest="DATA_OUTPUT", type=str) +parser.add_argument('--mode', dest="FREEDV_MODE", default=14, type=int) parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) args = parser.parse_args() @@ -26,17 +25,17 @@ args = parser.parse_args() N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 -DATA_OUTPUT = args.DATA_OUTPUT AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT MODE = args.FREEDV_MODE + # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = 8000 AUDIO_SAMPLE_RATE_TX = 48000 # check if we want to use an audio device then do an pyaudio init -if DATA_OUTPUT == "audio": +if AUDIO_OUTPUT_DEVICE != 0: # pyaudio init p = pyaudio.PyAudio() stream_tx = p.open(format=pyaudio.paInt16, @@ -143,7 +142,7 @@ for i in range(1,N_BURSTS+1): print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr) # check if we want to use an audio device or stdout - if DATA_OUTPUT == "audio": + if AUDIO_OUTPUT_DEVICE != 0: # sample rate conversion from 8000Hz to 48000Hz audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) @@ -156,6 +155,6 @@ for i in range(1,N_BURSTS+1): # and at last check if we had an openend pyaudio instance and close it -if DATA_OUTPUT == "audio": +if AUDIO_OUTPUT_DEVICE != 0: stream_tx.close() p.terminate() From 4ea11ff99c482e3af18bbdd7aa18773df423deb4 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Mon, 13 Dec 2021 07:14:18 +1030 Subject: [PATCH 10/70] ctest github action workflow --- .github/workflows/ctest.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/ctest.yml diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml new file mode 100644 index 00000000..90f9d551 --- /dev/null +++ b/.github/workflows/ctest.yml @@ -0,0 +1,35 @@ +name: CMake + +on: [push] + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install packages + shell: bash + run: | + sudo apt-get update + sudo apt-get install octave octave-common octave-signal sox python3-numpy pip3 portaudio19-dev python3-pyaudio + pip3 install miniaudio crcengine threading + + - name: Build codec2 + shell: bash + run: | + git clone https://github.com/drowe67/codec2.git + cd codec2 && git checkout dr-tnc && git pull + mkdir -p build_linux && cd build_linux && cmake .. && make + + - name: run ctests + shell: bash + working-directory: ${{github.workspace}} + run: | + mkdir build && cd build && cmake .. + ctest From 8536d16724d58e661bcd769a4a19514dd0771a0e Mon Sep 17 00:00:00 2001 From: drowe67 Date: Mon, 13 Dec 2021 07:19:01 +1030 Subject: [PATCH 11/70] debugging workflow ... --- .github/workflows/ctest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 90f9d551..3f8286bd 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -1,4 +1,4 @@ -name: CMake +name: CTest on: [push] @@ -17,7 +17,7 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install octave octave-common octave-signal sox python3-numpy pip3 portaudio19-dev python3-pyaudio + sudo apt-get install octave octave-common octave-signal sox python3-numpy python pip3 portaudio19-dev python3-pyaudio pip3 install miniaudio crcengine threading - name: Build codec2 From 2bdaa45de98fef890f29f0003fb685da75c05de7 Mon Sep 17 00:00:00 2001 From: drowe67 <45574645+drowe67@users.noreply.github.com> Date: Mon, 13 Dec 2021 07:21:03 +1030 Subject: [PATCH 12/70] Update ctest.yml debugging ... --- .github/workflows/ctest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 3f8286bd..5e1a5bbb 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -17,7 +17,7 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install octave octave-common octave-signal sox python3-numpy python pip3 portaudio19-dev python3-pyaudio + sudo apt-get install octave octave-common octave-signal sox python3-numpy python-pip3 portaudio19-dev python3-pyaudio pip3 install miniaudio crcengine threading - name: Build codec2 From d5d6a80b34a63cc3849ae7bbfc7d2adde2a9f055 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 12 Dec 2021 21:52:03 +0100 Subject: [PATCH 13/70] outsourced ctypes header --- test/001_highsnr_stdio_audio/test_rx.py | 45 ++++++------------------ test/001_highsnr_stdio_audio/test_tx.py | 46 ++++++------------------- test/codec2.py | 37 ++++++++++++++++++++ 3 files changed, 58 insertions(+), 70 deletions(-) create mode 100644 test/codec2.py diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index a854dbc5..ea97ffd6 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -51,33 +51,9 @@ if AUDIO_INPUT_DEVICE != 0: input_device_index=AUDIO_INPUT_DEVICE, ) -# LOAD FREEDV -libname = "libcodec2.so" -c_lib = ctypes.CDLL(libname) - -# ctypes function init - -c_lib.freedv_open.argype = [c_int] -c_lib.freedv_open.restype = c_void_p - -c_lib.freedv_get_bits_per_modem_frame.argtype = [c_void_p] -c_lib.freedv_get_bits_per_modem_frame.restype = c_int - -c_lib.freedv_nin.argtype = [c_void_p] -c_lib.freedv_nin.restype = c_int - -c_lib.freedv_rawdatarx.argtype = [c_void_p, c_char_p, c_char_p] -c_lib.freedv_rawdatarx.restype = c_int - -c_lib.freedv_get_n_max_modem_samples.argtype = [c_void_p] -c_lib.freedv_get_n_max_modem_samples.restype = c_int - -c_lib.freedv_set_frames_per_burst.argtype = [c_void_p, c_int] -c_lib.freedv_set_frames_per_burst.restype = c_void_p - -c_lib.freedv_get_rx_status.argtype = [c_void_p] -c_lib.freedv_get_rx_status.restype = c_int +sys.path.insert(0,'..') +import codec2 # ---------------------------------------------------------------- @@ -85,18 +61,17 @@ c_lib.freedv_get_rx_status.restype = c_int # DATA CHANNEL INITIALISATION # open codec2 instance -freedv = cast(c_lib.freedv_open(MODE), c_void_p) +freedv = cast(codec2.api.freedv_open(MODE), c_void_p) # get number of bytes per frame for mode -bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) +bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv)/8) payload_bytes_per_frame = bytes_per_frame -2 -n_max_modem_samples = c_lib.freedv_get_n_max_modem_samples(freedv) +n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(freedv) bytes_out = create_string_buffer(bytes_per_frame * 2) -c_lib.freedv_set_frames_per_burst(freedv,N_FRAMES_PER_BURST) +codec2.api.freedv_set_frames_per_burst(freedv,N_FRAMES_PER_BURST) - total_n_bytes = 0 rx_total_frames = 0 rx_frames = 0 @@ -109,7 +84,7 @@ while receive and time.time() < timeout: data_in = b'' if AUDIO_INPUT_DEVICE != 0: - nin = c_lib.freedv_nin(freedv) + nin = codec2.api.freedv_nin(freedv) nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) if DEBUGGING_MODE == True: print(f"NIN: {nin} [{nin_converted}]", file=sys.stderr) @@ -119,14 +94,14 @@ while receive and time.time() < timeout: data_in = data_in[0].rstrip(b'\x00') else: - nin = c_lib.freedv_nin(freedv) * 2 + nin = codec2.api.freedv_nin(freedv) * 2 data_in = sys.stdin.buffer.read(nin) - nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio total_n_bytes = total_n_bytes + nbytes if DEBUGGING_MODE == True: - print(f"SYNC: {c_lib.freedv_get_rx_status(freedv)}", file=sys.stderr) + print(f"SYNC: {codec2.api.freedv_get_rx_status(freedv)}", file=sys.stderr) if nbytes == bytes_per_frame: rx_total_frames = rx_total_frames + 1 diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 9efe7c26..1be332f1 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -50,55 +50,31 @@ if AUDIO_OUTPUT_DEVICE != 0: # data binary string data_out = b'HELLO WORLD!' -# LOAD FREEDV -libname = "libcodec2.so" -c_lib = ctypes.CDLL(libname) -# ctypes function init - -c_lib.freedv_open.argype = [c_int] -c_lib.freedv_open.restype = c_void_p - -c_lib.freedv_get_bits_per_modem_frame.argtype = [c_void_p] -c_lib.freedv_get_bits_per_modem_frame.restype = c_int - -c_lib.freedv_get_n_tx_preamble_modem_samples.argtype = [c_void_p] -c_lib.freedv_get_n_tx_preamble_modem_samples.restype = c_int - -c_lib.freedv_get_n_tx_postamble_modem_samples.argtype = [c_void_p] -c_lib.freedv_get_n_tx_postamble_modem_samples.restype = c_int - -c_lib.freedv_gen_crc16.argtype = [c_void_p, c_int] -c_lib.freedv_gen_crc16.restype = c_void_p - -c_lib.freedv_nin.argtype = [c_void_p] -c_lib.freedv_nin.restype = c_int - -c_lib.freedv_rawdatatx.argtype = [c_void_p, c_char_p, c_char_p] -c_lib.freedv_rawdatatx.restype = c_int - +sys.path.insert(0,'..') +import codec2 # ---------------------------------------------------------------- # open codec2 instance -freedv = cast(c_lib.freedv_open(MODE), c_void_p) +freedv = cast(codec2.api.freedv_open(MODE), c_void_p) # get number of bytes per frame for mode -bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) +bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv)/8) payload_bytes_per_frame = bytes_per_frame -2 # init buffer for data -n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) +n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv) mod_out = create_string_buffer(n_tx_modem_samples * 2) # init buffer for preample -n_tx_preamble_modem_samples = c_lib.freedv_get_n_tx_preamble_modem_samples(freedv) +n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv) mod_out_preamble = create_string_buffer(n_tx_preamble_modem_samples * 2) # init buffer for postamble -n_tx_postamble_modem_samples = c_lib.freedv_get_n_tx_postamble_modem_samples(freedv) +n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv) mod_out_postamble = create_string_buffer(n_tx_postamble_modem_samples * 2) @@ -108,7 +84,7 @@ buffer[:len(data_out)] = data_out # set buffersize to length of data which will # create crc for data frame - we are using the crc function shipped with codec2 to avoid # crc algorithm incompatibilities -crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # generate CRC16 +crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # generate CRC16 crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string buffer += crc # append crc16 to buffer @@ -118,21 +94,21 @@ print(f"TOTAL BURSTS: {N_BURSTS} TOTAL FRAMES_PER_BURST: {N_FRAMES_PER_BURST}", for i in range(1,N_BURSTS+1): # write preamble to txbuffer - c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble) + codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) txbuffer = bytes(mod_out_preamble) # create modulaton for N = FRAMESPERBURST and append it to txbuffer for n in range(1,N_FRAMES_PER_BURST+1): data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer + codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer txbuffer += bytes(mod_out) print(f"BURST: {i}/{N_BURSTS} FRAME: {n}/{N_FRAMES_PER_BURST}", file=sys.stderr) # append postamble to txbuffer - c_lib.freedv_rawdatapostambletx(freedv, mod_out_postamble) + codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) txbuffer += bytes(mod_out_postamble) # append a delay between bursts as audio silence diff --git a/test/codec2.py b/test/codec2.py new file mode 100644 index 00000000..7600a351 --- /dev/null +++ b/test/codec2.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import ctypes +from ctypes import * +import sys + + +print("loading module", file=sys.stderr) + + +# LOAD FREEDV +libname = "libcodec2.so" +api = ctypes.CDLL(libname) + +# ctypes function init + +api.freedv_open.argype = [c_int] +api.freedv_open.restype = c_void_p + +api.freedv_get_bits_per_modem_frame.argtype = [c_void_p] +api.freedv_get_bits_per_modem_frame.restype = c_int + +api.freedv_nin.argtype = [c_void_p] +api.freedv_nin.restype = c_int + +api.freedv_rawdatarx.argtype = [c_void_p, c_char_p, c_char_p] +api.freedv_rawdatarx.restype = c_int + +api.freedv_get_n_max_modem_samples.argtype = [c_void_p] +api.freedv_get_n_max_modem_samples.restype = c_int + +api.freedv_set_frames_per_burst.argtype = [c_void_p, c_int] +api.freedv_set_frames_per_burst.restype = c_void_p + +api.freedv_get_rx_status.argtype = [c_void_p] +api.freedv_get_rx_status.restype = c_int From bedff2d3b225a5064fb3b29a681e96d07207d05a Mon Sep 17 00:00:00 2001 From: drowe67 <45574645+drowe67@users.noreply.github.com> Date: Mon, 13 Dec 2021 07:22:45 +1030 Subject: [PATCH 14/70] Update ctest.yml debugging ... --- .github/workflows/ctest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 5e1a5bbb..23a7c621 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -17,7 +17,7 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install octave octave-common octave-signal sox python3-numpy python-pip3 portaudio19-dev python3-pyaudio + sudo apt-get install octave octave-common octave-signal sox python3-numpy python3-pip portaudio19-dev python3-pyaudio pip3 install miniaudio crcengine threading - name: Build codec2 From d850d4073b90d353af6006c29a7acceceb0b7e8d Mon Sep 17 00:00:00 2001 From: drowe67 <45574645+drowe67@users.noreply.github.com> Date: Mon, 13 Dec 2021 07:28:30 +1030 Subject: [PATCH 15/70] Update ctest.yml debugging ... --- .github/workflows/ctest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 23a7c621..94524a8d 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -17,8 +17,8 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install octave octave-common octave-signal sox python3-numpy python3-pip portaudio19-dev python3-pyaudio - pip3 install miniaudio crcengine threading + sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio + pip3 install psutil crcengine ujson pyserial numpy structlog - name: Build codec2 shell: bash From 34788be3ba436c7378d2549b4c4a20ba6f8034b6 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 12 Dec 2021 22:00:50 +0100 Subject: [PATCH 16/70] updated codec2 header file --- test/codec2.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/codec2.py b/test/codec2.py index 7600a351..6432a539 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -6,7 +6,7 @@ from ctypes import * import sys -print("loading module", file=sys.stderr) +print("loading codec2 module", file=sys.stderr) # LOAD FREEDV @@ -27,6 +27,15 @@ api.freedv_nin.restype = c_int api.freedv_rawdatarx.argtype = [c_void_p, c_char_p, c_char_p] api.freedv_rawdatarx.restype = c_int +api.freedv_rawdatatx.argtype = [c_void_p, c_char_p, c_char_p] +api.freedv_rawdatatx.restype = c_int + +api.freedv_rawdatapostambletx.argtype = [c_void_p, c_char_p, c_char_p] +api.freedv_rawdatapostambletx.restype = c_int + +api.freedv_rawdatapreambletx.argtype = [c_void_p, c_char_p, c_char_p] +api.freedv_rawdatapreambletx.restype = c_int + api.freedv_get_n_max_modem_samples.argtype = [c_void_p] api.freedv_get_n_max_modem_samples.restype = c_int @@ -35,3 +44,13 @@ api.freedv_set_frames_per_burst.restype = c_void_p api.freedv_get_rx_status.argtype = [c_void_p] api.freedv_get_rx_status.restype = c_int + +api.freedv_get_n_tx_postamble_modem_samples.argtype = [c_void_p] +api.freedv_get_n_tx_postamble_modem_samples.restype = c_int + +api.freedv_get_n_tx_preamble_modem_samples.argtype = [c_void_p] +api.freedv_get_n_tx_preamble_modem_samples.restype = c_int + +api.freedv_get_n_tx_modem_samples.argtype = [c_void_p] +api.freedv_get_n_tx_modem_samples.restype = c_int + From c52c0943224836c9024a7472b8949d0fbb769113 Mon Sep 17 00:00:00 2001 From: drowe67 <45574645+drowe67@users.noreply.github.com> Date: Mon, 13 Dec 2021 07:35:18 +1030 Subject: [PATCH 17/70] Update ctest.yml debugging ... --- .github/workflows/ctest.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 94524a8d..d7318c68 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -31,5 +31,6 @@ jobs: shell: bash working-directory: ${{github.workspace}} run: | - mkdir build && cd build && cmake .. + mkdir build && cd build + cmake -DCODEC2_BUILD_DIR=$GITHUB_WORKSPACE/codec2/build_linux .. ctest From 4b0e326b2e02be7c3409d57299dbea0df5b5fefc Mon Sep 17 00:00:00 2001 From: drowe67 <45574645+drowe67@users.noreply.github.com> Date: Mon, 13 Dec 2021 07:39:54 +1030 Subject: [PATCH 18/70] Update ctest.yml --- .github/workflows/ctest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index d7318c68..d2509682 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -18,7 +18,7 @@ jobs: run: | sudo apt-get update sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio - pip3 install psutil crcengine ujson pyserial numpy structlog + pip3 install psutil crcengine ujson pyserial numpy structlog miniaudio - name: Build codec2 shell: bash @@ -33,4 +33,4 @@ jobs: run: | mkdir build && cd build cmake -DCODEC2_BUILD_DIR=$GITHUB_WORKSPACE/codec2/build_linux .. - ctest + ctest --output-on-failure From 1a5d9e632bb7c689450b1ce108fa1088e38a0a00 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Mon, 13 Dec 2021 08:09:03 +1030 Subject: [PATCH 19/70] added constants to codec2.py, interpreting modes as meaningful strings --- CMakeLists.txt | 18 +++++++++--------- test/001_highsnr_stdio_audio/test_tx.py | 17 +++++++++++------ test/codec2.py | 4 ++++ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9abc5b18..0e4559a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,26 +26,26 @@ add_test(NAME 000_audio_tests COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/test/000_audio_tests; python3 sinustest.py") -add_test(NAME 001_highsnr_stdio_P<>C +add_test(NAME 001_highsnr_stdio_P_C 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; - python3 test_tx.py --mode 14 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | + python3 test_tx.py --mode datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | freedv_data_raw_rx datac0 - - --framesperburst ${FRAMESPERBURST} | hexdump -C") - set_tests_properties(001_highsnr_stdio_P<>C PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") + set_tests_properties(001_highsnr_stdio_P_C PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") -add_test(NAME 001_highsnr_stdio_C<>P +add_test(NAME 001_highsnr_stdio_C_P 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; - freedv_data_raw_tx --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} datac0 /dev/zero - | + freedv_data_raw_tx datac0 --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} /dev/zero - | python3 test_rx.py --mode 14 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") - set_tests_properties(001_highsnr_stdio_C<>P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") + set_tests_properties(001_highsnr_stdio_C_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") -add_test(NAME 001_highsnr_stdio_P<>P +add_test(NAME 001_highsnr_stdio_P_P 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; - python3 test_tx.py --mode 14 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | + python3 test_tx.py --mode datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | python3 test_rx.py --mode 14 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") - set_tests_properties(001_highsnr_stdio_P<>P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") + set_tests_properties(001_highsnr_stdio_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 1be332f1..25e94149 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -11,13 +11,16 @@ import threading import audioop import argparse import sys +sys.path.insert(0,'..') +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('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) -parser.add_argument('--mode', dest="FREEDV_MODE", default=14, type=int) +parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) args = parser.parse_args() @@ -26,12 +29,17 @@ N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT -MODE = args.FREEDV_MODE +if args.FREEDV_MODE == "datac0": + MODE = codec2.api.FREEDV_MODE_DATAC0 +if args.FREEDV_MODE == "datac1": + MODE = codec2.api.FREEDV_MODE_DATAC1 +if args.FREEDV_MODE == "datac1": + MODE = codec2.api.FREEDV_MODE_DATAC3 # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 -MODEM_SAMPLE_RATE = 8000 +MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_SAMPLE_RATE_TX = 48000 # check if we want to use an audio device then do an pyaudio init @@ -51,9 +59,6 @@ if AUDIO_OUTPUT_DEVICE != 0: data_out = b'HELLO WORLD!' -sys.path.insert(0,'..') -import codec2 - # ---------------------------------------------------------------- diff --git a/test/codec2.py b/test/codec2.py index 6432a539..7bbe3f9e 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -54,3 +54,7 @@ api.freedv_get_n_tx_preamble_modem_samples.restype = c_int api.freedv_get_n_tx_modem_samples.argtype = [c_void_p] api.freedv_get_n_tx_modem_samples.restype = c_int +api.FREEDV_MODE_DATAC1 = 10 +api.FREEDV_MODE_DATAC3 = 12 +api.FREEDV_MODE_DATAC0 = 14 +api.FREEDV_FS_8000 = 8000 From 9c294f3b6d37949c7f86c7a0f8061bb460d3418e Mon Sep 17 00:00:00 2001 From: drowe67 <45574645+drowe67@users.noreply.github.com> Date: Mon, 13 Dec 2021 08:14:12 +1030 Subject: [PATCH 20/70] Update ctest.yml --- .github/workflows/ctest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index d2509682..6c59a23a 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -31,6 +31,8 @@ jobs: shell: bash working-directory: ${{github.workspace}} run: | + sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT + aplay -l mkdir build && cd build cmake -DCODEC2_BUILD_DIR=$GITHUB_WORKSPACE/codec2/build_linux .. ctest --output-on-failure From 6be962b6fcf5a8e56e38a6458226b38ad1a3da2f Mon Sep 17 00:00:00 2001 From: drowe67 <45574645+drowe67@users.noreply.github.com> Date: Mon, 13 Dec 2021 08:27:29 +1030 Subject: [PATCH 21/70] Update ctest.yml --- .github/workflows/ctest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 6c59a23a..aab6338d 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -17,7 +17,7 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio + sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio alsa-utils pip3 install psutil crcengine ujson pyserial numpy structlog miniaudio - name: Build codec2 From c290660d519934641af4623a2a3eb138772e402a Mon Sep 17 00:00:00 2001 From: drowe67 <45574645+drowe67@users.noreply.github.com> Date: Mon, 13 Dec 2021 08:33:18 +1030 Subject: [PATCH 22/70] Update ctest.yml --- .github/workflows/ctest.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index aab6338d..968dbe0f 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -31,8 +31,6 @@ jobs: shell: bash working-directory: ${{github.workspace}} run: | - sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT - aplay -l mkdir build && cd build cmake -DCODEC2_BUILD_DIR=$GITHUB_WORKSPACE/codec2/build_linux .. ctest --output-on-failure From 2f4a04a2334f9bd0cf0dfee1f4ecb3a56581ec79 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Mon, 13 Dec 2021 08:51:27 +1030 Subject: [PATCH 23/70] removed test 000 --- CMakeLists.txt | 4 --- test/000_audio_tests/README.md | 14 --------- test/000_audio_tests/sinustest.py | 50 ------------------------------- 3 files changed, 68 deletions(-) delete mode 100644 test/000_audio_tests/README.md delete mode 100644 test/000_audio_tests/sinustest.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e4559a9..027b8171 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,10 +22,6 @@ set(FRAMESPERBURST 3) set(BURSTS 1) set(TESTFRAMES 3) -add_test(NAME 000_audio_tests - COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/test/000_audio_tests; - python3 sinustest.py") - add_test(NAME 001_highsnr_stdio_P_C COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; PATH=$PATH:${CODEC2_BUILD_DIR}/src; diff --git a/test/000_audio_tests/README.md b/test/000_audio_tests/README.md deleted file mode 100644 index b62cfa04..00000000 --- a/test/000_audio_tests/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# FreeDV-JATE -## Just Another TNC Experiment - -Audio tests! - -## Frame rate conversion -### 48000Hz down to 8000Hz with different frame rate converters - -#### Install -pip3 install miniaudio - - -#### Run -python3 sinustest.py diff --git a/test/000_audio_tests/sinustest.py b/test/000_audio_tests/sinustest.py deleted file mode 100644 index 8b74d1eb..00000000 --- a/test/000_audio_tests/sinustest.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import pyaudio -import numpy as np -import audioop -import miniaudio - -volume = 0.5 -fshigh = 48000 -fslow = 8000 -duration = 50.0 -f = 440.0 - -samples_48000 = (np.sin(2*np.pi*np.arange(fshigh*duration)*f/fshigh)).astype(np.float32) -samples_8000 = (np.sin(2*np.pi*np.arange(fslow*duration)*f/fslow)).astype(np.float32) -samples_converted = audioop.ratecv(samples_48000,2,1,fshigh, fslow , None) -samples_converted = bytes(samples_converted[0]) - -converted_frames = miniaudio.convert_frames(miniaudio.SampleFormat.FLOAT32, 1, 48000, bytes(samples_48000), miniaudio.SampleFormat.FLOAT32, 1, 8000) -#converted_frames = bytes(converted_frames) - -print(type(samples_8000)) -print(type(samples_converted)) -print(type(converted_frames)) - -# TODO - write ouputs to .int16 files so we can compare them - -''' -p = pyaudio.PyAudio() -stream = p.open(format=pyaudio.paFloat32, - channels=2, - rate=fshigh, - output=True, - output_device_index=0 #static.AUDIO_OUTPUT_DEVICE - ) -print("original 48000Hz sample") -stream.write(samples_48000) -print("original 8000Hz sample") -stream.write(samples_8000) -print("48000Hz to 8000Hz with audioop") -stream.write(samples_converted) -print("48000Hz to 8000Hz with miniaudio") -stream.write(converted_frames) - - -stream.stop_stream() -stream.close() -p.terminate() -''' From 9565005f71e75921bab68e81dd6cbebffd592c86 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Mon, 13 Dec 2021 19:00:38 +0100 Subject: [PATCH 24/70] meaningful mode names easier mode selection by string name like datac0 instead of 14 --- CMakeLists.txt | 4 ++-- test/001_highsnr_stdio_audio/test_rx.py | 11 +++++------ test/001_highsnr_stdio_audio/test_tx.py | 9 +++------ test/codec2.py | 22 ++++++++++++++++++---- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 027b8171..8b1affdf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ add_test(NAME 001_highsnr_stdio_C_P PATH=$PATH:${CODEC2_BUILD_DIR}/src; cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; freedv_data_raw_tx datac0 --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} /dev/zero - | - python3 test_rx.py --mode 14 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") + python3 test_rx.py --mode datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") set_tests_properties(001_highsnr_stdio_C_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") add_test(NAME 001_highsnr_stdio_P_P @@ -43,5 +43,5 @@ add_test(NAME 001_highsnr_stdio_P_P PATH=$PATH:${CODEC2_BUILD_DIR}/src; cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; python3 test_tx.py --mode datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | - python3 test_rx.py --mode 14 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") + python3 test_rx.py --mode datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") set_tests_properties(001_highsnr_stdio_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index ea97ffd6..64d6f6e1 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -17,12 +17,14 @@ import time import threading import sys import argparse +sys.path.insert(0,'..') +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('--mode', dest="FREEDV_MODE", default=14, type=int) +parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int) parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") @@ -31,13 +33,13 @@ args = parser.parse_args() N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST AUDIO_INPUT_DEVICE = args.AUDIO_INPUT -MODE = args.FREEDV_MODE +MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value DEBUGGING_MODE = args.DEBUGGING_MODE # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 -MODEM_SAMPLE_RATE = 8000 +MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_SAMPLE_RATE_TX = 48000 # check if we want to use an audio device then do an pyaudio init @@ -51,9 +53,6 @@ if AUDIO_INPUT_DEVICE != 0: input_device_index=AUDIO_INPUT_DEVICE, ) - -sys.path.insert(0,'..') -import codec2 # ---------------------------------------------------------------- diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 25e94149..bb2a5e26 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -30,12 +30,9 @@ N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT -if args.FREEDV_MODE == "datac0": - MODE = codec2.api.FREEDV_MODE_DATAC0 -if args.FREEDV_MODE == "datac1": - MODE = codec2.api.FREEDV_MODE_DATAC1 -if args.FREEDV_MODE == "datac1": - MODE = codec2.api.FREEDV_MODE_DATAC3 + +MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value + # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 diff --git a/test/codec2.py b/test/codec2.py index 7bbe3f9e..8369aa63 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -4,11 +4,20 @@ import ctypes from ctypes import * import sys - +from enum import Enum print("loading codec2 module", file=sys.stderr) +# Enum for codec2 modes +class FREEDV_MODE(Enum): + datac0 = 14 + datac1 = 10 + datac3 = 12 + + + + # LOAD FREEDV libname = "libcodec2.so" api = ctypes.CDLL(libname) @@ -54,7 +63,12 @@ api.freedv_get_n_tx_preamble_modem_samples.restype = c_int api.freedv_get_n_tx_modem_samples.argtype = [c_void_p] api.freedv_get_n_tx_modem_samples.restype = c_int -api.FREEDV_MODE_DATAC1 = 10 -api.FREEDV_MODE_DATAC3 = 12 -api.FREEDV_MODE_DATAC0 = 14 + api.FREEDV_FS_8000 = 8000 + + + + + + + From e79aa0b4576f9100d2e5e73d6ad437fb707c9d26 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Mon, 13 Dec 2021 21:11:09 +0100 Subject: [PATCH 25/70] multimode testrun added multimode test and added a SM and MM for single mode and multimode --- CMakeLists.txt | 20 ++- .../test_multimode_rx.py | 144 ++++++++++++++++++ .../test_multimode_tx.py | 122 +++++++++++++++ test/codec2.py | 9 +- 4 files changed, 288 insertions(+), 7 deletions(-) create mode 100755 test/001_highsnr_stdio_audio/test_multimode_rx.py create mode 100644 test/001_highsnr_stdio_audio/test_multimode_tx.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b1affdf..f657e60e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,26 +22,34 @@ set(FRAMESPERBURST 3) set(BURSTS 1) set(TESTFRAMES 3) -add_test(NAME 001_highsnr_stdio_P_C +add_test(NAME 001_highsnr_stdio_P_C_SM 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; python3 test_tx.py --mode datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | freedv_data_raw_rx datac0 - - --framesperburst ${FRAMESPERBURST} | hexdump -C") - set_tests_properties(001_highsnr_stdio_P_C PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") + set_tests_properties(001_highsnr_stdio_P_C_SM PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") -add_test(NAME 001_highsnr_stdio_C_P +add_test(NAME 001_highsnr_stdio_C_P_SM 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; freedv_data_raw_tx datac0 --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} /dev/zero - | python3 test_rx.py --mode datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") - set_tests_properties(001_highsnr_stdio_C_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") + set_tests_properties(001_highsnr_stdio_C_P_SM PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") -add_test(NAME 001_highsnr_stdio_P_P +add_test(NAME 001_highsnr_stdio_P_P_SM 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; python3 test_tx.py --mode datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | python3 test_rx.py --mode datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") - set_tests_properties(001_highsnr_stdio_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") + set_tests_properties(001_highsnr_stdio_P_P_SM PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") + +add_test(NAME 001_highsnr_stdio_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; + python3 test_multimode_tx.py --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | + python3 test_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") + set_tests_properties(001_highsnr_stdio_P_P_MM PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}") diff --git a/test/001_highsnr_stdio_audio/test_multimode_rx.py b/test/001_highsnr_stdio_audio/test_multimode_rx.py new file mode 100755 index 00000000..0834fea7 --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_multimode_rx.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import pyaudio +import audioop +import time +import argparse +import sys +import ctypes +from ctypes import * +import pathlib +sys.path.insert(0,'..') +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('--audioinput', dest="AUDIO_INPUT", default=0, type=int) +parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") + +args = parser.parse_args() + +N_BURSTS = args.N_BURSTS +N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST +AUDIO_INPUT_DEVICE = args.AUDIO_INPUT + + +# SET COUNTERS +rx_total_frames_datac0 = 0 +rx_frames_datac0 = 0 +rx_bursts_datac0 = 0 + +rx_total_frames_datac1 = 0 +rx_frames_datac1 = 0 +rx_bursts_datac1 = 0 + +rx_total_frames_datac3 = 0 +rx_frames_datac3 = 0 +rx_bursts_datac3 = 0 + + +# open codec2 instance +datac0_freedv = cast(codec2.api.freedv_open(14), c_void_p) +datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac0_freedv)/8) +datac0_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac0_freedv) +datac0_bytes_out = create_string_buffer(datac0_bytes_per_frame * 2) +codec2.api.freedv_set_frames_per_burst(datac0_freedv,N_FRAMES_PER_BURST) +datac0_buffer = bytes() + +datac1_freedv = cast(codec2.api.freedv_open(10), c_void_p) +datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac1_freedv)/8) +datac1_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac1_freedv) +datac1_bytes_out = create_string_buffer(datac1_bytes_per_frame * 2) +codec2.api.freedv_set_frames_per_burst(datac1_freedv,N_FRAMES_PER_BURST) +datac1_buffer = bytes() + +datac3_freedv = cast(codec2.api.freedv_open(12), c_void_p) +datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac3_freedv)/8) +datac3_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac3_freedv) +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 = bytes() + +# check if we want to use an audio device then do an pyaudio init +if AUDIO_INPUT_DEVICE != 0: + p = pyaudio.PyAudio() + # --------------------------------------------OPEN AUDIO CHANNEL RX + stream_rx = p.open(format=pyaudio.paInt16, + channels=1, + rate=48000, + frames_per_buffer=16, + input=True, + input_device_index=5 + ) + + +timeout = time.time() + 10 +receive = True + +while receive and time.time() < timeout: + if AUDIO_INPUT_DEVICE != 0: + data_in = stream_rx.read(1024, exception_on_overflow=True) + data_in = audioop.ratecv(data_in, 2, 1, 48000, 8000, None) + data_in = data_in[0] # .rstrip(b'\x00') + else: + data_in = sys.stdin.buffer.read(1024) + + + datac0_buffer += data_in + datac1_buffer += data_in + datac3_buffer += data_in + + + datac0_nin = codec2.api.freedv_nin(datac0_freedv) * 2 + + if len(datac0_buffer) >= (datac0_nin): + datac0_audio = datac0_buffer[:datac0_nin] + 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 + + if rx_frames_datac0 == N_FRAMES_PER_BURST: + rx_frames_datac0 = 0 + rx_bursts_datac0 = rx_bursts_datac0 + 1 + + + datac1_nin = codec2.api.freedv_nin(datac1_freedv) * 2 + if len(datac1_buffer) >= (datac1_nin): + datac1_audio = datac1_buffer[:datac1_nin] + 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 + + if rx_frames_datac1 == N_FRAMES_PER_BURST: + rx_frames_datac1 = 0 + rx_bursts_datac1 = rx_bursts_datac1 + 1 + + datac3_nin = codec2.api.freedv_nin(datac3_freedv) * 2 + if len(datac3_buffer) >= (datac3_nin): + datac3_audio = datac3_buffer[:datac3_nin] + 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 + + if rx_frames_datac3 == N_FRAMES_PER_BURST: + rx_frames_datac3 = 0 + rx_bursts_datac3 = rx_bursts_datac3 + 1 + + + if rx_bursts_datac0 == N_BURSTS and rx_bursts_datac1 == N_BURSTS and rx_bursts_datac3 == N_BURSTS: + receive = False + +# INFO IF WE REACHED TIMEOUT +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 new file mode 100644 index 00000000..f99132a5 --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +import ctypes +from ctypes import * +import pathlib +import pyaudio +import time +import threading +import audioop +import argparse +import sys +sys.path.insert(0,'..') +import codec2 + + +# GET PARAMETER INPUTS +parser = argparse.ArgumentParser(description='FreeDATA TEST') +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('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) +parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) + +args = parser.parse_args() + +N_BURSTS = args.N_BURSTS +N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST +DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 +AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT + + +# AUDIO PARAMETERS +AUDIO_FRAMES_PER_BUFFER = 2048 +MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 +AUDIO_SAMPLE_RATE_TX = 48000 + + + +if AUDIO_OUTPUT_DEVICE != 0: + # pyaudio init + p = pyaudio.PyAudio() + stream_tx = p.open(format=pyaudio.paInt16, + channels=1, + rate=AUDIO_SAMPLE_RATE_TX, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples + output=True, + output_device_index=AUDIO_OUTPUT_DEVICE, + ) + + +modes = [14,10,12] +for m in modes: + + freedv = cast(codec2.api.freedv_open(m), c_void_p) + + n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv) + mod_out = create_string_buffer(2*n_tx_modem_samples) + + n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv) + mod_out_preamble = create_string_buffer(2*n_tx_preamble_modem_samples) + + n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv) + mod_out_postamble = create_string_buffer(2*n_tx_postamble_modem_samples) + + bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) + payload_per_frame = bytes_per_frame - 2 + + + # data binary string + data_out = b'HELLO WORLD!' + + buffer = bytearray(payload_per_frame) + # set buffersize to length of data which will be send + buffer[:len(data_out)] = data_out + + crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16 + # convert crc to 2 byte hex string + crc = crc.value.to_bytes(2, byteorder='big') + buffer += crc # append crc16 to buffer + data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) + + for i in range(1,N_BURSTS+1): + + # write preamble to txbuffer + codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) + txbuffer = bytes(mod_out_preamble) + + # create modulaton for N = FRAMESPERBURST and append it to txbuffer + for n in range(1,N_FRAMES_PER_BURST+1): + + data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) + codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer + + txbuffer += bytes(mod_out) + + # append postamble to txbuffer + codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) + txbuffer += bytes(mod_out_postamble) + + # append a delay between bursts as audio silence + samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) + mod_out_silence = create_string_buffer(samples_delay) + txbuffer += bytes(mod_out_silence) + + # check if we want to use an audio device or stdout + if AUDIO_OUTPUT_DEVICE != 0: + + # sample rate conversion from 8000Hz to 48000Hz + audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) + stream_tx.write(audio[0]) + + else: + # print data to terminal for piping the output to other programs + sys.stdout.buffer.write(txbuffer) + sys.stdout.flush() + + +# and at last check if we had an openend pyaudio instance and close it +if AUDIO_OUTPUT_DEVICE != 0: + stream_tx.close() + p.terminate() diff --git a/test/codec2.py b/test/codec2.py index 8369aa63..e51d25a9 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -4,6 +4,7 @@ import ctypes from ctypes import * import sys +import pathlib from enum import Enum print("loading codec2 module", file=sys.stderr) @@ -14,7 +15,9 @@ class FREEDV_MODE(Enum): datac0 = 14 datac1 = 10 datac3 = 12 - + +def FREEDV_GET_MODE(mode): + return FREEDV_MODE[mode].value @@ -22,6 +25,7 @@ class FREEDV_MODE(Enum): libname = "libcodec2.so" api = ctypes.CDLL(libname) + # ctypes function init api.freedv_open.argype = [c_int] @@ -54,6 +58,9 @@ api.freedv_set_frames_per_burst.restype = c_void_p api.freedv_get_rx_status.argtype = [c_void_p] api.freedv_get_rx_status.restype = c_int +api.freedv_get_modem_stats.argtype = [c_void_p, c_void_p, c_void_p] +api.freedv_get_modem_stats.restype = c_int + api.freedv_get_n_tx_postamble_modem_samples.argtype = [c_void_p] api.freedv_get_n_tx_postamble_modem_samples.restype = c_int From cd8f522f85341c564f7b340fa610eb9e7880db4e Mon Sep 17 00:00:00 2001 From: dj2ls Date: Mon, 13 Dec 2021 21:47:30 +0100 Subject: [PATCH 26/70] added time measurement writing to stdout seems to be really slow after the first run... --- .../test_multimode_tx.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_tx.py b/test/001_highsnr_stdio_audio/test_multimode_tx.py index f99132a5..3074b3a8 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -48,10 +48,10 @@ if AUDIO_OUTPUT_DEVICE != 0: output_device_index=AUDIO_OUTPUT_DEVICE, ) - modes = [14,10,12] for m in modes: + freedv = cast(codec2.api.freedv_open(m), c_void_p) n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv) @@ -102,7 +102,8 @@ for m in modes: samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) mod_out_silence = create_string_buffer(samples_delay) txbuffer += bytes(mod_out_silence) - + + # check if we want to use an audio device or stdout if AUDIO_OUTPUT_DEVICE != 0: @@ -111,12 +112,22 @@ for m in modes: stream_tx.write(audio[0]) 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.flush() - + # and at least print the needed time to see which time we needed + timeneeded = time.time()-starttime + print(f"time: {timeneeded}", file=sys.stderr) + + # and at last check if we had an openend pyaudio instance and close it if AUDIO_OUTPUT_DEVICE != 0: stream_tx.close() p.terminate() + + From af99e53ec747bd9825a570b3bc48e99bd6194836 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Mon, 13 Dec 2021 22:03:56 +0100 Subject: [PATCH 27/70] more debugging info added buffer length to multimode test to see why it takes so much time --- test/001_highsnr_stdio_audio/test_multimode_tx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_tx.py b/test/001_highsnr_stdio_audio/test_multimode_tx.py index 3074b3a8..61aa53aa 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -122,7 +122,7 @@ for m in modes: # and at least print the needed time to see which time we needed timeneeded = time.time()-starttime - print(f"time: {timeneeded}", file=sys.stderr) + print(f"time: {timeneeded} buffer: {len(txbuffer)}", file=sys.stderr) # and at last check if we had an openend pyaudio instance and close it From 52841da285bf5d086eb70bc635115fbd5ab53c2f Mon Sep 17 00:00:00 2001 From: drowe67 Date: Tue, 14 Dec 2021 12:35:19 +1030 Subject: [PATCH 28/70] added cmd line arument to list audiodev by number, test_tx.py and test_rx.py working OK with virtual audio card, updated README.md with latest examples --- test/001_highsnr_stdio_audio/README.md | 108 ++++++++++-------------- test/001_highsnr_stdio_audio/test_rx.py | 19 +++-- test/001_highsnr_stdio_audio/test_tx.py | 12 ++- 3 files changed, 67 insertions(+), 72 deletions(-) diff --git a/test/001_highsnr_stdio_audio/README.md b/test/001_highsnr_stdio_audio/README.md index b944f8c8..8f6f0607 100644 --- a/test/001_highsnr_stdio_audio/README.md +++ b/test/001_highsnr_stdio_audio/README.md @@ -1,63 +1,36 @@ +# 001_HIGHSNR_STDIO_AUDIO TEST SUITE -# FreeDV-JATE [Just Another TNC Experiment] +1. Install + ``` + sudo apt update + sudo apt upgrade + sudo apt install git cmake build-essential python3-pip portaudio19-dev python3-pyaudio + pip3 install crcengine + pip3 install threading + ``` +1. Install codec2, and set up the `libcodec2.so` shared library path, for example + ``` + export LD_LIBRARY_PATH=${HOME}/codec2/build_linux/src + ``` -## 001_HIGHSNR_STDIO_AUDIO TEST SUITE +## STDIO tests + +Pipes are used to move audio samples from the Tx to Rx: -### INSTALL TEST SUITE -#### Install prerequierements ``` -sudo apt update -sudo apt upgrade -sudo apt install git cmake build-essential python3-pip portaudio19-dev python3-pyaudio -pip3 install crcengine -pip3 install threading +python3 test_tx.py --mode datac1 --delay 500 --frames 2 --bursts 1 | python3 test_rx.py --mode datac1 --frames 2 --bursts 1 ``` -Change into a directory of your choice -Run the following commands --> They will download and compile the latest codec2 ( dr-packet ) files and LPCNet as well into the directory of your choice -``` -wget https://raw.githubusercontent.com/DJ2LS/FreeDV-JATE/001_HIGHSNR_STDIO_AUDIO/install_test_suite.sh -chmod +x install_test_suite.sh -./install_test_suite.sh -``` - - - -### PARAMETERS -| parameter | description | side -|--|--|--| -| - -mode 12 | set the mode for FreeDV ( 10,11,12 ) | TX & RX -| - -delay 1 | set the delay between burst | TX -| - -frames 1 | set the number of frames per burst | TX & RX -| - -bursts 1 | set the number of bursts | TX & RX -| - -input "audio" | if set, program switches to audio instead of stdin | RX -| - -audioinput 2 | set the audio device | RX -| - -output "audio" | if set, program switches to audio instead of stdout | TX -| - -audiooutput 1 | set the audio device | TX -| - -debug | if used, print additional debugging output | RX - - - -### STDIO TESTS FOR TERMINAL USAGE ONLY - - python3 test_tx.py --mode 12 --delay 500 --frames 2 --bursts 1 | python3 test_rx.py --mode 12 --frames 2 --bursts 1 - - - -### AUDIO TESTS VIA VIRTUAL AUDIO DEVICE - - #### Create audio sinkhole and subdevices - Note: This command needs to be run again after every reboot - ``` -sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2 -``` -check if devices have been created - +## AUDIO test via virtual audio devices +1. Create virtual audio devices. Note: This command needs to be run again after every reboot + ``` + sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2 + ``` +1. Check if devices have been created + ``` aplay -l -Output should be like this: - Karte 0: Intel [HDA Intel], Gerät 0: Generic Analog [Generic Analog] Sub-Geräte: 1/1 @@ -74,17 +47,24 @@ Output should be like this: Karte 2: CHAT2 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] Sub-Geräte: 1/1 Sub-Gerät #0: subdevice #0 + ``` + +1. Determine the audio device number you would like to use: + ``` + python3 test_rx.py --list + + audiodev: 0 HDA Intel PCH: ALC269VC Analog (hw:0,0) + audiodev: 1 HDA Intel PCH: HDMI 0 (hw:0,3) + audiodev: 2 HDA Intel PCH: HDMI 1 (hw:0,7) + audiodev: 3 HDA Intel PCH: HDMI 2 (hw:0,8) + audiodev: 4 Loopback: PCM (hw:1,0) + audiodev: 5 Loopback: PCM (hw:1,1) + audiodev: 6 Loopback: PCM (hw:2,0) + audiodev: 7 Loopback: PCM (hw:2,1) + ``` + In this case we choose audiodev 4 for the RX and 5 for the Tx. -#### Run tests: -Its important, to run TEST_RX at first to reduce the chance that we get some system side audio errors. Tests are showing, that its important to start with audio device "2" at first and then go to the lower virtual devices "1". -Audio device "0" is the default sound card. - -##### RX side - - python3 test_rx.py --mode 12 --frames 2 --bursts 1 --input "audio" --audioinput 2 --debug - -##### TX side - - python3 test_tx.py --mode 12 --delay 500 --frames 2 --bursts 1 --output "audio" --audiooutput 1 - - +1. Start the Rx first, then Tx in separate consoles: + ``` + python3 test_rx.py --mode datac0 --frames 2 --bursts 1 --audiodev 4 --debug + python3 test_tx.py --mode datac0 --frames 2 --bursts 1 --audiodev 5 diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 64d6f6e1..c3ae5a0b 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -25,14 +25,21 @@ 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('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) -parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int) +parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=0, 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") args = parser.parse_args() +if args.LIST: + p = pyaudio.PyAudio() + for dev in range(0,p.get_device_count()): + print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) + quit() + N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST -AUDIO_INPUT_DEVICE = args.AUDIO_INPUT +AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value DEBUGGING_MODE = args.DEBUGGING_MODE @@ -40,7 +47,8 @@ DEBUGGING_MODE = args.DEBUGGING_MODE # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 -AUDIO_SAMPLE_RATE_TX = 48000 +AUDIO_SAMPLE_RATE_RX = 48000 +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 != 0: @@ -56,8 +64,7 @@ if AUDIO_INPUT_DEVICE != 0: # ---------------------------------------------------------------- - - # DATA CHANNEL INITIALISATION +# DATA CHANNEL INITIALISATION # open codec2 instance freedv = cast(codec2.api.freedv_open(MODE), c_void_p) @@ -119,5 +126,5 @@ print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames}", file=s # and at last check if we had an openend pyaudio instance and close it if AUDIO_INPUT_DEVICE != 0: - stream_tx.close() + stream_rx.close() p.terminate() diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index bb2a5e26..389c0ce9 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -21,14 +21,21 @@ 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('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) -parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) +parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=0, type=int, help="audio output device number to use") +parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") args = parser.parse_args() +if args.LIST: + p = pyaudio.PyAudio() + for dev in range(0,p.get_device_count()): + print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) + quit() + N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 -AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT +AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value @@ -38,6 +45,7 @@ MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_SAMPLE_RATE_TX = 48000 +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 != 0: From b8a9f4126b00953203648c0cb7fff31ca8568cf2 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Tue, 14 Dec 2021 13:18:12 +1030 Subject: [PATCH 29/70] first attempt at test using virtual sound cards --- CMakeLists.txt | 14 +++++++++++ test/001_highsnr_stdio_audio/test_virtual.sh | 25 ++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100755 test/001_highsnr_stdio_audio/test_virtual.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index f657e60e..3ed0e2a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,3 +53,17 @@ add_test(NAME 001_highsnr_stdio_P_P_MM python3 test_multimode_tx.py --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | python3 test_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") set_tests_properties(001_highsnr_stdio_P_P_MM PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}") + + +# These tests can't run on GitHub actions +if(NOT DEFINED ENV{GITHUB_RUN_ID}) + +add_test(NAME 001_highsnr_virtual_P_P + 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.sh") + set_tests_properties(001_highsnr_virtual_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 1 RECEIVED FRAMES: 2") + +endif() + diff --git a/test/001_highsnr_stdio_audio/test_virtual.sh b/test/001_highsnr_stdio_audio/test_virtual.sh new file mode 100755 index 00000000..8ce57ef5 --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_virtual.sh @@ -0,0 +1,25 @@ +#!/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_rx.py --mode datac0 --frames 2 --bursts 1 --audiodev 4 --debug 2>&1 | tee ${RX_LOG} & +sleep 1 +python3 test_tx.py --mode datac0 --frames 2 --bursts 1 --audiodev 5 + +tail -f ${RX_LOG} | sed '/RECEIVED BURSTS/ q' From e7fbca9405f3fa1a1b3c3796d62c78714af4b265 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Tue, 14 Dec 2021 13:42:47 +1030 Subject: [PATCH 30/70] enable use of default sound card devnum 0 --- test/001_highsnr_stdio_audio/test_rx.py | 8 ++++---- test/001_highsnr_stdio_audio/test_tx.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index c3ae5a0b..e9d72b3d 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -25,7 +25,7 @@ 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('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) -parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=0, type=int, help="audio device number to use") +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") @@ -51,7 +51,7 @@ AUDIO_SAMPLE_RATE_RX = 48000 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 != 0: +if AUDIO_INPUT_DEVICE != -1: p = pyaudio.PyAudio() stream_rx = p.open(format=pyaudio.paInt16, channels=1, @@ -88,7 +88,7 @@ receive = True while receive and time.time() < timeout: data_in = b'' - if AUDIO_INPUT_DEVICE != 0: + if AUDIO_INPUT_DEVICE != -1: nin = codec2.api.freedv_nin(freedv) nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) @@ -125,6 +125,6 @@ while receive and time.time() < timeout: print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames}", file=sys.stderr) # and at last check if we had an openend pyaudio instance and close it -if AUDIO_INPUT_DEVICE != 0: +if AUDIO_INPUT_DEVICE != -1: stream_rx.close() p.terminate() diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 389c0ce9..bd47a046 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -21,7 +21,7 @@ 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('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) -parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=0, type=int, help="audio output device number to use") +parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, help="audio output device number to use") parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") args = parser.parse_args() @@ -48,7 +48,7 @@ AUDIO_SAMPLE_RATE_TX = 48000 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 != 0: +if AUDIO_OUTPUT_DEVICE != -1: # pyaudio init p = pyaudio.PyAudio() stream_tx = p.open(format=pyaudio.paInt16, @@ -128,7 +128,7 @@ for i in range(1,N_BURSTS+1): print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr) # check if we want to use an audio device or stdout - if AUDIO_OUTPUT_DEVICE != 0: + 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) @@ -141,6 +141,6 @@ for i in range(1,N_BURSTS+1): # 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() From 290d2dd8b79a84e678b4345b6986bac6a1bc0a75 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Tue, 14 Dec 2021 13:46:20 +1030 Subject: [PATCH 31/70] test_multimode_tx.py sounds OK through a sound card --- .../test_multimode_tx.py | 18 +++++++++++++----- test/codec2.py | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_tx.py b/test/001_highsnr_stdio_audio/test_multimode_tx.py index 61aa53aa..69da149a 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -20,24 +20,32 @@ parser = argparse.ArgumentParser(description='FreeDATA TEST') 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('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) -parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) +parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, help="audio output device number to use") +parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") args = parser.parse_args() +if args.LIST: + p = pyaudio.PyAudio() + for dev in range(0,p.get_device_count()): + print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) + quit() + N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 -AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT +AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_SAMPLE_RATE_TX = 48000 +assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 -if AUDIO_OUTPUT_DEVICE != 0: +if AUDIO_OUTPUT_DEVICE != -1: # pyaudio init p = pyaudio.PyAudio() stream_tx = p.open(format=pyaudio.paInt16, @@ -48,7 +56,7 @@ if AUDIO_OUTPUT_DEVICE != 0: output_device_index=AUDIO_OUTPUT_DEVICE, ) -modes = [14,10,12] +modes = [codec2.api.FREEDV_MODE_DATAC0, codec2.api.FREEDV_MODE_DATAC1, codec2.api.FREEDV_MODE_DATAC3] for m in modes: @@ -105,7 +113,7 @@ for m in modes: # check if we want to use an audio device or stdout - if AUDIO_OUTPUT_DEVICE != 0: + 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) diff --git a/test/codec2.py b/test/codec2.py index e51d25a9..a1777a9c 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -72,6 +72,9 @@ api.freedv_get_n_tx_modem_samples.restype = c_int api.FREEDV_FS_8000 = 8000 +api.FREEDV_MODE_DATAC1 = 10 +api.FREEDV_MODE_DATAC3 = 12 +api.FREEDV_MODE_DATAC0 = 14 From dee3f87cebcaa5fc970755e886705c57793a798e Mon Sep 17 00:00:00 2001 From: drowe67 Date: Tue, 14 Dec 2021 14:24:45 +1030 Subject: [PATCH 32/70] sound card input coded but not working in test_multimode_rx.py --- .../test_multimode_rx.py | 41 +++++++++++++------ test/001_highsnr_stdio_audio/test_rx.py | 2 +- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_rx.py b/test/001_highsnr_stdio_audio/test_multimode_rx.py index 0834fea7..93d7da10 100755 --- a/test/001_highsnr_stdio_audio/test_multimode_rx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_rx.py @@ -16,15 +16,29 @@ import codec2 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('--audioinput', dest="AUDIO_INPUT", default=0, 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") +parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends") args = parser.parse_args() +if args.LIST: + p = pyaudio.PyAudio() + for dev in range(0,p.get_device_count()): + print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) + quit() + N_BURSTS = args.N_BURSTS N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST -AUDIO_INPUT_DEVICE = args.AUDIO_INPUT +AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE +TIMEOUT = args.TIMEOUT +# AUDIO PARAMETERS +AUDIO_FRAMES_PER_BUFFER = 2048 +MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 +AUDIO_SAMPLE_RATE_RX = 48000 +assert (AUDIO_SAMPLE_RATE_RX % MODEM_SAMPLE_RATE) == 0 # SET COUNTERS rx_total_frames_datac0 = 0 @@ -63,28 +77,31 @@ codec2.api.freedv_set_frames_per_burst(datac3_freedv,N_FRAMES_PER_BURST) datac3_buffer = bytes() # check if we want to use an audio device then do an pyaudio init -if AUDIO_INPUT_DEVICE != 0: +if AUDIO_INPUT_DEVICE != -1: p = pyaudio.PyAudio() # --------------------------------------------OPEN AUDIO CHANNEL RX stream_rx = p.open(format=pyaudio.paInt16, channels=1, - rate=48000, - frames_per_buffer=16, + rate=AUDIO_SAMPLE_RATE_RX, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, input=True, - input_device_index=5 + input_device_index=AUDIO_INPUT_DEVICE ) -timeout = time.time() + 10 +timeout = time.time() + TIMEOUT receive = True +BUF_SIZE = 160 + while receive and time.time() < timeout: - if AUDIO_INPUT_DEVICE != 0: - data_in = stream_rx.read(1024, exception_on_overflow=True) - data_in = audioop.ratecv(data_in, 2, 1, 48000, 8000, None) - data_in = data_in[0] # .rstrip(b'\x00') + 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') else: - data_in = sys.stdin.buffer.read(1024) + data_in = sys.stdin.buffer.read(BUF_SIZE) datac0_buffer += data_in diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index e9d72b3d..3ee89a7d 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -58,7 +58,7 @@ if AUDIO_INPUT_DEVICE != -1: rate=AUDIO_SAMPLE_RATE_RX, frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, input=True, - input_device_index=AUDIO_INPUT_DEVICE, + input_device_index=AUDIO_INPUT_DEVICE ) From eeb221a31bf59f239204bdac05ce0cfc3ca048bd Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 06:47:36 +1030 Subject: [PATCH 33/70] simpler script --- test/001_highsnr_stdio_audio/test_virtual.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_virtual.sh b/test/001_highsnr_stdio_audio/test_virtual.sh index 8ce57ef5..6fd08b6d 100755 --- a/test/001_highsnr_stdio_audio/test_virtual.sh +++ b/test/001_highsnr_stdio_audio/test_virtual.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 1 --audiodev 4 --debug 2>&1 | tee ${RX_LOG} & +python3 test_rx.py --mode datac0 --frames 2 --bursts 1 --audiodev 4 --debug & +rx_pid=$! sleep 1 python3 test_tx.py --mode datac0 --frames 2 --bursts 1 --audiodev 5 - -tail -f ${RX_LOG} | sed '/RECEIVED BURSTS/ q' +wait ${rx_pid} From e93dc4d1973d5df44ece33f47aba909ae7cc823a Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 06:48:18 +1030 Subject: [PATCH 34/70] rx_status table to return flags in text format --- test/001_highsnr_stdio_audio/test_rx.py | 6 ++++-- test/codec2.py | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 3ee89a7d..330c9954 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -93,7 +93,7 @@ while receive and time.time() < timeout: nin = codec2.api.freedv_nin(freedv) nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) if DEBUGGING_MODE == True: - print(f"NIN: {nin} [{nin_converted}]", file=sys.stderr) + 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 = audioop.ratecv(data_in,2,1,AUDIO_SAMPLE_RATE_RX, MODEM_SAMPLE_RATE, None) @@ -107,7 +107,9 @@ while receive and time.time() < timeout: total_n_bytes = total_n_bytes + nbytes if DEBUGGING_MODE == True: - print(f"SYNC: {codec2.api.freedv_get_rx_status(freedv)}", file=sys.stderr) + rx_status = codec2.api.freedv_get_rx_status(freedv) + rx_status_string = codec2.api.rx_sync_flags_to_text[rx_status] + print(f"rx_status: {rx_status_string}", file=sys.stderr) if nbytes == bytes_per_frame: rx_total_frames = rx_total_frames + 1 diff --git a/test/codec2.py b/test/codec2.py index a1777a9c..7c73922f 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -19,8 +19,6 @@ class FREEDV_MODE(Enum): def FREEDV_GET_MODE(mode): return FREEDV_MODE[mode].value - - # LOAD FREEDV libname = "libcodec2.so" api = ctypes.CDLL(libname) @@ -76,6 +74,23 @@ api.FREEDV_MODE_DATAC1 = 10 api.FREEDV_MODE_DATAC3 = 12 api.FREEDV_MODE_DATAC0 = 14 +api.rx_sync_flags_to_text = [ + "----", + "---T", + "--S-", + "--ST", + "-B--", + "-B-T", + "-BS-", + "-BST", + "E---", + "E--T", + "E-S-", + "E-ST", + "EB--", + "EB-T", + "EBS-", + "EBST"] From 55ae1c31ef52d009337c7246905d1ba81cf9a2f5 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 07:00:20 +1030 Subject: [PATCH 35/70] counting errors based on rx_status --- CMakeLists.txt | 2 +- test/001_highsnr_stdio_audio/test_rx.py | 7 +++++-- test/001_highsnr_stdio_audio/test_virtual.sh | 4 ++-- test/codec2.py | 6 ++++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ed0e2a6..ba5d1eda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ add_test(NAME 001_highsnr_virtual_P_P PATH=$PATH:${CODEC2_BUILD_DIR}/src; cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; ./test_virtual.sh") - set_tests_properties(001_highsnr_virtual_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 1 RECEIVED FRAMES: 2") + set_tests_properties(001_highsnr_virtual_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") endif() diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 330c9954..6f88ddd9 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -82,6 +82,7 @@ total_n_bytes = 0 rx_total_frames = 0 rx_frames = 0 rx_bursts = 0 +rx_errors = 0 timeout = time.time() + 10 receive = True @@ -106,8 +107,8 @@ while receive and time.time() < timeout: nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio total_n_bytes = total_n_bytes + nbytes + rx_status = codec2.api.freedv_get_rx_status(freedv) if DEBUGGING_MODE == True: - rx_status = codec2.api.freedv_get_rx_status(freedv) rx_status_string = codec2.api.rx_sync_flags_to_text[rx_status] print(f"rx_status: {rx_status_string}", file=sys.stderr) @@ -119,12 +120,14 @@ while receive and time.time() < timeout: rx_frames = 0 rx_bursts = rx_bursts + 1 + if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: + rx_errors = rx_errors + 1 if rx_bursts == N_BURSTS: receive = False -print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames}", file=sys.stderr) +print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames} RX_ERRORS: {rx_errors}", file=sys.stderr) # and at last check if we had an openend pyaudio instance and close it if AUDIO_INPUT_DEVICE != -1: diff --git a/test/001_highsnr_stdio_audio/test_virtual.sh b/test/001_highsnr_stdio_audio/test_virtual.sh index 6fd08b6d..367fd628 100755 --- a/test/001_highsnr_stdio_audio/test_virtual.sh +++ b/test/001_highsnr_stdio_audio/test_virtual.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 1 --audiodev 4 --debug & +python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --audiodev 4 --debug & rx_pid=$! sleep 1 -python3 test_tx.py --mode datac0 --frames 2 --bursts 1 --audiodev 5 +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 --audiodev 5 wait ${rx_pid} diff --git a/test/codec2.py b/test/codec2.py index 7c73922f..4019138d 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -74,6 +74,12 @@ api.FREEDV_MODE_DATAC1 = 10 api.FREEDV_MODE_DATAC3 = 12 api.FREEDV_MODE_DATAC0 = 14 +# Return code flags for freedv_get_rx_status() function +api.FREEDV_RX_TRIAL_SYNC = 0x1 # demodulator has trial sync +api.FREEDV_RX_SYNC = 0x2 # demodulator has sync +api.FREEDV_RX_BITS = 0x4 # data bits have been returned +api.FREEDV_RX_BIT_ERRORS = 0x8 # FEC may not have corrected all bit errors (not all parity checks OK) + api.rx_sync_flags_to_text = [ "----", "---T", From 2642ae414bd4926d52afbf948fad60182908d1a2 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 07:21:54 +1030 Subject: [PATCH 36/70] added finer grained tests - cirtual sound card using aplay/arecord which works well --- CMakeLists.txt | 15 ++++++++--- test/001_highsnr_stdio_audio/test_rx.py | 2 ++ test/001_highsnr_stdio_audio/test_virtual1.sh | 27 +++++++++++++++++++ .../{test_virtual.sh => test_virtual2.sh} | 0 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100755 test/001_highsnr_stdio_audio/test_virtual1.sh rename test/001_highsnr_stdio_audio/{test_virtual.sh => test_virtual2.sh} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba5d1eda..6a8a891b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,12 +58,21 @@ add_test(NAME 001_highsnr_stdio_P_P_MM # These tests can't run on GitHub actions if(NOT DEFINED ENV{GITHUB_RUN_ID}) -add_test(NAME 001_highsnr_virtual_P_P +# uses aplay/arecord then pipe to Python +add_test(NAME 001_highsnr_virtual1_P_P 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.sh") - set_tests_properties(001_highsnr_virtual_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") + ./test_virtual1.sh") + set_tests_properties(001_highsnr_virtual1_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") + +# lets Python do audio I/O +add_test(NAME 001_highsnr_virtual2_P_P + 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_virtual2.sh") + set_tests_properties(001_highsnr_virtual2_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") endif() diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 6f88ddd9..5781da0f 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -102,6 +102,8 @@ while receive and time.time() < timeout: 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 diff --git a/test/001_highsnr_stdio_audio/test_virtual1.sh b/test/001_highsnr_stdio_audio/test_virtual1.sh new file mode 100755 index 00000000..60cb149e --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_virtual1.sh @@ -0,0 +1,27 @@ +#!/bin/bash -x +# Run a test using the virtual sound cards, but sound I/O performed by aplay/arecord, and +# we pipe to Python utilities + +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) +MAX_RUN_TIME=2600 + +# make sure all child processes are killed when we exit +trap 'jobs -p | xargs -r kill' EXIT + +arecord --device="plughw:CARD=CHAT2,DEV=0" -f S16_LE -d $MAX_RUN_TIME | python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug & +rx_pid=$! +sleep 1 +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 | aplay --device="plughw:CARD=CHAT2,DEV=1" -f S16_LE +wait ${rx_pid} diff --git a/test/001_highsnr_stdio_audio/test_virtual.sh b/test/001_highsnr_stdio_audio/test_virtual2.sh similarity index 100% rename from test/001_highsnr_stdio_audio/test_virtual.sh rename to test/001_highsnr_stdio_audio/test_virtual2.sh From b27563fe777d983dda97e9b80050070620908498 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Tue, 14 Dec 2021 23:07:01 +0100 Subject: [PATCH 37/70] 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} From 8a93ec48962dbc7cb7eb1acbed10e5a2d8396c33 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 09:26:55 +1030 Subject: [PATCH 38/70] display tx auto sound device selection --- test/001_highsnr_stdio_audio/test_rx.py | 6 ++++-- test/001_highsnr_stdio_audio/test_tx.py | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 42042427..20d24a5b 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -25,7 +25,8 @@ 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('--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('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int, + help="audio device number to use, use -2 to automatically select a loopback device") 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") @@ -66,7 +67,8 @@ if AUDIO_INPUT_DEVICE != -1: 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) + 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, diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 6a818e50..1d1c0a68 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -21,7 +21,8 @@ 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('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int) parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) -parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, help="audio output device number to use") +parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, + help="audio output device number to use, use -2 to automatically select a loopback device") parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") args = parser.parse_args() @@ -60,6 +61,9 @@ if AUDIO_OUTPUT_DEVICE != -1: print(f"loopback_list tx: {loopback_list}", file=sys.stderr) else: quit() + print(f"AUDIO OUTPUT DEVICE: {AUDIO_OUTPUT_DEVICE} DEVICE: {p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['name']} \ + AUDIO SAMPLE RATE: {AUDIO_SAMPLE_RATE_TX}", file=sys.stderr) + # pyaudio init stream_tx = p.open(format=pyaudio.paInt16, channels=1, From 8c9f1c57616f9d082b87f16ea8faf82eda152737 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 11:12:31 +1030 Subject: [PATCH 39/70] fine grained tests at 48000 Hz, currently won't work as resampler commented out of test_tx.py and test_rx.py --- test/001_highsnr_stdio_audio/test_rx.py | 39 +++++++++---------- test/001_highsnr_stdio_audio/test_tx.py | 15 +++---- test/001_highsnr_stdio_audio/test_virtual1.sh | 4 +- .../001_highsnr_stdio_audio/test_virtual1a.sh | 31 +++++++++++++++ .../001_highsnr_stdio_audio/test_virtual1b.sh | 29 ++++++++++++++ .../001_highsnr_stdio_audio/test_virtual1c.sh | 29 ++++++++++++++ test/001_highsnr_stdio_audio/test_virtual2.sh | 2 +- 7 files changed, 119 insertions(+), 30 deletions(-) create mode 100755 test/001_highsnr_stdio_audio/test_virtual1a.sh create mode 100755 test/001_highsnr_stdio_audio/test_virtual1b.sh create mode 100755 test/001_highsnr_stdio_audio/test_virtual1c.sh diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 20d24a5b..afcd2cf6 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -22,8 +22,8 @@ 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('--bursts', dest="N_BURSTS", default=1, type=int) +parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int) 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, use -2 to automatically select a loopback device") @@ -49,7 +49,7 @@ TIMEOUT = args.TIMEOUT # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 # seems to be best if >=1024 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 -AUDIO_SAMPLE_RATE_RX = 48000 +AUDIO_SAMPLE_RATE_RX = 8000 assert (AUDIO_SAMPLE_RATE_RX % MODEM_SAMPLE_RATE) == 0 # check if we want to use an audio device then do an pyaudio init @@ -104,13 +104,14 @@ receive = True audio_buffer = bytes() nbytes = 0 BUF_SIZE = 160 +ratecv_state = None while receive and time.time() < timeout: if AUDIO_INPUT_DEVICE != -1: 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) - audio_buffer += data_in[0] + #data_in, ratecv_state = audioop.ratecv(data_in,2,1,AUDIO_SAMPLE_RATE_RX, MODEM_SAMPLE_RATE, ratecv_state) + audio_buffer += data_in else: @@ -118,32 +119,30 @@ while receive and time.time() < timeout: nin = codec2.api.freedv_nin(freedv) * 2 - 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:] + rx_status = codec2.api.freedv_get_rx_status(freedv) + if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: + rx_errors = rx_errors + 1 + if DEBUGGING_MODE: + state = codec2.api.rx_sync_flags_to_text[rx_status] + print(f"NIN: {nin} RX_STATUS: {state} BUFFER: {len(audio_buffer)}", file=sys.stderr) - total_n_bytes = total_n_bytes + nbytes + total_n_bytes = total_n_bytes + nbytes - if nbytes == bytes_per_frame: - rx_total_frames = rx_total_frames + 1 - rx_frames = rx_frames + 1 + if nbytes == bytes_per_frame: + rx_total_frames = rx_total_frames + 1 + rx_frames = rx_frames + 1 if rx_frames == N_FRAMES_PER_BURST: rx_frames = 0 rx_bursts = rx_bursts + 1 - - if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: - rx_errors = rx_errors + 1 - - if rx_bursts == N_BURSTS: - receive = False + + if rx_bursts == N_BURSTS: + receive = False print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames} RX_ERRORS: {rx_errors}", file=sys.stderr) diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 1d1c0a68..e30c015e 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -17,9 +17,10 @@ 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('--delay', dest="DELAY_BETWEEN_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('--delay', dest="DELAY_BETWEEN_BURSTS", default=500, type=int, + help="delay between bursts in ms") parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, help="audio output device number to use, use -2 to automatically select a loopback device") @@ -44,7 +45,7 @@ MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 -AUDIO_SAMPLE_RATE_TX = 48000 +AUDIO_SAMPLE_RATE_TX = 8000 assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 # check if we want to use an audio device then do an pyaudio init @@ -136,7 +137,7 @@ for i in range(1,N_BURSTS+1): txbuffer += bytes(mod_out_postamble) # append a delay between bursts as audio silence - samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) + samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS*2) mod_out_silence = create_string_buffer(samples_delay) txbuffer += bytes(mod_out_silence) print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr) @@ -145,8 +146,8 @@ for i in range(1,N_BURSTS+1): 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(audio[0]) + #audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) + stream_tx.write(txbuffer) else: # print data to terminal for piping the output to other programs diff --git a/test/001_highsnr_stdio_audio/test_virtual1.sh b/test/001_highsnr_stdio_audio/test_virtual1.sh index 60cb149e..00e09b84 100755 --- a/test/001_highsnr_stdio_audio/test_virtual1.sh +++ b/test/001_highsnr_stdio_audio/test_virtual1.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -# Run a test using the virtual sound cards, but sound I/O performed by aplay/arecord, and -# we pipe to Python utilities +# Run a test using the virtual sound cards, sound I/O performed by aplay/arecord at +# Fs=8000 Hz, and we pipe to Python utilities function check_alsa_loopback { lsmod | grep snd_aloop >> /dev/null diff --git a/test/001_highsnr_stdio_audio/test_virtual1a.sh b/test/001_highsnr_stdio_audio/test_virtual1a.sh new file mode 100755 index 00000000..fdcff59d --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_virtual1a.sh @@ -0,0 +1,31 @@ +#!/bin/bash -x +# Run a test using the virtual sound cards, tx sound I/O performed by aplay +# and arecord at Fs=48000Hz, we pipe to Python utilities + +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) +MAX_RUN_TIME=2600 + +# make sure all child processes are killed when we exit +trap 'jobs -p | xargs -r kill' EXIT + +arecord -r 48000 --device="plughw:CARD=CHAT1,DEV=0" -f S16_LE -d $MAX_RUN_TIME | \ + sox -t .s16 -r 48000 -c 1 - -t .s16 -r 8000 -c 1 - | \ + python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug & +rx_pid=$! +sleep 1 +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 | \ + sox -t .s16 -r 8000 -c 1 - -t .s16 -r 48000 -c 1 - | \ + aplay -r 48000 --device="plughw:CARD=CHAT1,DEV=1" -f S16_LE +wait ${rx_pid} diff --git a/test/001_highsnr_stdio_audio/test_virtual1b.sh b/test/001_highsnr_stdio_audio/test_virtual1b.sh new file mode 100755 index 00000000..100f037e --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_virtual1b.sh @@ -0,0 +1,29 @@ +#!/bin/bash -x +# Run a test using the virtual sound cards, tx sound I/O performed by Python, +# rx using arecord, Fs=48000Hz + +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) +MAX_RUN_TIME=2600 + +# make sure all child processes are killed when we exit +trap 'jobs -p | xargs -r kill' EXIT + +arecord -r 48000 --device="plughw:CARD=CHAT1,DEV=0" -f S16_LE -d $MAX_RUN_TIME | \ + sox -t .s16 -r 48000 -c 1 - -t .s16 -r 8000 -c 1 - | \ + python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug & +rx_pid=$! +sleep 1 +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_virtual1c.sh b/test/001_highsnr_stdio_audio/test_virtual1c.sh new file mode 100755 index 00000000..f20bb79f --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_virtual1c.sh @@ -0,0 +1,29 @@ +#!/bin/bash -x +# Run a test using the virtual sound cards, tx sound I/O performed by aplay, +# rx sound I/O by Python, Fs=48000Hz. + +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) +MAX_RUN_TIME=2600 + +# 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 --debug --audiodev -2 & +rx_pid=$! +sleep 1 +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 | \ + sox -t .s16 -r 8000 -c 1 - -t .s16 -r 48000 -c 1 - | \ + aplay -r 48000 --device="plughw:CARD=CHAT1,DEV=1" -f S16_LE +wait ${rx_pid} diff --git a/test/001_highsnr_stdio_audio/test_virtual2.sh b/test/001_highsnr_stdio_audio/test_virtual2.sh index 504ae54a..c94f3f8a 100755 --- a/test/001_highsnr_stdio_audio/test_virtual2.sh +++ b/test/001_highsnr_stdio_audio/test_virtual2.sh @@ -1,5 +1,5 @@ #!/bin/bash -x -# Run a test using the virtual sound cards +# Run a test using the virtual sound cards, Python audio I/O function check_alsa_loopback { lsmod | grep snd_aloop >> /dev/null From b619b341d1ee190729d196d447060eef972ccf26 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 11:33:07 +1030 Subject: [PATCH 40/70] wait until output buffer empties before finishing --- test/001_highsnr_stdio_audio/test_tx.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index e30c015e..2b1c0c9f 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -155,7 +155,9 @@ for i in range(1,N_BURSTS+1): sys.stdout.flush() -# and at last check if we had an openend pyaudio instance and close it -if AUDIO_OUTPUT_DEVICE != -1: +# and at last check if we had an opened pyaudio instance and close it +if AUDIO_OUTPUT_DEVICE != -1: + time.sleep(stream_tx.get_output_latency()) + stream_tx.stop_stream() stream_tx.close() p.terminate() From ae80958b65b8d87e17a714bd08510169ba8e3907 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 12:19:06 +1030 Subject: [PATCH 41/70] longer timeout on test_virtual2 --- test/001_highsnr_stdio_audio/test_rx.py | 2 ++ test/001_highsnr_stdio_audio/test_tx.py | 1 - test/001_highsnr_stdio_audio/test_virtual2.sh | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index afcd2cf6..3c25683f 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -144,6 +144,8 @@ while receive and time.time() < timeout: if rx_bursts == N_BURSTS: receive = False +if time.time() >= timeout: + print("TIMEOUT REACHED") print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames} RX_ERRORS: {rx_errors}", file=sys.stderr) diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 2b1c0c9f..acb13792 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -113,7 +113,6 @@ crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_p crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string buffer += crc # append crc16 to buffer - print(f"TOTAL BURSTS: {N_BURSTS} TOTAL FRAMES_PER_BURST: {N_FRAMES_PER_BURST}", file=sys.stderr) for i in range(1,N_BURSTS+1): diff --git a/test/001_highsnr_stdio_audio/test_virtual2.sh b/test/001_highsnr_stdio_audio/test_virtual2.sh index c94f3f8a..d3e5522b 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 -2 --debug & +python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --audiodev -2 --debug --timeout 20 & rx_pid=$! sleep 1 -python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 --audiodev -2 +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 250 --audiodev -2 wait ${rx_pid} From f1992d16f69d2ceaafe3d260d7f3b66982cb4852 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 12:19:43 +1030 Subject: [PATCH 42/70] test_multimode_tx.py sounds good through sound card, and test_rx.py can receiver each mode OK --- .../test_multimode_tx.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_tx.py b/test/001_highsnr_stdio_audio/test_multimode_tx.py index 3aa0906c..b4fc6f02 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -17,9 +17,9 @@ import codec2 # GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='FreeDATA TEST') -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('--delay', dest="DELAY_BETWEEN_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('--delay', dest="DELAY_BETWEEN_BURSTS", default=500, type=int) parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, help="audio output device number to use") parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") @@ -40,11 +40,9 @@ AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 -AUDIO_SAMPLE_RATE_TX = 48000 +AUDIO_SAMPLE_RATE_TX = 8000 assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 - - if AUDIO_OUTPUT_DEVICE != -1: p = pyaudio.PyAudio() # auto search for loopback devices @@ -69,7 +67,6 @@ if AUDIO_OUTPUT_DEVICE != -1: modes = [codec2.api.FREEDV_MODE_DATAC0, codec2.api.FREEDV_MODE_DATAC1, codec2.api.FREEDV_MODE_DATAC3] for m in modes: - freedv = cast(codec2.api.freedv_open(m), c_void_p) @@ -119,16 +116,15 @@ for m in modes: # append a delay between bursts as audio silence samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) - mod_out_silence = create_string_buffer(samples_delay) + mod_out_silence = create_string_buffer(samples_delay*2) txbuffer += bytes(mod_out_silence) - # 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(audio[0]) + #audio = audioop.ratecv(txbuffer,2,1,MODEM_SAMPLE_RATE, AUDIO_SAMPLE_RATE_TX, None) + stream_tx.write(txbuffer) else: @@ -146,6 +142,8 @@ for m in modes: # and at last check if we had an openend pyaudio instance and close it if AUDIO_OUTPUT_DEVICE != -1: + time.sleep(stream_tx.get_output_latency()) + stream_tx.stop_stream() stream_tx.close() p.terminate() From c7159fdce291ff973dc2bc90a5576d1b2b32a732 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 14:07:26 +1030 Subject: [PATCH 43/70] tweak comments --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d19cba7..8beb1f4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ add_test(NAME 001_highsnr_virtual1_P_P ./test_virtual1.sh") set_tests_properties(001_highsnr_virtual1_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") -# lets Python do audio I/O +# let Python do audio I/O add_test(NAME 001_highsnr_virtual2_P_P COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; PATH=$PATH:${CODEC2_BUILD_DIR}/src; @@ -74,7 +74,7 @@ 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 +# Multimode test with Python I/O 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; From 3e1dc4ee1cdfb309bec1125aaa314fc4cae495d1 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 14:08:53 +1030 Subject: [PATCH 44/70] buffering system based on design in codec2/demo/freedv_datac0c1_rx.c --- test/001_highsnr_stdio_audio/test_rx.py | 75 +++++++++++++++---------- test/codec2.py | 4 +- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 3c25683f..1d501c9c 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -10,13 +10,13 @@ import ctypes from ctypes import * import pathlib import pyaudio -import audioop import sys import logging import time import threading import sys import argparse +import numpy as np sys.path.insert(0,'..') import codec2 @@ -101,51 +101,64 @@ rx_bursts = 0 rx_errors = 0 timeout = time.time() + TIMEOUT receive = True -audio_buffer = bytes() -nbytes = 0 -BUF_SIZE = 160 -ratecv_state = None +naudio_buffer_max = codec2.api.freedv_get_n_max_modem_samples(freedv)*2 +audio_buffer = np.zeros(naudio_buffer_max, dtype=np.int16) +naudio_buffer = 0 + +# initial number of samples we need +nin = codec2.api.freedv_nin(freedv) + while receive and time.time() < timeout: - if AUDIO_INPUT_DEVICE != -1: - - data_in = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) - #data_in, ratecv_state = audioop.ratecv(data_in,2,1,AUDIO_SAMPLE_RATE_RX, MODEM_SAMPLE_RATE, ratecv_state) - audio_buffer += data_in - + if AUDIO_INPUT_DEVICE != -1: + data_in = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) else: + data_in = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) - audio_buffer += sys.stdin.buffer.read(BUF_SIZE) - - nin = codec2.api.freedv_nin(freedv) * 2 + # insert samples in buffer + x = np.frombuffer(data_in, dtype=np.int16) + if len(x) == 0: + receive = False + audio_buffer[naudio_buffer:naudio_buffer+len(x)] = x + naudio_buffer += len(x) + assert naudio_buffer <= naudio_buffer_max - #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:] + # when we have enough samples call FreeDV Rx + while naudio_buffer >= nin: + nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, audio_buffer.ctypes) # demodulate audio + + # remove old samples from buffer + naudio_buffer -= nin; + audio_buffer[:naudio_buffer] = audio_buffer[nin:nin+naudio_buffer] + + # call me on every loop! + nin = codec2.api.freedv_nin(freedv) + rx_status = codec2.api.freedv_get_rx_status(freedv) if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: rx_errors = rx_errors + 1 if DEBUGGING_MODE: - state = codec2.api.rx_sync_flags_to_text[rx_status] - print(f"NIN: {nin} RX_STATUS: {state} BUFFER: {len(audio_buffer)}", file=sys.stderr) + rx_status = codec2.api.rx_sync_flags_to_text[rx_status] + print("nin: %5d rx_status: %4s naudio_buffer: %4d len(audio_buffer): %d)" % \ + (nin,rx_status,naudio_buffer,len(audio_buffer)), file=sys.stderr) - total_n_bytes = total_n_bytes + nbytes + if nbytes: + total_n_bytes = total_n_bytes + nbytes - if nbytes == bytes_per_frame: - rx_total_frames = rx_total_frames + 1 - rx_frames = rx_frames + 1 + if nbytes == bytes_per_frame: + rx_total_frames = rx_total_frames + 1 + rx_frames = rx_frames + 1 - if rx_frames == N_FRAMES_PER_BURST: - rx_frames = 0 - rx_bursts = rx_bursts + 1 + if rx_frames == N_FRAMES_PER_BURST: + rx_frames = 0 + rx_bursts = rx_bursts + 1 - if rx_bursts == N_BURSTS: - receive = False + if rx_bursts == N_BURSTS: + receive = False -if time.time() >= timeout: - print("TIMEOUT REACHED") + if time.time() >= timeout: + print("TIMEOUT REACHED") print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames} RX_ERRORS: {rx_errors}", file=sys.stderr) diff --git a/test/codec2.py b/test/codec2.py index 4019138d..1e5f4966 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -68,7 +68,9 @@ api.freedv_get_n_tx_preamble_modem_samples.restype = c_int api.freedv_get_n_tx_modem_samples.argtype = [c_void_p] api.freedv_get_n_tx_modem_samples.restype = c_int - +api.freedv_get_n_max_modem_samples.argtype = [c_void_p] +api.freedv_get_n_max_modem_samples.restype = c_int + api.FREEDV_FS_8000 = 8000 api.FREEDV_MODE_DATAC1 = 10 api.FREEDV_MODE_DATAC3 = 12 From da478d3b830296af52df5564a29ba227a1864871 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 14:38:29 +1030 Subject: [PATCH 45/70] create audiobuffer class --- test/001_highsnr_stdio_audio/test_rx.py | 24 +++++++++--------------- test/codec2.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 1d501c9c..a0f6edcd 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -101,9 +101,7 @@ rx_bursts = 0 rx_errors = 0 timeout = time.time() + TIMEOUT receive = True -naudio_buffer_max = codec2.api.freedv_get_n_max_modem_samples(freedv)*2 -audio_buffer = np.zeros(naudio_buffer_max, dtype=np.int16) -naudio_buffer = 0 +audio_buffer = codec2.audio_buffer(codec2.api.freedv_get_n_max_modem_samples(freedv)) # initial number of samples we need nin = codec2.api.freedv_nin(freedv) @@ -119,19 +117,15 @@ while receive and time.time() < timeout: x = np.frombuffer(data_in, dtype=np.int16) if len(x) == 0: receive = False - audio_buffer[naudio_buffer:naudio_buffer+len(x)] = x - naudio_buffer += len(x) - assert naudio_buffer <= naudio_buffer_max + audio_buffer.push(x) # when we have enough samples call FreeDV Rx - while naudio_buffer >= nin: - - nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, audio_buffer.ctypes) # demodulate audio - - # remove old samples from buffer - naudio_buffer -= nin; - audio_buffer[:naudio_buffer] = audio_buffer[nin:nin+naudio_buffer] + while audio_buffer.nbuffer >= nin: + # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, audio_buffer.buffer.ctypes) + audio_buffer.pop(nin) + # call me on every loop! nin = codec2.api.freedv_nin(freedv) @@ -140,8 +134,8 @@ while receive and time.time() < timeout: rx_errors = rx_errors + 1 if DEBUGGING_MODE: rx_status = codec2.api.rx_sync_flags_to_text[rx_status] - print("nin: %5d rx_status: %4s naudio_buffer: %4d len(audio_buffer): %d)" % \ - (nin,rx_status,naudio_buffer,len(audio_buffer)), file=sys.stderr) + print("nin: %5d rx_status: %4s naudio_buffer: %4d" % \ + (nin,rx_status,audio_buffer.nbuffer), file=sys.stderr) if nbytes: total_n_bytes = total_n_bytes + nbytes diff --git a/test/codec2.py b/test/codec2.py index 1e5f4966..21af4c0d 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -6,6 +6,7 @@ from ctypes import * import sys import pathlib from enum import Enum +import numpy as np print("loading codec2 module", file=sys.stderr) @@ -19,6 +20,22 @@ class FREEDV_MODE(Enum): def FREEDV_GET_MODE(mode): return FREEDV_MODE[mode].value +class audio_buffer: + def __init__(self, size): + self.size = size + self.buffer = np.zeros(size, dtype=np.int16) + self.nbuffer = 0 + def push(self,samples): + self.buffer[self.nbuffer:self.nbuffer+len(samples)] = samples + self.nbuffer += len(samples) + assert self.nbuffer <= self.size + def nbuffer(self): + return self.nbuffer + def pop(self,size): + 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) From 2e66a6788446b656f62c01c75ebbb2e718877bd8 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 15:05:30 +1030 Subject: [PATCH 46/70] test_multimode_rx.py using audio_buffer calls and working well :) --- .../test_multimode_rx.py | 87 +++++++++---------- .../test_multimode_tx.py | 2 +- test/001_highsnr_stdio_audio/test_rx.py | 2 +- test/codec2.py | 3 +- 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_rx.py b/test/001_highsnr_stdio_audio/test_multimode_rx.py index 67be78cb..d36d2923 100755 --- a/test/001_highsnr_stdio_audio/test_multimode_rx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_rx.py @@ -11,6 +11,7 @@ from ctypes import * import pathlib sys.path.insert(0,'..') import codec2 +import numpy as np #--------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='Simons TEST TNC') @@ -36,13 +37,11 @@ DEBUGGING_MODE = args.DEBUGGING_MODE TIMEOUT = args.TIMEOUT # AUDIO PARAMETERS -AUDIO_FRAMES_PER_BUFFER = 4096 +AUDIO_FRAMES_PER_BUFFER = 2048 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 -AUDIO_SAMPLE_RATE_RX = 48000 +AUDIO_SAMPLE_RATE_RX = 8000 assert (AUDIO_SAMPLE_RATE_RX % MODEM_SAMPLE_RATE) == 0 -BUF_SIZE = 160 - # SET COUNTERS rx_total_frames_datac0 = 0 rx_frames_datac0 = 0 @@ -58,26 +57,26 @@ rx_bursts_datac3 = 0 # open codec2 instance -datac0_freedv = cast(codec2.api.freedv_open(14), c_void_p) +datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p) datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac0_freedv)/8) datac0_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac0_freedv) datac0_bytes_out = create_string_buffer(datac0_bytes_per_frame * 2) codec2.api.freedv_set_frames_per_burst(datac0_freedv,N_FRAMES_PER_BURST) -datac0_buffer = bytes() +datac0_buffer = codec2.audio_buffer(datac0_n_max_modem_samples) -datac1_freedv = cast(codec2.api.freedv_open(10), c_void_p) +datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p) datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac1_freedv)/8) datac1_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac1_freedv) datac1_bytes_out = create_string_buffer(datac1_bytes_per_frame * 2) codec2.api.freedv_set_frames_per_burst(datac1_freedv,N_FRAMES_PER_BURST) -datac1_buffer = bytes() +datac1_buffer = codec2.audio_buffer(datac1_n_max_modem_samples) -datac3_freedv = cast(codec2.api.freedv_open(12), c_void_p) +datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p) datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac3_freedv)/8) datac3_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac3_freedv) 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 = bytes() +datac3_buffer = codec2.audio_buffer(datac3_n_max_modem_samples) # check if we want to use an audio device then do an pyaudio init if AUDIO_INPUT_DEVICE != -1: @@ -105,32 +104,32 @@ if AUDIO_INPUT_DEVICE != -1: timeout = time.time() + TIMEOUT +print(time.time(),TIMEOUT, timeout) receive = True +# initial nin values +datac0_nin = codec2.api.freedv_nin(datac0_freedv) +datac1_nin = codec2.api.freedv_nin(datac1_freedv) +datac3_nin = codec2.api.freedv_nin(datac3_freedv) while receive and time.time() < timeout: - if AUDIO_INPUT_DEVICE != -1: - + if AUDIO_INPUT_DEVICE != -1: 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) + data_in = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) - datac0_buffer += data_in - datac1_buffer += data_in - datac3_buffer += data_in + x = np.frombuffer(data_in, dtype=np.int16) + if len(x) == 0: + receive = False + datac0_buffer.push(x) + datac1_buffer.push(x) + datac3_buffer.push(x) - - datac0_nin = codec2.api.freedv_nin(datac0_freedv) * 2 - if len(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:] + while datac0_buffer.nbuffer >= datac0_nin: + # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(datac0_freedv, datac0_bytes_out, datac0_buffer.buffer.ctypes) + datac0_buffer.pop(datac0_nin) + datac0_nin = codec2.api.freedv_nin(datac0_freedv) if nbytes == datac0_bytes_per_frame: rx_total_frames_datac0 = rx_total_frames_datac0 + 1 rx_frames_datac0 = rx_frames_datac0 + 1 @@ -140,10 +139,11 @@ while receive and time.time() < timeout: rx_bursts_datac0 = rx_bursts_datac0 + 1 - datac1_nin = codec2.api.freedv_nin(datac1_freedv) * 2 - if len(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:] + while datac1_buffer.nbuffer >= datac1_nin: + # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(datac1_freedv, datac1_bytes_out, datac1_buffer.buffer.ctypes) + datac1_buffer.pop(datac1_nin) + datac1_nin = codec2.api.freedv_nin(datac1_freedv) if nbytes == datac1_bytes_per_frame: rx_total_frames_datac1 = rx_total_frames_datac1 + 1 rx_frames_datac1 = rx_frames_datac1 + 1 @@ -152,10 +152,11 @@ while receive and time.time() < timeout: rx_frames_datac1 = 0 rx_bursts_datac1 = rx_bursts_datac1 + 1 - datac3_nin = codec2.api.freedv_nin(datac3_freedv) * 2 - if len(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:] + while datac3_buffer.nbuffer >= datac3_nin: + # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(datac3_freedv, datac3_bytes_out, datac3_buffer.buffer.ctypes) + datac3_buffer.pop(datac3_nin) + datac3_nin = codec2.api.freedv_nin(datac3_freedv) if nbytes == datac3_bytes_per_frame: rx_total_frames_datac3 = rx_total_frames_datac3 + 1 rx_frames_datac3 = rx_frames_datac3 + 1 @@ -164,22 +165,18 @@ while receive and time.time() < timeout: rx_frames_datac3 = 0 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] + datac0_rxstatus = 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] + datac1_rxstatus = 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] + datac3_rxstatus = 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) + print("NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % \ + (datac0_nin, datac0_rxstatus, datac1_nin, datac1_rxstatus, datac3_nin, datac3_rxstatus)) if rx_bursts_datac0 == N_BURSTS and rx_bursts_datac1 == N_BURSTS and rx_bursts_datac3 == N_BURSTS: diff --git a/test/001_highsnr_stdio_audio/test_multimode_tx.py b/test/001_highsnr_stdio_audio/test_multimode_tx.py index b4fc6f02..3d79f14e 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -137,7 +137,7 @@ for m in modes: # and at least print the needed time to see which time we needed timeneeded = time.time()-starttime - print(f"time: {timeneeded} buffer: {len(txbuffer)}", file=sys.stderr) + #print(f"time: {timeneeded} buffer: {len(txbuffer)}", file=sys.stderr) # and at last check if we had an openend pyaudio instance and close it diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index a0f6edcd..969bc64e 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -122,7 +122,7 @@ while receive and time.time() < timeout: # when we have enough samples call FreeDV Rx while audio_buffer.nbuffer >= nin: - # demodulate audio + # demodulate audio nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, audio_buffer.buffer.ctypes) audio_buffer.pop(nin) diff --git a/test/codec2.py b/test/codec2.py index 21af4c0d..75c6cec1 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -22,13 +22,14 @@ def FREEDV_GET_MODE(mode): class audio_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): + assert self.nbuffer+len(samples) <= self.size self.buffer[self.nbuffer:self.nbuffer+len(samples)] = samples self.nbuffer += len(samples) - assert self.nbuffer <= self.size def nbuffer(self): return self.nbuffer def pop(self,size): From e418f0be7a758bff7423ac421fe71657f40a6431 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 16:42:38 +1030 Subject: [PATCH 47/70] larger buffers, and an different approach to logging --- .../test_multimode_rx.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_rx.py b/test/001_highsnr_stdio_audio/test_multimode_rx.py index d36d2923..fbc22f34 100755 --- a/test/001_highsnr_stdio_audio/test_multimode_rx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_rx.py @@ -62,21 +62,21 @@ datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac0_f datac0_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac0_freedv) datac0_bytes_out = create_string_buffer(datac0_bytes_per_frame * 2) codec2.api.freedv_set_frames_per_burst(datac0_freedv,N_FRAMES_PER_BURST) -datac0_buffer = codec2.audio_buffer(datac0_n_max_modem_samples) +datac0_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER) datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p) datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac1_freedv)/8) datac1_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac1_freedv) datac1_bytes_out = create_string_buffer(datac1_bytes_per_frame * 2) codec2.api.freedv_set_frames_per_burst(datac1_freedv,N_FRAMES_PER_BURST) -datac1_buffer = codec2.audio_buffer(datac1_n_max_modem_samples) +datac1_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER) datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p) datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac3_freedv)/8) datac3_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac3_freedv) 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(datac3_n_max_modem_samples) +datac3_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER) # check if we want to use an audio device then do an pyaudio init if AUDIO_INPUT_DEVICE != -1: @@ -112,6 +112,21 @@ datac0_nin = codec2.api.freedv_nin(datac0_freedv) datac1_nin = codec2.api.freedv_nin(datac1_freedv) datac3_nin = codec2.api.freedv_nin(datac3_freedv) +def print_stats(): + if DEBUGGING_MODE: + datac0_rxstatus = codec2.api.freedv_get_rx_status(datac0_freedv) + datac0_rxstatus = codec2.api.rx_sync_flags_to_text[datac0_rxstatus] + + datac1_rxstatus = codec2.api.freedv_get_rx_status(datac1_freedv) + datac1_rxstatus = codec2.api.rx_sync_flags_to_text[datac1_rxstatus] + + datac3_rxstatus = codec2.api.freedv_get_rx_status(datac3_freedv) + datac3_rxstatus = codec2.api.rx_sync_flags_to_text[datac3_rxstatus] + + print("NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % \ + (datac0_nin, datac0_rxstatus, datac1_nin, datac1_rxstatus, datac3_nin, datac3_rxstatus), + file=sys.stderr) + while receive and time.time() < timeout: if AUDIO_INPUT_DEVICE != -1: data_in = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) @@ -124,7 +139,8 @@ while receive and time.time() < timeout: datac0_buffer.push(x) datac1_buffer.push(x) datac3_buffer.push(x) - + print_something = False + while datac0_buffer.nbuffer >= datac0_nin: # demodulate audio nbytes = codec2.api.freedv_rawdatarx(datac0_freedv, datac0_bytes_out, datac0_buffer.buffer.ctypes) @@ -137,7 +153,7 @@ while receive and time.time() < timeout: if rx_frames_datac0 == N_FRAMES_PER_BURST: rx_frames_datac0 = 0 rx_bursts_datac0 = rx_bursts_datac0 + 1 - + print_stats() while datac1_buffer.nbuffer >= datac1_nin: # demodulate audio @@ -151,6 +167,7 @@ while receive and time.time() < timeout: if rx_frames_datac1 == N_FRAMES_PER_BURST: rx_frames_datac1 = 0 rx_bursts_datac1 = rx_bursts_datac1 + 1 + print_stats() while datac3_buffer.nbuffer >= datac3_nin: # demodulate audio @@ -164,20 +181,7 @@ while receive and time.time() < timeout: if rx_frames_datac3 == N_FRAMES_PER_BURST: rx_frames_datac3 = 0 rx_bursts_datac3 = rx_bursts_datac3 + 1 - - if DEBUGGING_MODE: - datac0_rxstatus = codec2.api.freedv_get_rx_status(datac0_freedv) - datac0_rxstatus = codec2.api.rx_sync_flags_to_text[datac0_rxstatus] - - datac1_rxstatus = codec2.api.freedv_get_rx_status(datac1_freedv) - datac1_rxstatus = codec2.api.rx_sync_flags_to_text[datac1_rxstatus] - - datac3_rxstatus = codec2.api.freedv_get_rx_status(datac3_freedv) - datac3_rxstatus = codec2.api.rx_sync_flags_to_text[datac3_rxstatus] - - print("NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % \ - (datac0_nin, datac0_rxstatus, datac1_nin, datac1_rxstatus, datac3_nin, datac3_rxstatus)) - + print_stats() if rx_bursts_datac0 == N_BURSTS and rx_bursts_datac1 == N_BURSTS and rx_bursts_datac3 == N_BURSTS: receive = False From 53a88bfaae992a03adadcfdcd8fde744eb686a5b Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 16:43:30 +1030 Subject: [PATCH 48/70] tell us when we Tx a frame --- test/001_highsnr_stdio_audio/test_multimode_tx.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_tx.py b/test/001_highsnr_stdio_audio/test_multimode_tx.py index 3d79f14e..7318b77d 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -109,7 +109,8 @@ for m in modes: codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer txbuffer += bytes(mod_out) - + 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) txbuffer += bytes(mod_out_postamble) From cbd89324f37ec110e2bd4b4762e66ad2d079e35c Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 15 Dec 2021 16:43:47 +1030 Subject: [PATCH 49/70] multimode virtual test working better --- CMakeLists.txt | 2 +- test/001_highsnr_stdio_audio/test_virtual_mm.sh | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8beb1f4a..2c5f4ad2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,6 @@ add_test(NAME 001_highsnr_virtual3_P_P_MM 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") + set_tests_properties(001_highsnr_virtual3_P_P_MM PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: 2/4 DATAC1: 2/4 DATAC3: 2/4") endif() diff --git a/test/001_highsnr_stdio_audio/test_virtual_mm.sh b/test/001_highsnr_stdio_audio/test_virtual_mm.sh index 186c334e..5460ba2a 100755 --- a/test/001_highsnr_stdio_audio/test_virtual_mm.sh +++ b/test/001_highsnr_stdio_audio/test_virtual_mm.sh @@ -11,19 +11,22 @@ function check_alsa_loopback { fi } +myInterruptHandler() +{ + exit 1 +} + check_alsa_loopback RX_LOG=$(mktemp) +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 30 --framesperburst 2 --bursts 1 --audiodev -2 --debug & +python3 test_multimode_rx.py --timeout 60 --framesperburst 2 --bursts 2 --audiodev -2 & rx_pid=$! sleep 1 -python3 test_multimode_tx.py --framesperburst 2 --bursts 1 --audiodev -2 - -#tail -f ${RX_LOG} | sed '/RECEIVED BURSTS/ q' - - +python3 test_multimode_tx.py --framesperburst 2 --bursts 2 --audiodev -2 --delay 500 wait ${rx_pid} From 09ae2b78fe37e3d93b4afd970dc52a7c178f1e88 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 15 Dec 2021 17:24:10 +1030 Subject: [PATCH 50/70] extend timeout for slightly slower machine --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c5f4ad2..15a5b975 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ add_test(NAME 001_highsnr_stdio_P_P_MM PATH=$PATH:${CODEC2_BUILD_DIR}/src; cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; python3 test_multimode_tx.py --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | - python3 test_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") + python3 test_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} --timeout 20") set_tests_properties(001_highsnr_stdio_P_P_MM PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}") From 3f2bedfd4f199227e3fe68224e06da06ee34fec4 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 06:15:33 +1030 Subject: [PATCH 51/70] building up resampler support and audio_buffer comments --- test/codec2.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/codec2.py b/test/codec2.py index 75c6cec1..5442ded3 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -21,18 +21,20 @@ 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 nbuffer(self): - return self.nbuffer 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 @@ -88,7 +90,7 @@ api.freedv_get_n_tx_modem_samples.restype = c_int api.freedv_get_n_max_modem_samples.argtype = [c_void_p] api.freedv_get_n_max_modem_samples.restype = c_int - + api.FREEDV_FS_8000 = 8000 api.FREEDV_MODE_DATAC1 = 10 api.FREEDV_MODE_DATAC3 = 12 @@ -119,7 +121,11 @@ api.rx_sync_flags_to_text = [ "EBST"] +# resampler --------------------------------------------------------- - - +api.FDMDV_OS_48 = 6 # oversampling rate +api.FDMDV_OS_TAPS_48K = 48 # number of OS filter taps at 48kHz +api.FDMDV_OS_TAPS_48_8K = (api.FDMDV_OS_TAPS_48K/api.FDMDV_OS_48) # number of OS filter taps at 8kHz +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] From dfcc136583d7f586ef8d3e78e3ea51312b19ff88 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 07:07:30 +1030 Subject: [PATCH 52/70] Work in progress t48_8_short.py --- test/000_resampler/t48_8_short.py | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/000_resampler/t48_8_short.py diff --git a/test/000_resampler/t48_8_short.py b/test/000_resampler/t48_8_short.py new file mode 100644 index 00000000..a8850319 --- /dev/null +++ b/test/000_resampler/t48_8_short.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Unit test for FreeDV API resampler functions, from +# codec2/unittest/t48_8_short.c - generate a sine wave at 8 KHz, +# upsample to 48 kHz, add an interferer, then downsample back to 8 kHz + +import ctypes +from ctypes import * +import pathlib +import argparse +import sys +sys.path.insert(0,'..') +import codec2 +import numpy as np + +# dig some constants out +FDMDV_OS_48 = codec2.api.FDMDV_OS_48 +FDMDV_OS_TAPS_48K = codec2.api.FDMDV_OS_TAPS_48K +FDMDV_OS_TAPS_48_8K = codec2.api.FDMDV_OS_TAPS_48_8K + +N8 = int(180) # processing buffer size at 8 kHz +N48 = int(N8*FDMDV_OS_48) # processing buffer size at 48 kHz +MEM8 = int(FDMDV_OS_TAPS_48_8K) # 8kHz signal filter memory +MEM48 = int(FDMDV_OS_TAPS_48K) # 48kHz signal filter memory +FRAMES = int(50) # number of frames to test +FS8 = 8000 +FS48 = 48000 +AMP = 16000 # sine wave amplitude +FTEST8 = 800 # input test frequency at FS=8kHz +FINTER48 = 10000 # interferer frequency at FS=48kHz + +in8k = np.zeros(MEM8 + N8, dtype=np.int16) +out48k = np.zeros(N48, dtype=np.int16) +in48k = np.zeros(MEM48 + N48, dtype=np.int16) +out8k = np.zeros(N8, dtype=np.int16) + +t = 0 +fin8 = open("in8.raw", mode='wb') +f48 = open("out48.raw", mode='wb') + +for f in range(FRAMES): + + in8k[MEM8:] = AMP*np.cos(2*np.pi*np.arange(t,t+N8)*FTEST8/FS8) + t += N8 + in8k[MEM8:].tofile(fin8) + + codec2.api.fdmdv_8_to_48_short(out48k.ctypes, in8k.ctypes + MEM8, N8); + out48k.tofile(f48) + +fin8.close() +f48.close() From 653713918dce8d8b57aea881d517fe9096a62dd5 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 07:22:02 +1030 Subject: [PATCH 53/70] C array offset working, upsampled sine wave sounds OK :-) --- test/000_resampler/t48_8_short.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/000_resampler/t48_8_short.py b/test/000_resampler/t48_8_short.py index a8850319..10bcb0cf 100644 --- a/test/000_resampler/t48_8_short.py +++ b/test/000_resampler/t48_8_short.py @@ -4,6 +4,14 @@ # Unit test for FreeDV API resampler functions, from # codec2/unittest/t48_8_short.c - generate a sine wave at 8 KHz, # upsample to 48 kHz, add an interferer, then downsample back to 8 kHz +# +# You can listen to the output files with: +# +# aplay -f S16_LE in8.raw +# aplay -r 48000 -f S16_LE out48.raw +# aplay -f S16_LE out8.raw +# +# They should sound like clean sine waves import ctypes from ctypes import * @@ -44,8 +52,10 @@ for f in range(FRAMES): in8k[MEM8:] = AMP*np.cos(2*np.pi*np.arange(t,t+N8)*FTEST8/FS8) t += N8 in8k[MEM8:].tofile(fin8) - - codec2.api.fdmdv_8_to_48_short(out48k.ctypes, in8k.ctypes + MEM8, N8); + + pin8k,flag = in8k.__array_interface__['data'] + pin8k += 2*MEM8 + codec2.api.fdmdv_8_to_48_short(out48k.ctypes, pin8k, N8); out48k.tofile(f48) fin8.close() From b95562740a464e9b56499dc734a1a1868db8cede Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 07:50:24 +1030 Subject: [PATCH 54/70] automated test for FreeDV resampler --- CMakeLists.txt | 6 +++ test/000_resampler/t48_8_short.py | 49 ++++++++++++++++++++++++- test/001_highsnr_stdio_audio/test_tx.py | 4 +- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 15a5b975..4be3d23b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,12 @@ set(FRAMESPERBURST 3) set(BURSTS 1) set(TESTFRAMES 3) +add_test(NAME 000_resampler + COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; + cd ${CMAKE_CURRENT_SOURCE_DIR}/test/000_resampler; + python3 t48_8_short.py") + set_tests_properties(000_resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS") + add_test(NAME 001_highsnr_stdio_P_C_SM COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; PATH=$PATH:${CODEC2_BUILD_DIR}/src; diff --git a/test/000_resampler/t48_8_short.py b/test/000_resampler/t48_8_short.py index 10bcb0cf..2acb40ed 100644 --- a/test/000_resampler/t48_8_short.py +++ b/test/000_resampler/t48_8_short.py @@ -38,25 +38,72 @@ AMP = 16000 # sine wave amplitude FTEST8 = 800 # input test frequency at FS=8kHz FINTER48 = 10000 # interferer frequency at FS=48kHz +# Due to the design of these resamplers, the processing buffer (at 8kHz) +# must be an integer multiple of oversampling ratio +assert N8 % FDMDV_OS_48 == 0 + in8k = np.zeros(MEM8 + N8, dtype=np.int16) out48k = np.zeros(N48, dtype=np.int16) in48k = np.zeros(MEM48 + N48, dtype=np.int16) out8k = np.zeros(N8, dtype=np.int16) +# time indexes, we advance every frame t = 0 +t1 = 0 + fin8 = open("in8.raw", mode='wb') f48 = open("out48.raw", mode='wb') +fout8 = open("out8.raw", mode='wb') for f in range(FRAMES): - + + # input sine wave in8k[MEM8:] = AMP*np.cos(2*np.pi*np.arange(t,t+N8)*FTEST8/FS8) t += N8 in8k[MEM8:].tofile(fin8) + # upsample pin8k,flag = in8k.__array_interface__['data'] pin8k += 2*MEM8 codec2.api.fdmdv_8_to_48_short(out48k.ctypes, pin8k, N8); out48k.tofile(f48) + # add interfering sine wave (down sampling filter should remove) + in48k[MEM48:] = out48k + (AMP/2)*np.cos(2*np.pi*np.arange(t1,t1+N48)*FINTER48/FS48) + t1 += N48 + + # downsample + pin48k,flag = in48k.__array_interface__['data'] + pin48k += 2*MEM48 + codec2.api.fdmdv_48_to_8_short(out8k.ctypes, pin48k, N8); + out8k.tofile(fout8) + fin8.close() f48.close() +fout8.close() + +# Automated test evaluation -------------------------------------------- + +# The input and output signals will not be time aligned due to the filter +# delays, so compare the magnitude spectrum + +in8k = np.fromfile("in8.raw", dtype=np.int16) +out8k = np.fromfile("out8.raw", dtype=np.int16) +assert len(in8k) == len(out8k) + +n = len(in8k) + +h = np.hanning(len(in8k)) +S1 = np.abs(np.fft.fft(in8k * h)) +S2 = np.abs(np.fft.fft(out8k * h)) + +error = S1-S2 +error_energy = np.dot(error,error) +ratio = error_energy/np.dot(S1,S1) +ratio_dB = 10*np.log10(ratio); +print("ratio_dB: %4.2f" % (ratio_dB)); +threshdB = -40 +if ratio_dB < threshdB: + print("PASS") +else: + print("FAIL") diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index acb13792..19a838fb 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -1,4 +1,4 @@ - #!/usr/bin/env python3 +#!/usr/bin/env python3 # -*- coding: utf-8 -*- @@ -7,8 +7,6 @@ from ctypes import * import pathlib import pyaudio import time -import threading -import audioop import argparse import sys sys.path.insert(0,'..') From 84f5cd315efed4c27109c63543f50cfc89154454 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 11:56:39 +1030 Subject: [PATCH 55/70] resampler class --- test/000_resampler/t48_8_short.py | 37 ++++++++++--------------- test/001_highsnr_stdio_audio/test_rx.py | 15 ++++++---- test/codec2.py | 36 ++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/test/000_resampler/t48_8_short.py b/test/000_resampler/t48_8_short.py index 2acb40ed..915b6319 100644 --- a/test/000_resampler/t48_8_short.py +++ b/test/000_resampler/t48_8_short.py @@ -29,8 +29,8 @@ FDMDV_OS_TAPS_48_8K = codec2.api.FDMDV_OS_TAPS_48_8K N8 = int(180) # processing buffer size at 8 kHz N48 = int(N8*FDMDV_OS_48) # processing buffer size at 48 kHz -MEM8 = int(FDMDV_OS_TAPS_48_8K) # 8kHz signal filter memory -MEM48 = int(FDMDV_OS_TAPS_48K) # 48kHz signal filter memory +MEM8 = FDMDV_OS_TAPS_48_8K # 8kHz signal filter memory +MEM48 = FDMDV_OS_TAPS_48K # 48kHz signal filter memory FRAMES = int(50) # number of frames to test FS8 = 8000 FS48 = 48000 @@ -42,42 +42,33 @@ FINTER48 = 10000 # interferer frequency at FS=48kHz # must be an integer multiple of oversampling ratio assert N8 % FDMDV_OS_48 == 0 -in8k = np.zeros(MEM8 + N8, dtype=np.int16) -out48k = np.zeros(N48, dtype=np.int16) -in48k = np.zeros(MEM48 + N48, dtype=np.int16) -out8k = np.zeros(N8, dtype=np.int16) - # time indexes, we advance every frame t = 0 t1 = 0 +# output files to listen to/evaluate result fin8 = open("in8.raw", mode='wb') f48 = open("out48.raw", mode='wb') fout8 = open("out8.raw", mode='wb') +resampler = codec2.resampler(N48,N8) + for f in range(FRAMES): - # input sine wave - in8k[MEM8:] = AMP*np.cos(2*np.pi*np.arange(t,t+N8)*FTEST8/FS8) + sine_in8k = (AMP*np.cos(2*np.pi*np.arange(t,t+N8)*FTEST8/FS8)).astype(np.int16) t += N8 - in8k[MEM8:].tofile(fin8) - - # upsample - pin8k,flag = in8k.__array_interface__['data'] - pin8k += 2*MEM8 - codec2.api.fdmdv_8_to_48_short(out48k.ctypes, pin8k, N8); - out48k.tofile(f48) + sine_in8k.tofile(fin8) + sine_out48k = resampler.resample8_to_48(sine_in8k) + sine_out48k.tofile(f48) + # add interfering sine wave (down sampling filter should remove) - in48k[MEM48:] = out48k + (AMP/2)*np.cos(2*np.pi*np.arange(t1,t1+N48)*FINTER48/FS48) + sine_in48k = (sine_out48k + (AMP/2)*np.cos(2*np.pi*np.arange(t1,t1+N48)*FINTER48/FS48)).astype(np.int16) t1 += N48 - # downsample - pin48k,flag = in48k.__array_interface__['data'] - pin48k += 2*MEM48 - codec2.api.fdmdv_48_to_8_short(out8k.ctypes, pin48k, N8); - out8k.tofile(fout8) - + sine_out8k = resampler.resample48_to_8(sine_in48k) + sine_out8k.tofile(fout8) + fin8.close() f48.close() fout8.close() diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 969bc64e..15df2d7b 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -49,8 +49,10 @@ TIMEOUT = args.TIMEOUT # AUDIO PARAMETERS AUDIO_FRAMES_PER_BUFFER = 2048 # seems to be best if >=1024 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) == api.FDMDV_OS_48 # check if we want to use an audio device then do an pyaudio init if AUDIO_INPUT_DEVICE != -1: @@ -109,12 +111,15 @@ nin = codec2.api.freedv_nin(freedv) while receive and time.time() < timeout: if AUDIO_INPUT_DEVICE != -1: - data_in = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) + data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) else: - data_in = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) + data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) + # resample to 8 kHz + data_in8k = resampler.resample(data_in48k) + # insert samples in buffer - x = np.frombuffer(data_in, dtype=np.int16) + x = np.frombuffer(data_in8k, dtype=np.int16) if len(x) == 0: receive = False audio_buffer.push(x) diff --git a/test/codec2.py b/test/codec2.py index 5442ded3..743c841a 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -123,9 +123,39 @@ api.rx_sync_flags_to_text = [ # resampler --------------------------------------------------------- -api.FDMDV_OS_48 = 6 # oversampling rate -api.FDMDV_OS_TAPS_48K = 48 # number of OS filter taps at 48kHz -api.FDMDV_OS_TAPS_48_8K = (api.FDMDV_OS_TAPS_48K/api.FDMDV_OS_48) # number of OS filter taps at 8kHz +api.FDMDV_OS_48 = int(6) # oversampling rate +api.FDMDV_OS_TAPS_48K = int(48) # number of OS filter taps at 48kHz +api.FDMDV_OS_TAPS_48_8K = int(api.FDMDV_OS_TAPS_48K/api.FDMDV_OS_48) # number of OS filter taps at 8kHz 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 + MEM8 = api.FDMDV_OS_TAPS_48_8K + MEM48 = api.FDMDV_OS_TAPS_48K + def __init__(self, n48, n8): + print("create 48<->8 kHz resampler with buffers of %d at 48 kHz and %d at 8 kHz" % (n48, n8)) + assert (n48 / n8) == api.FDMDV_OS_48 + self.n8 = n8 + self.n48 = n48 + self.in8 = np.zeros(self.MEM8 + n8, dtype=np.int16) + self.out48 = np.zeros(n48, dtype=np.int16) + self.in48 = np.zeros(self.MEM48 + n48, dtype=np.int16) + self.out8 = np.zeros(n8, dtype=np.int16) + def resample48_to_8(self,in48): + assert in48.dtype == np.int16 + assert len(in48) == self.n48 + self.in48[self.MEM48:] = in48 + pin48,flag = self.in48.__array_interface__['data'] + pin48 += 2*self.MEM48 + api.fdmdv_48_to_8_short(self.out8.ctypes, pin48, self.n8); + return self.out8 + def resample8_to_48(self,in8): + assert in8.dtype == np.int16 + assert len(in8) == self.n8 + self.in8[self.MEM8:] = in8 + pin8,flag = self.in8.__array_interface__['data'] + pin8 += 2*self.MEM8 + api.fdmdv_8_to_48_short(self.out48.ctypes, pin8, self.n8); + return self.out48 From 5b9ca6d463adec3fb0ccd65cc20a21dca4ed6ea6 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 12:10:03 +1030 Subject: [PATCH 56/70] resampler on test_rx.py is doing something sensible --- test/001_highsnr_stdio_audio/test_rx.py | 16 ++++++++-------- test/codec2.py | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 15df2d7b..e2076658 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -47,12 +47,12 @@ DEBUGGING_MODE = args.DEBUGGING_MODE TIMEOUT = args.TIMEOUT # AUDIO PARAMETERS -AUDIO_FRAMES_PER_BUFFER = 2048 # seems to be best if >=1024 +AUDIO_FRAMES_PER_BUFFER = 2400 # seems to be best if >=1024 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_SAMPLE_RATE_RX = 48000 # make sure our resampler will work -assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == api.FDMDV_OS_48 +assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 # check if we want to use an audio device then do an pyaudio init if AUDIO_INPUT_DEVICE != -1: @@ -104,6 +104,8 @@ rx_errors = 0 timeout = time.time() + TIMEOUT receive = True audio_buffer = codec2.audio_buffer(codec2.api.freedv_get_n_max_modem_samples(freedv)) +AUDIO_FRAMES_PER_BUFFER_8K = int(AUDIO_FRAMES_PER_BUFFER/codec2.api.FDMDV_OS_48) +resampler = codec2.resampler(AUDIO_FRAMES_PER_BUFFER,AUDIO_FRAMES_PER_BUFFER_8K) # initial number of samples we need nin = codec2.api.freedv_nin(freedv) @@ -115,13 +117,11 @@ while receive and time.time() < timeout: else: data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) - # resample to 8 kHz - data_in8k = resampler.resample(data_in48k) - # insert samples in buffer - x = np.frombuffer(data_in8k, dtype=np.int16) - if len(x) == 0: - receive = False + x = np.frombuffer(data_in48k, dtype=np.int16) + if len(x) != AUDIO_FRAMES_PER_BUFFER: + break + x = resampler.resample48_to_8(x) audio_buffer.push(x) # when we have enough samples call FreeDV Rx diff --git a/test/codec2.py b/test/codec2.py index 743c841a..7316f57c 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -137,12 +137,12 @@ class resampler: def __init__(self, n48, n8): print("create 48<->8 kHz resampler with buffers of %d at 48 kHz and %d at 8 kHz" % (n48, n8)) assert (n48 / n8) == api.FDMDV_OS_48 - self.n8 = n8 - self.n48 = n48 - self.in8 = np.zeros(self.MEM8 + n8, dtype=np.int16) - self.out48 = np.zeros(n48, dtype=np.int16) - self.in48 = np.zeros(self.MEM48 + n48, dtype=np.int16) - self.out8 = np.zeros(n8, dtype=np.int16) + self.n8 = int(n8) + self.n48 = int(n48) + self.in8 = np.zeros(self.MEM8 + self.n8, dtype=np.int16) + self.out48 = np.zeros(self.n48, dtype=np.int16) + self.in48 = np.zeros(self.MEM48 + self.n48, dtype=np.int16) + self.out8 = np.zeros(self.n8, dtype=np.int16) def resample48_to_8(self,in48): assert in48.dtype == np.int16 assert len(in48) == self.n48 From a04cfa62f31b2245c2df08ae0b32fbb1cc5fc097 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 12:31:35 +1030 Subject: [PATCH 57/70] refactored resampler to handle arb length arrays --- test/000_resampler/t48_8_short.py | 2 +- test/codec2.py | 53 ++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/test/000_resampler/t48_8_short.py b/test/000_resampler/t48_8_short.py index 915b6319..e982410f 100644 --- a/test/000_resampler/t48_8_short.py +++ b/test/000_resampler/t48_8_short.py @@ -51,7 +51,7 @@ fin8 = open("in8.raw", mode='wb') f48 = open("out48.raw", mode='wb') fout8 = open("out8.raw", mode='wb') -resampler = codec2.resampler(N48,N8) +resampler = codec2.resampler() for f in range(FRAMES): diff --git a/test/codec2.py b/test/codec2.py index 7316f57c..3c68fd71 100644 --- a/test/codec2.py +++ b/test/codec2.py @@ -134,28 +134,43 @@ class resampler: # self.nbuffer is the current number of samples in the buffer MEM8 = api.FDMDV_OS_TAPS_48_8K MEM48 = api.FDMDV_OS_TAPS_48K - def __init__(self, n48, n8): - print("create 48<->8 kHz resampler with buffers of %d at 48 kHz and %d at 8 kHz" % (n48, n8)) - assert (n48 / n8) == api.FDMDV_OS_48 - self.n8 = int(n8) - self.n48 = int(n48) - self.in8 = np.zeros(self.MEM8 + self.n8, dtype=np.int16) - self.out48 = np.zeros(self.n48, dtype=np.int16) - self.in48 = np.zeros(self.MEM48 + self.n48, dtype=np.int16) - self.out8 = np.zeros(self.n8, dtype=np.int16) + + def __init__(self): + print("create 48<->8 kHz resampler") + self.filter_mem8 = np.zeros(self.MEM8, dtype=np.int16) + self.filter_mem48 = np.zeros(self.MEM48) + def resample48_to_8(self,in48): assert in48.dtype == np.int16 - assert len(in48) == self.n48 - self.in48[self.MEM48:] = in48 - pin48,flag = self.in48.__array_interface__['data'] + # length of input vector must be an interger 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 + + pin48,flag = in48_mem.__array_interface__['data'] pin48 += 2*self.MEM48 - api.fdmdv_48_to_8_short(self.out8.ctypes, pin48, self.n8); - return self.out8 + 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); + self.filter_mem48 = in48_mem[:self.MEM48] + + return out8 + def resample8_to_48(self,in8): assert in8.dtype == np.int16 - assert len(in8) == self.n8 - self.in8[self.MEM8:] = in8 - pin8,flag = self.in8.__array_interface__['data'] + + # concat filter memory and input samples + in8_mem = np.zeros(self.MEM8+len(in8), dtype=np.int16) + in8_mem[:self.MEM8] = self.filter_mem8 + in8_mem[self.MEM8:] = in8 + + pin8,flag = in8_mem.__array_interface__['data'] pin8 += 2*self.MEM8 - api.fdmdv_8_to_48_short(self.out48.ctypes, pin8, self.n8); - return self.out48 + out48 = np.zeros(api.FDMDV_OS_48*len(in8), dtype=np.int16) + api.fdmdv_8_to_48_short(out48.ctypes, pin8, len(in8)); + self.filter_mem8 = in8_mem[:self.MEM8] + + return out48 From 64b758291333f4514bf5214ea3a7641dec07f1a3 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 13:54:59 +1030 Subject: [PATCH 58/70] resampler working with pipe tests, but not virtual sound card --- .github/workflows/ctest.yml | 2 +- CMakeLists.txt | 2 ++ test/001_highsnr_stdio_audio/test_rx.py | 6 +++--- test/001_highsnr_stdio_audio/test_tx.py | 27 ++++++++++++++----------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 968dbe0f..d2509682 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -17,7 +17,7 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio alsa-utils + sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio pip3 install psutil crcengine ujson pyserial numpy structlog miniaudio - name: Build codec2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 4be3d23b..c0c4b291 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ add_test(NAME 001_highsnr_stdio_P_C_SM PATH=$PATH:${CODEC2_BUILD_DIR}/src; cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; python3 test_tx.py --mode datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | + sox -t .s16 -r 48000 -c 1 - -t .s16 -r 8000 -c 1 - | freedv_data_raw_rx datac0 - - --framesperburst ${FRAMESPERBURST} | hexdump -C") set_tests_properties(001_highsnr_stdio_P_C_SM PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") @@ -41,6 +42,7 @@ add_test(NAME 001_highsnr_stdio_C_P_SM PATH=$PATH:${CODEC2_BUILD_DIR}/src; cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; freedv_data_raw_tx datac0 --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} /dev/zero - | + sox -t .s16 -r 8000 -c 1 - -t .s16 -r 48000 -c 1 - | python3 test_rx.py --mode datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") set_tests_properties(001_highsnr_stdio_C_P_SM PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index e2076658..8327c14f 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -105,7 +105,7 @@ timeout = time.time() + TIMEOUT receive = True audio_buffer = codec2.audio_buffer(codec2.api.freedv_get_n_max_modem_samples(freedv)) AUDIO_FRAMES_PER_BUFFER_8K = int(AUDIO_FRAMES_PER_BUFFER/codec2.api.FDMDV_OS_48) -resampler = codec2.resampler(AUDIO_FRAMES_PER_BUFFER,AUDIO_FRAMES_PER_BUFFER_8K) +resampler = codec2.resampler() # initial number of samples we need nin = codec2.api.freedv_nin(freedv) @@ -120,8 +120,8 @@ while receive and time.time() < timeout: # insert samples in buffer x = np.frombuffer(data_in48k, dtype=np.int16) if len(x) != AUDIO_FRAMES_PER_BUFFER: - break - x = resampler.resample48_to_8(x) + receive = False + x = resampler.resample48_to_8(x) audio_buffer.push(x) # when we have enough samples call FreeDV Rx diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 19a838fb..c021711f 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -11,7 +11,7 @@ import argparse import sys sys.path.insert(0,'..') import codec2 - +import numpy as np # GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='Simons TEST TNC') @@ -39,11 +39,10 @@ AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value - # 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 # check if we want to use an audio device then do an pyaudio init @@ -73,6 +72,9 @@ 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 data_out = b'HELLO WORLD!' @@ -134,21 +136,22 @@ for i in range(1,N_BURSTS+1): txbuffer += bytes(mod_out_postamble) # append a delay between bursts as audio silence - samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS*2) - mod_out_silence = create_string_buffer(samples_delay) + samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) + mod_out_silence = create_string_buffer(samples_delay*2) txbuffer += bytes(mod_out_silence) print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr) + + # resample up to 48k + x = np.frombuffer(txbuffer, dtype=np.int16) + txbuffer_48k = resampler.resample8_to_48(x) + print(len(txbuffer), len(x), len(txbuffer_48k), file=sys.stderr) # 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) else: # 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() From 8fcf882767bffc8e93fcabad0c5e6865e411b4b3 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 17:42:29 +1030 Subject: [PATCH 59/70] Shouldn't pass np.int16 to pyaudio stream.write(), must convert to Python bytes. Working OK now --- test/001_highsnr_stdio_audio/test_tx.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index c021711f..987f0fc1 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -68,7 +68,7 @@ if AUDIO_OUTPUT_DEVICE != -1: rate=AUDIO_SAMPLE_RATE_TX, frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples output=True, - output_device_index=AUDIO_OUTPUT_DEVICE, + output_device_index=AUDIO_OUTPUT_DEVICE ) @@ -139,16 +139,16 @@ for i in range(1,N_BURSTS+1): samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) mod_out_silence = create_string_buffer(samples_delay*2) txbuffer += bytes(mod_out_silence) - print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr) + #print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr) - # resample up to 48k + # resample up to 48k (resampler works on np.int16) x = np.frombuffer(txbuffer, dtype=np.int16) txbuffer_48k = resampler.resample8_to_48(x) - print(len(txbuffer), len(x), len(txbuffer_48k), file=sys.stderr) # check if we want to use an audio device or stdout - if AUDIO_OUTPUT_DEVICE != -1: - stream_tx.write(txbuffer_48k) + if AUDIO_OUTPUT_DEVICE != -1: + # Gotcha: we have to convert from np.int16 to Python "bytes" + stream_tx.write(txbuffer_48k.tobytes()) else: # print data to terminal for piping the output to other programs sys.stdout.buffer.write(txbuffer_48k) From 27a43f1cb0bd366058e9d86ec58267f0ad4fb66d Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 17:44:07 +1030 Subject: [PATCH 60/70] fine grained virtual audio tests working PyAudio at 48 kHz --- test/001_highsnr_stdio_audio/test_virtual1a.sh | 15 --------------- test/001_highsnr_stdio_audio/test_virtual1b.sh | 18 ++---------------- test/001_highsnr_stdio_audio/test_virtual1c.sh | 16 +--------------- 3 files changed, 3 insertions(+), 46 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_virtual1a.sh b/test/001_highsnr_stdio_audio/test_virtual1a.sh index fdcff59d..b9f32cd8 100755 --- a/test/001_highsnr_stdio_audio/test_virtual1a.sh +++ b/test/001_highsnr_stdio_audio/test_virtual1a.sh @@ -2,30 +2,15 @@ # Run a test using the virtual sound cards, tx sound I/O performed by aplay # and arecord at Fs=48000Hz, we pipe to Python utilities -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) MAX_RUN_TIME=2600 # make sure all child processes are killed when we exit trap 'jobs -p | xargs -r kill' EXIT arecord -r 48000 --device="plughw:CARD=CHAT1,DEV=0" -f S16_LE -d $MAX_RUN_TIME | \ - sox -t .s16 -r 48000 -c 1 - -t .s16 -r 8000 -c 1 - | \ python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug & rx_pid=$! sleep 1 python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 | \ - sox -t .s16 -r 8000 -c 1 - -t .s16 -r 48000 -c 1 - | \ aplay -r 48000 --device="plughw:CARD=CHAT1,DEV=1" -f S16_LE wait ${rx_pid} diff --git a/test/001_highsnr_stdio_audio/test_virtual1b.sh b/test/001_highsnr_stdio_audio/test_virtual1b.sh index 100f037e..be4babdc 100755 --- a/test/001_highsnr_stdio_audio/test_virtual1b.sh +++ b/test/001_highsnr_stdio_audio/test_virtual1b.sh @@ -2,28 +2,14 @@ # Run a test using the virtual sound cards, tx sound I/O performed by Python, # rx using arecord, Fs=48000Hz -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) MAX_RUN_TIME=2600 # make sure all child processes are killed when we exit trap 'jobs -p | xargs -r kill' EXIT arecord -r 48000 --device="plughw:CARD=CHAT1,DEV=0" -f S16_LE -d $MAX_RUN_TIME | \ - sox -t .s16 -r 48000 -c 1 - -t .s16 -r 8000 -c 1 - | \ - python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug & + python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug --timeout 20 & rx_pid=$! sleep 1 -python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 --audiodev -2 +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 2000 --audiodev -2 wait ${rx_pid} diff --git a/test/001_highsnr_stdio_audio/test_virtual1c.sh b/test/001_highsnr_stdio_audio/test_virtual1c.sh index f20bb79f..81443a0f 100755 --- a/test/001_highsnr_stdio_audio/test_virtual1c.sh +++ b/test/001_highsnr_stdio_audio/test_virtual1c.sh @@ -2,19 +2,6 @@ # Run a test using the virtual sound cards, tx sound I/O performed by aplay, # rx sound I/O by Python, Fs=48000Hz. -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) MAX_RUN_TIME=2600 # make sure all child processes are killed when we exit @@ -23,7 +10,6 @@ trap 'jobs -p | xargs -r kill' EXIT python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug --audiodev -2 & rx_pid=$! sleep 1 -python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 | \ - sox -t .s16 -r 8000 -c 1 - -t .s16 -r 48000 -c 1 - | \ +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 | \ aplay -r 48000 --device="plughw:CARD=CHAT1,DEV=1" -f S16_LE wait ${rx_pid} From fa4e22c64b3acd52bb442251d74c6005ece3bd85 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 17:52:42 +1030 Subject: [PATCH 61/70] catching exceptions on stream.read() to help us spot buffer size issues, also saving a file of samples received to help debugging --- test/001_highsnr_stdio_audio/test_rx.py | 35 +++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 8327c14f..cc82f51a 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -47,7 +47,7 @@ DEBUGGING_MODE = args.DEBUGGING_MODE TIMEOUT = args.TIMEOUT # AUDIO PARAMETERS -AUDIO_FRAMES_PER_BUFFER = 2400 # seems to be best if >=1024 +AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_SAMPLE_RATE_RX = 48000 @@ -101,24 +101,33 @@ rx_total_frames = 0 rx_frames = 0 rx_bursts = 0 rx_errors = 0 +nread_exceptions = 0 timeout = time.time() + TIMEOUT receive = True -audio_buffer = codec2.audio_buffer(codec2.api.freedv_get_n_max_modem_samples(freedv)) -AUDIO_FRAMES_PER_BUFFER_8K = int(AUDIO_FRAMES_PER_BUFFER/codec2.api.FDMDV_OS_48) +audio_buffer = codec2.audio_buffer(AUDIO_FRAMES_PER_BUFFER*2) resampler = codec2.resampler() +# Copy received 48 kHz to a file. Listen to this file with: +# aplay -r 48000 -f S16_LE rx48.raw +# Corruption of this file is a good way to detect audio card issues +frx = open("rx48.raw", mode='wb') + # initial number of samples we need nin = codec2.api.freedv_nin(freedv) - while receive and time.time() < timeout: - - if AUDIO_INPUT_DEVICE != -1: - data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = False) + if AUDIO_INPUT_DEVICE != -1: + try: + 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 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) + if len(x) != AUDIO_FRAMES_PER_BUFFER: receive = False x = resampler.resample48_to_8(x) @@ -154,12 +163,16 @@ while receive and time.time() < timeout: rx_bursts = rx_bursts + 1 if rx_bursts == N_BURSTS: - receive = False - + receive = False + if time.time() >= timeout: print("TIMEOUT REACHED") - + +if nread_exceptions: + print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \ + nread_exceptions, file=sys.stderr) print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames} RX_ERRORS: {rx_errors}", file=sys.stderr) +frx.close() # and at last check if we had an openend pyaudio instance and close it if AUDIO_INPUT_DEVICE != -1: From 37c57b83f64952dd8821117eaefb4973dc486b41 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 17:53:11 +1030 Subject: [PATCH 62/70] test_virtual2.sh pasing most of the time --- CMakeLists.txt | 2 +- test/001_highsnr_stdio_audio/test_virtual1.sh | 4 ++-- test/001_highsnr_stdio_audio/test_virtual2.sh | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c0c4b291..94967586 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ add_test(NAME 001_highsnr_virtual2_P_P PATH=$PATH:${CODEC2_BUILD_DIR}/src; cd ${CMAKE_CURRENT_SOURCE_DIR}/test/001_highsnr_stdio_audio; ./test_virtual2.sh") - set_tests_properties(001_highsnr_virtual2_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") + set_tests_properties(001_highsnr_virtual2_P_P PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0") # Multimode test with Python I/O add_test(NAME 001_highsnr_virtual3_P_P_MM diff --git a/test/001_highsnr_stdio_audio/test_virtual1.sh b/test/001_highsnr_stdio_audio/test_virtual1.sh index 00e09b84..dcb46693 100755 --- a/test/001_highsnr_stdio_audio/test_virtual1.sh +++ b/test/001_highsnr_stdio_audio/test_virtual1.sh @@ -20,8 +20,8 @@ MAX_RUN_TIME=2600 # make sure all child processes are killed when we exit trap 'jobs -p | xargs -r kill' EXIT -arecord --device="plughw:CARD=CHAT2,DEV=0" -f S16_LE -d $MAX_RUN_TIME | python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug & +arecord --device="plughw:CARD=CHAT2,DEV=0" -r 48000 -f S16_LE -d $MAX_RUN_TIME | python3 test_rx.py --mode datac0 --frames 2 --bursts 5 --debug & rx_pid=$! sleep 1 -python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 | aplay --device="plughw:CARD=CHAT2,DEV=1" -f S16_LE +python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 | aplay --device="plughw:CARD=CHAT2,DEV=1" -r 48000 -f S16_LE wait ${rx_pid} diff --git a/test/001_highsnr_stdio_audio/test_virtual2.sh b/test/001_highsnr_stdio_audio/test_virtual2.sh index d3e5522b..5176040a 100755 --- a/test/001_highsnr_stdio_audio/test_virtual2.sh +++ b/test/001_highsnr_stdio_audio/test_virtual2.sh @@ -13,13 +13,11 @@ function check_alsa_loopback { 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_rx.py --mode datac0 --frames 2 --bursts 5 --audiodev -2 --debug --timeout 20 & +python3 test_rx.py --mode datac0 --frames 2 --bursts 3 --audiodev -2 --debug & rx_pid=$! sleep 1 -python3 test_tx.py --mode datac0 --frames 2 --bursts 5 --delay 250 --audiodev -2 +python3 test_tx.py --mode datac0 --frames 2 --bursts 3 --audiodev -2 wait ${rx_pid} From 03dccc434870b89a12cf18ec25c84a119ddb5f03 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 18:33:07 +1030 Subject: [PATCH 63/70] test program to get experience with Pyaudio --- test/001_highsnr_stdio_audio/test_pa.py | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/001_highsnr_stdio_audio/test_pa.py diff --git a/test/001_highsnr_stdio_audio/test_pa.py b/test/001_highsnr_stdio_audio/test_pa.py new file mode 100644 index 00000000..6d417f9e --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_pa.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Throw away test program to help understand the care and feeding of PyAudio + +import pyaudio +import numpy as np + +CHUNK = 1024 +FS48 = 48000 +FTEST = 800 +AMP = 16000 + +# 1. play sine wave out of default sound device + +p = pyaudio.PyAudio() +stream = p.open(format=pyaudio.paInt16, + channels=1, + rate=FS48, + frames_per_buffer=CHUNK, + output=True +) + +f48 = open("out48.raw", mode='wb') +t = 0; +for f in range(50): + sine_48k = (AMP*np.cos(2*np.pi*np.arange(t,t+CHUNK)*FTEST/FS48)).astype(np.int16) + t += CHUNK + sine_48k.tofile(f48) + stream.write(sine_48k.tobytes()) + sil_48k = np.zeros(CHUNK, dtype=np.int16) +for f in range(50): + sil_48k.tofile(f48) + stream.write(sil_48k) + +stream.stop_stream() +stream.close() +p.terminate() +f48.close() From 2bf5c44cbbf3f9d7c32b1820e2d0c2f57940f124 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 16 Dec 2021 21:40:30 +1030 Subject: [PATCH 64/70] 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 From a0e7c210fbde2b750f16b0dec07e97dbe3937143 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 17 Dec 2021 06:16:40 +1030 Subject: [PATCH 65/70] fixed exception checking logic, still getting occasional fails on virtual sound card tests --- test/001_highsnr_stdio_audio/test_multimode_rx.py | 9 +++++++-- test/001_highsnr_stdio_audio/test_rx.py | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/test/001_highsnr_stdio_audio/test_multimode_rx.py b/test/001_highsnr_stdio_audio/test_multimode_rx.py index da2728f9..0f14182d 100755 --- a/test/001_highsnr_stdio_audio/test_multimode_rx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_rx.py @@ -137,9 +137,10 @@ 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) - if str(err).find("Input overflowed"): + if str(err).find("Input overflowed") != -1: nread_exceptions += 1 - if str(err).find("Stream closed"): + if str(err).find("Stream closed") != -1: + print("Ending....") receive = False else: data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) @@ -147,6 +148,7 @@ while receive and time.time() < timeout: # insert samples in buffer x = np.frombuffer(data_in48k, dtype=np.int16) if len(x) != AUDIO_FRAMES_PER_BUFFER: + print("len(x)",len(x)) receive = False x = resampler.resample48_to_8(x) @@ -209,3 +211,6 @@ if time.time() > timeout: 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) +if AUDIO_INPUT_DEVICE != -1: + stream_rx.close() + p.terminate() diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index b4f89233..5e90b435 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -120,9 +120,10 @@ 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) - if str(err).find("Input overflowed"): + if str(err).find("Input overflowed") != -1: nread_exceptions += 1 - if str(err).find("Stream closed"): + if str(err).find("Stream closed") != -1: + print("Ending...") receive = False else: data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2) From a6d60dea1c8d71532f2c63914d7c591a2c5f54e0 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 19 Dec 2021 15:04:35 +0100 Subject: [PATCH 66/70] initial callback test crashes at the moment within resambler --- CMakeLists.txt | 10 + .../test_callback_rx.py | 178 ++++++++++++++++++ test/001_highsnr_stdio_audio/test_virtual3.sh | 23 +++ 3 files changed, 211 insertions(+) create mode 100644 test/001_highsnr_stdio_audio/test_callback_rx.py create mode 100755 test/001_highsnr_stdio_audio/test_virtual3.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 94967586..56979c0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,5 +89,15 @@ add_test(NAME 001_highsnr_virtual3_P_P_MM 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 "DATAC0: 2/4 DATAC1: 2/4 DATAC3: 2/4") + +# let Python do audio I/O via pyaudio callback mode +add_test(NAME 001_highsnr_virtual4_P_P_SM_CB + 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_virtual3.sh") + set_tests_properties(001_highsnr_virtual4_P_P_SM_CB PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0") + + endif() diff --git a/test/001_highsnr_stdio_audio/test_callback_rx.py b/test/001_highsnr_stdio_audio/test_callback_rx.py new file mode 100644 index 00000000..9c61d224 --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_callback_rx.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 23 07:04:24 2020 + +@author: DJ2LS +""" + +import ctypes +from ctypes import * +import pathlib +import pyaudio +import sys +import logging +import time +import threading +import sys +import argparse +import numpy as np +sys.path.insert(0,'..') +import codec2 + +#--------------------------------------------GET PARAMETER INPUTS +parser = argparse.ArgumentParser(description='FreeDATA audio test') +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('--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, use -2 to automatically select a loopback device") +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() + +if args.LIST: + p = pyaudio.PyAudio() + for dev in range(0,p.get_device_count()): + print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) + quit() + +N_BURSTS = args.N_BURSTS +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 = 2400*2 # <- consider increasing if you get nread_exceptions > 0 +MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 +AUDIO_SAMPLE_RATE_RX = 48000 + +# make sure our resampler will work +assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 + + +# ------------------------------------------------ PYAUDIO CALLBACK +def callback(data_in48k, frame_count, time_info, status): + x = np.frombuffer(data_in48k, dtype=np.int16) + x.tofile(frx) + x = resampler.resample48_to_8(x) + audio_buffer.push(x) + return (None, pyaudio.paContinue) + + +# 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, + frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, + input=True, + output=False, + input_device_index=AUDIO_INPUT_DEVICE, + stream_callback=callback + ) + try: + print(f"starting pyaudio callback", file=sys.stderr) + stream_rx.start_stream() + except Exception as e: + print(f"pyAudio error: {e}", file=sys.stderr) +# ---------------------------------------------------------------- + +# DATA CHANNEL INITIALISATION + +# open codec2 instance +freedv = cast(codec2.api.freedv_open(MODE), c_void_p) + +# get number of bytes per frame for mode +bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv)/8) +payload_bytes_per_frame = bytes_per_frame -2 + +n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(freedv) +bytes_out = create_string_buffer(bytes_per_frame * 2) + +codec2.api.freedv_set_frames_per_burst(freedv,N_FRAMES_PER_BURST) + +total_n_bytes = 0 +rx_total_frames = 0 +rx_frames = 0 +rx_bursts = 0 +rx_errors = 0 +nread_exceptions = 0 +timeout = time.time() + TIMEOUT +receive = True +audio_buffer = codec2.audio_buffer(AUDIO_FRAMES_PER_BUFFER*2) +resampler = codec2.resampler() + +# Copy received 48 kHz to a file. Listen to this file with: +# aplay -r 48000 -f S16_LE rx48_callback.raw +# Corruption of this file is a good way to detect audio card issues +frx = open("rx48_callback.raw", mode='wb') + +# initial number of samples we need +nin = codec2.api.freedv_nin(freedv) +while receive and time.time() < timeout: + + # when we have enough samples call FreeDV Rx + while audio_buffer.nbuffer >= nin: + + # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, audio_buffer.buffer.ctypes) + audio_buffer.pop(nin) + + # call me on every loop! + nin = codec2.api.freedv_nin(freedv) + + rx_status = codec2.api.freedv_get_rx_status(freedv) + if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: + rx_errors = rx_errors + 1 + if DEBUGGING_MODE: + rx_status = codec2.api.rx_sync_flags_to_text[rx_status] + print("nin: %5d rx_status: %4s naudio_buffer: %4d" % \ + (nin,rx_status,audio_buffer.nbuffer), file=sys.stderr) + + if nbytes: + total_n_bytes = total_n_bytes + nbytes + + if nbytes == bytes_per_frame: + rx_total_frames = rx_total_frames + 1 + rx_frames = rx_frames + 1 + + if rx_frames == N_FRAMES_PER_BURST: + rx_frames = 0 + rx_bursts = rx_bursts + 1 + + if rx_bursts == N_BURSTS: + receive = False + + if time.time() >= timeout: + print("TIMEOUT REACHED") + +if nread_exceptions: + print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \ + nread_exceptions, file=sys.stderr) +print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames} RX_ERRORS: {rx_errors}", file=sys.stderr) +frx.close() + +# cloese pyaudio instance +stream_rx.close() +p.terminate() diff --git a/test/001_highsnr_stdio_audio/test_virtual3.sh b/test/001_highsnr_stdio_audio/test_virtual3.sh new file mode 100755 index 00000000..9a4d5493 --- /dev/null +++ b/test/001_highsnr_stdio_audio/test_virtual3.sh @@ -0,0 +1,23 @@ +#!/bin/bash -x +# Run a test using the virtual sound cards, Python audio I/O + +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 + +# make sure all child processes are killed when we exit +trap 'jobs -p | xargs -r kill' EXIT + +python3 test_callback_rx.py --mode datac0 --frames 2 --bursts 3 --audiodev -2 --debug & +rx_pid=$! +#sleep 1 +python3 test_tx.py --mode datac0 --frames 2 --bursts 3 --audiodev -2 +wait ${rx_pid} From 8159e9b1ab697c291f6f154665f47660575ca7c9 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 19 Dec 2021 15:14:52 +0100 Subject: [PATCH 67/70] moved codec2 to tnc folder preparation for migration to codec2 module for the entire TNC --- test/001_highsnr_stdio_audio/test_multimode_rx.py | 4 ++-- test/001_highsnr_stdio_audio/test_multimode_tx.py | 4 ++-- test/001_highsnr_stdio_audio/test_rx.py | 5 +++-- test/001_highsnr_stdio_audio/test_tx.py | 4 ++-- {test => tnc}/codec2.py | 3 ++- 5 files changed, 11 insertions(+), 9 deletions(-) rename {test => tnc}/codec2.py (99%) diff --git a/test/001_highsnr_stdio_audio/test_multimode_rx.py b/test/001_highsnr_stdio_audio/test_multimode_rx.py index 0f14182d..8a0ffa94 100755 --- a/test/001_highsnr_stdio_audio/test_multimode_rx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_rx.py @@ -9,8 +9,8 @@ import sys import ctypes from ctypes import * import pathlib -sys.path.insert(0,'..') -import codec2 +sys.path.insert(0,'../..') +from tnc import codec2 import numpy as np #--------------------------------------------GET PARAMETER INPUTS diff --git a/test/001_highsnr_stdio_audio/test_multimode_tx.py b/test/001_highsnr_stdio_audio/test_multimode_tx.py index ae0308c7..22f77004 100644 --- a/test/001_highsnr_stdio_audio/test_multimode_tx.py +++ b/test/001_highsnr_stdio_audio/test_multimode_tx.py @@ -11,8 +11,8 @@ import threading import audioop import argparse import sys -sys.path.insert(0,'..') -import codec2 +sys.path.insert(0,'../..') +from tnc import codec2 import numpy as np # GET PARAMETER INPUTS diff --git a/test/001_highsnr_stdio_audio/test_rx.py b/test/001_highsnr_stdio_audio/test_rx.py index 5e90b435..f29ea33d 100644 --- a/test/001_highsnr_stdio_audio/test_rx.py +++ b/test/001_highsnr_stdio_audio/test_rx.py @@ -17,8 +17,9 @@ import threading import sys import argparse import numpy as np -sys.path.insert(0,'..') -import codec2 +sys.path.insert(0,'../..') +from tnc import codec2 + #--------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='Simons TEST TNC') diff --git a/test/001_highsnr_stdio_audio/test_tx.py b/test/001_highsnr_stdio_audio/test_tx.py index 1eeac6a7..581eae2d 100644 --- a/test/001_highsnr_stdio_audio/test_tx.py +++ b/test/001_highsnr_stdio_audio/test_tx.py @@ -9,8 +9,8 @@ import pyaudio import time import argparse import sys -sys.path.insert(0,'..') -import codec2 +sys.path.insert(0,'../..') +from tnc import codec2 import numpy as np # GET PARAMETER INPUTS diff --git a/test/codec2.py b/tnc/codec2.py similarity index 99% rename from test/codec2.py rename to tnc/codec2.py index 6ce6e252..5126d167 100644 --- a/test/codec2.py +++ b/tnc/codec2.py @@ -139,7 +139,8 @@ class resampler: print("create 48<->8 kHz resampler") self.filter_mem8 = np.zeros(self.MEM8, dtype=np.int16) self.filter_mem48 = np.zeros(self.MEM48) - + + def resample48_to_8(self,in48): assert in48.dtype == np.int16 # length of input vector must be an integer multiple of api.FDMDV_OS_48 From 8b8678853d87f20b60de71325398fa3822eecabf Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 19 Dec 2021 16:10:39 +0100 Subject: [PATCH 68/70] new codec2.py location forgot this file.... --- test/000_resampler/t48_8_short.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/000_resampler/t48_8_short.py b/test/000_resampler/t48_8_short.py index e982410f..f76cd5b2 100644 --- a/test/000_resampler/t48_8_short.py +++ b/test/000_resampler/t48_8_short.py @@ -18,8 +18,8 @@ from ctypes import * import pathlib import argparse import sys -sys.path.insert(0,'..') -import codec2 +sys.path.insert(0,'../..') +from tnc import codec2 import numpy as np # dig some constants out From 72b653b188041112bcdfc771e022aae617b6882c Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 19 Dec 2021 19:45:08 +0100 Subject: [PATCH 69/70] preparation for ARQ test added a directory 003_highsnr_stdio_arq for working on ARQ tests --- test/003_highsnr_stdio_arq/test_arq_tx.py | 22 ++++++++++++++ tnc/codec2.py | 36 +++++++++++++++++++---- tnc/data_handler.py | 5 ++-- tnc/modem.py | 36 +++++++++++++++++++---- tnc/static.py | 6 ++-- 5 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 test/003_highsnr_stdio_arq/test_arq_tx.py diff --git a/test/003_highsnr_stdio_arq/test_arq_tx.py b/test/003_highsnr_stdio_arq/test_arq_tx.py new file mode 100644 index 00000000..d4915750 --- /dev/null +++ b/test/003_highsnr_stdio_arq/test_arq_tx.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 23 07:04:24 2020 + +@author: DJ2LS +""" + +import sys +sys.path.insert(0,'../..') +sys.path.insert(0,'../../tnc') +import data_handler + + +teststring = b'HELLO WORLD' + +data_handler.arq_transmit(teststring, 10, 1) + + + + + diff --git a/tnc/codec2.py b/tnc/codec2.py index 5126d167..76caf8bf 100644 --- a/tnc/codec2.py +++ b/tnc/codec2.py @@ -7,8 +7,7 @@ import sys import pathlib from enum import Enum import numpy as np - -print("loading codec2 module", file=sys.stderr) +#print("loading codec2 module", file=sys.stderr) # Enum for codec2 modes @@ -17,14 +16,39 @@ class FREEDV_MODE(Enum): datac1 = 10 datac3 = 12 -def FREEDV_GET_MODE(mode): +def freedv_get_mode(mode): return FREEDV_MODE[mode].value -# LOAD FREEDV -libname = "libcodec2.so" -api = ctypes.CDLL(libname) +# -------------------------------------------- LOAD FREEDV +# codec2 search pathes in descending order +# libcodec2.so ctests +# pathlib.Path("codec2/build_linux/src/libcodec2.so.1.0") manual build +# pathlib.Path("lib/codec2/linux/libcodec2.so.1.0") precompiled +# pathlib.Path("../../tnc/codec2/build_linux/src/libcodec2.so.1.0") external loading manual build +# pathlib.Path("../../tnc/lib/codec2/linux/libcodec2.so.1.0") external loading precompiled +libname = ["libcodec2.so", \ + pathlib.Path("codec2/build_linux/src/libcodec2.so.1.0"), \ + pathlib.Path("lib/codec2/linux/libcodec2.so.1.0"), \ + pathlib.Path("../../tnc/codec2/build_linux/src/libcodec2.so.1.0"), \ + pathlib.Path("../../tnc/lib/codec2/linux/libcodec2.so.1.0"), \ + ] +# iterate through codec2 search pathes +for i in libname: + try: + api = ctypes.CDLL(i) + print(f"[C2 ] Codec2 library found - {i}", file=sys.stderr) + break + except: + print(f"[C2 ] Codec2 library not found - {i}", file=sys.stderr) + pass +# quit module if codec2 cant be loaded +if not 'api' in locals(): + print(f"[C2 ] Loading Codec2 library failed", file=sys.stderr) + quit() + + # ctypes function init api.freedv_open.argype = [c_int] diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 5ed86abd..702a61cc 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -5,14 +5,13 @@ Created on Sun Dec 27 20:43:40 2020 @author: DJ2LS """ - - +import sys import logging, structlog, log_handler import threading import time from random import randrange import asyncio -import sys + import ujson as json import static diff --git a/tnc/modem.py b/tnc/modem.py index b2c42c5c..48ce95a0 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -21,6 +21,8 @@ import static import data_handler import re +import codec2 + # option for testing miniaudio instead of audioop for sample rate conversion #import miniaudio @@ -109,7 +111,11 @@ class RF(): self.AUDIO_CHUNKS = 48 #8 * (self.AUDIO_SAMPLE_RATE_RX/self.MODEM_SAMPLE_RATE) #48 self.AUDIO_CHANNELS = 1 + # small hack for initializing codec2 via codec2.py module + # TODO: we need to change the entire modem module to integrate codec2 module + self.c_lib = codec2.api + ''' # -------------------------------------------- LOAD FREEDV try: # we check at first for libcodec2 compiled from source @@ -132,11 +138,11 @@ class RF(): structlog.get_logger("structlog").info("[TNC] Codec2 found", path=libname, origin="precompiled") else: structlog.get_logger("structlog").critical("[TNC] Codec2 not found") - - + ''' + ''' # --------------------------------------------CTYPES FUNCTION INIT # TODO: WE STILL HAVE SOME MISSING FUNCTIONS! - + self.c_lib.freedv_open.argype = [c_int] self.c_lib.freedv_open.restype = c_void_p @@ -154,8 +160,7 @@ class RF(): self.c_lib.freedv_set_frames_per_burst.argtype = [c_void_p, c_int] self.c_lib.freedv_set_frames_per_burst.restype = c_int - - + ''' # --------------------------------------------CREATE PYAUDIO INSTANCE try: @@ -168,6 +173,17 @@ class RF(): self.p = pyaudio.PyAudio() atexit.register(self.p.terminate) # --------------------------------------------OPEN AUDIO CHANNEL RX + # optional auto selection of loopback device if using in testmode + if static.AUDIO_INPUT_DEVICE == -2: + loopback_list = [] + for dev in range(0,self.p.get_device_count()): + if 'Loopback: PCM' in self.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) + + self.stream_rx = self.p.open(format=pyaudio.paInt16, channels=self.AUDIO_CHANNELS, rate=self.AUDIO_SAMPLE_RATE_RX, @@ -176,6 +192,16 @@ class RF(): input_device_index=static.AUDIO_INPUT_DEVICE ) # --------------------------------------------OPEN AUDIO CHANNEL TX + # optional auto selection of loopback device if using in testmode + if static.AUDIO_OUTPUT_DEVICE == -2: + loopback_list = [] + for dev in range(0,self.p.get_device_count()): + if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]: + loopback_list.append(dev) + if len(loopback_list) >= 2: + static.AUDIO_OUTPUT_DEVICE = loopback_list[1] #0 = RX 1 = TX + print(f"loopback_list tx: {loopback_list}", file=sys.stderr) + self.stream_tx = self.p.open(format=pyaudio.paInt16, channels=self.AUDIO_CHANNELS, rate=self.AUDIO_SAMPLE_RATE_TX, diff --git a/tnc/static.py b/tnc/static.py index d0c04316..060e988f 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -36,7 +36,7 @@ SOCKET_TIMEOUT = 3 # seconds HAMLIB_PTT_TYPE = 'RTS' PTT_STATE = False -HAMLIB_DEVICE_ID = 0 +HAMLIB_DEVICE_ID = 'RIG_MODEL_DUMMY_NOVFO' HAMLIB_DEVICE_PORT = '/dev/ttyUSB0' HAMLIB_SERIAL_SPEED = '9600' @@ -54,8 +54,8 @@ SCATTER = [] # --------------------------------- # Audio Defaults -AUDIO_INPUT_DEVICE = 1 -AUDIO_OUTPUT_DEVICE = 1 +AUDIO_INPUT_DEVICE = -2 +AUDIO_OUTPUT_DEVICE = -2 AUDIO_RMS = 0 From b0dfa666bfb26d3729284623644d7be7272869e2 Mon Sep 17 00:00:00 2001 From: dj2ls Date: Sun, 19 Dec 2021 20:31:53 +0100 Subject: [PATCH 70/70] updated tnc modem with latest test results lets see how the results perform within the TNC environment --- tnc/modem.py | 123 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 99 insertions(+), 24 deletions(-) diff --git a/tnc/modem.py b/tnc/modem.py index 48ce95a0..75de6897 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -105,16 +105,19 @@ class RF(): def __init__(self): self.AUDIO_SAMPLE_RATE_RX = 48000 self.AUDIO_SAMPLE_RATE_TX = 48000 - self.MODEM_SAMPLE_RATE = 8000 - self.AUDIO_FRAMES_PER_BUFFER_RX = 8192 #8192 + self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 + self.AUDIO_FRAMES_PER_BUFFER_RX = 2400*2 #8192 self.AUDIO_FRAMES_PER_BUFFER_TX = 8 #8192 Lets to some tests with very small chunks for TX self.AUDIO_CHUNKS = 48 #8 * (self.AUDIO_SAMPLE_RATE_RX/self.MODEM_SAMPLE_RATE) #48 self.AUDIO_CHANNELS = 1 + # make sure our resampler will work + assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 + # small hack for initializing codec2 via codec2.py module # TODO: we need to change the entire modem module to integrate codec2 module self.c_lib = codec2.api - + self.resampler = codec2.resampler() ''' # -------------------------------------------- LOAD FREEDV try: @@ -472,11 +475,45 @@ class RF(): # -------------------------------------------------------------------------------------------------------- def receive(self): - + ''' freedv_mode_datac0 = 14 freedv_mode_datac1 = 10 freedv_mode_datac3 = 12 + ''' + + # open codec2 instance + datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p) + datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac0_freedv)/8) + datac0_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac0_freedv) + datac0_bytes_out = create_string_buffer(datac0_bytes_per_frame * 2) + codec2.api.freedv_set_frames_per_burst(datac0_freedv,1) + datac0_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER_RX) + datac0_modem_stats_snr = c_float() + datac0_modem_stats_sync = c_int() + static.FREEDV_SIGNALLING_BYTES_PER_FRAME = datac0_bytes_per_frame + static.FREEDV_SIGNALLING_PAYLOAD_PER_FRAME = datac0_bytes_per_frame - 2 + + datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p) + datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac1_freedv)/8) + datac1_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac1_freedv) + datac1_bytes_out = create_string_buffer(datac1_bytes_per_frame * 2) + codec2.api.freedv_set_frames_per_burst(datac1_freedv,1) + datac1_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER_RX) + datac1_modem_stats_snr = c_float() + datac1_modem_stats_sync = c_int() + + + datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p) + datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac3_freedv)/8) + datac3_n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(datac3_freedv) + datac3_bytes_out = create_string_buffer(datac3_bytes_per_frame * 2) + codec2.api.freedv_set_frames_per_burst(datac3_freedv,1) + datac3_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER_RX) + datac3_modem_stats_snr = c_float() + datac3_modem_stats_sync = c_int() + + ''' # DATAC0 datac0_freedv = cast(self.c_lib.freedv_open(freedv_mode_datac0), c_void_p) @@ -511,20 +548,66 @@ class RF(): datac3_modem_stats_snr = c_float() datac3_modem_stats_sync = c_int() datac3_buffer = bytes() - ''' - if mode == static.ARQ_DATA_CHANNEL_MODE: - static.FREEDV_DATA_BYTES_PER_FRAME = bytes_per_frame - static.FREEDV_DATA_PAYLOAD_PER_FRAME = bytes_per_frame - 2 - self.c_lib.freedv_set_frames_per_burst(freedv, 0) - else: - #pass - self.c_lib.freedv_set_frames_per_burst(freedv, 0) - ''' fft_buffer = bytes() - while True: + receive = True + while receive: + try: + data_in48k = self.stream_rx.read(self.AUDIO_FRAMES_PER_BUFFER_RX, exception_on_overflow = True) + except OSError as err: + print(err, file=sys.stderr) + if str(err).find("Input overflowed") != -1: + nread_exceptions += 1 + if str(err).find("Stream closed") != -1: + print("Ending...") + receive = False + + + + # insert samples in buffer + x = np.frombuffer(data_in48k, dtype=np.int16) + # x.tofile(frx) + if len(x) != self.AUDIO_FRAMES_PER_BUFFER_RX: + receive = False + x = self.resampler.resample48_to_8(x) + + datac0_buffer.push(x) + datac1_buffer.push(x) + datac3_buffer.push(x) + + # when we have enough samples call FreeDV Rx + while datac0_buffer.nbuffer >= datac0_nin: + # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(datac0_freedv, datac0_bytes_out, datac0_buffer.buffer.ctypes) + datac0_buffer.pop(datac0_nin) + datac0_nin = codec2.api.freedv_nin(datac0_freedv) + if nbytes == datac0_bytes_per_frame: + datac0_task = threading.Thread(target=self.process_data, args=[datac0_bytes_out, datac0_freedv, datac0_bytes_per_frame]) + datac0_task.start() + + while datac1_buffer.nbuffer >= datac1_nin: + # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(datac1_freedv, datac1_bytes_out, datac1_buffer.buffer.ctypes) + datac1_buffer.pop(datac1_nin) + datac1_nin = codec2.api.freedv_nin(datac1_freedv) + if nbytes == datac1_bytes_per_frame: + datac1_task = threading.Thread(target=self.process_data, args=[datac1_bytes_out, datac1_freedv, datac1_bytes_per_frame]) + datac1_task.start() + + while datac3_buffer.nbuffer >= datac3_nin: + # demodulate audio + nbytes = codec2.api.freedv_rawdatarx(datac3_freedv, datac3_bytes_out, datac3_buffer.buffer.ctypes) + datac3_buffer.pop(datac3_nin) + datac3_nin = codec2.api.freedv_nin(datac3_freedv) + if nbytes == datac3_bytes_per_frame: + datac3_task = threading.Thread(target=self.process_data, args=[datac3_bytes_out, datac1_freedv, datac1_bytes_per_frame]) + datac3_task.start() + + + + ''' data_in = bytes() data_in = self.stream_rx.read(self.AUDIO_CHUNKS, exception_on_overflow=False) data_in = audioop.ratecv(data_in, 2, 1, self.AUDIO_SAMPLE_RATE_RX, self.MODEM_SAMPLE_RATE, None) @@ -535,15 +618,7 @@ class RF(): datac1_nin = self.c_lib.freedv_nin(datac1_freedv) * 2 datac3_nin = self.c_lib.freedv_nin(datac3_freedv) * 2 - ''' - # refill buffer only if every mode has worked with its data - if (len(datac0_buffer) < (datac0_nin)) and (len(datac1_buffer) < (datac1_nin)) and (len(datac3_buffer) < (datac3_nin)): - - datac0_buffer += data_in - datac1_buffer += data_in - datac3_buffer += data_in - - ''' + datac0_buffer += data_in datac1_buffer += data_in datac3_buffer += data_in @@ -608,7 +683,7 @@ class RF(): datac3_task = threading.Thread(target=self.process_data, args=[datac3_bytes_out, datac3_freedv, datac3_bytes_per_frame]) datac3_task.start() - + ''' # forward data only if broadcast or we are the receiver # bytes_out[1:2] == callsign check for signalling frames, # bytes_out[6:7] == callsign check for data frames,