Add FM network audio gain and optional pre- and de-emphasis.

This commit is contained in:
Jonathan Naylor 2021-03-14 14:59:34 +00:00
parent fb0d1ef5e2
commit 75e8e95aad
8 changed files with 147 additions and 74 deletions

View file

@ -283,12 +283,16 @@ m_pocsagLocalPort(0U),
m_pocsagNetworkModeHang(3U),
m_pocsagNetworkDebug(false),
m_fmNetworkEnabled(false),
m_fmNetworkFormat("MMDVM"),
m_fmNetworkProtocol("MMDVM"),
m_fmGatewayAddress(),
m_fmGatewayPort(0U),
m_fmLocalAddress(),
m_fmLocalPort(0U),
m_fmSampleRate(8000U),
m_fmPreEmphasis(true),
m_fmDeEmphasis(true),
m_fmTXAudioGain(1.0F),
m_fmRXAudioGain(1.0F),
m_fmNetworkModeHang(3U),
m_fmNetworkDebug(false),
m_ax25NetworkEnabled(false),
@ -999,8 +1003,8 @@ bool CConf::read()
} else if (section == SECTION_FM_NETWORK) {
if (::strcmp(key, "Enable") == 0)
m_fmNetworkEnabled = ::atoi(value) == 1;
else if (::strcmp(key, "Format") == 0)
m_fmNetworkFormat = value;
else if (::strcmp(key, "Protocol") == 0)
m_fmNetworkProtocol = value;
else if (::strcmp(key, "LocalAddress") == 0)
m_fmLocalAddress = value;
else if (::strcmp(key, "LocalPort") == 0)
@ -1011,6 +1015,14 @@ bool CConf::read()
m_fmGatewayPort = (unsigned int)::atoi(value);
else if (::strcmp(key, "SampleRate") == 0)
m_fmSampleRate = (unsigned int)::atoi(value);
else if (::strcmp(key, "PreEmphasis") == 0)
m_fmPreEmphasis = ::atoi(value) == 1;
else if (::strcmp(key, "DeEmphasis") == 0)
m_fmDeEmphasis = ::atoi(value) == 1;
else if (::strcmp(key, "TXAudioGain") == 0)
m_fmTXAudioGain = float(::atof(value));
else if (::strcmp(key, "RXAudioGain") == 0)
m_fmRXAudioGain = float(::atof(value));
else if (::strcmp(key, "ModeHang") == 0)
m_fmNetworkModeHang = (unsigned int)::atoi(value);
else if (::strcmp(key, "Debug") == 0)
@ -2200,9 +2212,9 @@ bool CConf::getFMNetworkEnabled() const
return m_fmNetworkEnabled;
}
std::string CConf::getFMNetworkFormat() const
std::string CConf::getFMNetworkProtocol() const
{
return m_fmNetworkFormat;
return m_fmNetworkProtocol;
}
std::string CConf::getFMGatewayAddress() const
@ -2230,6 +2242,26 @@ unsigned int CConf::getFMSampleRate() const
return m_fmSampleRate;
}
bool CConf::getFMPreEmphasis() const
{
return m_fmPreEmphasis;
}
bool CConf::getFMDeEmphasis() const
{
return m_fmDeEmphasis;
}
float CConf::getFMTXAudioGain() const
{
return m_fmTXAudioGain;
}
float CConf::getFMRXAudioGain() const
{
return m_fmRXAudioGain;
}
unsigned int CConf::getFMNetworkModeHang() const
{
return m_fmNetworkModeHang;

12
Conf.h
View file

@ -296,12 +296,16 @@ public:
// The FM Network section
bool getFMNetworkEnabled() const;
std::string getFMNetworkFormat() const;
std::string getFMNetworkProtocol() const;
std::string getFMGatewayAddress() const;
unsigned int getFMGatewayPort() const;
std::string getFMLocalAddress() const;
unsigned int getFMLocalPort() const;
unsigned int getFMSampleRate() const;
bool getFMPreEmphasis() const;
bool getFMDeEmphasis() const;
float getFMTXAudioGain() const;
float getFMRXAudioGain() const;
unsigned int getFMNetworkModeHang() const;
bool getFMNetworkDebug() const;
@ -604,12 +608,16 @@ private:
bool m_pocsagNetworkDebug;
bool m_fmNetworkEnabled;
std::string m_fmNetworkFormat;
std::string m_fmNetworkProtocol;
std::string m_fmGatewayAddress;
unsigned int m_fmGatewayPort;
std::string m_fmLocalAddress;
unsigned int m_fmLocalPort;
unsigned int m_fmSampleRate;
bool m_fmPreEmphasis;
bool m_fmDeEmphasis;
float m_fmTXAudioGain;
float m_fmRXAudioGain;
unsigned int m_fmNetworkModeHang;
bool m_fmNetworkDebug;

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 by Jonathan Naylor G4KLX
* Copyright (C) 2020,2021 by Jonathan Naylor G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -31,20 +31,27 @@ const float PREEMPHASIS_GAIN_DB = 0.0F; // Audio gain adjustment
const float FILTER_GAIN_DB = 2.0F; // Audio gain adjustment
const unsigned int FM_MASK = 0x00000FFFU;
CFMControl::CFMControl(CFMNetwork* network) :
CFMControl::CFMControl(CFMNetwork* network, float txAudioGain, float rxAudioGain, bool preEmphasisOn, bool deEmphasisOn) :
m_network(network),
m_txAudioGain(txAudioGain),
m_rxAudioGain(rxAudioGain),
m_preEmphasisOn(preEmphasisOn),
m_deEmphasisOn(deEmphasisOn),
m_enabled(false),
m_incomingRFAudio(1600U, "Incoming RF FM Audio"),
m_preemphasis (NULL),
m_deemphasis (NULL),
m_preEmphasis(NULL),
m_deEmphasis(NULL),
m_filterStage1(NULL),
m_filterStage2(NULL),
m_filterStage3(NULL)
{
m_preemphasis = new CIIRDirectForm1Filter(8.315375384336983F,-7.03334621603483F,0.0F,1.0F,0.282029168302153F,0.0F, PREEMPHASIS_GAIN_DB);
m_deemphasis = new CIIRDirectForm1Filter(0.07708787090460224F,0.07708787090460224F,0.0F,1.0F,-0.8458242581907955F,0.0F, DEEMPHASIS_GAIN_DB);
assert(txAudioGain > 0.0F);
assert(rxAudioGain > 0.0F);
//cheby type 1 0.2dB cheby type 1 3rd order 300-2700Hz fs=8000
m_preEmphasis = new CIIRDirectForm1Filter(8.315375384336983F, -7.03334621603483F,0.0F,1.0F, 0.282029168302153F,0.0F, PREEMPHASIS_GAIN_DB);
m_deEmphasis = new CIIRDirectForm1Filter(0.07708787090460224F, 0.07708787090460224F,0.0F, 1.0F, -0.8458242581907955F,0.0F, DEEMPHASIS_GAIN_DB);
// Chebyshev type 1 0.2dB cheby type 1 3rd order 300-2700Hz fs=8000
m_filterStage1 = new CIIRDirectForm1Filter(0.29495028f, 0.0f, -0.29495028f, 1.0f, -0.61384624f, -0.057158668f, FILTER_GAIN_DB);
m_filterStage2 = new CIIRDirectForm1Filter(1.0f, 2.0f, 1.0f, 1.0f, 0.9946123f, 0.6050482f, FILTER_GAIN_DB);
m_filterStage3 = new CIIRDirectForm1Filter(1.0f, -2.0f, 1.0f, 1.0f, -1.8414584f, 0.8804949f, FILTER_GAIN_DB);
@ -52,8 +59,8 @@ m_filterStage3(NULL)
CFMControl::~CFMControl()
{
delete m_preemphasis ;
delete m_deemphasis ;
delete m_preEmphasis;
delete m_deEmphasis;
delete m_filterStage1;
delete m_filterStage2;
@ -79,42 +86,45 @@ bool CFMControl::writeModem(const unsigned char* data, unsigned int length)
m_incomingRFAudio.addData(data + 1U, length - 1U);
unsigned int bufferLength = m_incomingRFAudio.dataSize();
if (bufferLength > 240U) //160 samples 12-bit
bufferLength = 240U; //160 samples 12-bit
if (bufferLength > 240U) // 160 samples 12-bit
bufferLength = 240U; // 160 samples 12-bit
if (bufferLength >= 3U) {
bufferLength = bufferLength - bufferLength % 3U; //round down to nearest multiple of 3
unsigned char bufferData[240U]; //160 samples 12-bit
bufferLength = bufferLength - bufferLength % 3U; // Round down to nearest multiple of 3
unsigned char bufferData[240U]; // 160 samples 12-bit
m_incomingRFAudio.getData(bufferData, bufferLength);
unsigned int pack = 0U;
unsigned char* packPointer = (unsigned char*)&pack;
float out[160U]; //160 samples 12-bit
float out[160U]; // 160 samples 12-bit
unsigned int nOut = 0U;
short unpackedSamples[2U];
for (unsigned int i = 0U; i < bufferLength; i += 3U) {
//extract unsigned 12 bit unsigned sample pairs packed into 3 bytes to 16 bit signed
packPointer[0U] = bufferData[i];
// Extract unsigned 12 bit unsigned sample pairs packed into 3 bytes to 16 bit signed
packPointer[0U] = bufferData[i + 0U];
packPointer[1U] = bufferData[i + 1U];
packPointer[2U] = bufferData[i + 2U];
unpackedSamples[1U] = short(int(pack & FM_MASK) - 2048);
unpackedSamples[0U] = short(int(pack >> 12 & FM_MASK) - 2048); //
//process unpacked sample pair
// Process unpacked sample pair
for (unsigned char j = 0U; j < 2U; j++) {
// Convert to float (-1.0 to +1.0)
float sampleFloat = float(unpackedSamples[j]) / 2048.0F;
float sampleFloat = (float(unpackedSamples[j]) * m_rxAudioGain) / 2048.0F;
// De-emphasise and remove CTCSS
sampleFloat = m_deemphasis->filter(sampleFloat);
if (m_deEmphasisOn)
sampleFloat = m_deEmphasis->filter(sampleFloat);
out[nOut++] = m_filterStage3->filter(m_filterStage2->filter(m_filterStage1->filter(sampleFloat)));
}
}
#if defined(DUMP_RF_AUDIO)
FILE * audiofile = fopen("./audiodump.bin", "ab");
if(audiofile != NULL) {
if (audiofile != NULL) {
fwrite(out, sizeof(float), nOut, audiofile);
fclose(audiofile);
}
@ -133,8 +143,8 @@ unsigned int CFMControl::readModem(unsigned char* data, unsigned int space)
if (m_network == NULL)
return 0U;
if (space > 240U) //160 samples 12-bit
space = 240U; //160 samples 12-bit
if (space > 240U) // 160 samples 12-bit
space = 240U; // 160 samples 12-bit
float netData[160U]; // Modem can handle up to 160 samples at a time
unsigned int length = m_network->read(netData, 160U); //160 samples 12-bit
@ -146,14 +156,17 @@ unsigned int CFMControl::readModem(unsigned char* data, unsigned int space)
unsigned int nData = 0U;
for (unsigned int i = 0; i < length; i++) {
float sampleFloat = netData[i] * m_txAudioGain;
// Pre-emphasis
float sampleFloat = m_preemphasis->filter(netData[i]);
if (m_preEmphasisOn)
sampleFloat = m_preEmphasis->filter(sampleFloat);
// Convert float to 12-bit samples (0 to 4095)
unsigned int sample12bit = (unsigned int)((sampleFloat + 1.0F) * 2048.0F + 0.5F);
// pack 2 samples onto 3 bytes
if((i & 1U) == 0) {
// Pack 2 samples into 3 bytes
if ((i & 1U) == 0) {
pack = 0U;
pack = sample12bit << 12;
} else {

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 by Jonathan Naylor G4KLX
* Copyright (C) 2020,2021 by Jonathan Naylor G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -30,7 +30,7 @@
class CFMControl {
public:
CFMControl(CFMNetwork* network);
CFMControl(CFMNetwork* network, float txAudioGain, float rxAudioGain, bool preEmphasisOn, bool deEmphasisOn);
~CFMControl();
bool writeModem(const unsigned char* data, unsigned int length);
@ -43,10 +43,14 @@ public:
private:
CFMNetwork* m_network;
float m_txAudioGain;
float m_rxAudioGain;
bool m_preEmphasisOn;
bool m_deEmphasisOn;
bool m_enabled;
CRingBuffer<unsigned char> m_incomingRFAudio;
CIIRDirectForm1Filter* m_preemphasis;
CIIRDirectForm1Filter* m_deemphasis;
CIIRDirectForm1Filter* m_preEmphasis;
CIIRDirectForm1Filter* m_deEmphasis;
CIIRDirectForm1Filter* m_filterStage1;
CIIRDirectForm1Filter* m_filterStage2;
CIIRDirectForm1Filter* m_filterStage3;

View file

@ -27,8 +27,8 @@
const unsigned int BUFFER_LENGTH = 500U;
CFMNetwork::CFMNetwork(const std::string& format, const std::string& localAddress, unsigned int localPort, const std::string& gatewayAddress, unsigned int gatewayPort, unsigned int sampleRate, bool debug) :
m_format(FMF_MMDVM),
CFMNetwork::CFMNetwork(const std::string& protocol, const std::string& localAddress, unsigned int localPort, const std::string& gatewayAddress, unsigned int gatewayPort, unsigned int sampleRate, bool debug) :
m_protocol(FMNP_MMDVM),
m_socket(localAddress, localPort),
m_addr(),
m_addrLen(0U),
@ -46,8 +46,8 @@ m_seqNo(0U)
if (CUDPSocket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0)
m_addrLen = 0U;
if (format == "USRP")
m_format = FMF_USRP;
if (protocol == "USRP")
m_protocol = FMNP_USRP;
#if !defined(_WIN32) && !defined(_WIN64)
int error;
@ -116,7 +116,7 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples)
unsigned int length = 0U;
if (m_format == FMF_USRP) {
if (m_protocol == FMNP_USRP) {
buffer[length++] = 'U';
buffer[length++] = 'S';
buffer[length++] = 'R';
@ -187,7 +187,7 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples)
bool CFMNetwork::writeEOT()
{
if (m_format == FMF_MMDVM) {
if (m_protocol == FMNP_MMDVM) {
unsigned char buffer[10U];
::memset(buffer, 0x00U, 10U);
@ -220,11 +220,11 @@ void CFMNetwork::clock(unsigned int ms)
if (length <= 0)
return;
// Check if the data is for us // does not accept data from USRP
//if (!CUDPSocket::match(addr, m_addr)) {
// LogMessage("FM packet received from an invalid source");
// return;
//}
// Check if the data is for us
if (!CUDPSocket::match(addr, m_addr)) {
LogMessage("FM packet received from an invalid source");
return;
}
if (!m_enabled)
return;
@ -232,7 +232,7 @@ void CFMNetwork::clock(unsigned int ms)
if (m_debug)
CUtils::dump(1U, "FM Network Data Received", buffer, length);
if (m_format == FMF_USRP) {
if (m_protocol == FMNP_USRP) {
// Invalid packet type?
if (::memcmp(buffer, "USRP", 4U) != 0)
return;
@ -304,9 +304,9 @@ unsigned int CFMNetwork::read(float* data, unsigned int nSamples)
} else {
#endif
for (unsigned int i = 0U; i < nSamples; i++) {
short val = ((buffer[i * 2U + 0U] & 0xFFU) << 0) + // Changing audio format from U16BE to S16LE
((buffer[i * 2U + 1U] & 0xFFU) << 8); // Changing audio format from U16BE to S16LE
data[i] = (float(val) / 65536.0F); // Changing audio format from U16BE to S16LE
short val = ((buffer[i * 2U + 0U] & 0xFFU) << 0) +
((buffer[i * 2U + 1U] & 0xFFU) << 8);
data[i] = float(val) / 65536.0F;
}
return nSamples;
@ -347,7 +347,7 @@ void CFMNetwork::enable(bool enabled)
void CFMNetwork::writePoll()
{
if (m_format == FMF_MMDVM) {
if (m_protocol == FMNP_MMDVM) {
unsigned char buffer[3U];
buffer[0U] = 'F';

View file

@ -30,14 +30,14 @@
#include <cstdint>
#include <string>
enum FM_FORMAT {
FMF_MMDVM,
FMF_USRP
enum FM_NETWORK_PROTOCOL {
FMNP_MMDVM,
FMNP_USRP
};
class CFMNetwork {
public:
CFMNetwork(const std::string& format, const std::string& myAddress, unsigned int myPort, const std::string& gatewayAddress, unsigned int gatewayPort, unsigned int sampleRate, bool debug);
CFMNetwork(const std::string& protocol, const std::string& myAddress, unsigned int myPort, const std::string& gatewayAddress, unsigned int gatewayPort, unsigned int sampleRate, bool debug);
~CFMNetwork();
bool open();
@ -57,19 +57,19 @@ public:
void clock(unsigned int ms);
private:
FM_FORMAT m_format;
CUDPSocket m_socket;
sockaddr_storage m_addr;
unsigned int m_addrLen;
unsigned int m_sampleRate;
bool m_debug;
bool m_enabled;
FM_NETWORK_PROTOCOL m_protocol;
CUDPSocket m_socket;
sockaddr_storage m_addr;
unsigned int m_addrLen;
unsigned int m_sampleRate;
bool m_debug;
bool m_enabled;
CRingBuffer<unsigned char> m_buffer;
CTimer m_pollTimer;
unsigned int m_seqNo;
CTimer m_pollTimer;
unsigned int m_seqNo;
#if !defined(_WIN32) && !defined(_WIN64)
SRC_STATE* m_incoming;
SRC_STATE* m_outgoing;
SRC_STATE* m_incoming;
SRC_STATE* m_outgoing;
#endif
void writePoll();

View file

@ -276,12 +276,16 @@ Debug=0
[FM Network]
Enable=1
# Values are MMDVM and USRP
Format=USRP
Protocol=USRP
LocalAddress=127.0.0.1
LocalPort=3810
GatewayAddress=127.0.0.1
GatewayPort=4810
SampleRate=8000
PreEmphasis=1
DeEmphasis=1
TXAudioGain=1.0
RXAudioGain=1.0
# ModeHang=3
Debug=0

View file

@ -681,15 +681,19 @@ int CMMDVMHost::run()
}
if (m_fmEnabled) {
m_fmRFModeHang = m_conf.getFMModeHang();
bool preEmphasis = m_conf.getFMPreEmphasis();
bool deEmphasis = m_conf.getFMDeEmphasis();
float txAudioGain = m_conf.getFMTXAudioGain();
float rxAudioGain = m_conf.getFMRXAudioGain();
m_fmRFModeHang = m_conf.getFMModeHang();
m_fm = new CFMControl(m_fmNetwork);
m_fm = new CFMControl(m_fmNetwork, txAudioGain, rxAudioGain, preEmphasis, deEmphasis);
}
bool remoteControlEnabled = m_conf.getRemoteControlEnabled();
if (remoteControlEnabled) {
std::string address = m_conf.getRemoteControlAddress();
unsigned int port = m_conf.getRemoteControlPort();
unsigned int port = m_conf.getRemoteControlPort();
LogInfo("Remote Control Parameters");
LogInfo(" Address: %s", address.c_str());
@ -1796,25 +1800,33 @@ bool CMMDVMHost::createPOCSAGNetwork()
bool CMMDVMHost::createFMNetwork()
{
std::string format = m_conf.getFMNetworkFormat();
std::string protocol = m_conf.getFMNetworkProtocol();
std::string gatewayAddress = m_conf.getFMGatewayAddress();
unsigned int gatewayPort = m_conf.getFMGatewayPort();
std::string localAddress = m_conf.getFMLocalAddress();
unsigned int localPort = m_conf.getFMLocalPort();
unsigned int sampleRate = m_conf.getFMSampleRate();
bool preEmphasis = m_conf.getFMPreEmphasis();
bool deEmphasis = m_conf.getFMDeEmphasis();
float txAudioGain = m_conf.getFMTXAudioGain();
float rxAudioGain = m_conf.getFMRXAudioGain();
m_fmNetModeHang = m_conf.getFMNetworkModeHang();
bool debug = m_conf.getFMNetworkDebug();
LogInfo("FM Network Parameters");
LogInfo(" Format: %s", format.c_str());
LogInfo(" Protocol: %s", protocol.c_str());
LogInfo(" Gateway Address: %s", gatewayAddress.c_str());
LogInfo(" Gateway Port: %u", gatewayPort);
LogInfo(" Local Address: %s", localAddress.c_str());
LogInfo(" Local Port: %u", localPort);
LogInfo(" Sample Rate: %u", sampleRate);
LogInfo(" Pre-Emphasis: %s", preEmphasis ? "yes" : "no");
LogInfo(" De-Emphasis: %s", deEmphasis ? "yes" : "no");
LogInfo(" TX Audio Gain: %.2f", txAudioGain);
LogInfo(" RX Audio Gain: %.2f", rxAudioGain);
LogInfo(" Mode Hang: %us", m_fmNetModeHang);
m_fmNetwork = new CFMNetwork(format, localAddress, localPort, gatewayAddress, gatewayPort, sampleRate, debug);
m_fmNetwork = new CFMNetwork(protocol, localAddress, localPort, gatewayAddress, gatewayPort, sampleRate, debug);
bool ret = m_fmNetwork->open();
if (!ret) {