Merge branch 'main' into pep8_improvements

This commit is contained in:
DJ2LS 2022-05-23 09:46:42 +02:00 committed by GitHub
commit 507e3a5b06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 3707 additions and 1811 deletions

View file

@ -2,7 +2,7 @@ name: Linux
on: on:
push: push:
tags: tags:
- 'v*' - "v*"
jobs: jobs:
build_linux_release: build_linux_release:
@ -20,15 +20,13 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
ref: main ref: main
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.8 python-version: 3.8
- name: Install Linux dependencies - name: Install Linux dependencies
if: matrix.os == 'ubuntu-20.04' if: matrix.os == 'ubuntu-20.04'
run: | run: |
@ -44,7 +42,6 @@ jobs:
pip3 install structlog pip3 install structlog
pip3 install sounddevice pip3 install sounddevice
#- name: Build Hamlib Python Binding #- name: Build Hamlib Python Binding
# if: matrix.os == 'ubuntu-latest' # if: matrix.os == 'ubuntu-latest'
# working-directory: tnc # working-directory: tnc
@ -58,19 +55,15 @@ jobs:
# make # make
# make install # make install
- name: Build codec2 Linux - name: Build codec2 Linux
if: matrix.os == 'ubuntu-20.04' if: matrix.os == 'ubuntu-20.04'
working-directory: tnc working-directory: tnc
run: | run: |
git clone https://github.com/drowe67/codec2.git git clone https://github.com/drowe67/codec2.git
cd codec2 && mkdir build_linux && cd build_linux cd codec2 && git checkout master && mkdir build_linux && cd build_linux
cmake ../ cmake ../
make make
- name: Build Linux Daemon - name: Build Linux Daemon
if: matrix.os == 'ubuntu-20.04' if: matrix.os == 'ubuntu-20.04'
working-directory: tnc working-directory: tnc
@ -82,7 +75,6 @@ jobs:
run: | run: |
ls -R ls -R
- name: Compress Linux - name: Compress Linux
shell: bash shell: bash
run: | run: |
@ -96,8 +88,6 @@ jobs:
name: tnc-artifact name: tnc-artifact
path: ./tnc/dist/compressed/* path: ./tnc/dist/compressed/*
- name: Copy TNC to GUI Linux - name: Copy TNC to GUI Linux
if: matrix.os == 'ubuntu-20.04' if: matrix.os == 'ubuntu-20.04'
run: | run: |
@ -124,6 +114,3 @@ jobs:
# release the app after building # release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }} release: ${{ startsWith(github.ref, 'refs/tags/v') }}
args: "-p always" args: "-p always"

View file

@ -2,7 +2,7 @@ name: macOS
on: on:
push: push:
tags: tags:
- 'v*' - "v*"
jobs: jobs:
build_linux_release: build_linux_release:
@ -20,15 +20,13 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
ref: main ref: main
- name: Set up Python 3.9 - name: Set up Python 3.9
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.9 python-version: 3.9
- name: Install macOS dependencies - name: Install macOS dependencies
if: matrix.os == 'macos-10.15' if: matrix.os == 'macos-10.15'
run: | run: |
@ -45,20 +43,18 @@ jobs:
- name: Install Portaudio - name: Install Portaudio
if: matrix.os == 'macos-10.15' if: matrix.os == 'macos-10.15'
run: | run: |
brew install portaudio brew install portaudio
pip3 install pyaudio pip3 install pyaudio
- name: Build codec2 macOS - name: Build codec2 macOS
if: matrix.os == 'macos-10.15' if: matrix.os == 'macos-10.15'
working-directory: tnc working-directory: tnc
run: | run: |
git clone https://github.com/drowe67/codec2.git git clone https://github.com/drowe67/codec2.git
cd codec2 && mkdir build_mac && cd build_mac cd codec2 && git checkout master && mkdir build_mac && cd build_mac
cmake ../ cmake ../
make make
- name: Build macOS pyinstaller - name: Build macOS pyinstaller
if: matrix.os == 'macos-10.15' if: matrix.os == 'macos-10.15'
working-directory: tnc working-directory: tnc
@ -70,7 +66,6 @@ jobs:
run: | run: |
ls -R ls -R
- name: Compress - name: Compress
shell: bash shell: bash
run: | run: |
@ -84,9 +79,6 @@ jobs:
name: tnc-artifact name: tnc-artifact
path: ./tnc/dist/compressed/* path: ./tnc/dist/compressed/*
- name: Copy TNC to GUI - name: Copy TNC to GUI
if: matrix.os == 'macos-10.15' if: matrix.os == 'macos-10.15'
run: | run: |
@ -113,6 +105,3 @@ jobs:
# release the app after building # release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }} release: ${{ startsWith(github.ref, 'refs/tags/v') }}
args: "-p always" args: "-p always"

View file

@ -11,26 +11,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install packages - name: Install packages
shell: bash shell: bash
run: | run: |
sudo apt-get update 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
pip3 install psutil crcengine ujson pyserial numpy structlog miniaudio sounddevice pip3 install psutil crcengine ujson pyserial numpy structlog miniaudio sounddevice pytest
- name: Build codec2 - name: Build codec2
shell: bash shell: bash
run: | run: |
git clone https://github.com/drowe67/codec2.git git clone https://github.com/drowe67/codec2.git
cd codec2 && git checkout dr-tnc && git pull cd codec2 && git checkout master # This should be pinned to a release
mkdir -p build_linux && cd build_linux && cmake .. && make mkdir -p build_linux && cd build_linux && cmake .. && make
- name: run ctests - name: run ctests
shell: bash shell: bash
working-directory: ${{github.workspace}} working-directory: ${{github.workspace}}
run: | run: |
mkdir build && cd build mkdir build && cd build
cmake -DCODEC2_BUILD_DIR=$GITHUB_WORKSPACE/codec2/build_linux .. cmake -DCODEC2_BUILD_DIR=$GITHUB_WORKSPACE/codec2/build_linux ..
ctest --output-on-failure ctest --output-on-failure

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ tnc/codec2
**/Testing **/Testing
package-lock.json package-lock.json
.DS_Store

View file

@ -24,16 +24,80 @@ set(TESTFRAMES 3)
add_test(NAME audio_buffer add_test(NAME audio_buffer
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test; cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_audiobuffer.py") python3 test_audiobuffer.py")
set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
add_test(NAME resampler add_test(NAME resampler
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test; cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 t48_8_short.py") python3 test_resample_48_8.py")
set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS") set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS")
add_test(NAME tnc_state_machine
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_tnc_states.py")
set_tests_properties(tnc_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
add_test(NAME tnc_irs_iss
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_tnc.py")
set_tests_properties(tnc_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
add_test(NAME helper_routines
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_helpers.py")
set_tests_properties(helper_routines PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
add_test(NAME py_highsnr_stdio_P_P_multi
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
export BURSTS=${BURSTS};
export FRAMESPERBURST=${FRAMESPERBURST};
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_highsnr_stdio_P_P_multi.py")
set_tests_properties(py_highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
add_test(NAME py_highsnr_stdio_P_P_datacx
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
export BURSTS=${BURSTS};
export FRAMESPERBURST=${FRAMESPERBURST};
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_highsnr_stdio_P_P_datacx.py")
set_tests_properties(py_highsnr_stdio_P_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
add_test(NAME py_highsnr_stdio_P_C_datacx
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
export BURSTS=${BURSTS};
export FRAMESPERBURST=${FRAMESPERBURST};
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_highsnr_stdio_P_C_datacx.py")
set_tests_properties(py_highsnr_stdio_P_C_datacx PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD")
add_test(NAME py_highsnr_stdio_C_P_datacx
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
export BURSTS=${BURSTS};
export FRAMESPERBURST=${FRAMESPERBURST};
export TESTFRAMES=${TESTFRAMES};
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_highsnr_stdio_C_P_datacx.py")
set_tests_properties(py_highsnr_stdio_C_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
add_test(NAME highsnr_stdio_P_C_single add_test(NAME highsnr_stdio_P_C_single
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
PATH=$PATH:${CODEC2_BUILD_DIR}/src; PATH=$PATH:${CODEC2_BUILD_DIR}/src;
@ -59,7 +123,7 @@ add_test(NAME highsnr_stdio_P_P_single
python3 test_tx.py --mode datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | python3 test_tx.py --mode datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} |
python3 test_rx.py --debug --mode datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") python3 test_rx.py --debug --mode datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}")
set_tests_properties(highsnr_stdio_P_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") set_tests_properties(highsnr_stdio_P_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
add_test(NAME highsnr_stdio_P_P_multi add_test(NAME highsnr_stdio_P_P_multi
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
PATH=$PATH:${CODEC2_BUILD_DIR}/src; PATH=$PATH:${CODEC2_BUILD_DIR}/src;
@ -68,7 +132,6 @@ add_test(NAME highsnr_stdio_P_P_multi
python3 test_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} --timeout 20") python3 test_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} --timeout 20")
set_tests_properties(highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}") set_tests_properties(highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
# These tests can't run on GitHub actions as we don't have a virtual sound card # These tests can't run on GitHub actions as we don't have a virtual sound card
if(NOT DEFINED ENV{GITHUB_RUN_ID}) if(NOT DEFINED ENV{GITHUB_RUN_ID})
@ -127,7 +190,7 @@ add_test(NAME highsnr_virtual5_P_P_multi_callback_outside
cd ${CMAKE_CURRENT_SOURCE_DIR}/test; cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
./test_virtual4b.sh") ./test_virtual4b.sh")
set_tests_properties(highsnr_virtual5_P_P_multi_callback_outside PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: 2/4 DATAC1: 2/4 DATAC3: 2/4") set_tests_properties(highsnr_virtual5_P_P_multi_callback_outside PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: 2/4 DATAC1: 2/4 DATAC3: 2/4")
# ARQ test short # ARQ test short
add_test(NAME highsnr_ARQ_short add_test(NAME highsnr_ARQ_short
@ -135,11 +198,11 @@ add_test(NAME highsnr_ARQ_short
PATH=$PATH:${CODEC2_BUILD_DIR}/src; PATH=$PATH:${CODEC2_BUILD_DIR}/src;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test; cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_arq_short.py") python3 test_arq_short.py")
set_tests_properties(highsnr_ARQ_short PROPERTIES PASS_REGULAR_EXPRESSION "ARQ | TX | DATA TRANSMITTED!") set_tests_properties(highsnr_ARQ_short PROPERTIES PASS_REGULAR_EXPRESSION "ARQ | TX | DATA TRANSMITTED!")
endif() endif()

View file

@ -30,4 +30,3 @@ Download the latest developer release from the releases section, unpack it and j
## Installation ## Installation
Please check the [wiki](https://wiki.freedata.app) for installation instructions Please check the [wiki](https://wiki.freedata.app) for installation instructions

View file

@ -1,6 +1,6 @@
{ {
"name": "FreeDATA", "name": "FreeDATA",
"version": "0.4.0-alpha.8", "version": "0.4.0-alpha.9",
"description": "FreeDATA ", "description": "FreeDATA ",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View file

@ -511,8 +511,9 @@ update_chat = function(obj) {
`; `;
var controlarea_receive = ''; var controlarea_receive = '';
} }
} catch { } catch (err) {
console.log("error with database parsing...") console.log("error with database parsing...")
console.log(err)
} }
// CALLSIGN LIST // CALLSIGN LIST
if (!(document.getElementById('chat-' + dxcallsign + '-list'))) { if (!(document.getElementById('chat-' + dxcallsign + '-list'))) {

View file

@ -1,4 +1,52 @@
# Unit Test Menu
The following `CTest` tests cover some TNC functionality and the interface to codec2:
1. Name: `audio_buffer`
Tests the thread safety of the audio buffer routines.
1. Name: `resampler`
Tests FreeDATA audio resampling from 48KHz to 8KHz.
1. Name: `tnc_state_machine`
Tests TNC transitions between states.
1. Name: `helper_routines`
Tests various helper routines.
1. Name: `py_highsnr_stdio_P_P_multi`
Tests a high signal-to-noise ratio (good quality) audio path using multiple codecs. (Pure python.)
1. Name: `py_highsnr_stdio_P_P_datacx`
Tests a high signal-to-noise ratio audio path using multiple individual codecs.
1. Name: `py_highsnr_stdio_P_C_datacx`
Tests a high signal-to-noise ratio audio path using multiple individual codecs.
1. Name: `py_highsnr_stdio_C_P_datacx`
Tests a high signal-to-noise ratio audio path using multiple individual codecs.
1. Name: `highsnr_stdio_P_C_single`
Tests compatibility with FreeDATA's transmit and freedv's raw data receive.
1. Name: `highsnr_stdio_C_P_single`
Tests compatibility with freedv's raw data transmit and FreeDATA's receive.
1. Name: `highsnr_stdio_P_P_single`
Tests a high signal-to-noise ratio audio path using multiple codecs. (Requires POSIX system.)
1. Name: `highsnr_stdio_P_P_multi`
Tests a high signal-to-noise ratio audio path using multiple codecs. (Requires POSIX system.)
The following tests can not currently be run with GitHub's pipeline as they require the ALSA dummy device
kernel module to be installed. They also do not perform reliably. These tests are slowly being
replaced with equivalent pipeline-compatible tests.
1. Name: `highsnr_virtual1_P_P_single_alsa`
Tests a high signal-to-noise ratio audio path using a single codec directly over an ALSA dummy device.
1. Name: `highsnr_virtual2_P_P_single`
Tests a high signal-to-noise ratio audio path using a single codec over an ALSA dummy device.
**Not functional** due to an incompatibility between the two scripts in the way they determine audio devices.
1. Name: `highsnr_virtual3_P_P_multi`
Tests a high signal-to-noise ratio audio path using multiple codecs over an ALSA dummy device.
1. Name: `highsnr_virtual4_P_P_single_callback`
**Not functional** due to an incompatibility between the two scripts in the way they determine audio devices.
1. Name: `highsnr_virtual4_P_P_single_callback_outside`
**Not functional** due to an incompatibility between the two scripts in the way they determine audio devices.
1. Name: `highsnr_virtual5_P_P_multi_callback`
1. Name: `highsnr_virtual5_P_P_multi_callback_outside`
1. Name: `highsnr_ARQ_short`
**Not functional**, it is an obsolete or not yet completed test.
# Instructions # Instructions
1. Install: 1. Install:
@ -58,7 +106,7 @@ The virtual audio devices are great for testing, but they are also a little bit
1. Create virtual audio devices. Note: This command needs to be run again after every reboot 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 sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2
``` ```
1. Check if devices have been created 1. Check if devices have been created
@ -81,7 +129,7 @@ The virtual audio devices are great for testing, but they are also a little bit
Sub-Geräte: 1/1 Sub-Geräte: 1/1
Sub-Gerät #0: subdevice #0 Sub-Gerät #0: subdevice #0
``` ```
1. Determine the audio device number you would like to use: 1. Determine the audio device number you would like to use:
``` ```
python3 test_rx.py --list python3 test_rx.py --list

View file

@ -1,113 +1,117 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import ctypes
from ctypes import *
import pathlib
import pyaudio
import time
import threading
import argparse import argparse
import sys import ctypes
import pathlib
import threading
import time
#--------------------------------------------GET PARAMETER INPUTS import pyaudio
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() # --------------------------------------------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_known_args()
N_BURSTS = args.N_BURSTS N_BURSTS = args.N_BURSTS
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT
AUDIO_INPUT_DEVICE = args.AUDIO_INPUT AUDIO_INPUT_DEVICE = args.AUDIO_INPUT
# 1024 good for mode 6 # 1024 good for mode 6
AUDIO_FRAMES_PER_BUFFER = 2048 AUDIO_FRAMES_PER_BUFFER = 2048
MODEM_SAMPLE_RATE = 8000 MODEM_SAMPLE_RATE = 8000
FREEDV_TX_MODE = args.FREEDV_TX_MODE FREEDV_TX_MODE = args.FREEDV_TX_MODE
FREEDV_RX_MODE = args.FREEDV_RX_MODE FREEDV_RX_MODE = args.FREEDV_RX_MODE
DEBUGGING_MODE = args.DEBUGGING_MODE DEBUGGING_MODE = args.DEBUGGING_MODE
#-------------------------------------------- LOAD FREEDV # -------------------------------------------- LOAD FREEDV
libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so" libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so"
c_lib = ctypes.CDLL(libname) c_lib = ctypes.CDLL(str(libname))
#--------------------------------------------CREATE PYAUDIO INSTANCE # --------------------------------------------CREATE PYAUDIO INSTANCE
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
#--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE # --------------------------------------------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_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_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate'])
AUDIO_SAMPLE_RATE_TX = 8000 AUDIO_SAMPLE_RATE_TX = 8000
AUDIO_SAMPLE_RATE_RX = 8000 AUDIO_SAMPLE_RATE_RX = 8000
#--------------------------------------------OPEN AUDIO CHANNEL TX # --------------------------------------------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,
)
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(): def receive():
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
freedv = c_lib.freedv_open(FREEDV_RX_MODE) freedv = c_lib.freedv_open(FREEDV_RX_MODE)
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv) / 8)
payload_per_frame = bytes_per_frame -2 payload_per_frame = bytes_per_frame - 2
n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) 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 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
bytes_out = (ctypes.c_ubyte * bytes_per_frame) #bytes_per_frame
bytes_out = bytes_out() #get pointer from bytes_out
total_n_bytes = 0 total_n_bytes = 0
rx_total_frames = 0 rx_total_frames = 0
rx_frames = 0 rx_frames = 0
rx_bursts = 0 rx_bursts = 0
receive = True receive = True
while receive == True: while receive:
time.sleep(0.01) time.sleep(0.01)
nin = c_lib.freedv_nin(freedv) nin = c_lib.freedv_nin(freedv)
nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) nin_converted = int(nin * (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE))
if DEBUGGING_MODE == True: if DEBUGGING_MODE:
print("-----------------------------") print("-----------------------------")
print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]") print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]")
data_in = stream_rx.read(nin_converted, exception_on_overflow = False) data_in = stream_rx.read(nin_converted, exception_on_overflow=False)
data_in = data_in.rstrip(b'\x00') 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 c_lib.freedv_rawdatarx.argtype = [
nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio 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 total_n_bytes = total_n_bytes + nbytes
if DEBUGGING_MODE == True: if DEBUGGING_MODE:
print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv))) print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv)))
if nbytes == bytes_per_frame: if nbytes == bytes_per_frame:
rx_total_frames = rx_total_frames + 1 rx_total_frames = rx_total_frames + 1
rx_frames = rx_frames + 1 rx_frames = rx_frames + 1
@ -115,84 +119,111 @@ def receive():
if rx_frames == N_FRAMES_PER_BURST: if rx_frames == N_FRAMES_PER_BURST:
rx_frames = 0 rx_frames = 0
rx_bursts = rx_bursts + 1 rx_bursts = rx_bursts + 1
c_lib.freedv_set_sync(freedv,0) c_lib.freedv_set_sync(freedv, 0)
burst = bytes_out[0] burst = bytes_out[0]
n_total_burst = bytes_out[1] n_total_burst = bytes_out[1]
frame = bytes_out[2] frame = bytes_out[2]
n_total_frame = bytes_out[3] n_total_frame = bytes_out[3]
print(
print("RX | PONG | BURST [" + str(burst) + "/" + str(n_total_burst) + "] FRAME [" + str(frame) + "/" + str(n_total_frame) + "]") "RX | PONG | BURST ["
+ str(burst)
+ "/"
+ str(n_total_burst)
+ "] FRAME ["
+ str(frame)
+ "/"
+ str(n_total_frame)
+ "]"
)
print("-----------------------------------------------------------------") print("-----------------------------------------------------------------")
c_lib.freedv_set_sync(freedv,0) c_lib.freedv_set_sync(freedv, 0)
if rx_bursts == N_BURSTS: if rx_bursts == N_BURSTS:
receive = False receive = False
RECEIVE = threading.Thread(target=receive, name="RECEIVE THREAD") RECEIVE = threading.Thread(target=receive, name="RECEIVE THREAD")
RECEIVE.start() RECEIVE.start()
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
freedv = c_lib.freedv_open(FREEDV_TX_MODE) freedv = c_lib.freedv_open(FREEDV_TX_MODE)
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv) / 8)
payload_per_frame = bytes_per_frame -2 payload_per_frame = bytes_per_frame - 2
n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) 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 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 = ctypes.c_short * n_tx_modem_samples
mod_out = mod_out() 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 = ctypes.c_short * (
1760 * 2
) # 1760 for mode 10,11,12 #4000 for mode 9
mod_out_preamble = mod_out_preamble() mod_out_preamble = mod_out_preamble()
print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST) ) print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST))
print("-----------------------------------------------------------------") print("-----------------------------------------------------------------")
for i in range(0,N_BURSTS): for i in range(N_BURSTS):
c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble); c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble)
txbuffer = bytearray() txbuffer = bytearray()
txbuffer += bytes(mod_out_preamble) txbuffer += bytes(mod_out_preamble)
for n in range(0,N_FRAMES_PER_BURST): for n in range(N_FRAMES_PER_BURST):
data_out = bytearray() data_out = bytearray()
data_out += bytes([i+1]) data_out += bytes([i + 1])
data_out += bytes([N_BURSTS]) data_out += bytes([N_BURSTS])
data_out += bytes([n+1]) data_out += bytes([n + 1])
data_out += bytes([N_FRAMES_PER_BURST]) data_out += bytes([N_FRAMES_PER_BURST])
buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) buffer = bytearray(
buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send 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
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) 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 safe it into mod_out pointer
txbuffer += bytes(mod_out) txbuffer += bytes(mod_out)
print("TX | PING | BURST [" + str(i+1) + "/" + str(N_BURSTS) + "] FRAME [" + str(n+1) + "/" + str(N_FRAMES_PER_BURST) + "]") print(
"TX | PING | BURST ["
+ str(i + 1)
+ "/"
+ str(N_BURSTS)
+ "] FRAME ["
+ str(n + 1)
+ "/"
+ str(N_FRAMES_PER_BURST)
+ "]"
)
stream_tx.write(bytes(txbuffer)) stream_tx.write(bytes(txbuffer))
ACK_TIMEOUT = time.time() + 3 ACK_TIMEOUT = time.time() + 3
txbuffer = bytearray() txbuffer = bytearray()
#time.sleep(DELAY_BETWEEN_BURSTS) # time.sleep(DELAY_BETWEEN_BURSTS)
# WAIT UNTIL WE RECEIVD AN ACK/DATAC0 FRAME # WAIT UNTIL WE RECEIVD AN ACK/DATAC0 FRAME
while ACK_TIMEOUT >= time.time(): while ACK_TIMEOUT >= time.time():
time.sleep(0.01) time.sleep(0.01)
time.sleep(1) time.sleep(1)
stream_tx.close() stream_tx.close()

