2022-05-22 20:28:55 +00:00
|
|
|
"""
|
|
|
|
Python interface to the C-language codec2 library.
|
|
|
|
"""
|
2021-12-12 20:52:03 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
2022-05-22 20:28:55 +00:00
|
|
|
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
2022-05-11 22:10:59 +00:00
|
|
|
|
2021-12-12 20:52:03 +00:00
|
|
|
import ctypes
|
2024-04-04 20:46:12 +00:00
|
|
|
from ctypes import *
|
|
|
|
import hashlib
|
2022-05-11 22:10:59 +00:00
|
|
|
import glob
|
2022-01-12 06:27:42 +00:00
|
|
|
import os
|
2022-05-11 22:10:59 +00:00
|
|
|
import sys
|
2021-12-13 18:00:38 +00:00
|
|
|
from enum import Enum
|
2021-12-20 21:57:13 +00:00
|
|
|
from threading import Lock
|
2022-05-11 22:10:59 +00:00
|
|
|
|
|
|
|
import numpy as np
|
2022-02-04 15:24:04 +00:00
|
|
|
import structlog
|
|
|
|
|
2022-06-01 00:35:35 +00:00
|
|
|
log = structlog.get_logger("codec2")
|
2021-12-12 20:52:03 +00:00
|
|
|
|
2023-04-21 08:22:34 +00:00
|
|
|
|
2021-12-13 18:00:38 +00:00
|
|
|
# Enum for codec2 modes
|
|
|
|
class FREEDV_MODE(Enum):
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-11 22:10:59 +00:00
|
|
|
Enumeration for codec2 modes and names
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2023-12-16 09:28:30 +00:00
|
|
|
signalling = 19
|
2024-03-31 16:53:46 +00:00
|
|
|
signalling_ack = 20
|
2022-05-22 20:28:55 +00:00
|
|
|
datac0 = 14
|
|
|
|
datac1 = 10
|
|
|
|
datac3 = 12
|
2023-04-21 08:22:34 +00:00
|
|
|
datac4 = 18
|
|
|
|
datac13 = 19
|
2024-03-29 13:39:55 +00:00
|
|
|
datac14 = 20
|
2024-04-04 20:46:12 +00:00
|
|
|
data_ofdm_500 = 21500
|
|
|
|
data_ofdm_2438 = 212438
|
2024-04-06 19:14:36 +00:00
|
|
|
qam16c2 = 22
|
2023-05-14 16:05:47 +00:00
|
|
|
|
2023-04-23 13:59:30 +00:00
|
|
|
class FREEDV_MODE_USED_SLOTS(Enum):
|
|
|
|
"""
|
|
|
|
Enumeration for codec2 used slots
|
|
|
|
"""
|
|
|
|
sig0 = [False, False, True, False, False]
|
|
|
|
sig1 = [False, False, True, False, False]
|
|
|
|
datac0 = [False, False, True, False, False]
|
|
|
|
datac1 = [False, True, True, True, False]
|
|
|
|
datac3 = [False, False, True, False, False]
|
|
|
|
datac4 = [False, False, True, False, False]
|
|
|
|
datac13 = [False, False, True, False, False]
|
2024-03-29 13:39:55 +00:00
|
|
|
datac14 = [False, False, True, False, False]
|
2024-04-04 20:46:12 +00:00
|
|
|
data_ofdm_500 = [False, False, True, False, False]
|
|
|
|
data_ofdm_2438 = [True, True, True, True, True]
|
2024-04-06 19:14:36 +00:00
|
|
|
qam16c2 = [True, True, True, True, True]
|
2023-04-23 13:59:30 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Function for returning the mode value
|
|
|
|
def freedv_get_mode_value_by_name(mode: str) -> int:
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-11 22:10:59 +00:00
|
|
|
Get the codec2 mode by entering its string
|
2022-03-04 15:50:32 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
Args:
|
2022-05-22 20:28:55 +00:00
|
|
|
mode: String representation of the codec2 mode.
|
2022-03-04 15:50:32 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
Returns:
|
|
|
|
int
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-22 20:28:55 +00:00
|
|
|
return FREEDV_MODE[mode.lower()].value
|
|
|
|
|
2022-01-12 06:27:42 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Function for returning the mode name
|
|
|
|
def freedv_get_mode_name_by_value(mode: int) -> str:
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-22 20:28:55 +00:00
|
|
|
Get the codec2 mode name as string
|
2022-03-04 15:50:32 +00:00
|
|
|
Args:
|
2022-05-22 20:28:55 +00:00
|
|
|
mode: Integer value of the codec2 mode.
|
2022-03-04 15:50:32 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
Returns:
|
|
|
|
string
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-01-15 18:23:28 +00:00
|
|
|
return FREEDV_MODE(mode).name
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
|
2024-02-29 19:34:27 +00:00
|
|
|
# Get the directory of the current script file
|
|
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
sys.path.append(script_dir)
|
|
|
|
# Use script_dir to construct the paths for file search
|
2022-05-22 20:28:55 +00:00
|
|
|
if sys.platform == "linux":
|
2024-02-29 19:34:27 +00:00
|
|
|
files = glob.glob(os.path.join(script_dir, "**/*libcodec2*"), recursive=True)
|
|
|
|
files.append(os.path.join(script_dir, "libcodec2.so"))
|
2022-05-22 20:28:55 +00:00
|
|
|
elif sys.platform == "darwin":
|
2023-01-22 22:35:52 +00:00
|
|
|
if hasattr(sys, "_MEIPASS"):
|
2024-02-29 19:34:27 +00:00
|
|
|
files = glob.glob(os.path.join(getattr(sys, "_MEIPASS"), '**/*libcodec2*'), recursive=True)
|
2023-01-22 22:35:52 +00:00
|
|
|
else:
|
2024-02-29 19:34:27 +00:00
|
|
|
files = glob.glob(os.path.join(script_dir, "**/*libcodec2*.dylib"), recursive=True)
|
2022-05-22 20:28:55 +00:00
|
|
|
elif sys.platform in ["win32", "win64"]:
|
2024-02-29 19:34:27 +00:00
|
|
|
files = glob.glob(os.path.join(script_dir, "**\\*libcodec2*.dll"), recursive=True)
|
2022-01-11 15:53:35 +00:00
|
|
|
else:
|
2022-02-04 15:24:04 +00:00
|
|
|
files = []
|
2022-05-11 22:10:59 +00:00
|
|
|
api = None
|
2022-02-04 15:24:04 +00:00
|
|
|
for file in files:
|
2021-12-19 18:45:08 +00:00
|
|
|
try:
|
2022-02-04 15:24:04 +00:00
|
|
|
api = ctypes.CDLL(file)
|
2023-10-21 20:51:40 +00:00
|
|
|
#log.info("[C2 ] Libcodec2 loaded", path=file)
|
2021-12-19 18:45:08 +00:00
|
|
|
break
|
2022-05-26 01:23:30 +00:00
|
|
|
except OSError as err:
|
2024-01-14 20:05:53 +00:00
|
|
|
pass
|
|
|
|
#log.info("[C2 ] Error: Libcodec2 found but not loaded", path=file, e=err)
|
2022-02-04 15:24:04 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Quit module if codec2 cant be loaded
|
2022-05-22 20:28:55 +00:00
|
|
|
if api is None or "api" not in locals():
|
2023-10-21 20:51:40 +00:00
|
|
|
log.critical("[C2 ] Error: Libcodec2 not loaded - Exiting")
|
2022-05-11 22:10:59 +00:00
|
|
|
sys.exit(1)
|
2024-04-04 20:46:12 +00:00
|
|
|
log.info("[C2 ] Libcodec2 loaded...", path=file)
|
2022-05-09 00:41:49 +00:00
|
|
|
# ctypes function init
|
2022-04-11 09:10:32 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
# api.freedv_set_tuning_range.restype = ctypes.c_int
|
|
|
|
# api.freedv_set_tuning_range.argype = [ctypes.c_void_p, ctypes.c_float, ctypes.c_float]
|
2022-02-19 19:45:57 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_open.argype = [ctypes.c_int] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_open.restype = ctypes.c_void_p
|
2021-12-12 20:52:03 +00:00
|
|
|
|
2023-04-25 13:46:17 +00:00
|
|
|
api.freedv_set_sync.argype = [ctypes.c_void_p, ctypes.c_int] # type: ignore
|
|
|
|
api.freedv_set_sync.restype = ctypes.c_void_p
|
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_open_advanced.argtype = [ctypes.c_int, ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_open_advanced.restype = ctypes.c_void_p
|
2022-03-20 13:51:30 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_get_bits_per_modem_frame.argtype = [ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_get_bits_per_modem_frame.restype = ctypes.c_int
|
2021-12-12 20:52:03 +00:00
|
|
|
|
2022-10-12 08:45:17 +00:00
|
|
|
api.freedv_get_modem_extended_stats.argtype = [ctypes.c_void_p, ctypes.c_void_p]
|
|
|
|
api.freedv_get_modem_extended_stats.restype = ctypes.c_int
|
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_nin.argtype = [ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_nin.restype = ctypes.c_int
|
2021-12-12 20:52:03 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_rawdatarx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_rawdatarx.restype = ctypes.c_int
|
2021-12-12 20:52:03 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_rawdatatx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_rawdatatx.restype = ctypes.c_int
|
2021-12-12 21:00:50 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_rawdatapostambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_rawdatapostambletx.restype = ctypes.c_int
|
2021-12-12 21:00:50 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_rawdatapreambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_rawdatapreambletx.restype = ctypes.c_int
|
2021-12-12 21:00:50 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_get_n_max_modem_samples.restype = ctypes.c_int
|
2021-12-12 20:52:03 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_set_frames_per_burst.argtype = [ctypes.c_void_p, ctypes.c_int] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_set_frames_per_burst.restype = ctypes.c_void_p
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_get_rx_status.argtype = [ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_get_rx_status.restype = ctypes.c_int
|
2021-12-12 21:00:50 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_get_modem_stats.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_get_modem_stats.restype = ctypes.c_int
|
2021-12-13 20:11:09 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_get_n_tx_postamble_modem_samples.argtype = [ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_get_n_tx_postamble_modem_samples.restype = ctypes.c_int
|
2021-12-12 21:00:50 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_get_n_tx_preamble_modem_samples.argtype = [ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_get_n_tx_preamble_modem_samples.restype = ctypes.c_int
|
2021-12-12 21:00:50 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_get_n_tx_modem_samples.argtype = [ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_get_n_tx_modem_samples.restype = ctypes.c_int
|
2021-12-12 21:00:50 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p] # type: ignore
|
2022-05-11 22:10:59 +00:00
|
|
|
api.freedv_get_n_max_modem_samples.restype = ctypes.c_int
|
2021-12-15 19:45:33 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.FREEDV_FS_8000 = 8000 # type: ignore
|
2023-04-21 08:22:34 +00:00
|
|
|
|
2022-03-24 19:49:13 +00:00
|
|
|
|
|
|
|
# ------- MODEM STATS STRUCTURES
|
2022-10-12 08:45:17 +00:00
|
|
|
MODEM_STATS_NC_MAX = 50 + 1 * 2
|
2023-04-21 20:23:38 +00:00
|
|
|
MODEM_STATS_NR_MAX = 320 * 2
|
2022-05-22 20:28:55 +00:00
|
|
|
MODEM_STATS_ET_MAX = 8
|
|
|
|
MODEM_STATS_EYE_IND_MAX = 160
|
|
|
|
MODEM_STATS_NSPEC = 512
|
|
|
|
MODEM_STATS_MAX_F_HZ = 4000
|
|
|
|
MODEM_STATS_MAX_F_EST = 4
|
|
|
|
|
|
|
|
|
2024-04-04 20:46:12 +00:00
|
|
|
|
|
|
|
|
2022-03-24 19:49:13 +00:00
|
|
|
class MODEMSTATS(ctypes.Structure):
|
2022-05-22 20:28:55 +00:00
|
|
|
"""Modem statistics structure"""
|
|
|
|
|
2022-03-24 19:49:13 +00:00
|
|
|
_fields_ = [
|
|
|
|
("Nc", ctypes.c_int),
|
|
|
|
("snr_est", ctypes.c_float),
|
2022-05-22 20:28:55 +00:00
|
|
|
("rx_symbols", (ctypes.c_float * MODEM_STATS_NR_MAX) * MODEM_STATS_NC_MAX),
|
2022-03-24 19:49:13 +00:00
|
|
|
("nr", ctypes.c_int),
|
|
|
|
("sync", ctypes.c_int),
|
|
|
|
("foff", ctypes.c_float),
|
|
|
|
("rx_timing", ctypes.c_float),
|
|
|
|
("clock_offset", ctypes.c_float),
|
|
|
|
("sync_metric", ctypes.c_float),
|
|
|
|
("pre", ctypes.c_int),
|
|
|
|
("post", ctypes.c_int),
|
|
|
|
("uw_fails", ctypes.c_int),
|
2022-10-12 08:45:17 +00:00
|
|
|
("rx_eye", (ctypes.c_float * MODEM_STATS_ET_MAX) * MODEM_STATS_EYE_IND_MAX),
|
2022-05-22 20:28:55 +00:00
|
|
|
("neyetr", ctypes.c_int), # How many eye traces are plotted
|
|
|
|
("neyesamp", ctypes.c_int), # How many samples in the eye diagram
|
2022-05-23 22:06:33 +00:00
|
|
|
("f_est", (ctypes.c_float * MODEM_STATS_MAX_F_EST)),
|
2022-03-24 19:49:13 +00:00
|
|
|
("fft_buf", (ctypes.c_float * MODEM_STATS_NSPEC * 2)),
|
2022-10-12 08:45:17 +00:00
|
|
|
("fft_cfg", ctypes.c_void_p)
|
2022-03-24 19:49:13 +00:00
|
|
|
]
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-06-24 18:55:59 +00:00
|
|
|
|
2021-12-14 20:30:20 +00:00
|
|
|
# Return code flags for freedv_get_rx_status() function
|
2022-05-22 20:28:55 +00:00
|
|
|
api.FREEDV_RX_TRIAL_SYNC = 0x1 # type: ignore # demodulator has trial sync
|
|
|
|
api.FREEDV_RX_SYNC = 0x2 # type: ignore # demodulator has sync
|
|
|
|
api.FREEDV_RX_BITS = 0x4 # type: ignore # data bits have been returned
|
|
|
|
api.FREEDV_RX_BIT_ERRORS = 0x8 # type: ignore # FEC may not have corrected all bit errors (not all parity checks OK)
|
2021-12-14 20:30:20 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
api.rx_sync_flags_to_text = [ # type: ignore
|
2021-12-14 20:18:18 +00:00
|
|
|
"----",
|
|
|
|
"---T",
|
|
|
|
"--S-",
|
|
|
|
"--ST",
|
|
|
|
"-B--",
|
|
|
|
"-B-T",
|
|
|
|
"-BS-",
|
|
|
|
"-BST",
|
|
|
|
"E---",
|
|
|
|
"E--T",
|
|
|
|
"E-S-",
|
|
|
|
"E-ST",
|
|
|
|
"EB--",
|
|
|
|
"EB-T",
|
|
|
|
"EBS-",
|
2022-05-22 20:28:55 +00:00
|
|
|
"EBST",
|
|
|
|
]
|
2021-12-13 18:00:38 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Audio buffer ---------------------------------------------------------
|
2021-12-16 11:10:30 +00:00
|
|
|
class audio_buffer:
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-26 01:23:30 +00:00
|
|
|
Thread-safe audio buffer, which fits the needs of codec2
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-03-04 15:50:32 +00:00
|
|
|
made by David Rowe, VK5DGR
|
|
|
|
"""
|
2022-05-22 20:28:55 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# A buffer of int16 samples, using a fixed length numpy array self.buffer for storage
|
2021-12-16 11:10:30 +00:00
|
|
|
# self.nbuffer is the current number of samples in the buffer
|
|
|
|
def __init__(self, size):
|
2022-05-26 01:23:30 +00:00
|
|
|
log.debug("[C2 ] Creating audio buffer", size=size)
|
2021-12-16 11:10:30 +00:00
|
|
|
self.size = size
|
|
|
|
self.buffer = np.zeros(size, dtype=np.int16)
|
|
|
|
self.nbuffer = 0
|
2021-12-20 21:57:13 +00:00
|
|
|
self.mutex = Lock()
|
2022-05-09 01:27:24 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
def push(self, samples):
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
|
|
|
Push new data to buffer
|
|
|
|
|
|
|
|
Args:
|
2022-05-11 22:10:59 +00:00
|
|
|
samples:
|
2022-03-04 15:50:32 +00:00
|
|
|
|
|
|
|
Returns:
|
2022-05-11 22:10:59 +00:00
|
|
|
Nothing
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2021-12-20 21:57:13 +00:00
|
|
|
self.mutex.acquire()
|
2022-05-11 22:10:59 +00:00
|
|
|
# Add samples at the end of the buffer
|
2022-05-22 20:28:55 +00:00
|
|
|
assert self.nbuffer + len(samples) <= self.size
|
|
|
|
self.buffer[self.nbuffer : self.nbuffer + len(samples)] = samples
|
2021-12-16 11:10:30 +00:00
|
|
|
self.nbuffer += len(samples)
|
2021-12-20 21:57:13 +00:00
|
|
|
self.mutex.release()
|
2022-05-09 01:27:24 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
def pop(self, size):
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
|
|
|
get data from buffer in size of NIN
|
|
|
|
Args:
|
2022-05-09 00:41:49 +00:00
|
|
|
size:
|
2022-03-04 15:50:32 +00:00
|
|
|
|
|
|
|
Returns:
|
2022-05-11 22:10:59 +00:00
|
|
|
Nothing
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2021-12-20 21:57:13 +00:00
|
|
|
self.mutex.acquire()
|
2022-05-11 22:10:59 +00:00
|
|
|
# Remove samples from the start of the buffer
|
|
|
|
self.nbuffer -= size
|
2022-05-22 20:28:55 +00:00
|
|
|
self.buffer[: self.nbuffer] = self.buffer[size : size + self.nbuffer]
|
2021-12-16 11:10:30 +00:00
|
|
|
assert self.nbuffer >= 0
|
2021-12-20 21:57:13 +00:00
|
|
|
self.mutex.release()
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Resampler ---------------------------------------------------------
|
2021-12-13 18:00:38 +00:00
|
|
|
|
2022-05-22 20:28:55 +00:00
|
|
|
# Oversampling rate
|
|
|
|
api.FDMDV_OS_48 = 6 # type: ignore
|
|
|
|
# Number of oversampling taps at 48kHz
|
|
|
|
api.FDMDV_OS_TAPS_48K = 48 # type: ignore
|
|
|
|
# Number of oversampling filter taps at 8kHz
|
|
|
|
api.FDMDV_OS_TAPS_48_8K = api.FDMDV_OS_TAPS_48K // api.FDMDV_OS_48 # type: ignore
|
|
|
|
api.fdmdv_8_to_48_short.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] # type: ignore
|
|
|
|
api.fdmdv_48_to_8_short.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] # type: ignore
|
|
|
|
|
2021-12-13 18:00:38 +00:00
|
|
|
|
2021-12-16 01:26:39 +00:00
|
|
|
class resampler:
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-11 22:10:59 +00:00
|
|
|
Re-sampler class
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-22 20:28:55 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Re-sample an array of variable length, we just store the filter memories here
|
2021-12-16 01:26:39 +00:00
|
|
|
MEM8 = api.FDMDV_OS_TAPS_48_8K
|
|
|
|
MEM48 = api.FDMDV_OS_TAPS_48K
|
2021-12-16 02:01:35 +00:00
|
|
|
|
|
|
|
def __init__(self):
|
2022-05-26 01:23:30 +00:00
|
|
|
log.debug("[C2 ] Create 48<->8 kHz resampler")
|
2021-12-16 02:01:35 +00:00
|
|
|
self.filter_mem8 = np.zeros(self.MEM8, dtype=np.int16)
|
|
|
|
self.filter_mem48 = np.zeros(self.MEM48)
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
def resample48_to_8(self, in48):
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-11 22:10:59 +00:00
|
|
|
Audio resampler integration from codec2
|
|
|
|
Downsample audio from 48000Hz to 8000Hz
|
2022-03-04 15:50:32 +00:00
|
|
|
Args:
|
2022-05-11 22:10:59 +00:00
|
|
|
in48: input data as np.int16
|
2022-03-04 15:50:32 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
Returns:
|
|
|
|
Downsampled 8000Hz data as np.int16
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2021-12-16 01:26:39 +00:00
|
|
|
assert in48.dtype == np.int16
|
2022-05-11 22:10:59 +00:00
|
|
|
# Length of input vector must be an integer multiple of api.FDMDV_OS_48
|
2022-05-22 20:28:55 +00:00
|
|
|
assert len(in48) % api.FDMDV_OS_48 == 0 # type: ignore
|
2021-12-16 02:01:35 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Concatenate filter memory and input samples
|
2022-05-22 20:28:55 +00:00
|
|
|
in48_mem = np.zeros(self.MEM48 + len(in48), dtype=np.int16)
|
|
|
|
in48_mem[: self.MEM48] = self.filter_mem48
|
|
|
|
in48_mem[self.MEM48 :] = in48
|
2021-12-16 11:10:30 +00:00
|
|
|
|
2021-12-19 23:06:39 +00:00
|
|
|
# In C: pin48=&in48_mem[MEM48]
|
2022-05-11 22:10:59 +00:00
|
|
|
pin48 = ctypes.byref(np.ctypeslib.as_ctypes(in48_mem), 2 * self.MEM48)
|
2022-05-22 20:28:55 +00:00
|
|
|
n8 = int(len(in48) / api.FDMDV_OS_48) # type: ignore
|
2021-12-16 02:01:35 +00:00
|
|
|
out8 = np.zeros(n8, dtype=np.int16)
|
2022-05-22 20:28:55 +00:00
|
|
|
api.fdmdv_48_to_8_short(out8.ctypes, pin48, n8) # type: ignore
|
2021-12-16 11:10:30 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Store memory for next time
|
2022-05-22 20:28:55 +00:00
|
|
|
self.filter_mem48 = in48_mem[: self.MEM48]
|
2021-12-16 02:01:35 +00:00
|
|
|
|
|
|
|
return out8
|
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
def resample8_to_48(self, in8):
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2022-05-11 22:10:59 +00:00
|
|
|
Audio resampler integration from codec2
|
|
|
|
Re-sample audio from 8000Hz to 48000Hz
|
2022-03-04 15:50:32 +00:00
|
|
|
Args:
|
2022-05-11 22:10:59 +00:00
|
|
|
in8: input data as np.int16
|
2022-03-04 15:50:32 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
Returns:
|
|
|
|
48000Hz audio as np.int16
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
2021-12-16 01:26:39 +00:00
|
|
|
assert in8.dtype == np.int16
|
2021-12-16 02:01:35 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Concatenate filter memory and input samples
|
2022-05-22 20:28:55 +00:00
|
|
|
in8_mem = np.zeros(self.MEM8 + len(in8), dtype=np.int16)
|
|
|
|
in8_mem[: self.MEM8] = self.filter_mem8
|
|
|
|
in8_mem[self.MEM8 :] = in8
|
2021-12-16 02:01:35 +00:00
|
|
|
|
2021-12-19 23:06:39 +00:00
|
|
|
# In C: pin8=&in8_mem[MEM8]
|
2022-05-11 22:10:59 +00:00
|
|
|
pin8 = ctypes.byref(np.ctypeslib.as_ctypes(in8_mem), 2 * self.MEM8)
|
2022-05-22 20:28:55 +00:00
|
|
|
out48 = np.zeros(api.FDMDV_OS_48 * len(in8), dtype=np.int16) # type: ignore
|
|
|
|
api.fdmdv_8_to_48_short(out48.ctypes, pin8, len(in8)) # type: ignore
|
2021-12-16 11:10:30 +00:00
|
|
|
|
2022-05-11 22:10:59 +00:00
|
|
|
# Store memory for next time
|
2022-05-22 20:28:55 +00:00
|
|
|
self.filter_mem8 = in8_mem[: self.MEM8]
|
2021-12-16 02:01:35 +00:00
|
|
|
|
|
|
|
return out48
|
2023-11-25 22:14:49 +00:00
|
|
|
|
|
|
|
def open_instance(mode: int) -> ctypes.c_void_p:
|
2024-04-04 20:46:12 +00:00
|
|
|
data_custom = 21
|
|
|
|
|
|
|
|
if mode in [FREEDV_MODE.data_ofdm_500.value, FREEDV_MODE.data_ofdm_2438.value]:
|
|
|
|
custom_params = ofdm_configurations[mode]
|
|
|
|
return ctypes.cast(
|
|
|
|
api.freedv_open_advanced(
|
|
|
|
data_custom,
|
|
|
|
ctypes.byref(custom_params),
|
|
|
|
),
|
|
|
|
ctypes.c_void_p,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
if mode not in [data_custom]:
|
|
|
|
return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p)
|
2023-11-25 22:14:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_bytes_per_frame(mode: int) -> int:
|
|
|
|
"""
|
|
|
|
Provide bytes per frame information for accessing from data handler
|
|
|
|
|
|
|
|
:param mode: Codec2 mode to query
|
|
|
|
:type mode: int or str
|
|
|
|
:return: Bytes per frame of the supplied codec2 data mode
|
|
|
|
:rtype: int
|
|
|
|
"""
|
|
|
|
freedv = open_instance(mode)
|
|
|
|
# TODO add close session
|
|
|
|
# get number of bytes per frame for mode
|
|
|
|
return int(api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
2024-04-04 20:46:12 +00:00
|
|
|
|
|
|
|
|
2024-04-06 10:06:45 +00:00
|
|
|
MAX_UW_BITS = 192
|
2024-04-04 20:46:12 +00:00
|
|
|
|
|
|
|
class OFDM_CONFIG(ctypes.Structure):
|
|
|
|
_fields_ = [
|
|
|
|
("tx_centre", ctypes.c_float), # TX Centre Audio Frequency
|
|
|
|
("rx_centre", ctypes.c_float), # RX Centre Audio Frequency
|
|
|
|
("fs", ctypes.c_float), # Sample Frequency
|
|
|
|
("rs", ctypes.c_float), # Symbol Rate
|
|
|
|
("ts", ctypes.c_float), # Symbol duration
|
|
|
|
("tcp", ctypes.c_float), # Cyclic Prefix duration
|
|
|
|
("timing_mx_thresh", ctypes.c_float), # Threshold for timing metrics
|
|
|
|
("nc", ctypes.c_int), # Number of carriers
|
|
|
|
("ns", ctypes.c_int), # Number of Symbol frames
|
|
|
|
("np", ctypes.c_int), # Number of modem frames per packet
|
|
|
|
("bps", ctypes.c_int), # Bits per Symbol
|
|
|
|
("txtbits", ctypes.c_int), # Number of auxiliary data bits
|
|
|
|
("nuwbits", ctypes.c_int), # Number of unique word bits
|
|
|
|
("bad_uw_errors", ctypes.c_int), # Threshold for bad unique word detection
|
|
|
|
("ftwindowwidth", ctypes.c_int), # Filter window width
|
|
|
|
("edge_pilots", ctypes.c_int), # Edge pilots configuration
|
|
|
|
("state_machine", ctypes.c_char_p), # Name of sync state machine used
|
|
|
|
("codename", ctypes.c_char_p), # LDPC codename
|
|
|
|
("tx_uw", ctypes.c_uint8 * MAX_UW_BITS), # User defined unique word
|
|
|
|
("amp_est_mode", ctypes.c_int), # Amplitude estimator algorithm mode
|
|
|
|
("tx_bpf_en", ctypes.c_bool), # TX BPF enable flag
|
|
|
|
("rx_bpf_en", ctypes.c_bool), # RX BPF enable flag
|
|
|
|
("foff_limiter", ctypes.c_bool), # Frequency offset limiter enable flag
|
|
|
|
("amp_scale", ctypes.c_float), # Amplitude scale factor
|
|
|
|
("clip_gain1", ctypes.c_float), # Pre-clipping gain
|
|
|
|
("clip_gain2", ctypes.c_float), # Post-clipping gain
|
|
|
|
("clip_en", ctypes.c_bool), # Clipping enable flag
|
|
|
|
("mode", ctypes.c_char * 16), # OFDM mode in string form
|
|
|
|
("data_mode", ctypes.c_char_p), # Data mode ("streaming", "burst", etc.)
|
|
|
|
("fmin", ctypes.c_float), # Minimum frequency for tuning range
|
|
|
|
("fmax", ctypes.c_float), # Maximum frequency for tuning range
|
2024-04-06 19:14:36 +00:00
|
|
|
("EsNodB", ctypes.c_float),
|
|
|
|
|
2024-04-04 20:46:12 +00:00
|
|
|
]
|
|
|
|
|
2024-04-04 21:19:57 +00:00
|
|
|
|
2024-04-04 20:46:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class FREEDV_ADVANCED(ctypes.Structure):
|
|
|
|
"""Advanced structure for fsk and ofdm modes"""
|
|
|
|
_fields_ = [
|
|
|
|
("interleave_frames", ctypes.c_int),
|
|
|
|
("M", ctypes.c_int),
|
|
|
|
("Rs", ctypes.c_int),
|
|
|
|
("Fs", ctypes.c_int),
|
|
|
|
("first_tone", ctypes.c_int),
|
|
|
|
("tone_spacing", ctypes.c_int),
|
|
|
|
("codename", ctypes.c_char_p),
|
|
|
|
("config", ctypes.POINTER(OFDM_CONFIG))
|
|
|
|
]
|
|
|
|
|
|
|
|
api.freedv_open_advanced.argtypes = [ctypes.c_int, ctypes.POINTER(FREEDV_ADVANCED)]
|
|
|
|
api.freedv_open_advanced.restype = ctypes.c_void_p
|
|
|
|
|
|
|
|
def create_default_ofdm_config():
|
2024-04-06 19:14:36 +00:00
|
|
|
uw_sequence = (c_uint8 * MAX_UW_BITS)(*([0] * MAX_UW_BITS))
|
2024-04-04 21:19:57 +00:00
|
|
|
|
|
|
|
ofdm_default_config = OFDM_CONFIG(
|
|
|
|
tx_centre=1500.0,
|
|
|
|
rx_centre=1500.0,
|
|
|
|
fs=8000.0,
|
2024-04-06 19:14:36 +00:00
|
|
|
rs=62.5,
|
2024-04-04 21:19:57 +00:00
|
|
|
ts=0.016,
|
|
|
|
tcp=0.006,
|
|
|
|
timing_mx_thresh=0.10,
|
|
|
|
nc=9,
|
|
|
|
ns=5,
|
|
|
|
np=29,
|
|
|
|
bps=2,
|
|
|
|
txtbits=0,
|
|
|
|
nuwbits=40,
|
|
|
|
bad_uw_errors=10,
|
|
|
|
ftwindowwidth=80,
|
2024-04-06 19:14:36 +00:00
|
|
|
edge_pilots=False,
|
2024-04-06 16:03:00 +00:00
|
|
|
state_machine="data".encode("utf-8"),
|
2024-04-06 19:14:36 +00:00
|
|
|
codename="H_1024_2048_4f".encode("utf-8"),
|
2024-04-04 21:19:57 +00:00
|
|
|
tx_uw=uw_sequence,
|
2024-04-06 19:14:36 +00:00
|
|
|
amp_est_mode=1,
|
2024-04-04 21:19:57 +00:00
|
|
|
tx_bpf_en=False,
|
|
|
|
rx_bpf_en=False,
|
|
|
|
foff_limiter=False,
|
|
|
|
amp_scale=300E3,
|
|
|
|
clip_gain1=2.2,
|
|
|
|
clip_gain2=0.8,
|
2024-04-06 19:14:36 +00:00
|
|
|
clip_en=False,
|
2024-04-04 21:19:57 +00:00
|
|
|
mode=b"CUSTOM",
|
|
|
|
data_mode=b"streaming",
|
|
|
|
fmin=-50.0,
|
|
|
|
fmax=50.0,
|
2024-04-06 19:14:36 +00:00
|
|
|
EsNodB=3.0,
|
|
|
|
|
2024-04-04 21:19:57 +00:00
|
|
|
)
|
|
|
|
|
2024-04-04 20:46:12 +00:00
|
|
|
return FREEDV_ADVANCED(
|
|
|
|
interleave_frames = 0,
|
|
|
|
M = 2,
|
|
|
|
Rs = 100,
|
|
|
|
Fs = 8000,
|
|
|
|
first_tone = 1000,
|
|
|
|
tone_spacing = 200,
|
|
|
|
codename = "H_256_512_4".encode("utf-8"),
|
|
|
|
config = ctypes.pointer(ofdm_default_config),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-04-06 21:17:38 +00:00
|
|
|
"""
|
|
|
|
# DATAC1
|
|
|
|
data_ofdm_500_config = create_default_ofdm_config()
|
|
|
|
data_ofdm_500_config.config.contents.ns = 5
|
|
|
|
data_ofdm_500_config.config.contents.np = 38
|
|
|
|
data_ofdm_500_config.config.contents.tcp = 0.006
|
|
|
|
data_ofdm_500_config.config.contents.ts = 0.016
|
|
|
|
data_ofdm_500_config.config.contents.rs = 1.0 / data_ofdm_500_config.config.contents.ts
|
|
|
|
data_ofdm_500_config.config.contents.nc = 27
|
|
|
|
data_ofdm_500_config.config.contents.nuwbits = 16
|
|
|
|
data_ofdm_500_config.config.contents.timing_mx_thresh = 0.10
|
|
|
|
data_ofdm_500_config.config.contents.bad_uw_errors = 6
|
|
|
|
data_ofdm_500_config.config.contents.codename = b"H_4096_8192_3d"
|
|
|
|
data_ofdm_500_config.config.contents.clip_gain1 = 2.2
|
|
|
|
data_ofdm_500_config.config.contents.clip_gain2 = 0.8
|
|
|
|
data_ofdm_500_config.config.contents.amp_scale = 145E3
|
|
|
|
|
|
|
|
uw_sequence = [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0]
|
|
|
|
data_ofdm_500_config.config.contents.tx_uw = (ctypes.c_uint8 * MAX_UW_BITS)(*(uw_sequence + [0]*(MAX_UW_BITS-len(uw_sequence))))
|
|
|
|
#tx_uw_array = (ctypes.c_uint8 * MAX_UW_BITS)(*([0] * MAX_UW_BITS))
|
|
|
|
#for i in range(min(len(uw_sequence), MAX_UW_BITS)):
|
|
|
|
# tx_uw_array[i] = uw_sequence[i]
|
|
|
|
#data_ofdm_500_config.config.contents.tx_uw = tx_uw_array
|
|
|
|
"""
|
|
|
|
|
2024-04-04 21:19:57 +00:00
|
|
|
|
2024-04-06 21:17:38 +00:00
|
|
|
# DATAC3
|
2024-04-04 21:19:57 +00:00
|
|
|
data_ofdm_500_config = create_default_ofdm_config()
|
|
|
|
data_ofdm_500_config.config.contents.ns = 5
|
|
|
|
data_ofdm_500_config.config.contents.np = 29
|
|
|
|
data_ofdm_500_config.config.contents.tcp = 0.006
|
|
|
|
data_ofdm_500_config.config.contents.ts = 0.016
|
2024-04-06 19:14:36 +00:00
|
|
|
data_ofdm_500_config.config.contents.rs = 1.0 / data_ofdm_500_config.config.contents.ts
|
2024-04-04 21:19:57 +00:00
|
|
|
data_ofdm_500_config.config.contents.nc = 9
|
|
|
|
data_ofdm_500_config.config.contents.nuwbits = 40
|
|
|
|
data_ofdm_500_config.config.contents.timing_mx_thresh = 0.10
|
|
|
|
data_ofdm_500_config.config.contents.bad_uw_errors = 10
|
2024-04-06 21:17:38 +00:00
|
|
|
data_ofdm_500_config.config.contents.codename = b"H_1024_2048_4f"
|
2024-04-06 19:14:36 +00:00
|
|
|
data_ofdm_500_config.config.contents.clip_gain1 = 2.2
|
|
|
|
data_ofdm_500_config.config.contents.clip_gain2 = 0.8
|
2024-04-06 16:03:00 +00:00
|
|
|
uw_sequence = [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]
|
2024-04-06 21:17:38 +00:00
|
|
|
data_ofdm_500_config.config.contents.tx_uw = (ctypes.c_uint8 * MAX_UW_BITS)(*(uw_sequence + [0]*(MAX_UW_BITS-len(uw_sequence))))
|
|
|
|
#tx_uw_array = (ctypes.c_uint8 * MAX_UW_BITS)(*([0] * MAX_UW_BITS))
|
|
|
|
#for i in range(min(len(uw_sequence), MAX_UW_BITS)):
|
|
|
|
# tx_uw_array[i] = uw_sequence[i]
|
|
|
|
#data_ofdm_500_config.config.contents.tx_uw = tx_uw_array
|
2024-04-06 19:14:36 +00:00
|
|
|
|
|
|
|
|
2024-04-04 21:19:57 +00:00
|
|
|
|
|
|
|
|
2024-04-04 20:46:12 +00:00
|
|
|
data_ofdm_2438_config = create_default_ofdm_config()
|
|
|
|
data_ofdm_2438_config.config.contents.ns = 5
|
2024-04-04 21:19:57 +00:00
|
|
|
data_ofdm_2438_config.config.contents.np = 51
|
2024-04-04 20:46:12 +00:00
|
|
|
data_ofdm_2438_config.config.contents.tcp = 0.005
|
|
|
|
data_ofdm_2438_config.config.contents.ts = 0.018
|
2024-04-06 19:14:36 +00:00
|
|
|
data_ofdm_500_config.config.contents.rs = 1.0 / data_ofdm_2438_config.config.contents.ts
|
2024-04-06 21:17:38 +00:00
|
|
|
data_ofdm_2438_config.config.contents.nc = 39
|
|
|
|
data_ofdm_2438_config.config.contents.nuwbits =12
|
2024-04-04 20:46:12 +00:00
|
|
|
data_ofdm_2438_config.config.contents.timing_mx_thresh = 0.10
|
|
|
|
data_ofdm_2438_config.config.contents.bad_uw_errors = 8
|
2024-04-04 21:19:57 +00:00
|
|
|
data_ofdm_2438_config.config.contents.amp_est_mode = 0
|
2024-04-04 20:46:12 +00:00
|
|
|
data_ofdm_2438_config.config.contents.amp_scale = 145E3
|
|
|
|
data_ofdm_2438_config.config.contents.codename = b"H_16200_9720"
|
2024-04-06 21:17:38 +00:00
|
|
|
data_ofdm_2438_config.config.contents.clip_gain1 = 2.7
|
|
|
|
data_ofdm_2438_config.config.contents.clip_gain2 = 0.8
|
|
|
|
data_ofdm_2438_config.config.contents.timing_mx_thresh = 0.10
|
|
|
|
|
2024-04-04 20:46:12 +00:00
|
|
|
# Fill the tx_uw field with the uw_sequence, and pad the rest with zeros if necessary
|
2024-04-04 21:19:57 +00:00
|
|
|
#uw_sequence = [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1]
|
|
|
|
uw_sequence = [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0]
|
2024-04-06 19:14:36 +00:00
|
|
|
data_ofdm_2438_config.config.contents.tx_uw = (ctypes.c_uint8 * MAX_UW_BITS)(*(uw_sequence + [0]*(MAX_UW_BITS-len(uw_sequence))))
|
2024-04-04 20:46:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ofdm_configurations = {
|
|
|
|
FREEDV_MODE.data_ofdm_500.value: data_ofdm_500_config,
|
|
|
|
FREEDV_MODE.data_ofdm_2438.value: data_ofdm_2438_config
|
|
|
|
|
|
|
|
}
|