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