View file

@ -17,17 +17,17 @@ import threading
import sys import sys
import argparse import argparse
#--------------------------------------------GET PARAMETER INPUTS #--------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='Simons TEST TNC') 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=0, type=int)
parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", 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('--txmode', dest="FREEDV_TX_MODE", default=0, type=int)
parser.add_argument('--rxmode', dest="FREEDV_RX_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('--audioinput', dest="AUDIO_INPUT", default=0, type=int)
parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", 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") parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
args = parser.parse_args() args, _ = parser.parse_known_args()
N_BURSTS = args.N_BURSTS N_BURSTS = args.N_BURSTS
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
@ -41,20 +41,20 @@ FREEDV_RX_MODE = args.FREEDV_RX_MODE
DEBUGGING_MODE = args.DEBUGGING_MODE DEBUGGING_MODE = args.DEBUGGING_MODE
# 1024 good for mode 6 # 1024 good for mode 6
AUDIO_FRAMES_PER_BUFFER = 2048 AUDIO_FRAMES_PER_BUFFER = 2048
MODEM_SAMPLE_RATE = 8000 MODEM_SAMPLE_RATE = 8000
#-------------------------------------------- LOAD FREEDV #-------------------------------------------- LOAD FREEDV
libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so" libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so"
c_lib = ctypes.CDLL(libname) c_lib = ctypes.CDLL(libname)
#--------------------------------------------CREATE PYAUDIO INSTANCE #--------------------------------------------CREATE PYAUDIO INSTANCE
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
#--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE #--------------------------------------------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_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_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate'])
AUDIO_SAMPLE_RATE_TX = 8000 AUDIO_SAMPLE_RATE_TX = 8000
AUDIO_SAMPLE_RATE_RX = 8000 AUDIO_SAMPLE_RATE_RX = 8000
#--------------------------------------------OPEN AUDIO CHANNEL RX #--------------------------------------------OPEN AUDIO CHANNEL RX
stream_tx = p.open(format=pyaudio.paInt16, stream_tx = p.open(format=pyaudio.paInt16,
@ -62,22 +62,22 @@ stream_tx = p.open(format=pyaudio.paInt16,
rate=AUDIO_SAMPLE_RATE_TX, rate=AUDIO_SAMPLE_RATE_TX,
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples
output=True, output=True,
output_device_index=AUDIO_OUTPUT_DEVICE, output_device_index=AUDIO_OUTPUT_DEVICE,
) )
stream_rx = p.open(format=pyaudio.paInt16, stream_rx = p.open(format=pyaudio.paInt16,
channels=1, channels=1,
rate=AUDIO_SAMPLE_RATE_RX, rate=AUDIO_SAMPLE_RATE_RX,
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, frames_per_buffer=AUDIO_FRAMES_PER_BUFFER,
input=True, input=True,
input_device_index=AUDIO_INPUT_DEVICE, input_device_index=AUDIO_INPUT_DEVICE,
) )
# GENERAL PARAMETERS # GENERAL PARAMETERS
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
def send_pong(burst,n_total_burst,frame,n_total_frame): def send_pong(burst,n_total_burst,frame,n_total_frame):
data_out = bytearray() data_out = bytearray()
@ -85,47 +85,47 @@ def send_pong(burst,n_total_burst,frame,n_total_frame):
data_out[1:2] = bytes([n_total_burst]) data_out[1:2] = bytes([n_total_burst])
data_out[2:3] = bytes([frame]) data_out[2:3] = bytes([frame])
data_out[4:5] = bytes([n_total_frame]) data_out[4:5] = bytes([n_total_frame])
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
freedv = c_lib.freedv_open(FREEDV_TX_MODE) freedv = c_lib.freedv_open(FREEDV_TX_MODE)
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8)
payload_per_frame = bytes_per_frame -2 payload_per_frame = bytes_per_frame -2
n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) 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 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 = ctypes.c_short * n_tx_modem_samples
mod_out = mod_out() 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 = ctypes.c_short * (1760*2) #1760 for mode 10,11,12 #4000 for mode 9
mod_out_preamble = mod_out_preamble() mod_out_preamble = mod_out_preamble()
buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) 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 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_per_frame)) # generate CRC16
crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string
buffer += crc # append crc16 to buffer buffer += crc # append crc16 to buffer
c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble); c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble);
txbuffer = bytearray() txbuffer = bytearray()
txbuffer += bytes(mod_out_preamble) txbuffer += bytes(mod_out_preamble)
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(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 c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer
txbuffer += bytes(mod_out) txbuffer += bytes(mod_out)
stream_tx.write(bytes(txbuffer)) stream_tx.write(bytes(txbuffer))
txbuffer = bytearray() txbuffer = bytearray()
# DATA CHANNEL INITIALISATION # DATA CHANNEL INITIALISATION
freedv = c_lib.freedv_open(FREEDV_RX_MODE) freedv = c_lib.freedv_open(FREEDV_RX_MODE)
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) 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) 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 = (ctypes.c_ubyte * bytes_per_frame) #bytes_per_frame
bytes_out = bytes_out() #get pointer from bytes_out bytes_out = bytes_out() #get pointer from bytes_out
@ -136,31 +136,31 @@ while receive == True:
time.sleep(0.01) time.sleep(0.01)
data_in = b'' data_in = b''
nin = c_lib.freedv_nin(freedv) nin = c_lib.freedv_nin(freedv)
nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE))
if DEBUGGING_MODE == True: if DEBUGGING_MODE == True:
print("-----------------------------") print("-----------------------------")
print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]") print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]")
data_in = stream_rx.read(nin_converted, exception_on_overflow = False) data_in = stream_rx.read(nin_converted, exception_on_overflow = False)
data_in = data_in.rstrip(b'\x00') 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 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 nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio
if DEBUGGING_MODE == True: if DEBUGGING_MODE == True:
print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv))) print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv)))
if nbytes == bytes_per_frame: if nbytes == bytes_per_frame:
burst = bytes_out[0] burst = bytes_out[0]
n_total_burst = bytes_out[1] n_total_burst = bytes_out[1]
frame = bytes_out[2] frame = bytes_out[2]
n_total_frame = bytes_out[3] n_total_frame = bytes_out[3]
print("RX | BURST [" + str(burst) + "/" + str(n_total_burst) + "] FRAME [" + str(frame) + "/" + str(n_total_frame) + "] >>> SENDING PONG") 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 = threading.Thread(target=send_pong, args=[burst,n_total_burst,frame,n_total_frame], name="SEND PONG")
TRANSMIT_PONG.start() TRANSMIT_PONG.start()
c_lib.freedv_set_sync(freedv,0) c_lib.freedv_set_sync(freedv,0)

View file

@ -1,100 +0,0 @@
#!/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
#
# 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 *
import pathlib
import argparse
import sys
sys.path.insert(0,'..')
from tnc 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 = 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
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
# 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()
for f in range(FRAMES):
sine_in8k = (AMP*np.cos(2*np.pi*np.arange(t,t+N8)*FTEST8/FS8)).astype(np.int16)
t += N8
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)
sine_in48k = (sine_out48k + (AMP/2)*np.cos(2*np.pi*np.arange(t1,t1+N48)*FINTER48/FS48)).astype(np.int16)
t1 += N48
sine_out8k = resampler.resample48_to_8(sine_in48k)
sine_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")

View file

@ -7,36 +7,64 @@ Created on Wed Dec 23 07:04:24 2020
""" """
import sys import sys
sys.path.insert(0,'..')
sys.path.insert(0,'../tnc')
import data_handler
import argparse
import codec2 import codec2
import data_handler
import modem import modem
import pytest
parser = argparse.ArgumentParser(description='ARQ TEST') import static
parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3'])
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
args = parser.parse_args()
bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}' bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}'
# start data handler
data_handler.DATA()
# start modem
modem = modem.RF()
mode = codec2.freedv_get_mode(args.FREEDV_MODE)
print(mode)
n_frames_per_burst = args.N_FRAMES_PER_BURST
# enable testmode @pytest.mark.parametrize("freedv_mode", ["datac0", "datac1", "datac3"])
data_handler.TESTMODE = True @pytest.mark.parametrize("n_frames_per_burst", [1, 2, 3])
def test_highsnr_arq_short(freedv_mode: str, n_frames_per_burst: int):
t_mode = t_repeats = t_repeat_delay = 0
t_frames = []
# add command to data qeue def t_tx_dummy(mode, repeats, repeat_delay, frames):
data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_FILE', bytes_out, mode, n_frames_per_burst]) """Replacement function for transmit"""
print(f"t_tx_dummy: In transmit({mode}, {repeats}, {repeat_delay}, {frames})")
nonlocal t_mode, t_repeats, t_repeat_delay, t_frames
t_mode = mode
t_repeats = repeats
t_repeat_delay = repeat_delay
t_frames = frames[:]
static.TRANSMITTING = False
# Enable testmode
modem.TESTMODE = True
# Set some inner variables so the modules don't throw exceptions.
modem.RXCHANNEL = "/tmp/rxpipe"
modem.TXCHANNEL = "/tmp/txpipe"
data_handler.TESTMODE = True
static.HAMLIB_RADIOCONTROL = "disabled"
# start data handler
data_handler.DATA()
# start modem
t_modem = modem.RF()
# Replace transmit routine with our own, an effective No-Op.
t_modem.transmit = t_tx_dummy
mode = codec2.freedv_get_mode_value_by_name(freedv_mode)
print(mode)
# add command to data qeue
data_handler.DATA_QUEUE_TRANSMIT.put(
["ARQ_FILE", bytes_out, mode, n_frames_per_burst]
)
# This test isn't complete yet, or is obsolete.
assert False
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

View file

@ -3,57 +3,86 @@
# #
# tests audio buffer thread safety # tests audio buffer thread safety
# pylint: disable=global-statement, invalid-name
import sys import sys
sys.path.insert(0,'..')
from tnc import codec2
import threading import threading
import numpy as np
from time import sleep from time import sleep
import codec2
import numpy as np
import pytest
BUFFER_SZ = 1024 BUFFER_SZ = 1024
N_MAX = 100 # write a repeating sequence of 0..N_MAX-1 N_MAX = 100 # write a repeating sequence of 0..N_MAX-1
WRITE_SZ = 10 # different read and write sized buffers WRITE_SZ = 10 # different read and write sized buffers
READ_SZ = 8 READ_SZ = 8
NTESTS = 10000 NTESTS = 10000
running = True running = True
audio_buffer = codec2.audio_buffer(BUFFER_SZ) audio_buffer = codec2.audio_buffer(BUFFER_SZ)
n_write = int(0) n_write = 0
n_read = int(0)
def writer():
def t_writer():
"""
Subprocess to handle writes to the NumPY audio "device."
"""
global n_write global n_write
print("writer starting") print("writer starting")
n = int(0) n = 0
buf = np.zeros(WRITE_SZ, dtype=np.int16) buf = np.zeros(WRITE_SZ, dtype=np.int16)
while running: while running:
nfree = audio_buffer.size - audio_buffer.nbuffer nfree = audio_buffer.size - audio_buffer.nbuffer
if nfree >= WRITE_SZ: if nfree >= WRITE_SZ:
for i in range(0,WRITE_SZ): for index in range(WRITE_SZ):
buf[i] = n; buf[index] = n
n += 1 n += 1
if n == N_MAX: if n == N_MAX:
n = 0 n = 0
n_write += 1 n_write += 1
audio_buffer.push(buf) audio_buffer.push(buf)
x = threading.Thread(target=writer)
x.start()
n_out = int(0)
errors = int(0)
for tests in range(1,NTESTS):
while audio_buffer.nbuffer < READ_SZ:
sleep(0.001)
for i in range(0,READ_SZ):
if audio_buffer.buffer[i] != n_out:
errors += 1
n_out += 1
if n_out == N_MAX:
n_out = 0
n_read += 1
audio_buffer.pop(READ_SZ)
running = False def test_audiobuffer():
print("n_write: %d n_read: %d errors: %d " % (n_write, n_read, errors)) """
Test for the audiobuffer
"""
global running
# Start the writer in a new thread.
writer_thread = threading.Thread(target=t_writer)
writer_thread.start()
n_out = n_read = errors = 0
for _ in range(NTESTS):
while audio_buffer.nbuffer < READ_SZ:
sleep(0.001)
for i in range(READ_SZ):
if audio_buffer.buffer[i] != n_out:
errors += 1
n_out += 1
if n_out == N_MAX:
n_out = 0
n_read += 1
audio_buffer.pop(READ_SZ)
print(f"n_write: {n_write} n_read: {n_read} errors: {errors} ")
# Indirectly stop the thread
running = False
sleep(0.1)
assert not writer_thread.is_alive()
assert n_write - n_read < BUFFER_SZ
assert errors == 0
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

View file

@ -6,41 +6,54 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS @author: DJ2LS
""" """
import ctypes
from ctypes import *
import pathlib
import pyaudio
import sys
import logging
import time
import threading
import sys
import argparse import argparse
import ctypes
import sys
import threading
import time
import numpy as np import numpy as np
sys.path.insert(0,'..') import pyaudio
sys.path.insert(0, "..")
from tnc import codec2 from tnc import codec2
#--------------------------------------------GET PARAMETER INPUTS # --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='FreeDATA audio test') parser = argparse.ArgumentParser(description="FreeDATA audio test")
parser.add_argument('--bursts', dest="N_BURSTS", default=1, 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("--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(
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") "--audiodev",
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") dest="AUDIO_INPUT_DEVICE",
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends") 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_known_args()
args = parser.parse_args()
if args.LIST: if args.LIST:
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
for dev in range(0,p.get_device_count()): for dev in range(p.get_device_count()):
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
quit() sys.exit()
class Test:
class Test():
def __init__(self): def __init__(self):
self.N_BURSTS = args.N_BURSTS self.N_BURSTS = args.N_BURSTS
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
@ -49,65 +62,89 @@ class Test():
self.TIMEOUT = args.TIMEOUT self.TIMEOUT = args.TIMEOUT
# AUDIO PARAMETERS # AUDIO PARAMETERS
self.AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0 # v-- consider increasing if you get nread_exceptions > 0
self.AUDIO_FRAMES_PER_BUFFER = 2400 * 2
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
self.AUDIO_SAMPLE_RATE_RX = 48000 self.AUDIO_SAMPLE_RATE_RX = 48000
# make sure our resampler will work # make sure our resampler will work
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 assert (
self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE
) == codec2.api.FDMDV_OS_48
# check if we want to use an audio device then do an pyaudio init # check if we want to use an audio device then do an pyaudio init
if self.AUDIO_INPUT_DEVICE != -1: if self.AUDIO_INPUT_DEVICE != -1:
self.p = pyaudio.PyAudio() self.p = pyaudio.PyAudio()
# auto search for loopback devices # auto search for loopback devices
if self.AUDIO_INPUT_DEVICE == -2: if self.AUDIO_INPUT_DEVICE == -2:
loopback_list = [] loopback_list = []
for dev in range(0,self.p.get_device_count()): for dev in range(self.p.get_device_count()):
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]: if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev) loopback_list.append(dev)
if len(loopback_list) >= 2: if len(loopback_list) >= 2:
self.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX # 0 = RX 1 = TX
self.AUDIO_INPUT_DEVICE = loopback_list[0]
print(f"loopback_list rx: {loopback_list}", file=sys.stderr) print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
else: else:
quit() sys.exit()
print(f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", file=sys.stderr)
self.stream_rx = self.p.open(format=pyaudio.paInt16,
channels=1,
rate=self.AUDIO_SAMPLE_RATE_RX,
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
input=True,
output=False,
input_device_index=self.AUDIO_INPUT_DEVICE,
stream_callback=self.callback
)
print(
f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}",
file=sys.stderr,
)
self.stream_rx = self.p.open(
format=pyaudio.paInt16,
channels=1,
rate=self.AUDIO_SAMPLE_RATE_RX,
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
input=True,
output=False,
input_device_index=self.AUDIO_INPUT_DEVICE,
stream_callback=self.callback,
)
else:
print("test_callback_multimode_rx: Not written for STDIN usage.")
print("Exiting.")
sys.exit()
# open codec2 instance # open codec2 instance
self.datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p) self.datac0_freedv = ctypes.cast(
self.datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv)/8) codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p
self.datac0_bytes_out = create_string_buffer(self.datac0_bytes_per_frame) )
codec2.api.freedv_set_frames_per_burst(self.datac0_freedv,self.N_FRAMES_PER_BURST) self.datac0_bytes_per_frame = int(
self.datac0_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER) codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8
)
self.datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p) self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame)
self.datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv)/8) codec2.api.freedv_set_frames_per_burst(
self.datac1_bytes_out = create_string_buffer(self.datac1_bytes_per_frame) self.datac0_freedv, self.N_FRAMES_PER_BURST
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv,self.N_FRAMES_PER_BURST) )
self.datac1_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER) self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
self.datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p)
self.datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv)/8)
self.datac3_bytes_out = create_string_buffer(self.datac3_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv,self.N_FRAMES_PER_BURST)
self.datac3_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER)
self.datac1_freedv = ctypes.cast(
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p
)
self.datac1_bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8
)
self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(
self.datac1_freedv, self.N_FRAMES_PER_BURST
)
self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
self.datac3_freedv = ctypes.cast(
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p
)
self.datac3_bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8
)
self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(
self.datac3_freedv, self.N_FRAMES_PER_BURST
)
self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
# SET COUNTERS # SET COUNTERS
self.rx_total_frames_datac0 = 0 self.rx_total_frames_datac0 = 0
@ -127,36 +164,38 @@ class Test():
self.timeout = time.time() + self.TIMEOUT self.timeout = time.time() + self.TIMEOUT
self.receive = True self.receive = True
self.resampler = codec2.resampler() self.resampler = codec2.resampler()
# Copy received 48 kHz to a file. Listen to this file with: # Copy received 48 kHz to a file. Listen to this file with:
# aplay -r 48000 -f S16_LE rx48_callback.raw # aplay -r 48000 -f S16_LE rx48_callback.raw
# Corruption of this file is a good way to detect audio card issues # Corruption of this file is a good way to detect audio card issues
self.frx = open("rx48_callback_multimode.raw", mode='wb') self.frx = open("rx48_callback_multimode.raw", mode="wb")
# initial nin values
# initial nin values self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
self.LOGGER_THREAD = threading.Thread(
self.LOGGER_THREAD = threading.Thread(target=self.print_stats, name="LOGGER_THREAD") target=self.print_stats, name="LOGGER_THREAD"
)
self.LOGGER_THREAD.start() self.LOGGER_THREAD.start()
def callback(self, data_in48k, frame_count, time_info, status): def callback(self, data_in48k, frame_count, time_info, status):
x = np.frombuffer(data_in48k, dtype=np.int16) x = np.frombuffer(data_in48k, dtype=np.int16)
x.tofile(self.frx) x.tofile(self.frx)
x = self.resampler.resample48_to_8(x) x = self.resampler.resample48_to_8(x)
self.datac0_buffer.push(x) self.datac0_buffer.push(x)
self.datac1_buffer.push(x) self.datac1_buffer.push(x)
self.datac3_buffer.push(x) self.datac3_buffer.push(x)
while self.datac0_buffer.nbuffer >= self.datac0_nin:
while self.datac0_buffer.nbuffer >= self.datac0_nin:
# demodulate audio # demodulate audio
nbytes = codec2.api.freedv_rawdatarx(self.datac0_freedv, self.datac0_bytes_out, self.datac0_buffer.buffer.ctypes) nbytes = codec2.api.freedv_rawdatarx(
self.datac0_freedv,
self.datac0_bytes_out,
self.datac0_buffer.buffer.ctypes,
)
self.datac0_buffer.pop(self.datac0_nin) self.datac0_buffer.pop(self.datac0_nin)
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
if nbytes == self.datac0_bytes_per_frame: if nbytes == self.datac0_bytes_per_frame:
@ -167,10 +206,13 @@ class Test():
self.rx_frames_datac0 = 0 self.rx_frames_datac0 = 0
self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1 self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1
while self.datac1_buffer.nbuffer >= self.datac1_nin: while self.datac1_buffer.nbuffer >= self.datac1_nin:
# demodulate audio # demodulate audio
nbytes = codec2.api.freedv_rawdatarx(self.datac1_freedv, self.datac1_bytes_out, self.datac1_buffer.buffer.ctypes) nbytes = codec2.api.freedv_rawdatarx(
self.datac1_freedv,
self.datac1_bytes_out,
self.datac1_buffer.buffer.ctypes,
)
self.datac1_buffer.pop(self.datac1_nin) self.datac1_buffer.pop(self.datac1_nin)
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv) self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
if nbytes == self.datac1_bytes_per_frame: if nbytes == self.datac1_bytes_per_frame:
@ -181,10 +223,13 @@ class Test():
self.rx_frames_datac1 = 0 self.rx_frames_datac1 = 0
self.rx_bursts_datac1 = self.rx_bursts_datac1 + 1 self.rx_bursts_datac1 = self.rx_bursts_datac1 + 1
while self.datac3_buffer.nbuffer >= self.datac3_nin: while self.datac3_buffer.nbuffer >= self.datac3_nin:
# demodulate audio # demodulate audio
nbytes = codec2.api.freedv_rawdatarx(self.datac3_freedv, self.datac3_bytes_out, self.datac3_buffer.buffer.ctypes) nbytes = codec2.api.freedv_rawdatarx(
self.datac3_freedv,
self.datac3_bytes_out,
self.datac3_buffer.buffer.ctypes,
)
self.datac3_buffer.pop(self.datac3_nin) self.datac3_buffer.pop(self.datac3_nin)
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
if nbytes == self.datac3_bytes_per_frame: if nbytes == self.datac3_bytes_per_frame:
@ -193,39 +238,59 @@ class Test():
if self.rx_frames_datac3 == self.N_FRAMES_PER_BURST: if self.rx_frames_datac3 == self.N_FRAMES_PER_BURST:
self.rx_frames_datac3 = 0 self.rx_frames_datac3 = 0
self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1 self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1
if (
if (self.rx_bursts_datac0 and self.rx_bursts_datac1 and self.rx_bursts_datac3) == self.N_BURSTS: self.rx_bursts_datac0 and self.rx_bursts_datac1 and self.rx_bursts_datac3
) == self.N_BURSTS:
self.receive = False self.receive = False
return (None, pyaudio.paContinue) return (None, pyaudio.paContinue)
def print_stats(self): def print_stats(self):
while self.receive: while self.receive:
time.sleep(0.01) time.sleep(0.01)
if self.DEBUGGING_MODE: if self.DEBUGGING_MODE:
self.datac0_rxstatus = codec2.api.freedv_get_rx_status(self.datac0_freedv) self.datac0_rxstatus = codec2.api.freedv_get_rx_status(
self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac0_rxstatus] self.datac0_freedv
)
self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[
self.datac0_rxstatus
]
self.datac1_rxstatus = codec2.api.freedv_get_rx_status(self.datac1_freedv) self.datac1_rxstatus = codec2.api.freedv_get_rx_status(
self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac1_rxstatus] self.datac1_freedv
)
self.datac3_rxstatus = codec2.api.freedv_get_rx_status(self.datac3_freedv) self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[
self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac3_rxstatus] self.datac1_rxstatus
]
self.datac3_rxstatus = codec2.api.freedv_get_rx_status(
self.datac3_freedv
)
self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[
self.datac3_rxstatus
]
print(
"NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s"
% (
self.datac0_nin,
self.datac0_rxstatus,
self.datac1_nin,
self.datac1_rxstatus,
self.datac3_nin,
self.datac3_rxstatus,
),
file=sys.stderr,
)
print("NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % \
(self.datac0_nin, self.datac0_rxstatus, self.datac1_nin, self.datac1_rxstatus, self.datac3_nin, self.datac3_rxstatus),
file=sys.stderr)
def run_audio(self): def run_audio(self):
try: try:
print(f"starting pyaudio callback", file=sys.stderr) print(f"starting pyaudio callback", file=sys.stderr)
self.stream_rx.start_stream() self.stream_rx.start_stream()
except Exception as e: except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr) print(f"pyAudio error: {e}", file=sys.stderr)
while self.receive and time.time() < self.timeout: while self.receive and time.time() < self.timeout:
time.sleep(1) time.sleep(1)
@ -233,18 +298,24 @@ class Test():
if time.time() >= self.timeout and self.stream_rx.is_active(): if time.time() >= self.timeout and self.stream_rx.is_active():
print("TIMEOUT REACHED") print("TIMEOUT REACHED")
self.receive = False self.receive = False
if self.nread_exceptions:
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
self.nread_exceptions, file=sys.stderr)
print(f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}", file=sys.stderr) if self.nread_exceptions:
print(
"nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..."
% self.nread_exceptions,
file=sys.stderr,
)
print(
f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}",
file=sys.stderr,
)
self.frx.close() self.frx.close()
# cloese pyaudio instance # cloese pyaudio instance
self.stream_rx.close() self.stream_rx.close()
self.p.terminate() self.p.terminate()
test = Test() test = Test()
test.run_audio() test.run_audio()

View file

@ -6,41 +6,52 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS @author: DJ2LS
""" """
import ctypes
from ctypes import *
import pathlib
import pyaudio
import sys
import logging
import time
import threading
import sys
import argparse import argparse
import ctypes
import sys
import time
import numpy as np import numpy as np
sys.path.insert(0,'..') import pyaudio
sys.path.insert(0, "..")
from tnc import codec2 from tnc import codec2
#--------------------------------------------GET PARAMETER INPUTS # --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='FreeDATA audio test') parser = argparse.ArgumentParser(description="FreeDATA audio test")
parser.add_argument('--bursts', dest="N_BURSTS", default=1, 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("--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(
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") "--audiodev",
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") dest="AUDIO_INPUT_DEVICE",
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends") 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_known_args()
args = parser.parse_args()
if args.LIST: if args.LIST:
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
for dev in range(0,p.get_device_count()): for dev in range(p.get_device_count()):
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
quit()
class Test:
class Test():
def __init__(self): def __init__(self):
self.N_BURSTS = args.N_BURSTS self.N_BURSTS = args.N_BURSTS
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
@ -49,63 +60,91 @@ class Test():
self.TIMEOUT = args.TIMEOUT self.TIMEOUT = args.TIMEOUT
# AUDIO PARAMETERS # AUDIO PARAMETERS
self.AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0 self.AUDIO_FRAMES_PER_BUFFER = (
2400 * 2
) # <- consider increasing if you get nread_exceptions > 0
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
self.AUDIO_SAMPLE_RATE_RX = 48000 self.AUDIO_SAMPLE_RATE_RX = 48000
# make sure our resampler will work # make sure our resampler will work
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 assert (
self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE
) == codec2.api.FDMDV_OS_48
# check if we want to use an audio device then do an pyaudio init # check if we want to use an audio device then do an pyaudio init
if self.AUDIO_INPUT_DEVICE != -1: if self.AUDIO_INPUT_DEVICE != -1:
self.p = pyaudio.PyAudio() self.p = pyaudio.PyAudio()
# auto search for loopback devices # auto search for loopback devices
if self.AUDIO_INPUT_DEVICE == -2: if self.AUDIO_INPUT_DEVICE == -2:
loopback_list = [] loopback_list = [
for dev in range(0,self.p.get_device_count()): dev
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]: for dev in range(self.p.get_device_count())
loopback_list.append(dev) if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]
]
if len(loopback_list) >= 2: if len(loopback_list) >= 2:
self.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr) print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
else: else:
quit() sys.exit()
print(f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", file=sys.stderr)
self.stream_rx = self.p.open(format=pyaudio.paInt16,
channels=1,
rate=self.AUDIO_SAMPLE_RATE_RX,
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
input=True,
output=False,
input_device_index=self.AUDIO_INPUT_DEVICE,
stream_callback=self.callback
)
print(
f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}",
file=sys.stderr,
)
self.stream_rx = self.p.open(
format=pyaudio.paInt16,
channels=1,
rate=self.AUDIO_SAMPLE_RATE_RX,
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
input=True,
output=False,
input_device_index=self.AUDIO_INPUT_DEVICE,
stream_callback=self.callback,
)
else:
print("test_callback_multimode_rx_outside: Not written for STDIN usage.")
print("Exiting.")
sys.exit()
# open codec2 instance # open codec2 instance
self.datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p) self.datac0_freedv = ctypes.cast(
self.datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv)/8) codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p
self.datac0_bytes_out = create_string_buffer(self.datac0_bytes_per_frame) )
codec2.api.freedv_set_frames_per_burst(self.datac0_freedv,self.N_FRAMES_PER_BURST) self.datac0_bytes_per_frame = int(
self.datac0_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER) codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8
)
self.datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p) self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame)
self.datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv)/8) codec2.api.freedv_set_frames_per_burst(
self.datac1_bytes_out = create_string_buffer(self.datac1_bytes_per_frame) self.datac0_freedv, self.N_FRAMES_PER_BURST
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv,self.N_FRAMES_PER_BURST) )
self.datac1_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER) self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
self.datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p)
self.datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv)/8)
self.datac3_bytes_out = create_string_buffer(self.datac3_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv,self.N_FRAMES_PER_BURST)
self.datac3_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER)
self.datac1_freedv = ctypes.cast(
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p
)
self.datac1_bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8
)
self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(
self.datac1_freedv, self.N_FRAMES_PER_BURST
)
self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
self.datac3_freedv = ctypes.cast(
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p
)
self.datac3_bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8
)
self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(
self.datac3_freedv, self.N_FRAMES_PER_BURST
)
self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
# SET COUNTERS # SET COUNTERS
self.rx_total_frames_datac0 = 0 self.rx_total_frames_datac0 = 0
@ -125,62 +164,74 @@ class Test():
self.timeout = time.time() + self.TIMEOUT self.timeout = time.time() + self.TIMEOUT
self.receive = True self.receive = True
self.resampler = codec2.resampler() self.resampler = codec2.resampler()
# Copy received 48 kHz to a file. Listen to this file with: # Copy received 48 kHz to a file. Listen to this file with:
# aplay -r 48000 -f S16_LE rx48_callback.raw # aplay -r 48000 -f S16_LE rx48_callback.raw
# Corruption of this file is a good way to detect audio card issues # Corruption of this file is a good way to detect audio card issues
self.frx = open("rx48_callback_multimode.raw", mode='wb') self.frx = open("rx48_callback_multimode.raw", mode="wb")
# initial nin values
# initial nin values self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
def callback(self, data_in48k, frame_count, time_info, status): def callback(self, data_in48k, frame_count, time_info, status):
x = np.frombuffer(data_in48k, dtype=np.int16) x = np.frombuffer(data_in48k, dtype=np.int16)
x.tofile(self.frx) x.tofile(self.frx)
x = self.resampler.resample48_to_8(x) x = self.resampler.resample48_to_8(x)
self.datac0_buffer.push(x) self.datac0_buffer.push(x)
self.datac1_buffer.push(x) self.datac1_buffer.push(x)
self.datac3_buffer.push(x) self.datac3_buffer.push(x)
return (None, pyaudio.paContinue) return (None, pyaudio.paContinue)
def print_stats(self): def print_stats(self):
if self.DEBUGGING_MODE: if self.DEBUGGING_MODE:
self.datac0_rxstatus = codec2.api.freedv_get_rx_status(self.datac0_freedv) self.datac0_rxstatus = codec2.api.freedv_get_rx_status(self.datac0_freedv)
self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac0_rxstatus] self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[
self.datac0_rxstatus
]
self.datac1_rxstatus = codec2.api.freedv_get_rx_status(self.datac1_freedv) self.datac1_rxstatus = codec2.api.freedv_get_rx_status(self.datac1_freedv)
self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac1_rxstatus] self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[
self.datac1_rxstatus
self.datac3_rxstatus = codec2.api.freedv_get_rx_status(self.datac3_freedv) ]
self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac3_rxstatus]
self.datac3_rxstatus = codec2.api.freedv_get_rx_status(self.datac3_freedv)
self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[
self.datac3_rxstatus
]
print(
"NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s"
% (
self.datac0_nin,
self.datac0_rxstatus,
self.datac1_nin,
self.datac1_rxstatus,
self.datac3_nin,
self.datac3_rxstatus,
),
file=sys.stderr,
)
print("NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % \
(self.datac0_nin, self.datac0_rxstatus, self.datac1_nin, self.datac1_rxstatus, self.datac3_nin, self.datac3_rxstatus),
file=sys.stderr)
def run_audio(self): def run_audio(self):
try: try:
print(f"starting pyaudio callback", file=sys.stderr) print(f"starting pyaudio callback", file=sys.stderr)
self.stream_rx.start_stream() self.stream_rx.start_stream()
except Exception as e: except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr) print(f"pyAudio error: {e}", file=sys.stderr)
while self.receive and time.time() < self.timeout: while self.receive and time.time() < self.timeout:
while self.datac0_buffer.nbuffer >= self.datac0_nin: while self.datac0_buffer.nbuffer >= self.datac0_nin:
# demodulate audio # demodulate audio
nbytes = codec2.api.freedv_rawdatarx(self.datac0_freedv, self.datac0_bytes_out, self.datac0_buffer.buffer.ctypes) nbytes = codec2.api.freedv_rawdatarx(
self.datac0_freedv,
self.datac0_bytes_out,
self.datac0_buffer.buffer.ctypes,
)
self.datac0_buffer.pop(self.datac0_nin) self.datac0_buffer.pop(self.datac0_nin)
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
if nbytes == self.datac0_bytes_per_frame: if nbytes == self.datac0_bytes_per_frame:
@ -192,10 +243,13 @@ class Test():
self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1 self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1
self.print_stats() self.print_stats()
while self.datac1_buffer.nbuffer >= self.datac1_nin: while self.datac1_buffer.nbuffer >= self.datac1_nin:
# demodulate audio # demodulate audio
nbytes = codec2.api.freedv_rawdatarx(self.datac1_freedv, self.datac1_bytes_out, self.datac1_buffer.buffer.ctypes) nbytes = codec2.api.freedv_rawdatarx(
self.datac1_freedv,
self.datac1_bytes_out,
self.datac1_buffer.buffer.ctypes,
)
self.datac1_buffer.pop(self.datac1_nin) self.datac1_buffer.pop(self.datac1_nin)
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv) self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
if nbytes == self.datac1_bytes_per_frame: if nbytes == self.datac1_bytes_per_frame:
@ -206,10 +260,14 @@ class Test():
self.rx_frames_datac1 = 0 self.rx_frames_datac1 = 0
self.rx_bursts_datac1 = self.rx_bursts_datac1 + 1 self.rx_bursts_datac1 = self.rx_bursts_datac1 + 1
self.print_stats() self.print_stats()
while self.datac3_buffer.nbuffer >= self.datac3_nin: while self.datac3_buffer.nbuffer >= self.datac3_nin:
# demodulate audio # demodulate audio
nbytes = codec2.api.freedv_rawdatarx(self.datac3_freedv, self.datac3_bytes_out, self.datac3_buffer.buffer.ctypes) nbytes = codec2.api.freedv_rawdatarx(
self.datac3_freedv,
self.datac3_bytes_out,
self.datac3_buffer.buffer.ctypes,
)
self.datac3_buffer.pop(self.datac3_nin) self.datac3_buffer.pop(self.datac3_nin)
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
if nbytes == self.datac3_bytes_per_frame: if nbytes == self.datac3_bytes_per_frame:
@ -218,27 +276,36 @@ class Test():
if self.rx_frames_datac3 == self.N_FRAMES_PER_BURST: if self.rx_frames_datac3 == self.N_FRAMES_PER_BURST:
self.rx_frames_datac3 = 0 self.rx_frames_datac3 = 0
self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1 self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1
self.print_stats() self.print_stats()
if (self.rx_bursts_datac0 and self.rx_bursts_datac1 and self.rx_bursts_datac3) == self.N_BURSTS: if (
self.receive = False self.rx_bursts_datac0
and self.rx_bursts_datac1
and self.rx_bursts_datac3
) == self.N_BURSTS:
self.receive = False
if time.time() >= self.timeout: if time.time() >= self.timeout:
print("TIMEOUT REACHED") print("TIMEOUT REACHED")
if self.nread_exceptions:
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
self.nread_exceptions, file=sys.stderr)
print(f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}", file=sys.stderr) if self.nread_exceptions:
print(
"nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..."
% self.nread_exceptions,
file=sys.stderr,
)
print(
f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}",
file=sys.stderr,
)
self.frx.close() self.frx.close()
# cloese pyaudio instance # cloese pyaudio instance
self.stream_rx.close() self.stream_rx.close()
self.p.terminate() self.p.terminate()
test = Test() test = Test()
test.run_audio() test.run_audio()

View file

@ -6,182 +6,227 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS @author: DJ2LS
""" """
import ctypes
from ctypes import *
import pathlib
import pyaudio
import sys
import logging
import time
import threading
import sys
import argparse import argparse
import ctypes
import queue import queue
import sys
import time
import numpy as np import numpy as np
sys.path.insert(0,'..') import pyaudio
sys.path.insert(0, "..")
from tnc import codec2 from tnc import codec2
#--------------------------------------------GET PARAMETER INPUTS # --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='FreeDATA audio test') parser = argparse.ArgumentParser(description="FreeDATA audio test")
parser.add_argument('--bursts', dest="N_BURSTS", default=1, 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("--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("--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(
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") "--audiodev",
parser.add_argument('--testframes', dest="TESTFRAMES", action="store_true", default=False, help="generate testframes") 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",
)
parser.add_argument(
"--testframes",
dest="TESTFRAMES",
action="store_true",
default=False,
help="generate testframes",
)
args = parser.parse_args() args, _ = parser.parse_known_args()
if args.LIST: if args.LIST:
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
for dev in range(0,p.get_device_count()): for dev in range(p.get_device_count()):
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
quit() sys.exit()
class Test:
class Test():
def __init__(self): def __init__(self):
self.dataqueue = queue.Queue() self.dataqueue = queue.Queue()
self.N_BURSTS = args.N_BURSTS self.N_BURSTS = args.N_BURSTS
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
# AUDIO PARAMETERS # AUDIO PARAMETERS
self.AUDIO_FRAMES_PER_BUFFER = 2400 # <- consider increasing if you get nread_exceptions > 0 # v-- consider increasing if you get nread_exceptions > 0
self.AUDIO_FRAMES_PER_BUFFER = 2400
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
self.AUDIO_SAMPLE_RATE_TX = 48000 self.AUDIO_SAMPLE_RATE_TX = 48000
# make sure our resampler will work # make sure our resampler will work
assert (self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 assert (
self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE
) == codec2.api.FDMDV_OS_48
self.transmit = True self.transmit = True
self.resampler = codec2.resampler() self.resampler = codec2.resampler()
# check if we want to use an audio device then do an pyaudio init # check if we want to use an audio device then do an pyaudio init
if self.AUDIO_OUTPUT_DEVICE != -1: if self.AUDIO_OUTPUT_DEVICE != -1:
self.p = pyaudio.PyAudio() self.p = pyaudio.PyAudio()
# auto search for loopback devices # auto search for loopback devices
if self.AUDIO_OUTPUT_DEVICE == -2: if self.AUDIO_OUTPUT_DEVICE == -2:
loopback_list = [] loopback_list = [
for dev in range(0,self.p.get_device_count()): dev
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]: for dev in range(self.p.get_device_count())
loopback_list.append(dev) if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]
]
if len(loopback_list) >= 2: if len(loopback_list) >= 2:
self.AUDIO_OUTPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX self.AUDIO_OUTPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr) print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
else: else:
quit() sys.exit()
print(f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} \ print(
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}", file=sys.stderr) f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} "
f"DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} "
self.stream_tx = self.p.open(format=pyaudio.paInt16, f"AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}",
channels=1, file=sys.stderr,
rate=self.AUDIO_SAMPLE_RATE_TX, )
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
input=False, self.stream_tx = self.p.open(
output=True, format=pyaudio.paInt16,
output_device_index=self.AUDIO_OUTPUT_DEVICE, channels=1,
stream_callback=self.callback rate=self.AUDIO_SAMPLE_RATE_TX,
) frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
input=False,
output=True,
output_device_index=self.AUDIO_OUTPUT_DEVICE,
stream_callback=self.callback,
)
else:
print("test_callback_multimode_tx: Not written for STDOUT usage.")
print("Exiting.")
sys.exit()
# Copy received 48 kHz to a file. Listen to this file with: # Copy received 48 kHz to a file. Listen to this file with:
# aplay -r 48000 -f S16_LE rx48_callback.raw # aplay -r 48000 -f S16_LE rx48_callback.raw
# Corruption of this file is a good way to detect audio card issues # Corruption of this file is a good way to detect audio card issues
self.ftx = open("tx48_callback.raw", mode='wb') self.ftx = open("tx48_callback.raw", mode="wb")
# data binary string # data binary string
if args.TESTFRAMES: if args.TESTFRAMES:
self.data_out = bytearray(14) self.data_out = bytearray(14)
self.data_out[:1] = bytes([255]) self.data_out[:1] = bytes([255])
self.data_out[1:2] = bytes([1]) self.data_out[1:2] = bytes([1])
self.data_out[2:] = b'HELLO WORLD' self.data_out[2:] = b"HELLO WORLD"
else: else:
self.data_out = b'HELLO WORLD!' self.data_out = b"HELLO WORLD!"
def callback(self, data_in48k, frame_count, time_info, status): def callback(self, data_in48k, frame_count, time_info, status):
data_out48k = self.dataqueue.get() data_out48k = self.dataqueue.get()
return (data_out48k, pyaudio.paContinue) return (data_out48k, pyaudio.paContinue)
def run_audio(self): def run_audio(self):
try: try:
print(f"starting pyaudio callback", file=sys.stderr) print(f"starting pyaudio callback", file=sys.stderr)
self.stream_tx.start_stream() self.stream_tx.start_stream()
except Exception as e: except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr) print(f"pyAudio error: {e}", file=sys.stderr)
sheeps = 0 sheeps = 0
while self.transmit: while self.transmit:
time.sleep(1) time.sleep(1)
sheeps = sheeps + 1 sheeps = sheeps + 1
print(f"counting sheeps...{sheeps}") print(f"counting sheeps...{sheeps}")
self.ftx.close() self.ftx.close()
# close pyaudio instance # close pyaudio instance
self.stream_tx.close() self.stream_tx.close()
self.p.terminate() self.p.terminate()
def create_modulation(self): def create_modulation(self):
modes = [codec2.api.FREEDV_MODE_DATAC0, codec2.api.FREEDV_MODE_DATAC1, codec2.api.FREEDV_MODE_DATAC3] modes = [
codec2.api.FREEDV_MODE_DATAC0,
codec2.api.FREEDV_MODE_DATAC1,
codec2.api.FREEDV_MODE_DATAC3,
]
for m in modes: 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) freedv = ctypes.cast(codec2.api.freedv_open(m), ctypes.c_void_p)
mod_out_postamble = create_string_buffer(2*n_tx_postamble_modem_samples)
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) mod_out = ctypes.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 = ctypes.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 = ctypes.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 payload_per_frame = bytes_per_frame - 2
buffer = bytearray(payload_per_frame) buffer = bytearray(payload_per_frame)
# set buffersize to length of data which will be send # set buffersize to length of data which will be send
buffer[:len(self.data_out)] = self.data_out buffer[: len(self.data_out)] = self.data_out
crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16 crc = ctypes.c_ushort(
codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame)
) # generate CRC16
# convert crc to 2 byte hex string # convert crc to 2 byte hex string
crc = crc.value.to_bytes(2, byteorder='big') crc = crc.value.to_bytes(2, byteorder="big")
buffer += crc # append crc16 to buffer buffer += crc # append crc16 to buffer
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
for i in range(1,self.N_BURSTS+1): for i in range(1, self.N_BURSTS + 1):
# write preamble to txbuffer # write preamble to txbuffer
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
txbuffer = bytes(mod_out_preamble) txbuffer = bytes(mod_out_preamble)
# create modulaton for N = FRAMESPERBURST and append it to txbuffer # create modulaton for N = FRAMESPERBURST and append it to txbuffer
for n in range(1,self.N_FRAMES_PER_BURST+1): for n in range(1, self.N_FRAMES_PER_BURST + 1):
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) 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 codec2.api.freedv_rawdatatx(
freedv, mod_out, data
) # modulate DATA and save it into mod_out pointer
txbuffer += bytes(mod_out) txbuffer += bytes(mod_out)
print(f"GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}", file=sys.stderr) print(
f"GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}",
file=sys.stderr,
)
# append postamble to txbuffer # append postamble to txbuffer
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
txbuffer += bytes(mod_out_postamble) txbuffer += bytes(mod_out_postamble)
# append a delay between bursts as audio silence # append a delay between bursts as audio silence
samples_delay = int(self.MODEM_SAMPLE_RATE*self.DELAY_BETWEEN_BURSTS) samples_delay = int(self.MODEM_SAMPLE_RATE * self.DELAY_BETWEEN_BURSTS)
mod_out_silence = create_string_buffer(samples_delay*2) mod_out_silence = ctypes.create_string_buffer(samples_delay * 2)
txbuffer += bytes(mod_out_silence) txbuffer += bytes(mod_out_silence)
# resample up to 48k (resampler works on np.int16) # resample up to 48k (resampler works on np.int16)
@ -189,20 +234,23 @@ class Test():
txbuffer_48k = self.resampler.resample8_to_48(x) txbuffer_48k = self.resampler.resample8_to_48(x)
# split modulated audio to chunks # split modulated audio to chunks
#https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python # https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python
txbuffer_48k = bytes(txbuffer_48k) txbuffer_48k = bytes(txbuffer_48k)
chunk = [txbuffer_48k[i:i+self.AUDIO_FRAMES_PER_BUFFER*2] for i in range(0, len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER*2)] chunk = [
txbuffer_48k[i : i + self.AUDIO_FRAMES_PER_BUFFER * 2]
for i in range(
0, len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER * 2
)
]
# add modulated chunks to fifo buffer # add modulated chunks to fifo buffer
for c in chunk: for c in chunk:
# if data is shorter than the expcected audio frames per buffer we need to append 0 # if data is shorter than the expcected audio frames per buffer we need to append 0
# to prevent the callback from stucking/crashing # to prevent the callback from stucking/crashing
if len(c) < self.AUDIO_FRAMES_PER_BUFFER*2: if len(c) < self.AUDIO_FRAMES_PER_BUFFER * 2:
c += bytes(self.AUDIO_FRAMES_PER_BUFFER*2 - len(c)) c += bytes(self.AUDIO_FRAMES_PER_BUFFER * 2 - len(c))
self.dataqueue.put(c) self.dataqueue.put(c)
test = Test() test = Test()
test.create_modulation() test.create_modulation()
test.run_audio() test.run_audio()

View file

@ -6,42 +6,56 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS @author: DJ2LS
""" """
import ctypes
from ctypes import *
import pathlib
import pyaudio
import sys
import logging
import time
import threading
import sys
import argparse import argparse
import ctypes
import sys
import time
import numpy as np import numpy as np
sys.path.insert(0,'..') import pyaudio
sys.path.insert(0, "..")
from tnc import codec2 from tnc import codec2
#--------------------------------------------GET PARAMETER INPUTS # --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='FreeDATA audio test') parser = argparse.ArgumentParser(description="FreeDATA audio test")
parser.add_argument('--bursts', dest="N_BURSTS", default=1, 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("--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(
parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int, "--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"]
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(
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends") "--audiodev",
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") 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() args, _ = parser.parse_known_args()
if args.LIST: if args.LIST:
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
for dev in range(0,p.get_device_count()): for dev in range(p.get_device_count()):
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
quit() sys.exit()
class Test:
class Test():
def __init__(self): def __init__(self):
self.N_BURSTS = args.N_BURSTS self.N_BURSTS = args.N_BURSTS
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
@ -51,51 +65,61 @@ class Test():
self.TIMEOUT = args.TIMEOUT self.TIMEOUT = args.TIMEOUT
# AUDIO PARAMETERS # AUDIO PARAMETERS
self.AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0 self.AUDIO_FRAMES_PER_BUFFER = (
2400 * 2
) # <- consider increasing if you get nread_exceptions > 0
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
self.AUDIO_SAMPLE_RATE_RX = 48000 self.AUDIO_SAMPLE_RATE_RX = 48000
# make sure our resampler will work # make sure our resampler will work
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 assert (
self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE
) == codec2.api.FDMDV_OS_48
# check if we want to use an audio device then do an pyaudio init # check if we want to use an audio device then do an pyaudio init
if self.AUDIO_INPUT_DEVICE != -1: if self.AUDIO_INPUT_DEVICE != -1:
self.p = pyaudio.PyAudio() self.p = pyaudio.PyAudio()
# auto search for loopback devices # auto search for loopback devices
if self.AUDIO_INPUT_DEVICE == -2: if self.AUDIO_INPUT_DEVICE == -2:
loopback_list = [] loopback_list = []
for dev in range(0,self.p.get_device_count()): for dev in range(self.p.get_device_count()):
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]: if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev) loopback_list.append(dev)
if len(loopback_list) >= 2: if len(loopback_list) >= 2:
self.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr) print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
else: else:
quit() sys.exit()
print(f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \ print(
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", file=sys.stderr) f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}",
self.stream_rx = self.p.open(format=pyaudio.paInt16, file=sys.stderr,
channels=1, )
rate=self.AUDIO_SAMPLE_RATE_RX,
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, self.stream_rx = self.p.open(
input=True, format=pyaudio.paInt16,
output=False, channels=1,
input_device_index=self.AUDIO_INPUT_DEVICE, rate=self.AUDIO_SAMPLE_RATE_RX,
stream_callback=self.callback frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
) input=True,
output=False,
input_device_index=self.AUDIO_INPUT_DEVICE,
stream_callback=self.callback,
)
# open codec2 instance
self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p)
# open codec2 instance
self.freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
# get number of bytes per frame for mode # get number of bytes per frame for mode
self.bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.freedv)/8) self.bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8
self.bytes_out = create_string_buffer(self.bytes_per_frame) )
codec2.api.freedv_set_frames_per_burst(self.freedv,self.N_FRAMES_PER_BURST) self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST)
self.total_n_bytes = 0 self.total_n_bytes = 0
self.rx_total_frames = 0 self.rx_total_frames = 0
self.rx_frames = 0 self.rx_frames = 0
@ -104,44 +128,48 @@ class Test():
self.nread_exceptions = 0 self.nread_exceptions = 0
self.timeout = time.time() + self.TIMEOUT self.timeout = time.time() + self.TIMEOUT
self.receive = True self.receive = True
self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER*2) self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER * 2)
self.resampler = codec2.resampler() self.resampler = codec2.resampler()
# Copy received 48 kHz to a file. Listen to this file with: # Copy received 48 kHz to a file. Listen to this file with:
# aplay -r 48000 -f S16_LE rx48_callback.raw # aplay -r 48000 -f S16_LE rx48_callback.raw
# Corruption of this file is a good way to detect audio card issues # Corruption of this file is a good way to detect audio card issues
self.frx = open("rx48_callback.raw", mode='wb') self.frx = open("rx48_callback.raw", mode="wb")
def callback(self, data_in48k, frame_count, time_info, status): def callback(self, data_in48k, frame_count, time_info, status):
x = np.frombuffer(data_in48k, dtype=np.int16) x = np.frombuffer(data_in48k, dtype=np.int16)
x.tofile(self.frx) x.tofile(self.frx)
x = self.resampler.resample48_to_8(x) x = self.resampler.resample48_to_8(x)
self.audio_buffer.push(x) self.audio_buffer.push(x)
# when we have enough samples call FreeDV Rx # when we have enough samples call FreeDV Rx
nin = codec2.api.freedv_nin(self.freedv) nin = codec2.api.freedv_nin(self.freedv)
while self.audio_buffer.nbuffer >= nin: while self.audio_buffer.nbuffer >= nin:
# demodulate audio # demodulate audio
nbytes = codec2.api.freedv_rawdatarx(self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes) nbytes = codec2.api.freedv_rawdatarx(
self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes
)
self.audio_buffer.pop(nin) self.audio_buffer.pop(nin)
# call me on every loop! # call me on every loop!
nin = codec2.api.freedv_nin(self.freedv) nin = codec2.api.freedv_nin(self.freedv)
rx_status = codec2.api.freedv_get_rx_status(self.freedv) rx_status = codec2.api.freedv_get_rx_status(self.freedv)
if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS:
self.rx_errors = self.rx_errors + 1 self.rx_errors = self.rx_errors + 1
if self.DEBUGGING_MODE: if self.DEBUGGING_MODE:
rx_status = codec2.api.rx_sync_flags_to_text[rx_status] rx_status = codec2.api.rx_sync_flags_to_text[rx_status]
print("nin: %5d rx_status: %4s naudio_buffer: %4d" % \ print(
(nin,rx_status,self.audio_buffer.nbuffer), file=sys.stderr) "nin: %5d rx_status: %4s naudio_buffer: %4d"
% (nin, rx_status, self.audio_buffer.nbuffer),
file=sys.stderr,
)
if nbytes: if nbytes:
self.total_n_bytes = self.total_n_bytes + nbytes self.total_n_bytes = self.total_n_bytes + nbytes
if nbytes == self.bytes_per_frame: if nbytes == self.bytes_per_frame:
self.rx_total_frames = self.rx_total_frames + 1 self.rx_total_frames = self.rx_total_frames + 1
self.rx_frames = self.rx_frames + 1 self.rx_frames = self.rx_frames + 1
@ -149,36 +177,41 @@ class Test():
if self.rx_frames == self.N_FRAMES_PER_BURST: if self.rx_frames == self.N_FRAMES_PER_BURST:
self.rx_frames = 0 self.rx_frames = 0
self.rx_bursts = self.rx_bursts + 1 self.rx_bursts = self.rx_bursts + 1
if self.rx_bursts == self.N_BURSTS: if self.rx_bursts == self.N_BURSTS:
self.receive = False self.receive = False
return (None, pyaudio.paContinue) return (None, pyaudio.paContinue)
def run_audio(self): def run_audio(self):
try: try:
print(f"starting pyaudio callback", file=sys.stderr) print(f"starting pyaudio callback", file=sys.stderr)
self.stream_rx.start_stream() self.stream_rx.start_stream()
except Exception as e: except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr) print(f"pyAudio error: {e}", file=sys.stderr)
while self.receive and time.time() < self.timeout: while self.receive and time.time() < self.timeout:
time.sleep(1) time.sleep(1)
if time.time() >= self.timeout: if time.time() >= self.timeout:
print("TIMEOUT REACHED") print("TIMEOUT REACHED")
if self.nread_exceptions: if self.nread_exceptions:
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \ print(
self.nread_exceptions, file=sys.stderr) "nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..."
print(f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}", file=sys.stderr) % self.nread_exceptions,
file=sys.stderr,
)
print(
f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}",
file=sys.stderr,
)
self.frx.close() self.frx.close()
# cloese pyaudio instance # cloese pyaudio instance
self.stream_rx.close() self.stream_rx.close()
self.p.terminate() self.p.terminate()
test = Test() test = Test()
test.run_audio() test.run_audio()

View file

@ -6,42 +6,56 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS @author: DJ2LS
""" """
import ctypes
from ctypes import *
import pathlib
import pyaudio
import sys
import logging
import time
import threading
import sys
import argparse import argparse
import ctypes
import sys
import time
import numpy as np import numpy as np
sys.path.insert(0,'..') import pyaudio
sys.path.insert(0, "..")
from tnc import codec2 from tnc import codec2
#--------------------------------------------GET PARAMETER INPUTS # --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='FreeDATA audio test') parser = argparse.ArgumentParser(description="FreeDATA audio test")
parser.add_argument('--bursts', dest="N_BURSTS", default=1, 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("--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(
parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int, "--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"]
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(
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends") "--audiodev",
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit") 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() args, _ = parser.parse_known_args()
if args.LIST: if args.LIST:
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
for dev in range(0,p.get_device_count()): for dev in range(p.get_device_count()):
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
quit() sys.exit()
class Test:
class Test():
def __init__(self): def __init__(self):
self.N_BURSTS = args.N_BURSTS self.N_BURSTS = args.N_BURSTS
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
@ -51,51 +65,61 @@ class Test():
self.TIMEOUT = args.TIMEOUT self.TIMEOUT = args.TIMEOUT
# AUDIO PARAMETERS # AUDIO PARAMETERS
self.AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0 self.AUDIO_FRAMES_PER_BUFFER = (
2400 * 2
) # <- consider increasing if you get nread_exceptions > 0
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
self.AUDIO_SAMPLE_RATE_RX = 48000 self.AUDIO_SAMPLE_RATE_RX = 48000
# make sure our resampler will work # make sure our resampler will work
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 assert (
self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE
) == codec2.api.FDMDV_OS_48
# check if we want to use an audio device then do an pyaudio init # check if we want to use an audio device then do an pyaudio init
if self.AUDIO_INPUT_DEVICE != -1: if self.AUDIO_INPUT_DEVICE != -1:
self.p = pyaudio.PyAudio() self.p = pyaudio.PyAudio()
# auto search for loopback devices # auto search for loopback devices
if self.AUDIO_INPUT_DEVICE == -2: if self.AUDIO_INPUT_DEVICE == -2:
loopback_list = [] loopback_list = []
for dev in range(0,self.p.get_device_count()): for dev in range(self.p.get_device_count()):
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]: if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev) loopback_list.append(dev)
if len(loopback_list) >= 2: if len(loopback_list) >= 2:
self.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr) print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
else: else:
quit() sys.exit()
print(f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \ print(
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", file=sys.stderr) f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}",
self.stream_rx = self.p.open(format=pyaudio.paInt16, file=sys.stderr,
channels=1, )
rate=self.AUDIO_SAMPLE_RATE_RX,
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, self.stream_rx = self.p.open(
input=True, format=pyaudio.paInt16,
output=False, channels=1,
input_device_index=self.AUDIO_INPUT_DEVICE, rate=self.AUDIO_SAMPLE_RATE_RX,
stream_callback=self.callback frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
) input=True,
output=False,
input_device_index=self.AUDIO_INPUT_DEVICE,
stream_callback=self.callback,
)
# open codec2 instance
self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p)
# open codec2 instance
self.freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
# get number of bytes per frame for mode # get number of bytes per frame for mode
self.bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.freedv)/8) self.bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8
self.bytes_out = create_string_buffer(self.bytes_per_frame * 2) )
codec2.api.freedv_set_frames_per_burst(self.freedv,self.N_FRAMES_PER_BURST) self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame * 2)
codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST)
self.total_n_bytes = 0 self.total_n_bytes = 0
self.rx_total_frames = 0 self.rx_total_frames = 0
self.rx_frames = 0 self.rx_frames = 0
@ -104,55 +128,59 @@ class Test():
self.nread_exceptions = 0 self.nread_exceptions = 0
self.timeout = time.time() + self.TIMEOUT self.timeout = time.time() + self.TIMEOUT
self.receive = True self.receive = True
self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER*2) self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER * 2)
self.resampler = codec2.resampler() self.resampler = codec2.resampler()
# Copy received 48 kHz to a file. Listen to this file with: # Copy received 48 kHz to a file. Listen to this file with:
# aplay -r 48000 -f S16_LE rx48_callback.raw # aplay -r 48000 -f S16_LE rx48_callback.raw
# Corruption of this file is a good way to detect audio card issues # Corruption of this file is a good way to detect audio card issues
self.frx = open("rx48_callback.raw", mode='wb') self.frx = open("rx48_callback.raw", mode="wb")
def callback(self, data_in48k, frame_count, time_info, status): def callback(self, data_in48k, frame_count, time_info, status):
x = np.frombuffer(data_in48k, dtype=np.int16) x = np.frombuffer(data_in48k, dtype=np.int16)
x.tofile(self.frx) x.tofile(self.frx)
x = self.resampler.resample48_to_8(x) x = self.resampler.resample48_to_8(x)
self.audio_buffer.push(x) self.audio_buffer.push(x)
return (None, pyaudio.paContinue) return (None, pyaudio.paContinue)
def run_audio(self): def run_audio(self):
try: try:
print(f"starting pyaudio callback", file=sys.stderr) print(f"starting pyaudio callback", file=sys.stderr)
self.stream_rx.start_stream() self.stream_rx.start_stream()
except Exception as e: except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr) print(f"pyAudio error: {e}", file=sys.stderr)
while self.receive and time.time() < self.timeout: while self.receive and time.time() < self.timeout:
#time.sleep(1) # time.sleep(1)
# when we have enough samples call FreeDV Rx # when we have enough samples call FreeDV Rx
nin = codec2.api.freedv_nin(self.freedv) nin = codec2.api.freedv_nin(self.freedv)
while self.audio_buffer.nbuffer >= nin: while self.audio_buffer.nbuffer >= nin:
# demodulate audio # demodulate audio
nbytes = codec2.api.freedv_rawdatarx(self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes) nbytes = codec2.api.freedv_rawdatarx(
self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes
)
self.audio_buffer.pop(nin) self.audio_buffer.pop(nin)
# call me on every loop! # call me on every loop!
nin = codec2.api.freedv_nin(self.freedv) nin = codec2.api.freedv_nin(self.freedv)
rx_status = codec2.api.freedv_get_rx_status(self.freedv) rx_status = codec2.api.freedv_get_rx_status(self.freedv)
if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS:
self.rx_errors = self.rx_errors + 1 self.rx_errors = self.rx_errors + 1
if self.DEBUGGING_MODE: if self.DEBUGGING_MODE:
rx_status = codec2.api.rx_sync_flags_to_text[rx_status] rx_status = codec2.api.rx_sync_flags_to_text[rx_status]
print("nin: %5d rx_status: %4s naudio_buffer: %4d" % \ print(
(nin,rx_status,self.audio_buffer.nbuffer), file=sys.stderr) "nin: %5d rx_status: %4s naudio_buffer: %4d"
% (nin, rx_status, self.audio_buffer.nbuffer),
file=sys.stderr,
)
if nbytes: if nbytes:
self.total_n_bytes = self.total_n_bytes + nbytes self.total_n_bytes = self.total_n_bytes + nbytes
if nbytes == self.bytes_per_frame: if nbytes == self.bytes_per_frame:
self.rx_total_frames = self.rx_total_frames + 1 self.rx_total_frames = self.rx_total_frames + 1
self.rx_frames = self.rx_frames + 1 self.rx_frames = self.rx_frames + 1
@ -160,22 +188,28 @@ class Test():
if self.rx_frames == self.N_FRAMES_PER_BURST: if self.rx_frames == self.N_FRAMES_PER_BURST:
self.rx_frames = 0 self.rx_frames = 0
self.rx_bursts = self.rx_bursts + 1 self.rx_bursts = self.rx_bursts + 1
if self.rx_bursts == self.N_BURSTS: if self.rx_bursts == self.N_BURSTS:
self.receive = False self.receive = False
if time.time() >= self.timeout: if time.time() >= self.timeout:
print("TIMEOUT REACHED") print("TIMEOUT REACHED")
if self.nread_exceptions: if self.nread_exceptions:
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \ print(
self.nread_exceptions, file=sys.stderr) "nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..."
print(f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}", file=sys.stderr) % self.nread_exceptions,
file=sys.stderr,
)
print(
f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}",
file=sys.stderr,
)
self.frx.close() self.frx.close()
# cloese pyaudio instance # cloese pyaudio instance
self.stream_rx.close() self.stream_rx.close()
self.p.terminate() self.p.terminate()
test = Test() test = Test()
test.run_audio() test.run_audio()

View file

@ -6,43 +6,58 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS @author: DJ2LS
""" """
import ctypes
from ctypes import *
import pathlib
import pyaudio
import sys
import logging
import time
import threading
import sys
import argparse import argparse
import ctypes
import queue import queue
import sys
import time
import numpy as np import numpy as np
sys.path.insert(0,'..') import pyaudio
sys.path.insert(0, "..")
from tnc import codec2 from tnc import codec2
#--------------------------------------------GET PARAMETER INPUTS # --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='FreeDATA audio test') parser = argparse.ArgumentParser(description="FreeDATA audio test")
parser.add_argument('--bursts', dest="N_BURSTS", default=1, 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("--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("--delay", dest="DELAY_BETWEEN_BURSTS", default=500, type=int)
parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3']) parser.add_argument(
parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, "--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"]
help="audio 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") parser.add_argument(
parser.add_argument('--testframes', dest="TESTFRAMES", action="store_true", default=False, help="generate testframes") "--audiodev",
dest="AUDIO_OUTPUT_DEVICE",
default=-1,
type=int,
help="audio 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",
)
parser.add_argument(
"--testframes",
dest="TESTFRAMES",
action="store_true",
default=False,
help="generate testframes",
)
args = parser.parse_args() args, _ = parser.parse_known_args()
if args.LIST: if args.LIST:
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
for dev in range(0,p.get_device_count()): for dev in range(p.get_device_count()):
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
quit() sys.exit()
class Test:
class Test():
def __init__(self): def __init__(self):
self.dataqueue = queue.Queue() self.dataqueue = queue.Queue()
@ -50,180 +65,206 @@ class Test():
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
self.MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value self.MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000 self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
# AUDIO PARAMETERS # AUDIO PARAMETERS
self.AUDIO_FRAMES_PER_BUFFER = 2400 # <- consider increasing if you get nread_exceptions > 0 # v-- consider increasing if you get nread_exceptions > 0
self.AUDIO_FRAMES_PER_BUFFER = 2400
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
self.AUDIO_SAMPLE_RATE_TX = 48000 self.AUDIO_SAMPLE_RATE_TX = 48000
# make sure our resampler will work # make sure our resampler will work
assert (self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 assert (
self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE
) == codec2.api.FDMDV_OS_48
self.transmit = True self.transmit = True
self.resampler = codec2.resampler() self.resampler = codec2.resampler()
# check if we want to use an audio device then do an pyaudio init # check if we want to use an audio device then do an pyaudio init
if self.AUDIO_OUTPUT_DEVICE != -1: if self.AUDIO_OUTPUT_DEVICE != -1:
self.p = pyaudio.PyAudio() self.p = pyaudio.PyAudio()
# auto search for loopback devices # auto search for loopback devices
if self.AUDIO_OUTPUT_DEVICE == -2: if self.AUDIO_OUTPUT_DEVICE == -2:
loopback_list = [] loopback_list = []
for dev in range(0,self.p.get_device_count()): for dev in range( self.p.get_device_count()):
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]: if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev) loopback_list.append(dev)
if len(loopback_list) >= 2: if len(loopback_list) >= 2:
self.AUDIO_OUTPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX self.AUDIO_OUTPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr) print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
else: else:
quit() sys.exit()
print(f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} \ print(
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}", file=sys.stderr) f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} \
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}",
self.stream_tx = self.p.open(format=pyaudio.paInt16, file=sys.stderr,
channels=1, )
rate=self.AUDIO_SAMPLE_RATE_TX,
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, self.stream_tx = self.p.open(
input=False, format=pyaudio.paInt16,
output=True, channels=1,
output_device_index=self.AUDIO_OUTPUT_DEVICE, rate=self.AUDIO_SAMPLE_RATE_TX,
stream_callback=self.callback frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
) input=False,
output=True,
output_device_index=self.AUDIO_OUTPUT_DEVICE,
stream_callback=self.callback,
)
# open codec2 instance
self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p)
# open codec2 instance
self.freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
# get number of bytes per frame for mode # get number of bytes per frame for mode
self.bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.freedv)/8) self.bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8
self.bytes_out = create_string_buffer(self.bytes_per_frame) )
codec2.api.freedv_set_frames_per_burst(self.freedv,self.N_FRAMES_PER_BURST) self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST)
# Copy received 48 kHz to a file. Listen to this file with: # Copy received 48 kHz to a file. Listen to this file with:
# aplay -r 48000 -f S16_LE rx48_callback.raw # aplay -r 48000 -f S16_LE rx48_callback.raw
# Corruption of this file is a good way to detect audio card issues # Corruption of this file is a good way to detect audio card issues
self.ftx = open("tx48_callback.raw", mode='wb') self.ftx = open("tx48_callback.raw", mode="wb")
# data binary string # data binary string
if args.TESTFRAMES: if args.TESTFRAMES:
self.data_out = bytearray(14) self.data_out = bytearray(14)
self.data_out[:1] = bytes([255]) self.data_out[:1] = bytes([255])
self.data_out[1:2] = bytes([1]) self.data_out[1:2] = bytes([1])
self.data_out[2:] = b'HELLO WORLD' self.data_out[2:] = b"HELLO WORLD"
else: else:
self.data_out = b'HELLO WORLD!' self.data_out = b"HELLO WORLD!"
def callback(self, data_in48k, frame_count, time_info, status): def callback(self, data_in48k, frame_count, time_info, status):
data_out48k = self.dataqueue.get() data_out48k = self.dataqueue.get()
return (data_out48k, pyaudio.paContinue) return (data_out48k, pyaudio.paContinue)
def run_audio(self): def run_audio(self):
try: try:
print(f"starting pyaudio callback", file=sys.stderr) print(f"starting pyaudio callback", file=sys.stderr)
self.stream_tx.start_stream() self.stream_tx.start_stream()
except Exception as e: except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr) print(f"pyAudio error: {e}", file=sys.stderr)
sheeps = 0 sheeps = 0
while self.transmit: while self.transmit:
time.sleep(1) time.sleep(1)
sheeps = sheeps + 1 sheeps = sheeps + 1
print(f"counting sheeps...{sheeps}") print(f"counting sheeps...{sheeps}")
self.ftx.close() self.ftx.close()
# close pyaudio instance # close pyaudio instance
self.stream_tx.close() self.stream_tx.close()
self.p.terminate() self.p.terminate()
def create_modulation(self): def create_modulation(self):
# open codec2 instance # open codec2 instance
freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p) freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p)
# get number of bytes per frame for mode # get number of bytes per frame for mode
bytes_per_frame = int(codec2.api.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 payload_bytes_per_frame = bytes_per_frame - 2
# init buffer for data # init buffer for data
n_tx_modem_samples = codec2.api.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) mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
# init buffer for preample # init buffer for preample
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv) n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
mod_out_preamble = create_string_buffer(n_tx_preamble_modem_samples * 2) freedv
)
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
# init buffer for postamble # init buffer for postamble
n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv) n_tx_postamble_modem_samples = (
mod_out_postamble = create_string_buffer(n_tx_postamble_modem_samples * 2) codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
)
mod_out_postamble = ctypes.create_string_buffer(n_tx_postamble_modem_samples * 2)
# create buffer for data # create buffer for data
buffer = bytearray(payload_bytes_per_frame) # use this if CRC16 checksum is required ( DATA1-3) buffer = bytearray(
buffer[:len(self.data_out)] = self.data_out # set buffersize to length of data which will be send payload_bytes_per_frame
) # use this if CRC16 checksum is required ( DATA1-3)
buffer[
: len(self.data_out)
] = self.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 # create crc for data frame - we are using the crc function shipped with codec2 to avoid
# crc algorithm incompatibilities # crc algorithm incompatibilities
crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # generate CRC16 crc = ctypes.c_ushort(
crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)
buffer += crc # append crc16 to buffer ) # generate CRC16
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: {self.N_BURSTS} TOTAL FRAMES_PER_BURST: {self.N_FRAMES_PER_BURST}", file=sys.stderr) print(
f"TOTAL BURSTS: {self.N_BURSTS} TOTAL FRAMES_PER_BURST: {self.N_FRAMES_PER_BURST}",
file=sys.stderr,
)
for i in range(1,self.N_BURSTS+1): for i in range(1, self.N_BURSTS + 1):
# write preamble to txbuffer # write preamble to txbuffer
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
txbuffer = bytes(mod_out_preamble) txbuffer = bytes(mod_out_preamble)
# create modulaton for N = FRAMESPERBURST and append it to txbuffer # create modulaton for N = FRAMESPERBURST and append it to txbuffer
for n in range(1,self.N_FRAMES_PER_BURST+1): for n in range(1, self.N_FRAMES_PER_BURST + 1):
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) 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 codec2.api.freedv_rawdatatx(
freedv, mod_out, data
) # modulate DATA and save it into mod_out pointer
txbuffer += bytes(mod_out) txbuffer += bytes(mod_out)
print(f" GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}", file=sys.stderr) print(
f" GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}",
# append postamble to txbuffer file=sys.stderr,
)
# append postamble to txbuffer
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
txbuffer += bytes(mod_out_postamble) txbuffer += bytes(mod_out_postamble)
# append a delay between bursts as audio silence # append a delay between bursts as audio silence
samples_delay = int(self.MODEM_SAMPLE_RATE*self.DELAY_BETWEEN_BURSTS) samples_delay = int(self.MODEM_SAMPLE_RATE * self.DELAY_BETWEEN_BURSTS)
mod_out_silence = create_string_buffer(samples_delay*2) mod_out_silence = ctypes.create_string_buffer(samples_delay * 2)
txbuffer += bytes(mod_out_silence) txbuffer += bytes(mod_out_silence)
print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {self.DELAY_BETWEEN_BURSTS}", file=sys.stderr) print(
f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {self.DELAY_BETWEEN_BURSTS}",
file=sys.stderr,
)
# resample up to 48k (resampler works on np.int16) # resample up to 48k (resampler works on np.int16)
x = np.frombuffer(txbuffer, dtype=np.int16) x = np.frombuffer(txbuffer, dtype=np.int16)
txbuffer_48k = self.resampler.resample8_to_48(x) txbuffer_48k = self.resampler.resample8_to_48(x)
# split modualted audio to chunks # split modualted audio to chunks
#https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python # https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python
txbuffer_48k = bytes(txbuffer_48k) txbuffer_48k = bytes(txbuffer_48k)
chunk = [txbuffer_48k[i:i+self.AUDIO_FRAMES_PER_BUFFER*2] for i in range(0, len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER*2)] chunk = [
txbuffer_48k[i : i + self.AUDIO_FRAMES_PER_BUFFER * 2]
for i in range( len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER * 2)
]
# add modulated chunks to fifo buffer # add modulated chunks to fifo buffer
for c in chunk: for c in chunk:
# if data is shorter than the expcected audio frames per buffer we need to append 0 # if data is shorter than the expcected audio frames per buffer we need to append 0
# to prevent the callback from stucking/crashing # to prevent the callback from stucking/crashing
if len(c) < self.AUDIO_FRAMES_PER_BUFFER*2: if len(c) < self.AUDIO_FRAMES_PER_BUFFER * 2:
c += bytes(self.AUDIO_FRAMES_PER_BUFFER*2 - len(c)) c += bytes(self.AUDIO_FRAMES_PER_BUFFER * 2 - len(c))
self.dataqueue.put(c) self.dataqueue.put(c)
test = Test() test = Test()
test.create_modulation() test.create_modulation()
test.run_audio() test.run_audio()

90
test/test_helpers.py Normal file
View file

@ -0,0 +1,90 @@
"""
Tests for the FreeDATA TNC state machine.
"""
import sys
import helpers
import pytest
import static
@pytest.mark.parametrize("callsign", ["AA1AA", "DE2DE", "E4AWQ-4"])
def test_check_callsign(callsign: str):
"""
Execute test to demonstrate how to create and verify callsign checksums.
"""
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
t_callsign_bytes = helpers.callsign_to_bytes(callsign)
t_callsign = helpers.bytes_to_callsign(t_callsign_bytes)
t_callsign_crc = helpers.get_crc_24(t_callsign)
dxcallsign_bytes = helpers.callsign_to_bytes("ZZ9ZZA-0")
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
assert helpers.check_callsign(t_callsign, t_callsign_crc)[0] is True
assert helpers.check_callsign(t_callsign, dxcallsign_crc)[0] is False
@pytest.mark.parametrize("callsign", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"])
def test_callsign_to_bytes(callsign: str):
"""
Execute test to demonsrate symmetry when converting callsigns to and from byte arrays.
"""
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
t_callsign_crc = helpers.get_crc_24(bytes(callsign, "UTF-8"))
t_callsign_bytes = helpers.callsign_to_bytes(callsign)
t_callsign = helpers.bytes_to_callsign(t_callsign_bytes)
assert helpers.check_callsign(t_callsign, t_callsign_crc)[0] is True
assert helpers.check_callsign(t_callsign, t_callsign_crc)[1] == bytes(
callsign, "UTF-8"
)
@pytest.mark.parametrize("callsign", ["AA1AA-2", "DE2DE-0", "e4awq-4"])
def test_encode_callsign(callsign: str):
"""
Execute test to demonsrate symmetry when encoding and decoding callsigns.
"""
callenc = helpers.encode_call(callsign)
calldec = helpers.decode_call(callenc)
assert callsign.upper() != calldec
@pytest.mark.parametrize("gridsq", ["EM98dc", "DE01GG", "EF42sW"])
def test_encode_grid(gridsq: str):
"""
Execute test to demonsrate symmetry when encoding and decoding grid squares.
"""
gridenc = helpers.encode_grid(gridsq)
griddec = helpers.decode_grid(gridenc)
assert gridsq.upper() == griddec
@pytest.mark.parametrize("gridsq", ["SM98dc", "DE01GZ", "EV42sY"])
@pytest.mark.xfail(reason="Invalid gridsquare provided")
def test_invalid_grid(gridsq: str):
"""
Execute test to demonsrate symmetry when encoding and decoding grid squares.
"""
gridenc = helpers.encode_grid(gridsq)
griddec = helpers.decode_grid(gridenc)
assert gridsq.upper() != griddec
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

View file

@ -0,0 +1,123 @@
"""
Tests a high signal-to-noise ratio path with codec2 data formats using codec2 to transmit.
"""
# pylint: disable=global-statement, invalid-name, unused-import
import os
import subprocess
import sys
import pytest
try:
BURSTS = int(os.environ["BURSTS"])
FRAMESPERBURST = int(os.environ["FRAMESPERBURST"])
TESTFRAMES = int(os.environ["TESTFRAMES"])
except KeyError:
BURSTS = 1
FRAMESPERBURST = 1
TESTFRAMES = 3
@pytest.mark.parametrize("bursts", [BURSTS, 2, 3])
@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3])
@pytest.mark.parametrize("testframes", [TESTFRAMES, 2, 1])
@pytest.mark.parametrize("mode", ["datac0", "datac1", "datac3"])
def test_HighSNR_P_P_DATACx(
bursts: int, frames_per_burst: int, testframes: int, mode: str
):
"""
Test a high signal-to-noise ratio path with DATAC0.
:param bursts: Number of bursts
:type bursts: str
:param frames_per_burst: Number of frames transmitted per burst
:type frames_per_burst: str
:param testframes: Number of test frames to transmit
:type testframes: str
"""
tx_side = "freedv_data_raw_tx"
# Facilitate running from main directory as well as inside test/
rx_side = "test_rx.py"
if os.path.exists("test") and os.path.exists(os.path.join("test", rx_side)):
rx_side = os.path.join("test", rx_side)
os.environ["PYTHONPATH"] += ":."
with subprocess.Popen(
args=[
tx_side,
mode,
"--testframes",
f"{testframes}",
"--bursts",
f"{bursts}",
"--framesperburst",
f"{frames_per_burst}",
"/dev/zero",
"-",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) as transmit:
with subprocess.Popen(
args=[
"sox",
"-t",
".s16",
"-r",
"8000",
"-c",
"1",
"-",
"-t",
".s16",
"-r",
"48000",
"-c",
"1",
"-",
],
stdin=transmit.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) as sox_filter:
with subprocess.Popen(
args=[
"python3",
rx_side,
"--mode",
mode,
"--framesperburst",
str(frames_per_burst),
"--bursts",
str(bursts),
],
stdin=sox_filter.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) as receive:
assert receive.stdout
lastline = "".join(
[
str(line, "UTF-8")
for line in receive.stdout.readlines()
if "RECEIVED " in str(line, "UTF-8")
]
)
assert f"RECEIVED BURSTS: {bursts}" in lastline
assert f"RECEIVED FRAMES: {frames_per_burst * bursts}" in lastline
assert "RX_ERRORS: 0" in lastline
print(lastline)
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", "-s", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

View file

@ -0,0 +1,126 @@
"""
Tests a high signal-to-noise ratio path with codec2 data formats using codec2 to receive.
"""
# pylint: disable=global-statement, invalid-name, unused-import
import os
import subprocess
import sys
import pytest
try:
BURSTS = int(os.environ["BURSTS"])
FRAMESPERBURST = int(os.environ["FRAMESPERBURST"])
TESTFRAMES = int(os.environ["TESTFRAMES"])
except KeyError:
BURSTS = 1
FRAMESPERBURST = 1
TESTFRAMES = 3
@pytest.mark.parametrize("bursts", [BURSTS, 2, 3])
@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3])
@pytest.mark.parametrize("mode", ["datac0", "datac1", "datac3"])
def test_HighSNR_P_P_DATACx(bursts: int, frames_per_burst: int, mode: str):
"""
Test a high signal-to-noise ratio path with DATAC0.
:param bursts: Number of bursts
:type bursts: str
:param frames_per_burst: Number of frames transmitted per burst
:type frames_per_burst: str
:param testframes: Number of test frames to transmit
:type testframes: str
"""
# Facilitate running from main directory as well as inside test/
tx_side = "test_tx.py"
if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)):
tx_side = os.path.join("test", tx_side)
os.environ["PYTHONPATH"] += ":."
rx_side = "freedv_data_raw_rx"
with subprocess.Popen(
args=[
"python3",
tx_side,
"--mode",
mode,
"--delay",
"500",
"--framesperburst",
f"{frames_per_burst}",
"--bursts",
f"{bursts}",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) as transmit:
with subprocess.Popen(
args=[
"sox",
"-t",
".s16",
"-r",
"48000",
"-c",
"1",
"-",
"-t",
".s16",
"-r",
"8000",
"-c",
"1",
"-",
],
stdin=transmit.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) as sox_filter:
with subprocess.Popen(
args=[
rx_side,
mode,
"-",
"-",
"--framesperburst",
str(frames_per_burst),
],
stdin=sox_filter.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) as receive:
with subprocess.Popen(
args=[
"hexdump",
"-C",
],
stdin=receive.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) as hexdump:
assert hexdump.stdout
lastline = "".join(
[
str(line, "UTF-8")
for line in hexdump.stdout.readlines()
if "HELLO" in str(line, "UTF-8")
]
)
assert "HELLO WORLD!" in lastline
print(lastline)
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", "-s", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

View file

@ -0,0 +1,97 @@
"""
Tests a high signal-to-noise ratio path with codec2 data formats.
"""
# pylint: disable=global-statement, invalid-name, unused-import
import os
import subprocess
import sys
import pytest
try:
BURSTS = int(os.environ["BURSTS"])
FRAMESPERBURST = int(os.environ["FRAMESPERBURST"])
TESTFRAMES = int(os.environ["TESTFRAMES"])
except KeyError:
BURSTS = 1
FRAMESPERBURST = 1
TESTFRAMES = 3
@pytest.mark.parametrize("bursts", [BURSTS, 2, 3])
@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3])
@pytest.mark.parametrize("mode", ["datac0", "datac1", "datac3"])
def test_HighSNR_P_P_DATACx(bursts: int, frames_per_burst: int, mode: str):
"""
Test a high signal-to-noise ratio path with Codec2 modes.
:param bursts: Number of bursts
:type bursts: str
:param frames_per_burst: Number of frames transmitted per burst
:type frames_per_burst: str
"""
# Facilitate running from main directory as well as inside test/
tx_side = "test_tx.py"
rx_side = "test_rx.py"
if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)):
tx_side = os.path.join("test", tx_side)
rx_side = os.path.join("test", rx_side)
os.environ["PYTHONPATH"] += ":."
with subprocess.Popen(
args=[
"python3",
tx_side,
"--mode",
mode,
"--delay",
"500",
"--framesperburst",
str(frames_per_burst),
"--bursts",
str(bursts),
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) as transmit:
with subprocess.Popen(
args=[
"python3",
rx_side,
"--mode",
mode,
"--framesperburst",
str(frames_per_burst),
"--bursts",
str(bursts),
"--timeout",
"20",
],
stdin=transmit.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) as receive:
assert receive.stdout
lastline = "".join(
[
str(line, "UTF-8")
for line in receive.stdout.readlines()
if "RECEIVED " in str(line, "UTF-8")
]
)
assert f"RECEIVED BURSTS: {bursts}" in lastline
assert f"RECEIVED FRAMES: {frames_per_burst * bursts}" in lastline
assert "RX_ERRORS: 0" in lastline
print(lastline)
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", "-s", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

View file

@ -0,0 +1,92 @@
"""
Tests a high signal-to-noise ratio path with multiple codec2 data formats.
"""
# pylint: disable=global-statement, invalid-name, unused-import
import os
import subprocess
import sys
import pytest
try:
BURSTS = int(os.environ["BURSTS"])
FRAMESPERBURST = int(os.environ["FRAMESPERBURST"])
TESTFRAMES = int(os.environ["TESTFRAMES"])
except KeyError:
BURSTS = 1
FRAMESPERBURST = 1
TESTFRAMES = 3
@pytest.mark.parametrize("bursts", [BURSTS, 2, 3])
@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3])
def test_HighSNR_P_P_Multi(bursts: int, frames_per_burst: int):
"""
Test a high signal-to-noise ratio path with DATAC0, DATAC1 and DATAC3.
:param bursts: Number of bursts
:type bursts: int
:param frames_per_burst: Number of frames transmitted per burst
:type frames_per_burst: int
"""
# Facilitate running from main directory as well as inside test/
tx_side = "test_multimode_tx.py"
rx_side = "test_multimode_rx.py"
if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)):
tx_side = os.path.join("test", tx_side)
rx_side = os.path.join("test", rx_side)
os.environ["PYTHONPATH"] += ":."
with subprocess.Popen(
args=[
"python3",
tx_side,
"--delay",
"500",
"--framesperburst",
str(frames_per_burst),
"--bursts",
str(bursts),
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) as transmit:
with subprocess.Popen(
args=[
"python3",
rx_side,
"--framesperburst",
str(frames_per_burst),
"--bursts",
str(bursts),
"--timeout",
"20",
],
stdin=transmit.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) as receive:
assert receive.stdout
lastline = "".join(
[
str(line, "UTF-8")
for line in receive.stdout.readlines()
if "DATAC" in str(line, "UTF-8")
]
)
assert f"DATAC0: {bursts}/{frames_per_burst * bursts}" in lastline
assert f"DATAC1: {bursts}/{frames_per_burst * bursts}" in lastline
assert f"DATAC3: {bursts}/{frames_per_burst * bursts}" in lastline
print(lastline)
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", "-s", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

160
test/test_modem.py Normal file
View file

@ -0,0 +1,160 @@
"""
Tests for the FreeDATA modem.
"""
import multiprocessing
import sys
import time
import pytest
sys.path.insert(0, "..")
sys.path.insert(0, "../tnc")
import helpers
import modem
import static
def print_frame(data: bytearray):
"""
Pretty-print the provided frame.
:param data: Frame to be output
:type data: bytearray
"""
print(f"Type : {int(data[0])}")
print(f"DXCRC : {bytes(data[1:4])}")
print(f"CallCRC: {bytes(data[4:7])}")
print(f"Call : {helpers.bytes_to_callsign(data[7:13])}")
def t_create_frame(frame_type: int, mycall: str, dxcall: str) -> bytearray:
"""
Generate the requested frame.
:param frame_type: The numerical type of the desired frame.
:type frame_type: int
:param mycall: Callsign of the near station
:type mycall: str
:param dxcall: Callsign of the far station
:type dxcall: str
:return: Bytearray of the requested frame
:rtype: bytearray
"""
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
mycallsign_crc = helpers.get_crc_24(mycallsign)
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
frame = bytearray(14)
frame[:1] = bytes([frame_type])
frame[1:4] = dxcallsign_crc
frame[4:7] = mycallsign_crc
frame[7:13] = mycallsign_bytes
return frame
def t_create_session_close(mycall: str, dxcall: str) -> bytearray:
"""
Generate the session_close frame.
:param mycall: Callsign of the near station
:type mycall: str
:param dxcall: Callsign of the far station
:type dxcall: str
:return: Bytearray of the requested frame
:rtype: bytearray
"""
return t_create_frame(223, mycall, dxcall)
def t_create_start_session(mycall: str, dxcall: str) -> bytearray:
"""
Generate the create_session frame.
:param mycall: Callsign of the near station
:type mycall: str
:param dxcall: Callsign of the far station
:type dxcall: str
:return: Bytearray of the requested frame
:rtype: bytearray
"""
return t_create_frame(221, mycall, dxcall)
def t_tsh_dummy():
"""Replacement function for transmit"""
print("In t_tsh_dummy")
def t_modem():
"""
Execute test to validate that receiving a session open frame sets the correct machine
state.
"""
t_mode = t_repeats = t_repeat_delay = 0
t_frames = []
# enable testmode
modem.TESTMODE = True
modem.RXCHANNEL = "/tmp/hfchannel1"
modem.TXCHANNEL = "/tmp/hfchannel2"
static.HAMLIB_RADIOCONTROL = "disabled"
def t_tx_dummy(mode, repeats, repeat_delay, frames):
"""Replacement function for transmit"""
print(f"t_tx_dummy: In transmit({mode}, {repeats}, {repeat_delay}, {frames})")
nonlocal t_mode, t_repeats, t_repeat_delay, t_frames
t_mode = mode
t_repeats = repeats
t_repeat_delay = repeat_delay
t_frames = frames[:]
static.TRANSMITTING = False
# Create the modem
local_modem = modem.RF()
# Replace transmit routine with our own, an effective No-Op.
local_modem.transmit = t_tx_dummy
txbuffer = t_create_start_session("AA9AA", "DC2EJ")
# Start the transmission
static.TRANSMITTING = True
modem.MODEM_TRANSMIT_QUEUE.put([14, 5, 250, txbuffer])
while static.TRANSMITTING:
time.sleep(0.1)
# Check that the contents were transferred correctly.
assert t_mode == 14
assert t_repeats == 5
assert t_repeat_delay == 250
assert t_frames == txbuffer
def test_modem_queue():
proc = multiprocessing.Process(target=t_modem, args=())
# print("Starting threads.")
proc.start()
time.sleep(0.5)
# print("Terminating threads.")
proc.terminate()
proc.join()
# print(f"\n{proc.exitcode=}")
assert proc.exitcode == 0
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", "-s", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

View file

@ -1,236 +1,242 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pyaudio
import audioop
import time
import argparse import argparse
import sys
import ctypes import ctypes
from ctypes import * import sys
import pathlib import time
sys.path.insert(0,'..') from typing import List
from tnc import codec2
import numpy as np import numpy as np
import pyaudio
#--------------------------------------------GET PARAMETER INPUTS sys.path.insert(0, "..")
parser = argparse.ArgumentParser(description='Simons TEST TNC') from tnc import codec2
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")
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: def test_mm_rx():
p = pyaudio.PyAudio() # AUDIO PARAMETERS
for dev in range(0,p.get_device_count()): AUDIO_FRAMES_PER_BUFFER = 2400 * 2
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
quit() AUDIO_SAMPLE_RATE_RX = 48000
# make sure our resampler will work
assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
N_BURSTS = args.N_BURSTS # SET COUNTERS
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST rx_bursts_datac = [0, 0, 0]
AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE rx_frames_datac = [0, 0, 0]
DEBUGGING_MODE = args.DEBUGGING_MODE rx_total_frames_datac = [0, 0, 0]
TIMEOUT = args.TIMEOUT
# AUDIO PARAMETERS # time meassurement
AUDIO_FRAMES_PER_BUFFER = 2400*2 time_end_datac = [0.0, 0.0, 0.0]
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 time_needed_datac = [0.0, 0.0, 0.0]
AUDIO_SAMPLE_RATE_RX = 48000 time_start_datac = [0.0, 0.0, 0.0]
# make sure our resampler will work
assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
# SET COUNTERS datac_buffer: List[codec2.audio_buffer] = []
rx_total_frames_datac0 = 0 datac_bytes_out: List[ctypes.Array] = []
rx_frames_datac0 = 0 datac_bytes_per_frame = []
rx_bursts_datac0 = 0 datac_freedv: List[ctypes.c_void_p] = []
rx_total_frames_datac1 = 0 args = parse_arguments()
rx_frames_datac1 = 0
rx_bursts_datac1 = 0
rx_total_frames_datac3 = 0 if args.LIST:
rx_frames_datac3 = 0 p_audio = pyaudio.PyAudio()
rx_bursts_datac3 = 0 for dev in range(p_audio.get_device_count()):
print("audiodev: ", dev, p_audio.get_device_info_by_index(dev)["name"])
sys.exit()
# time meassurement N_BURSTS = args.N_BURSTS
time_start_datac0 = 0 N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
time_end_datac0 = 0 AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE
time_start_datac1 = 0 DEBUGGING_MODE = args.DEBUGGING_MODE
time_end_datac1 = 0 MAX_TIME = args.TIMEOUT
time_start_datac3 = 0
time_end_datac3 = 0
time_needed_datac0 = 0
time_needed_datac1 = 0
time_needed_datac3 = 0
# open codec2 instance # open codec2 instances
datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p) for idx in range(3):
datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac0_freedv)/8) datac_freedv.append(
datac0_bytes_out = create_string_buffer(datac0_bytes_per_frame) ctypes.cast(
codec2.api.freedv_set_frames_per_burst(datac0_freedv,N_FRAMES_PER_BURST) codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p
datac0_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER) )
)
datac_bytes_per_frame.append(
int(codec2.api.freedv_get_bits_per_modem_frame(datac_freedv[idx]) / 8)
)
datac_bytes_out.append(ctypes.create_string_buffer(datac_bytes_per_frame[idx]))
codec2.api.freedv_set_frames_per_burst(datac_freedv[idx], N_FRAMES_PER_BURST)
datac_buffer.append(codec2.audio_buffer(2 * AUDIO_FRAMES_PER_BUFFER))
datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p) resampler = codec2.resampler()
datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac1_freedv)/8)
datac1_bytes_out = create_string_buffer(datac1_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(datac1_freedv,N_FRAMES_PER_BURST)
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) # check if we want to use an audio device then do an pyaudio init
datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac3_freedv)/8) if AUDIO_INPUT_DEVICE != -1:
datac3_bytes_out = create_string_buffer(datac3_bytes_per_frame) p_audio = pyaudio.PyAudio()
codec2.api.freedv_set_frames_per_burst(datac3_freedv,N_FRAMES_PER_BURST) # auto search for loopback devices
datac3_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER) if AUDIO_INPUT_DEVICE == -2:
loopback_list = [
dev
for dev in range(p_audio.get_device_count())
if "Loopback: PCM" in p_audio.get_device_info_by_index(dev)["name"]
]
resampler = codec2.resampler() 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:
sys.exit()
# check if we want to use an audio device then do an pyaudio init print(
if AUDIO_INPUT_DEVICE != -1: f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE} "
p = pyaudio.PyAudio() f"DEVICE: {p_audio.get_device_info_by_index(AUDIO_INPUT_DEVICE)['name']} "
# auto search for loopback devices f"AUDIO SAMPLE RATE: {AUDIO_SAMPLE_RATE_RX}",
if AUDIO_INPUT_DEVICE == -2: file=sys.stderr,
loopback_list = [] )
for dev in range(0,p.get_device_count()): stream_rx = p_audio.open(
if 'Loopback: PCM' in p.get_device_info_by_index(dev)["name"]: format=pyaudio.paInt16,
loopback_list.append(dev) channels=1,
if len(loopback_list) >= 2: rate=AUDIO_SAMPLE_RATE_RX,
AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX frames_per_buffer=AUDIO_FRAMES_PER_BUFFER,
print(f"loopback_list rx: {loopback_list}", file=sys.stderr) input=True,
input_device_index=AUDIO_INPUT_DEVICE,
)
timeout = time.time() + MAX_TIME
print(time.time(), MAX_TIME, timeout)
receive = True
nread_exceptions = 0
# initial nin values
datac_nin = [0, 0, 0]
for idx in range(3):
datac_nin[idx] = codec2.api.freedv_nin(datac_freedv[idx])
def print_stats(time_datac0, time_datac1, time_datac3):
if not DEBUGGING_MODE:
return
time_datac = [time_datac0, time_datac1, time_datac3]
datac_rxstatus = ["", "", ""]
for idx in range(3):
datac_rxstatus[idx] = codec2.api.rx_sync_flags_to_text[
codec2.api.freedv_get_rx_status(datac_freedv[idx])
]
text_out = ""
for idx in range(3):
text_out += f"NIN{idx}: {datac_nin[idx]:5d} "
text_out += f"RX_STATUS{idx}: {datac_rxstatus[idx]:4s} "
text_out += f"TIME: {round(time_datac[idx], 4):.4f} | "
text_out = text_out.rstrip(" ").rstrip("|").rstrip(" ")
print(text_out, file=sys.stderr)
while receive and time.time() < timeout:
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)
if "Input overflowed" in str(err):
nread_exceptions += 1
if "Stream closed" in str(err):
print("Ending....")
receive = False
else: else:
quit() data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER * 2)
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,
input_device_index=AUDIO_INPUT_DEVICE
)
timeout = time.time() + TIMEOUT # insert samples in buffer
print(time.time(),TIMEOUT, timeout) audio_buffer = np.frombuffer(data_in48k, dtype=np.int16)
receive = True if len(audio_buffer) != AUDIO_FRAMES_PER_BUFFER:
nread_exceptions = 0 print("len(x)", len(audio_buffer))
receive = False
audio_buffer = resampler.resample48_to_8(audio_buffer)
# initial nin values for idx in range(3):
datac0_nin = codec2.api.freedv_nin(datac0_freedv) datac_buffer[idx].push(audio_buffer)
datac1_nin = codec2.api.freedv_nin(datac1_freedv) while datac_buffer[idx].nbuffer >= datac_nin[idx]:
datac3_nin = codec2.api.freedv_nin(datac3_freedv) # demodulate audio
time_start_datac[idx] = time.time()
nbytes = codec2.api.freedv_rawdatarx(
datac_freedv[idx],
datac_bytes_out[idx],
datac_buffer[idx].buffer.ctypes,
)
time_end_datac[idx] = time.time()
datac_buffer[idx].pop(datac_nin[idx])
datac_nin[idx] = codec2.api.freedv_nin(datac_freedv[idx])
if nbytes == datac_bytes_per_frame[idx]:
rx_total_frames_datac[idx] += 1
rx_frames_datac[idx] += 1
def print_stats(time_needed_datac0, time_needed_datac1, time_needed_datac3): if rx_frames_datac[idx] == N_FRAMES_PER_BURST:
if DEBUGGING_MODE: rx_frames_datac[idx] = 0
datac0_rxstatus = codec2.api.freedv_get_rx_status(datac0_freedv) rx_bursts_datac[idx] += 1
datac0_rxstatus = codec2.api.rx_sync_flags_to_text[datac0_rxstatus] time_needed_datac[idx] = time_end_datac[idx] - time_start_datac[idx]
print_stats(
time_needed_datac[0], time_needed_datac[1], time_needed_datac[2]
)
datac1_rxstatus = codec2.api.freedv_get_rx_status(datac1_freedv) if (
datac1_rxstatus = codec2.api.rx_sync_flags_to_text[datac1_rxstatus] rx_bursts_datac[0] == N_BURSTS
and rx_bursts_datac[1] == N_BURSTS
datac3_rxstatus = codec2.api.freedv_get_rx_status(datac3_freedv) and rx_bursts_datac[2] == N_BURSTS
datac3_rxstatus = codec2.api.rx_sync_flags_to_text[datac3_rxstatus] ):
receive = False
print("NIN0: %5d RX_STATUS0: %4s TIME: %4s | NIN1: %5d RX_STATUS1: %4s TIME: %4s | NIN3: %5d RX_STATUS3: %4s TIME: %4s" % \ if nread_exceptions:
(datac0_nin, datac0_rxstatus, round(time_needed_datac0, 4), datac1_nin, datac1_rxstatus, round(time_needed_datac1, 4) ,datac3_nin, datac3_rxstatus, round(time_needed_datac3, 4)), print(
file=sys.stderr) f"nread_exceptions {nread_exceptions:d} - receive audio lost! "
"Consider increasing Pyaudio frames_per_buffer...",
file=sys.stderr,
)
# INFO IF WE REACHED TIMEOUT
if time.time() > timeout:
print("TIMEOUT REACHED", file=sys.stderr)
while receive and time.time() < timeout: print(
if AUDIO_INPUT_DEVICE != -1: f"DATAC0: {rx_bursts_datac[0]}/{rx_total_frames_datac[0]} "
try: f"DATAC1: {rx_bursts_datac[1]}/{rx_total_frames_datac[1]} "
data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True) f"DATAC3: {rx_bursts_datac[2]}/{rx_total_frames_datac[2]}",
except OSError as err: file=sys.stderr,
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
else:
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2)
# 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)
datac0_buffer.push(x)
datac1_buffer.push(x)
datac3_buffer.push(x)
print_something = False
while datac0_buffer.nbuffer >= datac0_nin:
# demodulate audio
time_start_datac0 = time.time()
nbytes = codec2.api.freedv_rawdatarx(datac0_freedv, datac0_bytes_out, datac0_buffer.buffer.ctypes)
time_end_datac0 = time.time()
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
if rx_frames_datac0 == N_FRAMES_PER_BURST: if AUDIO_INPUT_DEVICE != -1:
rx_frames_datac0 = 0 stream_rx.close()
rx_bursts_datac0 = rx_bursts_datac0 + 1 p_audio.terminate()
time_needed_datac0 = time_end_datac0 - time_start_datac0
print_stats(time_needed_datac0, time_needed_datac1, time_needed_datac3)
while datac1_buffer.nbuffer >= datac1_nin:
# demodulate audio
time_start_datac1 = time.time()
nbytes = codec2.api.freedv_rawdatarx(datac1_freedv, datac1_bytes_out, datac1_buffer.buffer.ctypes)
time_end_datac1 = time.time()
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
if rx_frames_datac1 == N_FRAMES_PER_BURST:
rx_frames_datac1 = 0
rx_bursts_datac1 = rx_bursts_datac1 + 1
time_needed_datac1 = time_end_datac1 - time_start_datac1
print_stats(time_needed_datac0, time_needed_datac1, time_needed_datac3)
while datac3_buffer.nbuffer >= datac3_nin:
# demodulate audio
time_start_datac3 = time.time()
nbytes = codec2.api.freedv_rawdatarx(datac3_freedv, datac3_bytes_out, datac3_buffer.buffer.ctypes)
time_end_datac3 = time.time()
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
if rx_frames_datac3 == N_FRAMES_PER_BURST:
rx_frames_datac3 = 0
rx_bursts_datac3 = rx_bursts_datac3 + 1
time_needed_datac3 = time_end_datac3 - time_start_datac3
print_stats(time_needed_datac0, time_needed_datac1, time_needed_datac3)
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)
def parse_arguments():
# --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description="Simons TEST TNC")
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")
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_known_args()
return args
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 __name__ == "__main__":
test_mm_rx()
if AUDIO_INPUT_DEVICE != -1:
stream_rx.close()
p.terminate()

View file

@ -2,150 +2,187 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import ctypes
from ctypes import *
import pathlib
import pyaudio
import time
import threading
import audioop
import argparse import argparse
import ctypes
import sys import sys
sys.path.insert(0,'..') import time
import numpy as np
import pyaudio
sys.path.insert(0, "..")
from tnc import codec2 from tnc import codec2
import numpy as np
# GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='FreeDATA 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('--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")
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_DEVICE
# AUDIO PARAMETERS def test_mm_tx():
AUDIO_FRAMES_PER_BUFFER = 2400 # AUDIO PARAMETERS
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 AUDIO_FRAMES_PER_BUFFER = 2400
AUDIO_SAMPLE_RATE_TX = 48000 MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 AUDIO_SAMPLE_RATE_TX = 48000
assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0
if AUDIO_OUTPUT_DEVICE != -1: args = parse_arguments()
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,
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples
output=True,
output_device_index=AUDIO_OUTPUT_DEVICE,
)
resampler = codec2.resampler() if args.LIST:
modes = [codec2.api.FREEDV_MODE_DATAC0, codec2.api.FREEDV_MODE_DATAC1, codec2.api.FREEDV_MODE_DATAC3] p_audio = pyaudio.PyAudio()
for m in modes: for dev in range(p_audio.get_device_count()):
print("audiodev: ", dev, p_audio.get_device_info_by_index(dev)["name"])
freedv = cast(codec2.api.freedv_open(m), c_void_p) sys.exit()
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) N_BURSTS = args.N_BURSTS
mod_out_postamble = create_string_buffer(2*n_tx_postamble_modem_samples) N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
payload_per_frame = bytes_per_frame - 2
resampler = codec2.resampler()
# 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 # Data binary string
# convert crc to 2 byte hex string data_out = b"HELLO WORLD!"
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 modes = [
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) codec2.api.FREEDV_MODE_DATAC0,
txbuffer = bytes(mod_out_preamble) codec2.api.FREEDV_MODE_DATAC1,
codec2.api.FREEDV_MODE_DATAC3,
]
# create modulaton for N = FRAMESPERBURST and append it to txbuffer if AUDIO_OUTPUT_DEVICE != -1:
for n in range(1,N_FRAMES_PER_BURST+1): p_audio = pyaudio.PyAudio()
# Auto search for loopback devices
if AUDIO_OUTPUT_DEVICE == -2:
loopback_list = [
dev
for dev in range(p_audio.get_device_count())
if "Loopback: PCM" in p_audio.get_device_info_by_index(dev)["name"]
]
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) if len(loopback_list) >= 2:
codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer AUDIO_OUTPUT_DEVICE = loopback_list[1] # 0 = RX 1 = TX
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
else:
sys.exit()
txbuffer += bytes(mod_out) # pyaudio init
print(f"TX BURST: {i}/{N_BURSTS} FRAME: {n}/{N_FRAMES_PER_BURST}", file=sys.stderr) stream_tx = p_audio.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,
)
# append postamble to txbuffer for mode in modes:
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) freedv = ctypes.cast(codec2.api.freedv_open(mode), ctypes.c_void_p)
txbuffer += bytes(mod_out_postamble)
# append a delay between bursts as audio silence n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS) mod_out = ctypes.create_string_buffer(2 * n_tx_modem_samples)
mod_out_silence = create_string_buffer(samples_delay*2)
txbuffer += bytes(mod_out_silence)
# resample up to 48k (resampler works on np.int16) n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
x = np.frombuffer(txbuffer, dtype=np.int16) freedv
txbuffer_48k = resampler.resample8_to_48(x) )
mod_out_preamble = ctypes.create_string_buffer(2 * n_tx_preamble_modem_samples)
# check if we want to use an audio device or stdout n_tx_postamble_modem_samples = (
if AUDIO_OUTPUT_DEVICE != -1: codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
stream_tx.write(txbuffer_48k.tobytes()) )
else: mod_out_postamble = ctypes.create_string_buffer(
# this test needs a lot of time, so we are having a look at times... 2 * n_tx_postamble_modem_samples
starttime = time.time() )
# print data to terminal for piping the output to other programs
sys.stdout.buffer.write(txbuffer_48k)
sys.stdout.flush()
# and at least print the needed time to see which time we needed bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
timeneeded = time.time()-starttime payload_per_frame = bytes_per_frame - 2
#print(f"time: {timeneeded} buffer: {len(txbuffer)}", file=sys.stderr)
# 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()
buffer = bytearray(payload_per_frame)
# Set buffersize to length of data which will be send
buffer[: len(data_out)] = data_out
# Generate CRC16
crc = ctypes.c_ushort(
codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame)
)
# 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 brst 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 frm in range(1, N_FRAMES_PER_BURST + 1):
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
# Modulate DATA and save it into mod_out pointer
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
txbuffer += bytes(mod_out)
print(
f"TX BURST: {brst}/{N_BURSTS} FRAME: {frm}/{N_FRAMES_PER_BURST}",
file=sys.stderr,
)
# 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 = ctypes.create_string_buffer(samples_delay * 2)
txbuffer += bytes(mod_out_silence)
# Resample up to 48k (resampler works on np.int16)
audio_buffer = np.frombuffer(txbuffer, dtype=np.int16)
txbuffer_48k = resampler.resample8_to_48(audio_buffer)
# Check if we want to use an audio device or stdout
if AUDIO_OUTPUT_DEVICE != -1:
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_48k)
sys.stdout.flush()
# 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)
# 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_audio.terminate()
def parse_arguments():
# GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description="FreeDATA 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("--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",
)
args, _ = parser.parse_known_args()
return args
if __name__ == "__main__":
test_mm_tx()

View file

@ -3,37 +3,46 @@
# #
# Throw away test program to help understand the care and feeding of PyAudio # Throw away test program to help understand the care and feeding of PyAudio
import pyaudio
import numpy as np import numpy as np
import pyaudio
CHUNK = 1024 CHUNK = 1024
FS48 = 48000 FS48 = 48000
FTEST = 800 FTEST = 800
AMP = 16000 AMP = 16000
# 1. play sine wave out of default sound device
p = pyaudio.PyAudio() def test_pa():
stream = p.open(format=pyaudio.paInt16, # 1. play sine wave out of default sound device
channels=1,
rate=FS48,
frames_per_buffer=CHUNK,
output=True
)
f48 = open("out48.raw", mode='wb') p_audio = pyaudio.PyAudio()
t = 0; stream = p_audio.open(
for f in range(50): format=pyaudio.paInt16,
sine_48k = (AMP*np.cos(2*np.pi*np.arange(t,t+CHUNK)*FTEST/FS48)).astype(np.int16) channels=1,
t += CHUNK rate=FS48,
sine_48k.tofile(f48) frames_per_buffer=CHUNK,
stream.write(sine_48k.tobytes()) output=True,
sil_48k = np.zeros(CHUNK, dtype=np.int16) )
for f in range(50):
sil_48k.tofile(f48) with open("out48.raw", mode="wb") as f48:
stream.write(sil_48k) temp = 0
for _ in range(50):
stream.stop_stream() sine_48k = (
stream.close() AMP * np.cos(2 * np.pi * np.arange(temp, temp + CHUNK) * FTEST / FS48)
p.terminate() ).astype(np.int16)
f48.close() temp += CHUNK
sine_48k.tofile(f48)
stream.write(sine_48k.tobytes())
sil_48k = np.zeros(CHUNK, dtype=np.int16)
for _ in range(50):
sil_48k.tofile(f48)
stream.write(sil_48k)
stream.stop_stream()
stream.close()
p_audio.terminate()
if __name__ == "__main__":
test_pa()

120
test/test_resample_48_8.py Normal file
View file

@ -0,0 +1,120 @@
#!/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
#
# 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
# pylint: disable=global-statement, invalid-name, unused-import
import os
import sys
import codec2
import numpy as np
import pytest
# 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 = 180 # processing buffer size at 8 kHz
N48 = N8 * FDMDV_OS_48 # processing buffer size at 48 kHz
MEM8 = FDMDV_OS_TAPS_48_8K # 8kHz signal filter memory
MEM48 = FDMDV_OS_TAPS_48K # 48kHz signal filter memory
FRAMES = 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
# 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
def test_resampler():
"""
Test for the codec2 audio resampling routine
"""
# time indexes, we advance every frame
t = 0
t1 = 0
# output files to listen to/evaluate result
with open("in8.raw", mode="wb") as fin8:
with open("out48.raw", mode="wb") as f48:
with open("out8.raw", mode="wb") as fout8:
resampler = codec2.resampler()
# Generate FRAMES of a sine wave
for _ in range(FRAMES):
# Primary sine wave, which the down-sampling filter should retain.
sine_in8k = (
AMP * np.cos(2 * np.pi * np.arange(t, t + N8) * FTEST8 / FS8)
).astype(np.int16)
t += N8
sine_in8k.tofile(fin8)
sine_out48k = resampler.resample8_to_48(sine_in8k)
sine_out48k.tofile(f48)
# Add an interfering sine wave, which the down-sampling filter should (mostly) remove
sine_in48k = (
sine_out48k
+ (AMP / 2)
* np.cos(2 * np.pi * np.arange(t1, t1 + N48) * FINTER48 / FS48)
).astype(np.int16)
t1 += N48
sine_out8k = resampler.resample48_to_8(sine_in48k)
sine_out8k.tofile(fout8)
# os.unlink("out48.raw")
# Automated test evaluation --------------------------------------------
# The input and output signals will not be time aligned due to the filter
# delays, so compare the magnitude spectrum
# Read the raw audio files
in8k = np.fromfile("in8.raw", dtype=np.int16)
out8k = np.fromfile("out8.raw", dtype=np.int16)
assert len(in8k) == len(out8k)
# os.unlink("in8.raw")
# os.unlink("out8.raw")
# Apply hanning filter to raw input data samples
h = np.hanning(len(in8k))
S1 = np.abs(np.fft.fft(in8k * h))
S2 = np.abs(np.fft.fft(out8k * h))
# Calculate the ratio between signal and noise (error energy).
error = S1 - S2
error_energy = np.dot(error, error)
ratio = error_energy / np.dot(S1, S1)
ratio_dB = 10 * np.log10(ratio)
# Establish -40.0 as the noise ratio ceiling
threshdB = -40.0
print(f"ratio_dB: {ratio_dB:4.2}" % (ratio_dB))
assert ratio_dB < threshdB
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", sys.argv[0]])
if ecode == 0:
print("PASS")
else:
print("FAIL")

View file

@ -6,201 +6,242 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS @author: DJ2LS
""" """
import ctypes
from ctypes import *
import pathlib
import sounddevice as sd
import sys
import logging
import time
import threading
import sys
import argparse import argparse
import ctypes
import sys
import time
import numpy as np import numpy as np
sys.path.insert(0,'..') import sounddevice as sd
sys.path.insert(0, "..")
from tnc import codec2 from tnc import codec2
#--------------------------------------------GET PARAMETER INPUTS def test_rx():
parser = argparse.ArgumentParser(description='Simons TEST TNC') args = parse_arguments()
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:
if args.LIST:
devices = sd.query_devices(device=None, kind=None)
index = 0
for device in devices:
print(f"{index} {device['name']}")
index += 1
sd._terminate()
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
# check if we want to use an audio device then do an pyaudio init
if AUDIO_INPUT_DEVICE != -1:
# auto search for loopback devices
if AUDIO_INPUT_DEVICE == -2:
loopback_list = []
devices = sd.query_devices(device=None, kind=None) devices = sd.query_devices(device=None, kind=None)
index = 0 index = 0
for device in devices: for device in devices:
if 'Loopback: PCM' in device['name']: print(f"{index} {device['name']}")
print(index)
loopback_list.append(index)
index += 1 index += 1
sd._terminate()
if len(loopback_list) >= 1: sys.exit()
AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
else:
print("not enough audio loopback devices ready...")
print("you should wait about 30 seconds...")
sd._terminate() N_BURSTS = args.N_BURSTS
quit() N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
print(f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE}", file=sys.stderr) AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE
MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
DEBUGGING_MODE = args.DEBUGGING_MODE
MAX_TIME = args.TIMEOUT
# audio stream init # AUDIO PARAMETERS
stream_rx = sd.RawStream(channels=1, dtype='int16', device=AUDIO_INPUT_DEVICE, samplerate = AUDIO_SAMPLE_RATE_RX, blocksize=4800) # v-- consider increasing if you get nread_exceptions > 0
stream_rx.start() AUDIO_FRAMES_PER_BUFFER = 2400 * 2
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
# ---------------------------------------------------------------- AUDIO_SAMPLE_RATE_RX = 48000
# DATA CHANNEL INITIALISATION
# open codec2 instance # make sure our resampler will work
freedv = cast(codec2.api.freedv_open(MODE), c_void_p) assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
# get number of bytes per frame for mode # check if we want to use an audio device then do an pyaudio init
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)
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()
# time meassurement
time_start = 0
time_end = 0
# 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: if AUDIO_INPUT_DEVICE != -1:
try: # auto search for loopback devices
#data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True) if AUDIO_INPUT_DEVICE == -2:
data_in48k, overflowed = stream_rx.read(AUDIO_FRAMES_PER_BUFFER) loopback_list = []
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
else:
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2)
# insert samples in buffer
x = np.frombuffer(data_in48k, dtype=np.int16)
#print(x)
#x = data_in48k
x.tofile(frx)
if len(x) != AUDIO_FRAMES_PER_BUFFER:
receive = False
x = resampler.resample48_to_8(x)
audio_buffer.push(x)
# when we have enough samples call FreeDV Rx
while audio_buffer.nbuffer >= nin:
# start time measurement
time_start = time.time()
# demodulate audio
nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, audio_buffer.buffer.ctypes)
time_end = time.time()
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]
time_needed = time_end - time_start
print("nin: %5d rx_status: %4s naudio_buffer: %4d time: %4s" % \ devices = sd.query_devices(device=None, kind=None)
(nin,rx_status,audio_buffer.nbuffer, time_needed), file=sys.stderr)
if nbytes: for index, device in enumerate(devices):
total_n_bytes = total_n_bytes + nbytes if "Loopback: PCM" in device["name"]:
print(index)
if nbytes == bytes_per_frame: loopback_list.append(index)
rx_total_frames = rx_total_frames + 1
rx_frames = rx_frames + 1
if rx_frames == N_FRAMES_PER_BURST: if loopback_list:
rx_frames = 0 # 0 = RX 1 = TX
rx_bursts = rx_bursts + 1 AUDIO_INPUT_DEVICE = loopback_list[0]
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
if rx_bursts == N_BURSTS: else:
receive = False print("not enough audio loopback devices ready...")
print("you should wait about 30 seconds...")
if time.time() >= timeout:
print("TIMEOUT REACHED") sd._terminate()
sys.exit()
if nread_exceptions: print(f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE}", file=sys.stderr)
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
nread_exceptions, file=sys.stderr) # audio stream init
print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames} RX_ERRORS: {rx_errors}", file=sys.stderr) stream_rx = sd.RawStream(
frx.close() channels=1,
dtype="int16",
device=AUDIO_INPUT_DEVICE,
# and at last check if we had an opened audio instance and close it samplerate=AUDIO_SAMPLE_RATE_RX,
if AUDIO_INPUT_DEVICE != -1: blocksize=4800,
sd._terminate() )
stream_rx.start()
# ----------------------------------------------------------------
# DATA CHANNEL INITIALISATION
# open codec2 instance
freedv = ctypes.cast(codec2.api.freedv_open(MODE), ctypes.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 = ctypes.create_string_buffer(bytes_per_frame)
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() + MAX_TIME
receive = True
audio_buffer = codec2.audio_buffer(AUDIO_FRAMES_PER_BUFFER * 2)
resampler = codec2.resampler()
# time meassurement
time_start = 0
time_end = 0
# 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:
try:
# data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True)
data_in48k, overflowed = stream_rx.read(AUDIO_FRAMES_PER_BUFFER)
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
else:
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER * 2)
# insert samples in buffer
x = np.frombuffer(data_in48k, dtype=np.int16)
# print(x)
# x = data_in48k
x.tofile(frx)
if len(x) != AUDIO_FRAMES_PER_BUFFER:
receive = False
x = resampler.resample48_to_8(x)
audio_buffer.push(x)
# when we have enough samples call FreeDV Rx
while audio_buffer.nbuffer >= nin:
# start time measurement
time_start = time.time()
# demodulate audio
nbytes = codec2.api.freedv_rawdatarx(
freedv, bytes_out, audio_buffer.buffer.ctypes
)
time_end = time.time()
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]
time_needed = time_end - time_start
print(
f"nin: {nin:5d} rx_status: {rx_status:4s} "
f"naudio_buffer: {audio_buffer.nbuffer:4d} time: {time_needed:4f}",
file=sys.stderr,
)
if nbytes:
total_n_bytes += nbytes
if nbytes == bytes_per_frame:
rx_total_frames += 1
rx_frames += 1
if rx_frames == N_FRAMES_PER_BURST:
rx_frames = 0
rx_bursts += 1
if rx_bursts == N_BURSTS:
receive = False
if time.time() >= timeout:
print("TIMEOUT REACHED")
if nread_exceptions:
print(
f"nread_exceptions {nread_exceptions:d} - receive audio lost! "
"Consider increasing Pyaudio frames_per_buffer...",
file=sys.stderr,
)
print(
f"RECEIVED BURSTS: {rx_bursts} "
f"RECEIVED FRAMES: {rx_total_frames} "
f"RX_ERRORS: {rx_errors}",
file=sys.stderr,
)
frx.close()
# and at last check if we had an opened audio instance and close it
if AUDIO_INPUT_DEVICE != -1:
sd._terminate()
def parse_arguments():
# --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description="Simons TEST TNC")
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_known_args()
return args
if __name__ == "__main__":
test_rx()

54
test/test_tnc.py Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import multiprocessing
import os
import sys
import time
import pytest
# pylint: disable=wrong-import-position
sys.path.insert(0, "..")
sys.path.insert(0, "../tnc")
sys.path.insert(0, "test")
import test_tnc_IRS as irs
import test_tnc_ISS as iss
# These do not update static.INFO.
# "CONNECT", "SEND_TEST_FRAME"
@pytest.mark.parametrize("command", ["CQ", "PING", "BEACON"])
def test_tnc(command):
iss_proc = multiprocessing.Process(target=iss.t_arq_iss, args=[command])
irs_proc = multiprocessing.Process(target=irs.t_arq_irs, args=[command])
# print("Starting threads.")
iss_proc.start()
irs_proc.start()
time.sleep(12)
# print("Terminating threads.")
irs_proc.terminate()
iss_proc.terminate()
irs_proc.join()
iss_proc.join()
for idx in range(2):
try:
os.unlink(f"/tmp/hfchannel{idx+1}")
except FileNotFoundError as fnfe:
print(f"Unlinking pipe: {fnfe}")
assert iss_proc.exitcode == 0, f"Transmit side failed test. {iss_proc}"
assert irs_proc.exitcode == 0, f"Receive side failed test. {irs_proc}"
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-s", "-v", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

81
test/test_tnc_IRS.py Normal file
View file

@ -0,0 +1,81 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS
"""
import os
import sys
import time
sys.path.insert(0, "..")
sys.path.insert(0, "../tnc")
import data_handler
import helpers
import modem
import static
IRS_original_arq_cleanup = object
MESSAGE: str
def irs_arq_cleanup():
"""Replacement for modem.arq_cleanup to detect when to exit process."""
if "TRANSMISSION;STOPPED" in static.INFO:
print(f"{static.INFO=}")
time.sleep(2)
# sys.exit does not terminate threads.
# pylint: disable=protected-access
if f"{MESSAGE};RECEIVING" not in static.INFO:
print(f"{MESSAGE} was not received.")
os._exit(1)
os._exit(0)
IRS_original_arq_cleanup()
def t_arq_irs(*args):
# pylint: disable=global-statement
global IRS_original_arq_cleanup, MESSAGE
MESSAGE = args[0]
# enable testmode
data_handler.TESTMODE = True
modem.TESTMODE = True
modem.RXCHANNEL = "/tmp/hfchannel2"
modem.TXCHANNEL = "/tmp/hfchannel1"
static.HAMLIB_RADIOCONTROL = "disabled"
static.RESPOND_TO_CQ = True
mycallsign = bytes("DN2LS-2", "utf-8")
mycallsign = helpers.callsign_to_bytes(mycallsign)
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
static.MYGRID = bytes("AA12aa", "utf-8")
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# start data handler
tnc = data_handler.DATA()
# Inject a way to exit the TNC infinite loop
IRS_original_arq_cleanup = tnc.arq_cleanup
tnc.arq_cleanup = irs_arq_cleanup
# start modem
t_modem = modem.RF()
# Set timeout
timeout = time.time() + 10
while time.time() < timeout:
time.sleep(0.1)
assert not "TIMEOUT!"
if __name__ == "__main__":
print("This cannot be run as an application.")
sys.exit(1)

118
test/test_tnc_ISS.py Normal file
View file

@ -0,0 +1,118 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS
"""
import os
import sys
import time
sys.path.insert(0, "..")
sys.path.insert(0, "../tnc")
import data_handler
import helpers
import modem
import static
ISS_original_arq_cleanup = object
MESSAGE: str
def iss_arq_cleanup():
"""Replacement for modem.arq_cleanup to detect when to exit process."""
if "TRANSMISSION;STOPPED" in static.INFO:
print(f"{static.INFO=}")
time.sleep(1)
# sys.exit does not terminate threads.
# pylint: disable=protected-access
if f"{MESSAGE};SENDING" not in static.INFO:
print(f"{MESSAGE} was not sent.")
os._exit(1)
os._exit(0)
ISS_original_arq_cleanup()
def t_arq_iss(*args):
# pylint: disable=global-statement
global ISS_original_arq_cleanup, MESSAGE
MESSAGE = args[0]
# enable testmode
data_handler.TESTMODE = True
modem.TESTMODE = True
modem.RXCHANNEL = "/tmp/hfchannel1"
modem.TXCHANNEL = "/tmp/hfchannel2"
static.HAMLIB_RADIOCONTROL = "disabled"
mycallsign = bytes("DJ2LS-2", "utf-8")
mycallsign = helpers.callsign_to_bytes(mycallsign)
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
static.MYGRID = bytes("AA12aa", "utf-8")
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
dxcallsign = b"DN2LS-0"
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
static.DXCALLSIGN = dxcallsign
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}'
# start data handler
tnc = data_handler.DATA()
# Inject a way to exit the TNC infinite loop
ISS_original_arq_cleanup = tnc.arq_cleanup
tnc.arq_cleanup = iss_arq_cleanup
# start modem
t_modem = modem.RF()
# mode = codec2.freedv_get_mode_value_by_name(FREEDV_MODE)
# n_frames_per_burst = N_FRAMES_PER_BURST
# add command to data qeue
"""
elif data[0] == 'ARQ_RAW':
# [0] ARQ_RAW
# [1] DATA_OUT bytes
# [2] MODE int
# [3] N_FRAMES_PER_BURST int
# [4] self.transmission_uuid str
# [5] mycallsign with ssid
"""
# data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_RAW', bytes_out, 255, n_frames_per_burst, '123', b'DJ2LS-0'])
# for _ in range(4):
if MESSAGE in ["BEACON"]:
data_handler.DATA_QUEUE_TRANSMIT.put([MESSAGE, 5, True])
elif MESSAGE in ["PING", "CONNECT"]:
data_handler.DATA_QUEUE_TRANSMIT.put([MESSAGE, dxcallsign])
else:
data_handler.DATA_QUEUE_TRANSMIT.put([MESSAGE])
time.sleep(1.5)
# for i in range(4):
# data_handler.DATA_QUEUE_TRANSMIT.put(['PING', b'DN2LS-2'])
data_handler.DATA_QUEUE_TRANSMIT.put(["STOP"])
# Set timeout
timeout = time.time() + 10
while time.time() < timeout:
time.sleep(0.1)
assert not "TIMEOUT!"
if __name__ == "__main__":
print("This cannot be run as an application.")
sys.exit(-1)

225
test/test_tnc_states.py Normal file
View file

@ -0,0 +1,225 @@
"""
Tests for the FreeDATA TNC state machine.
"""
import sys
import pytest
# pylint: disable=wrong-import-position
sys.path.insert(0, "..")
sys.path.insert(0, "../tnc")
import data_handler
import helpers
import static
def print_frame(data: bytearray):
"""
Pretty-print the provided frame.
:param data: Frame to be output
:type data: bytearray
"""
print(f"Type : {int(data[0])}")
print(f"DXCRC : {bytes(data[1:4])}")
print(f"CallCRC: {bytes(data[4:7])}")
print(f"Call : {helpers.bytes_to_callsign(data[7:13])}")
def t_create_frame(frame_type: int, mycall: str, dxcall: str) -> bytearray:
"""
Generate the requested frame.
:param frame_type: The numerical type of the desired frame.
:type frame_type: int
:param mycall: Callsign of the near station
:type mycall: str
:param dxcall: Callsign of the far station
:type dxcall: str
:return: Bytearray of the requested frame
:rtype: bytearray
"""
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
mycallsign_crc = helpers.get_crc_24(mycallsign)
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
frame = bytearray(14)
frame[:1] = bytes([frame_type])
frame[1:4] = dxcallsign_crc
frame[4:7] = mycallsign_crc
frame[7:13] = mycallsign_bytes
return frame
def t_create_session_close(mycall: str, dxcall: str) -> bytearray:
"""
Generate the session_close frame.
:param mycall: Callsign of the near station
:type mycall: str
:param dxcall: Callsign of the far station
:type dxcall: str
:return: Bytearray of the requested frame
:rtype: bytearray
"""
return t_create_frame(223, mycall, dxcall)
def t_create_start_session(mycall: str, dxcall: str) -> bytearray:
"""
Generate the create_session frame.
:param mycall: Callsign of the near station
:type mycall: str
:param dxcall: Callsign of the far station
:type dxcall: str
:return: Bytearray of the requested frame
:rtype: bytearray
"""
return t_create_frame(221, mycall, dxcall)
def t_tsh_dummy():
"""Replacement function for transmit_session_heartbeat"""
print("In transmit_session_heartbeat")
@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "M4AWQ-4"])
@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"])
def test_valid_disconnect(mycall: str, dxcall: str):
"""
Execute test to validate that receiving a session open frame sets the correct machine
state.
:param mycall: Callsign of the near station
:type mycall: str
:param dxcall: Callsign of the far station
:type dxcall: str
:return: Bytearray of the requested frame
:rtype: bytearray
"""
# Set the SSIDs we'll use for this test.
static.SSID_LIST = [0, 1, 2, 3, 4]
# Setup the static parameters for the connection.
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
static.MYCALLSIGN = mycallsign
static.MYCALLSIGN_CRC = helpers.get_crc_24(mycallsign)
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
static.DXCALLSIGN = dxcallsign
static.DXCALLSIGN_CRC = helpers.get_crc_24(dxcallsign)
# Create the TNC
tnc = data_handler.DATA()
# Replace the heartbeat transmit routine with our own, a No-Op.
tnc.transmit_session_heartbeat = t_tsh_dummy
# Create packet to be 'received' by this station.
create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall)
print_frame(create_frame)
tnc.received_session_opener(create_frame)
assert static.ARQ_SESSION is True
assert static.TNC_STATE == "BUSY"
assert static.ARQ_SESSION_STATE == "connecting"
# Create packet to be 'received' by this station.
close_frame = t_create_session_close(mycall=dxcall, dxcall=mycall)
print_frame(close_frame)
tnc.received_session_close(close_frame)
assert helpers.callsign_to_bytes(static.MYCALLSIGN) == mycallsign_bytes
assert helpers.callsign_to_bytes(static.DXCALLSIGN) == dxcallsign_bytes
assert static.ARQ_SESSION is False
assert static.TNC_STATE == "IDLE"
assert static.ARQ_SESSION_STATE == "disconnected"
@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"])
@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"])
def test_foreign_disconnect(mycall: str, dxcall: str):
"""
Execute test to validate that receiving a session open frame sets the correct machine
state.
:param mycall: Callsign of the near station
:type mycall: str
:param dxcall: Callsign of the far station
:type dxcall: str
:return: Bytearray of the requested frame
:rtype: bytearray
"""
# Setup the static parameters for the connection.
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
static.MYCALLSIGN = mycallsign
static.MYCALLSIGN_CRC = helpers.get_crc_24(mycallsign)
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
static.DXCALLSIGN = dxcallsign
static.DXCALLSIGN_CRC = helpers.get_crc_24(dxcallsign)
# Create the TNC
tnc = data_handler.DATA()
# Replace the heartbeat transmit routine with a No-Op.
tnc.transmit_session_heartbeat = t_tsh_dummy
# Create frame to be 'received' by this station.
create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall)
print_frame(create_frame)
tnc.received_session_opener(create_frame)
assert helpers.callsign_to_bytes(static.MYCALLSIGN) == mycallsign_bytes
assert helpers.callsign_to_bytes(static.DXCALLSIGN) == dxcallsign_bytes
assert static.ARQ_SESSION is True
assert static.TNC_STATE == "BUSY"
assert static.ARQ_SESSION_STATE == "connecting"
# Set up a frame from a non-associated station.
foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0")
foreigncall = helpers.bytes_to_callsign(foreigncall_bytes)
close_frame = t_create_session_close("ZZ0ZZ-0", "ZZ0ZZ-0")
print_frame(close_frame)
assert (
helpers.check_callsign(static.DXCALLSIGN, bytes(close_frame[4:7]))[0] is False
)
assert helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True
# Send the non-associated session close frame to the TNC
tnc.received_session_close(close_frame)
assert helpers.callsign_to_bytes(static.MYCALLSIGN) == helpers.callsign_to_bytes(
mycall
)
assert helpers.callsign_to_bytes(static.DXCALLSIGN) == helpers.callsign_to_bytes(
dxcall
)
assert static.ARQ_SESSION is True
assert static.TNC_STATE == "BUSY"
assert static.ARQ_SESSION_STATE == "connecting"
if __name__ == "__main__":
# Run pytest with the current script as the filename.
ecode = pytest.main(["-v", sys.argv[0]])
if ecode == 0:
print("errors: 0")
else:
print(ecode)

View file

@ -2,174 +2,217 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import ctypes
from ctypes import *
import pathlib
import sounddevice as sd
import time
import argparse import argparse
import ctypes
import sys import sys
sys.path.insert(0,'..')
from tnc import codec2
import numpy as np import numpy as np
import sounddevice as sd
# GET PARAMETER INPUTS sys.path.insert(0, "..")
parser = argparse.ArgumentParser(description='Simons TEST TNC') from tnc import codec2
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")
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
parser.add_argument('--testframes', dest="TESTFRAMES", action="store_true", default=False, help="list audio devices by number and exit")
args = parser.parse_args() def test_tx():
args = parse_arguments()
if args.LIST: if args.LIST:
devices = sd.query_devices(device=None, kind=None)
index = 0
for device in devices:
print(f"{index} {device['name']}")
index += 1
sd._terminate()
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_DEVICE
MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
# AUDIO PARAMETERS
AUDIO_FRAMES_PER_BUFFER = 2400
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 != -1:
# auto search for loopback devices
if AUDIO_OUTPUT_DEVICE == -2:
loopback_list = []
devices = sd.query_devices(device=None, kind=None) devices = sd.query_devices(device=None, kind=None)
index = 0
for device in devices:
if 'Loopback: PCM' in device['name']:
print(index)
loopback_list.append(index)
index += 1
if len(loopback_list) >= 1:
AUDIO_OUTPUT_DEVICE = loopback_list[len(loopback_list)-1] #0 = RX 1 = TX
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
else:
print("not enough audio loopback devices ready...")
print("you should wait about 30 seconds...")
sd._terminate()
quit()
print(f"AUDIO OUTPUT DEVICE: {AUDIO_OUTPUT_DEVICE}", file=sys.stderr)
# audio stream init for index, device in enumerate(devices):
stream_tx = sd.RawStream(channels=1, dtype='int16', device=(0, AUDIO_OUTPUT_DEVICE), samplerate = AUDIO_SAMPLE_RATE_TX, blocksize=4800) print(f"{index} {device['name']}")
resampler = codec2.resampler() sd._terminate()
sys.exit()
# data binary string N_BURSTS = args.N_BURSTS
if args.TESTFRAMES: N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
data_out = bytearray(14) DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
data_out[:1] = bytes([255]) AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
data_out[1:2] = bytes([1])
data_out[2:] = b'HELLO WORLD'
else:
data_out = b'HELLO WORLD!'
MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
# ---------------------------------------------------------------- # AUDIO PARAMETERS
AUDIO_FRAMES_PER_BUFFER = 2400
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
# 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
# init buffer for data
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 = 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 = codec2.api.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(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
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):
# 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)
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)
# 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*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 (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: if AUDIO_OUTPUT_DEVICE != -1:
stream_tx.start() # auto search for loopback devices
stream_tx.write(txbuffer_48k) if AUDIO_OUTPUT_DEVICE == -2:
loopback_list = []
devices = sd.query_devices(device=None, kind=None)
for index, device in enumerate(devices):
if "Loopback: PCM" in device["name"]:
print(index)
loopback_list.append(index)
if loopback_list:
# 0 = RX 1 = TX
AUDIO_OUTPUT_DEVICE = loopback_list[-1]
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
else:
print("not enough audio loopback devices ready...")
print("you should wait about 30 seconds...")
sd._terminate()
sys.exit()
print(f"AUDIO OUTPUT DEVICE: {AUDIO_OUTPUT_DEVICE}", file=sys.stderr)
# audio stream init
stream_tx = sd.RawStream(
channels=1,
dtype="int16",
device=(0, AUDIO_OUTPUT_DEVICE),
samplerate=AUDIO_SAMPLE_RATE_TX,
blocksize=4800,
)
resampler = codec2.resampler()
# data binary string
if args.TESTFRAMES:
data_out = bytearray(14)
data_out[:1] = bytes([255])
data_out[1:2] = bytes([1])
data_out[2:] = b"HELLO WORLD"
else: else:
# print data to terminal for piping the output to other programs data_out = b"HELLO WORLD!"
sys.stdout.buffer.write(txbuffer_48k)
sys.stdout.flush() # ----------------------------------------------------------------
# Open codec2 instance
freedv = ctypes.cast(codec2.api.freedv_open(MODE), ctypes.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
# Init buffer for data
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
# Init buffer for preample
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
freedv
)
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
# Init buffer for postamble
n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(
freedv
)
mod_out_postamble = ctypes.create_string_buffer(n_tx_postamble_modem_samples * 2)
# Create buffer for data
# Use this if CRC16 checksum is required (DATA1-3)
buffer = bytearray(payload_bytes_per_frame)
# set buffersize to length of data which will be send
buffer[: len(data_out)] = data_out
# Create CRC for data frame - we are using the CRC function shipped with codec2 to avoid
# CRC algorithm incompatibilities
# generate CRC16
crc = ctypes.c_ushort(
codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)
)
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 brst 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 frm in range(1, N_FRAMES_PER_BURST + 1):
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
# Modulate DATA and save it into mod_out pointer
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
txbuffer += bytes(mod_out)
print(
f"TX BURST: {brst}/{N_BURSTS} FRAME: {frm}/{N_FRAMES_PER_BURST}",
file=sys.stderr,
)
# 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 = ctypes.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 (resampler works on np.int16)
np_buffer = np.frombuffer(txbuffer, dtype=np.int16)
txbuffer_48k = resampler.resample8_to_48(np_buffer)
# Check if we want to use an audio device or stdout
if AUDIO_OUTPUT_DEVICE != -1:
stream_tx.start()
stream_tx.write(txbuffer_48k)
else:
# Print data to terminal for piping the output to other programs
sys.stdout.buffer.write(txbuffer_48k)
sys.stdout.flush()
# and at last check if we had an opened audio instance and close it
if AUDIO_OUTPUT_DEVICE != -1:
sd._terminate()
# and at last check if we had an opened audio instance and close it def parse_arguments():
if AUDIO_OUTPUT_DEVICE != -1: # GET PARAMETER INPUTS
sd._terminate() parser = argparse.ArgumentParser(description="Simons TEST TNC")
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",
)
parser.add_argument(
"--list",
dest="LIST",
action="store_true",
help="list audio devices by number and exit",
)
parser.add_argument(
"--testframes",
dest="TESTFRAMES",
action="store_true",
default=False,
help="list audio devices by number and exit",
)
args, _ = parser.parse_known_args()
return args
if __name__ == "__main__":
test_tx()

View file

@ -191,7 +191,7 @@ class DAEMON:
options.append('--radiocontrol') options.append('--radiocontrol')
options.append(data[13]) options.append(data[13])
if data[13] != 'rigctld': if data[13] == 'rigctld':
options.append('--rigctld_ip') options.append('--rigctld_ip')
options.append(data[14]) options.append(data[14])

File diff suppressed because it is too large Load diff

View file

@ -620,8 +620,8 @@ class RF():
snr = round(modem_stats_snr, 1) snr = round(modem_stats_snr, 1)
structlog.get_logger("structlog").info("[MDM] calculate_snr: ", snr=snr) structlog.get_logger("structlog").info("[MDM] calculate_snr: ", snr=snr)
# print(snr) # static.SNR = np.clip(snr, 0, 255) # limit to max value of 255
static.SNR = np.clip(snr, 0, 255) # limit to max value of 255 static.SNR = np.clip(snr, -128, 128) # limit to max value of -128/128 as a possible fix of #188
return static.SNR return static.SNR
except Exception as e: except Exception as e:
structlog.get_logger("structlog").error(f"[MDM] calculate_snr: Exception: {e}") structlog.get_logger("structlog").error(f"[MDM] calculate_snr: Exception: {e}")

View file

@ -51,7 +51,7 @@ class radio():
""" """
self.hostname = rigctld_ip self.hostname = rigctld_ip
self.port = int(rigctld_port) self.port = int(rigctld_port)
if self.connect(): if self.connect():
logging.debug("Rigctl intialized") logging.debug("Rigctl intialized")
return True return True