From 77d38f37e01d63a8e917f471f0fb76b83a56d63c Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Tue, 17 Oct 2023 12:56:54 +0100 Subject: [PATCH 1/4] Add RAW data in and out of the FM repeater for SVXLink. --- FMControl.cpp | 2 +- FMNetwork.cpp | 344 +++++++++++++++++++++++++++++--------------------- FMNetwork.h | 15 ++- MMDVM.ini | 3 +- Version.h | 2 +- 5 files changed, 213 insertions(+), 153 deletions(-) diff --git a/FMControl.cpp b/FMControl.cpp index 5f2d232..8cb7183 100644 --- a/FMControl.cpp +++ b/FMControl.cpp @@ -147,7 +147,7 @@ unsigned int CFMControl::readModem(unsigned char* data, unsigned int space) 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 + unsigned int length = m_network->readData(netData, 160U); // 160 samples 12-bit if (length == 0U) return 0U; diff --git a/FMNetwork.cpp b/FMNetwork.cpp index 48d2382..254ff5c 100644 --- a/FMNetwork.cpp +++ b/FMNetwork.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2020,2021,2023 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 @@ -50,8 +50,10 @@ m_seqNo(0U) if (pos != std::string::npos) m_callsign = callsign.substr(0U, pos); - // if (protocol == "USRP") - // m_protocol = FMNP_USRP; + if (protocol == "RAW") + m_protocol = FMNP_RAW; + else + m_protocol = FMNP_USRP; } CFMNetwork::~CFMNetwork() @@ -75,8 +77,21 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples) assert(data != NULL); assert(nSamples > 0U); + if (m_protocol == FMNP_USRP) + return writeUSRPData(data, nSamples); + else if (m_protocol == FMNP_RAW) + return writeRawData(data, nSamples); + else + return false; +} + +bool CFMNetwork::writeUSRPData(float* data, unsigned int nSamples) +{ + assert(data != NULL); + assert(nSamples > 0U); + if (m_seqNo == 0U) { - bool ret = writeStart(); + bool ret = writeUSRPStart(); if (!ret) return false; } @@ -86,53 +101,51 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples) unsigned int length = 0U; - if (m_protocol == FMNP_USRP) { - buffer[length++] = 'U'; - buffer[length++] = 'S'; - buffer[length++] = 'R'; - buffer[length++] = 'P'; + buffer[length++] = 'U'; + buffer[length++] = 'S'; + buffer[length++] = 'R'; + buffer[length++] = 'P'; - // Sequence number - buffer[length++] = (m_seqNo >> 24) & 0xFFU; - buffer[length++] = (m_seqNo >> 16) & 0xFFU; - buffer[length++] = (m_seqNo >> 8) & 0xFFU; - buffer[length++] = (m_seqNo >> 0) & 0xFFU; + // Sequence number + buffer[length++] = (m_seqNo >> 24) & 0xFFU; + buffer[length++] = (m_seqNo >> 16) & 0xFFU; + buffer[length++] = (m_seqNo >> 8) & 0xFFU; + buffer[length++] = (m_seqNo >> 0) & 0xFFU; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // PTT on - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x01U; + // PTT on + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x01U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // Type, 0 for audio - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + // Type, 0 for audio + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - } + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; for (unsigned int i = 0U; i < nSamples; i++) { - short val = short(data[i] * 32767.0F + 0.5F); // Changing audio format from float to S16LE + short val = short(data[i] * 32767.0F + 0.5F); // Changing audio format from float to S16LE buffer[length++] = (val >> 0) & 0xFFU; buffer[length++] = (val >> 8) & 0xFFU; @@ -146,59 +159,91 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples) return m_socket.write(buffer, length, m_addr, m_addrLen); } +bool CFMNetwork::writeRawData(float* data, unsigned int nSamples) +{ + assert(data != NULL); + assert(nSamples > 0U); + + unsigned char buffer[1000U]; + ::memset(buffer, 0x00U, 1000U); + + unsigned int length = 0U; + + for (unsigned int i = 0U; i < nSamples; i++) { + short val = short(data[i] * 32767.0F + 0.5F); // Changing audio format from float to S16LE + + buffer[length++] = (val >> 0) & 0xFFU; + buffer[length++] = (val >> 8) & 0xFFU; + + buffer[length++] = (val >> 0) & 0xFFU; + buffer[length++] = (val >> 8) & 0xFFU; + } + + if (m_debug) + CUtils::dump(1U, "FM Network Data Sent", buffer, length); + + return m_socket.write(buffer, length, m_addr, m_addrLen); +} + bool CFMNetwork::writeEnd() +{ + if (m_protocol == FMNP_USRP) + return writeUSRPEnd(); + else + return true; +} + +bool CFMNetwork::writeUSRPEnd() { unsigned char buffer[500U]; ::memset(buffer, 0x00U, 500U); unsigned int length = 0U; - if (m_protocol == FMNP_USRP) { - buffer[length++] = 'U'; - buffer[length++] = 'S'; - buffer[length++] = 'R'; - buffer[length++] = 'P'; + buffer[length++] = 'U'; + buffer[length++] = 'S'; + buffer[length++] = 'R'; + buffer[length++] = 'P'; - // Sequence number - buffer[length++] = (m_seqNo >> 24) & 0xFFU; - buffer[length++] = (m_seqNo >> 16) & 0xFFU; - buffer[length++] = (m_seqNo >> 8) & 0xFFU; - buffer[length++] = (m_seqNo >> 0) & 0xFFU; + // Sequence number + buffer[length++] = (m_seqNo >> 24) & 0xFFU; + buffer[length++] = (m_seqNo >> 16) & 0xFFU; + buffer[length++] = (m_seqNo >> 8) & 0xFFU; + buffer[length++] = (m_seqNo >> 0) & 0xFFU; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // PTT off - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + // PTT off + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // Type, 0 for audio - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + // Type, 0 for audio + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - length += 320U; - } + length += 320U; m_seqNo = 0U; @@ -250,10 +295,19 @@ void CFMNetwork::clock(unsigned int ms) if (type == 0U) m_buffer.addData(buffer + 32U, length - 32U); + } else if (m_protocol == FMNP_RAW) { + for (int i = 0U; i < length; i += 4U) { + unsigned char data[2U]; + + data[0U] = buffer[i + 0]; + data[1U] = buffer[i + 1]; + + m_buffer.addData(data, 2U); + } } } -unsigned int CFMNetwork::read(float* data, unsigned int nSamples) +unsigned int CFMNetwork::readData(float* data, unsigned int nSamples) { assert(data != NULL); assert(nSamples > 0U); @@ -298,94 +352,92 @@ void CFMNetwork::enable(bool enabled) m_enabled = enabled; } -bool CFMNetwork::writeStart() +bool CFMNetwork::writeUSRPStart() { unsigned char buffer[500U]; ::memset(buffer, 0x00U, 500U); unsigned int length = 0U; - if (m_protocol == FMNP_USRP) { - buffer[length++] = 'U'; - buffer[length++] = 'S'; - buffer[length++] = 'R'; - buffer[length++] = 'P'; + buffer[length++] = 'U'; + buffer[length++] = 'S'; + buffer[length++] = 'R'; + buffer[length++] = 'P'; - // Sequence number - buffer[length++] = (m_seqNo >> 24) & 0xFFU; - buffer[length++] = (m_seqNo >> 16) & 0xFFU; - buffer[length++] = (m_seqNo >> 8) & 0xFFU; - buffer[length++] = (m_seqNo >> 0) & 0xFFU; + // Sequence number + buffer[length++] = (m_seqNo >> 24) & 0xFFU; + buffer[length++] = (m_seqNo >> 16) & 0xFFU; + buffer[length++] = (m_seqNo >> 8) & 0xFFU; + buffer[length++] = (m_seqNo >> 0) & 0xFFU; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // PTT off - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + // PTT off + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // Type, 2 for metadata - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x02U; + // Type, 2 for metadata + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x02U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // TLV TAG for Metadata - buffer[length++] = 0x08U; + // TLV TAG for Metadata + buffer[length++] = 0x08U; - // TLV Length - buffer[length++] = 3U + 4U + 3U + 1U + 1U + m_callsign.size() + 1U; + // TLV Length + buffer[length++] = 3U + 4U + 3U + 1U + 1U + m_callsign.size() + 1U; - // DMR Id - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + // DMR Id + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // Rpt Id - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + // Rpt Id + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // Talk Group - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; - buffer[length++] = 0x00U; + // Talk Group + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; + buffer[length++] = 0x00U; - // Time Slot - buffer[length++] = 0x00U; + // Time Slot + buffer[length++] = 0x00U; - // Color Code - buffer[length++] = 0x00U; + // Color Code + buffer[length++] = 0x00U; - // Callsign - for (std::string::const_iterator it = m_callsign.cbegin(); it != m_callsign.cend(); ++it) - buffer[length++] = *it; + // Callsign + for (std::string::const_iterator it = m_callsign.cbegin(); it != m_callsign.cend(); ++it) + buffer[length++] = *it; - // End of Metadata - buffer[length++] = 0x00U; + // End of Metadata + buffer[length++] = 0x00U; - length = 70U; - } + length = 70U; if (length > 0U) { if (m_debug) diff --git a/FMNetwork.h b/FMNetwork.h index 606bad6..46ff275 100644 --- a/FMNetwork.h +++ b/FMNetwork.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2020,2021,2023 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 @@ -26,7 +26,8 @@ #include enum FM_NETWORK_PROTOCOL { - FMNP_USRP + FMNP_USRP, + FMNP_RAW }; class CFMNetwork { @@ -42,7 +43,7 @@ public: bool writeEnd(); - unsigned int read(float* data, unsigned int nSamples); + unsigned int readData(float* data, unsigned int nSamples); void reset(); @@ -61,7 +62,13 @@ private: CRingBuffer m_buffer; unsigned int m_seqNo; - bool writeStart(); + bool writeUSRPStart(); + + bool writeUSRPData(float* data, unsigned int nSamples); + bool writeRawData(float* data, unsigned int nSamples); + + bool writeUSRPEnd(); }; #endif + diff --git a/MMDVM.ini b/MMDVM.ini index 649572a..9a2df43 100644 --- a/MMDVM.ini +++ b/MMDVM.ini @@ -287,7 +287,8 @@ Debug=0 [FM Network] Enable=1 -# Protocol=USRP +# Protocol may be USRP or RAW +Protocol=USRP LocalAddress=127.0.0.1 LocalPort=3810 GatewayAddress=127.0.0.1 diff --git a/Version.h b/Version.h index 19548a7..5ff4f06 100644 --- a/Version.h +++ b/Version.h @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20230927"; +const char* VERSION = "20231017"; #endif From e91f640e7e1e4fcc7355aec72fb3224634430d86 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 18 Oct 2023 11:34:38 +0100 Subject: [PATCH 2/4] Add a resampler to the FM network RAW data mode. --- Conf.cpp | 10 +++- Conf.h | 4 +- FMNetwork.cpp | 127 +++++++++++++++++++++++++++++++------------ FMNetwork.h | 15 +++-- MMDVM.ini | 2 + MMDVMHost.cpp | 5 +- Makefile | 2 +- Makefile.Pi.Adafruit | 2 +- Makefile.Pi.HD44780 | 2 +- Makefile.Pi.I2C | 2 +- Makefile.Pi.OLED | 2 +- Makefile.Pi.PCF8574 | 2 +- 12 files changed, 127 insertions(+), 48 deletions(-) diff --git a/Conf.cpp b/Conf.cpp index 77bbe3e..3b117e3 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015-2023 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 @@ -290,6 +290,7 @@ m_pocsagNetworkModeHang(3U), m_pocsagNetworkDebug(false), m_fmNetworkEnabled(false), m_fmNetworkProtocol("USRP"), +m_fmNetworkSampleRate(48000U), m_fmGatewayAddress(), m_fmGatewayPort(0U), m_fmLocalAddress(), @@ -1033,6 +1034,8 @@ bool CConf::read() m_fmNetworkEnabled = ::atoi(value) == 1; else if (::strcmp(key, "Protocol") == 0) m_fmNetworkProtocol = value; + else if (::strcmp(key, "SampleRate") == 0) + m_fmNetworkSampleRate = (unsigned int)::atoi(value); else if (::strcmp(key, "LocalAddress") == 0) m_fmLocalAddress = value; else if (::strcmp(key, "LocalPort") == 0) @@ -2273,6 +2276,11 @@ std::string CConf::getFMNetworkProtocol() const return m_fmNetworkProtocol; } +unsigned int CConf::getFMNetworkSampleRate() const +{ + return m_fmNetworkSampleRate; +} + std::string CConf::getFMGatewayAddress() const { return m_fmGatewayAddress; diff --git a/Conf.h b/Conf.h index ed13f11..fb82a7a 100644 --- a/Conf.h +++ b/Conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015-2023 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 @@ -303,6 +303,7 @@ public: // The FM Network section bool getFMNetworkEnabled() const; std::string getFMNetworkProtocol() const; + unsigned int getFMNetworkSampleRate() const; std::string getFMGatewayAddress() const; unsigned short getFMGatewayPort() const; std::string getFMLocalAddress() const; @@ -620,6 +621,7 @@ private: bool m_fmNetworkEnabled; std::string m_fmNetworkProtocol; + unsigned int m_fmNetworkSampleRate; std::string m_fmGatewayAddress; unsigned short m_fmGatewayPort; std::string m_fmLocalAddress; diff --git a/FMNetwork.cpp b/FMNetwork.cpp index 254ff5c..ffb0d0a 100644 --- a/FMNetwork.cpp +++ b/FMNetwork.cpp @@ -25,22 +25,28 @@ #include #include +const unsigned int MMDVM_SAMPLERATE = 8000U; + const unsigned int BUFFER_LENGTH = 1500U; -CFMNetwork::CFMNetwork(const std::string& callsign, const std::string& protocol, const std::string& localAddress, unsigned short localPort, const std::string& gatewayAddress, unsigned short gatewayPort, bool debug) : +CFMNetwork::CFMNetwork(const std::string& callsign, const std::string& protocol, const std::string& localAddress, unsigned short localPort, const std::string& gatewayAddress, unsigned short gatewayPort, unsigned int sampleRate, bool debug) : m_callsign(callsign), m_protocol(FMNP_USRP), m_socket(localAddress, localPort), m_addr(), m_addrLen(0U), +m_sampleRate(sampleRate), m_debug(debug), m_enabled(false), m_buffer(2000U, "FM Network"), -m_seqNo(0U) +m_seqNo(0U), +m_resampler(NULL), +m_error(0) { assert(!callsign.empty()); assert(gatewayPort > 0U); assert(!gatewayAddress.empty()); + assert(sampleRate > 0U); if (CUDPSocket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0) m_addrLen = 0U; @@ -54,10 +60,13 @@ m_seqNo(0U) m_protocol = FMNP_RAW; else m_protocol = FMNP_USRP; + + m_resampler = ::src_new(SRC_SINC_FASTEST, 1, &m_error); } CFMNetwork::~CFMNetwork() { + ::src_delete(m_resampler); } bool CFMNetwork::open() @@ -72,7 +81,7 @@ bool CFMNetwork::open() return m_socket.open(m_addr); } -bool CFMNetwork::writeData(float* data, unsigned int nSamples) +bool CFMNetwork::writeData(const float* data, unsigned int nSamples) { assert(data != NULL); assert(nSamples > 0U); @@ -85,7 +94,7 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples) return false; } -bool CFMNetwork::writeUSRPData(float* data, unsigned int nSamples) +bool CFMNetwork::writeUSRPData(const float* data, unsigned int nSamples) { assert(data != NULL); assert(nSamples > 0U); @@ -159,24 +168,47 @@ bool CFMNetwork::writeUSRPData(float* data, unsigned int nSamples) return m_socket.write(buffer, length, m_addr, m_addrLen); } -bool CFMNetwork::writeRawData(float* data, unsigned int nSamples) +bool CFMNetwork::writeRawData(const float* in, unsigned int nIn) { - assert(data != NULL); - assert(nSamples > 0U); + assert(in != NULL); + assert(nIn > 0U); - unsigned char buffer[1000U]; - ::memset(buffer, 0x00U, 1000U); + unsigned char buffer[2000U]; unsigned int length = 0U; - for (unsigned int i = 0U; i < nSamples; i++) { - short val = short(data[i] * 32767.0F + 0.5F); // Changing audio format from float to S16LE + if (m_sampleRate != MMDVM_SAMPLERATE) { + unsigned int nOut = (nIn * m_sampleRate) / MMDVM_SAMPLERATE; - buffer[length++] = (val >> 0) & 0xFFU; - buffer[length++] = (val >> 8) & 0xFFU; + float out[1000U]; - buffer[length++] = (val >> 0) & 0xFFU; - buffer[length++] = (val >> 8) & 0xFFU; + SRC_DATA data; + data.data_in = in; + data.data_out = out; + data.input_frames = nIn; + data.output_frames = nOut; + data.end_of_input = 0; + data.src_ratio = float(m_sampleRate) / float(MMDVM_SAMPLERATE); + + int ret = ::src_process(m_resampler, &data); + if (ret != 0) { + LogError("Error from the write resampler - %d - %s", ret, ::src_strerror(ret)); + return false; + } + + for (unsigned int i = 0U; i < nOut; i++) { + short val = short(out[i] * 32767.0F + 0.5F); // Changing audio format from float to S16LE + + buffer[length++] = (val >> 0) & 0xFFU; + buffer[length++] = (val >> 8) & 0xFFU; + } + } else { + for (unsigned int i = 0U; i < nIn; i++) { + short val = short(in[i] * 32767.0F + 0.5F); // Changing audio format from float to S16LE + + buffer[length++] = (val >> 0) & 0xFFU; + buffer[length++] = (val >> 8) & 0xFFU; + } } if (m_debug) @@ -296,38 +328,64 @@ void CFMNetwork::clock(unsigned int ms) if (type == 0U) m_buffer.addData(buffer + 32U, length - 32U); } else if (m_protocol == FMNP_RAW) { - for (int i = 0U; i < length; i += 4U) { - unsigned char data[2U]; - - data[0U] = buffer[i + 0]; - data[1U] = buffer[i + 1]; - - m_buffer.addData(data, 2U); - } + m_buffer.addData(buffer, length); } } -unsigned int CFMNetwork::readData(float* data, unsigned int nSamples) +unsigned int CFMNetwork::readData(float* out, unsigned int nOut) { - assert(data != NULL); - assert(nSamples > 0U); + assert(out != NULL); + assert(nOut > 0U); unsigned int bytes = m_buffer.dataSize() / sizeof(unsigned short); if (bytes == 0U) return 0U; - if (bytes < nSamples) - nSamples = bytes; + if ((m_protocol == FMNP_RAW) && (m_sampleRate != MMDVM_SAMPLERATE)) { + unsigned int nIn = (nOut * m_sampleRate) / MMDVM_SAMPLERATE; - unsigned char buffer[1500U]; - m_buffer.getData(buffer, nSamples * sizeof(unsigned short)); + if (bytes < nIn) { + nIn = bytes; + nOut = (nIn * MMDVM_SAMPLERATE) / m_sampleRate; + } - for (unsigned int i = 0U; i < nSamples; i++) { - short val = ((buffer[i * 2U + 0U] & 0xFFU) << 0) + ((buffer[i * 2U + 1U] & 0xFFU) << 8); - data[i] = float(val) / 65536.0F; + unsigned char buffer[2000U]; + m_buffer.getData(buffer, nIn * sizeof(unsigned short)); + + float in[1000U]; + + for (unsigned int i = 0U; i < nIn; i++) { + short val = ((buffer[i * 2U + 0U] & 0xFFU) << 0) + ((buffer[i * 2U + 1U] & 0xFFU) << 8); + in[i] = float(val) / 65536.0F; + } + + SRC_DATA data; + data.data_in = in; + data.data_out = out; + data.input_frames = nIn; + data.output_frames = nOut; + data.end_of_input = 0; + data.src_ratio = float(MMDVM_SAMPLERATE) / float(m_sampleRate); + + int ret = ::src_process(m_resampler, &data); + if (ret != 0) { + LogError("Error from the read resampler - %d - %s", ret, ::src_strerror(ret)); + return false; + } + } else { + if (bytes < nOut) + nOut = bytes; + + unsigned char buffer[1500U]; + m_buffer.getData(buffer, nOut * sizeof(unsigned short)); + + for (unsigned int i = 0U; i < nOut; i++) { + short val = ((buffer[i * 2U + 0U] & 0xFFU) << 0) + ((buffer[i * 2U + 1U] & 0xFFU) << 8); + out[i] = float(val) / 65536.0F; + } } - return nSamples; + return nOut; } void CFMNetwork::reset() @@ -448,3 +506,4 @@ bool CFMNetwork::writeUSRPStart() return true; } } + diff --git a/FMNetwork.h b/FMNetwork.h index 46ff275..1eca534 100644 --- a/FMNetwork.h +++ b/FMNetwork.h @@ -22,6 +22,8 @@ #include "RingBuffer.h" #include "UDPSocket.h" +#include + #include #include @@ -32,18 +34,18 @@ enum FM_NETWORK_PROTOCOL { class CFMNetwork { public: - CFMNetwork(const std::string& callsign, const std::string& protocol, const std::string& localAddress, unsigned short localPort, const std::string& gatewayAddress, unsigned short gatewayPort, bool debug); + CFMNetwork(const std::string& callsign, const std::string& protocol, const std::string& localAddress, unsigned short localPort, const std::string& gatewayAddress, unsigned short gatewayPort, unsigned int sampleRate, bool debug); ~CFMNetwork(); bool open(); void enable(bool enabled); - bool writeData(float* data, unsigned int nSamples); + bool writeData(const float* data, unsigned int nSamples); bool writeEnd(); - unsigned int readData(float* data, unsigned int nSamples); + unsigned int readData(float* out, unsigned int nOut); void reset(); @@ -57,15 +59,18 @@ private: CUDPSocket m_socket; sockaddr_storage m_addr; unsigned int m_addrLen; + unsigned int m_sampleRate; bool m_debug; bool m_enabled; CRingBuffer m_buffer; unsigned int m_seqNo; + SRC_STATE* m_resampler; + int m_error; bool writeUSRPStart(); - bool writeUSRPData(float* data, unsigned int nSamples); - bool writeRawData(float* data, unsigned int nSamples); + bool writeUSRPData(const float* data, unsigned int nSamples); + bool writeRawData(const float* in, unsigned int nIn); bool writeUSRPEnd(); }; diff --git a/MMDVM.ini b/MMDVM.ini index 9a2df43..c72cdd8 100644 --- a/MMDVM.ini +++ b/MMDVM.ini @@ -289,6 +289,8 @@ Debug=0 Enable=1 # Protocol may be USRP or RAW Protocol=USRP +# SampleRate is only used in RAW mode +SampleRate=48000 LocalAddress=127.0.0.1 LocalPort=3810 GatewayAddress=127.0.0.1 diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index ef8f9d2..b807133 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -1886,6 +1886,7 @@ bool CMMDVMHost::createFMNetwork() { std::string callsign = m_conf.getFMCallsign(); std::string protocol = m_conf.getFMNetworkProtocol(); + unsigned int sampleRate = m_conf.getFMNetworkSampleRate(); std::string gatewayAddress = m_conf.getFMGatewayAddress(); unsigned short gatewayPort = m_conf.getFMGatewayPort(); std::string localAddress = m_conf.getFMLocalAddress(); @@ -1899,6 +1900,8 @@ bool CMMDVMHost::createFMNetwork() LogInfo("FM Network Parameters"); LogInfo(" Protocol: %s", protocol.c_str()); + if (protocol == "RAW") + LogInfo(" Sample Rate: %u", sampleRate); LogInfo(" Gateway Address: %s", gatewayAddress.c_str()); LogInfo(" Gateway Port: %hu", gatewayPort); LogInfo(" Local Address: %s", localAddress.c_str()); @@ -1909,7 +1912,7 @@ bool CMMDVMHost::createFMNetwork() LogInfo(" RX Audio Gain: %.2f", rxAudioGain); LogInfo(" Mode Hang: %us", m_fmNetModeHang); - m_fmNetwork = new CFMNetwork(callsign, protocol, localAddress, localPort, gatewayAddress, gatewayPort, debug); + m_fmNetwork = new CFMNetwork(callsign, protocol, localAddress, localPort, gatewayAddress, gatewayPort, sampleRate, debug); bool ret = m_fmNetwork->open(); if (!ret) { diff --git a/Makefile b/Makefile index bac0711..2d8de07 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -I/usr/local/include -LIBS = -lpthread -lutil +LIBS = -lpthread -lutil -lsamplerate LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Makefile.Pi.Adafruit b/Makefile.Pi.Adafruit index 5569a89..1b4e15b 100644 --- a/Makefile.Pi.Adafruit +++ b/Makefile.Pi.Adafruit @@ -4,7 +4,7 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DHD44780 -DADAFRUIT_DISPLAY -I/usr/local/include -LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil +LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil -lsamplerate LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Makefile.Pi.HD44780 b/Makefile.Pi.HD44780 index 4a48db6..31a3159 100644 --- a/Makefile.Pi.HD44780 +++ b/Makefile.Pi.HD44780 @@ -3,7 +3,7 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DHD44780 -I/usr/local/include -LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil +LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil -lsamplerate LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Makefile.Pi.I2C b/Makefile.Pi.I2C index 18b2bb6..44f436d 100644 --- a/Makefile.Pi.I2C +++ b/Makefile.Pi.I2C @@ -3,7 +3,7 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DRASPBERRY_PI -I/usr/local/include -LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil +LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil -lsamplerate LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Makefile.Pi.OLED b/Makefile.Pi.OLED index 3beb357..538ff64 100644 --- a/Makefile.Pi.OLED +++ b/Makefile.Pi.OLED @@ -3,7 +3,7 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DOLED -I/usr/local/include -LIBS = -lArduiPi_OLED -lpthread -lutil +LIBS = -lArduiPi_OLED -lpthread -lutil -lsamplerate # If you use NetBSD, add following CFLAGS #CFLAGS += -L/usr/local/lib -Wl,-rpath=/usr/local/lib diff --git a/Makefile.Pi.PCF8574 b/Makefile.Pi.PCF8574 index 18e975f..c1aa3d1 100644 --- a/Makefile.Pi.PCF8574 +++ b/Makefile.Pi.PCF8574 @@ -4,7 +4,7 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DHD44780 -DPCF8574_DISPLAY -I/usr/local/include -LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil +LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil -lsamplerate LDFLAGS = -g -L/usr/local/lib OBJECTS = \ From b6269c73da384ea4fc60f5f22136a6d1ff50384d Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 18 Oct 2023 19:59:03 +0100 Subject: [PATCH 3/4] Loosen packet source check in RAW mode. --- FMNetwork.cpp | 13 ++++++++++--- Version.h | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/FMNetwork.cpp b/FMNetwork.cpp index ffb0d0a..2bfdc8a 100644 --- a/FMNetwork.cpp +++ b/FMNetwork.cpp @@ -300,9 +300,16 @@ void CFMNetwork::clock(unsigned int ms) 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_protocol == FMNP_USRP) { + if (!CUDPSocket::match(addr, m_addr, IMT_ADDRESS_AND_PORT)) { + LogMessage("FM packet received from an invalid source"); + return; + } + } else { + if (!CUDPSocket::match(addr, m_addr, IMT_ADDRESS_ONLY)) { + LogMessage("FM packet received from an invalid source"); + return; + } } if (!m_enabled) diff --git a/Version.h b/Version.h index 5ff4f06..e8bf195 100644 --- a/Version.h +++ b/Version.h @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20231017"; +const char* VERSION = "20231018"; #endif From 3eb29a0bf5601a1c3737b6a50806d438082c7804 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 25 Oct 2023 12:39:09 +0100 Subject: [PATCH 4/4] Add the squelch file as needed by SVXLink. --- Conf.cpp | 8 +++++++ Conf.h | 2 ++ FMNetwork.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++++++--- FMNetwork.h | 6 ++++- MMDVM.ini | 2 ++ MMDVMHost.cpp | 7 ++++-- Version.h | 2 +- 7 files changed, 82 insertions(+), 7 deletions(-) diff --git a/Conf.cpp b/Conf.cpp index 3b117e3..83aa2c5 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -291,6 +291,7 @@ m_pocsagNetworkDebug(false), m_fmNetworkEnabled(false), m_fmNetworkProtocol("USRP"), m_fmNetworkSampleRate(48000U), +m_fmNetworkSquelchFile(), m_fmGatewayAddress(), m_fmGatewayPort(0U), m_fmLocalAddress(), @@ -1036,6 +1037,8 @@ bool CConf::read() m_fmNetworkProtocol = value; else if (::strcmp(key, "SampleRate") == 0) m_fmNetworkSampleRate = (unsigned int)::atoi(value); + else if (::strcmp(key, "SquelchFile") == 0) + m_fmNetworkSquelchFile = value; else if (::strcmp(key, "LocalAddress") == 0) m_fmLocalAddress = value; else if (::strcmp(key, "LocalPort") == 0) @@ -2281,6 +2284,11 @@ unsigned int CConf::getFMNetworkSampleRate() const return m_fmNetworkSampleRate; } +std::string CConf::getFMNetworkSquelchFile() const +{ + return m_fmNetworkSquelchFile; +} + std::string CConf::getFMGatewayAddress() const { return m_fmGatewayAddress; diff --git a/Conf.h b/Conf.h index fb82a7a..96f5773 100644 --- a/Conf.h +++ b/Conf.h @@ -304,6 +304,7 @@ public: bool getFMNetworkEnabled() const; std::string getFMNetworkProtocol() const; unsigned int getFMNetworkSampleRate() const; + std::string getFMNetworkSquelchFile() const; std::string getFMGatewayAddress() const; unsigned short getFMGatewayPort() const; std::string getFMLocalAddress() const; @@ -622,6 +623,7 @@ private: bool m_fmNetworkEnabled; std::string m_fmNetworkProtocol; unsigned int m_fmNetworkSampleRate; + std::string m_fmNetworkSquelchFile; std::string m_fmGatewayAddress; unsigned short m_fmGatewayPort; std::string m_fmLocalAddress; diff --git a/FMNetwork.cpp b/FMNetwork.cpp index 2bfdc8a..02ff5bf 100644 --- a/FMNetwork.cpp +++ b/FMNetwork.cpp @@ -25,23 +25,30 @@ #include #include +#include +#include +#include +#include + const unsigned int MMDVM_SAMPLERATE = 8000U; const unsigned int BUFFER_LENGTH = 1500U; -CFMNetwork::CFMNetwork(const std::string& callsign, const std::string& protocol, const std::string& localAddress, unsigned short localPort, const std::string& gatewayAddress, unsigned short gatewayPort, unsigned int sampleRate, bool debug) : +CFMNetwork::CFMNetwork(const std::string& callsign, const std::string& protocol, const std::string& localAddress, unsigned short localPort, const std::string& gatewayAddress, unsigned short gatewayPort, unsigned int sampleRate, const std::string& squelchFile, bool debug) : m_callsign(callsign), m_protocol(FMNP_USRP), m_socket(localAddress, localPort), m_addr(), m_addrLen(0U), m_sampleRate(sampleRate), +m_squelchFile(squelchFile), m_debug(debug), m_enabled(false), m_buffer(2000U, "FM Network"), m_seqNo(0U), m_resampler(NULL), -m_error(0) +m_error(0), +m_fd(-1) { assert(!callsign.empty()); assert(gatewayPort > 0U); @@ -78,6 +85,14 @@ bool CFMNetwork::open() LogMessage("Opening FM network connection"); + if (!m_squelchFile.empty()) { + m_fd = ::open(m_squelchFile.c_str(), O_WRONLY | O_SYNC); + if (m_fd == -1) { + LogError("Cannot open the squelch file: %s, errno=%d", m_squelchFile.c_str(), errno); + return false; + } + } + return m_socket.open(m_addr); } @@ -173,6 +188,12 @@ bool CFMNetwork::writeRawData(const float* in, unsigned int nIn) assert(in != NULL); assert(nIn > 0U); + if (m_seqNo == 0U) { + bool ret = writeRawStart(); + if (!ret) + return false; + } + unsigned char buffer[2000U]; unsigned int length = 0U; @@ -214,6 +235,8 @@ bool CFMNetwork::writeRawData(const float* in, unsigned int nIn) if (m_debug) CUtils::dump(1U, "FM Network Data Sent", buffer, length); + m_seqNo++; + return m_socket.write(buffer, length, m_addr, m_addrLen); } @@ -222,7 +245,7 @@ bool CFMNetwork::writeEnd() if (m_protocol == FMNP_USRP) return writeUSRPEnd(); else - return true; + return writeRawEnd(); } bool CFMNetwork::writeUSRPEnd() @@ -289,6 +312,21 @@ bool CFMNetwork::writeUSRPEnd() } } +bool CFMNetwork::writeRawEnd() +{ + m_seqNo = 0U; + + if (m_fd != -1) { + size_t n = ::write(m_fd, "Z", 1); + if (n != 1) { + LogError("Cannot write to the squelch file: %s, errno=%d", m_squelchFile.c_str(), errno); + return false; + } + } + + return true; +} + void CFMNetwork::clock(unsigned int ms) { unsigned char buffer[BUFFER_LENGTH]; @@ -404,6 +442,11 @@ void CFMNetwork::close() { m_socket.close(); + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } + LogMessage("Closing FM network connection"); } @@ -514,3 +557,16 @@ bool CFMNetwork::writeUSRPStart() } } +bool CFMNetwork::writeRawStart() +{ + if (m_fd != -1) { + size_t n = ::write(m_fd, "O", 1); + if (n != 1) { + LogError("Cannot write to the squelch file: %s, errno=%d", m_squelchFile.c_str(), errno); + return false; + } + } + + return true; +} + diff --git a/FMNetwork.h b/FMNetwork.h index 1eca534..9940f90 100644 --- a/FMNetwork.h +++ b/FMNetwork.h @@ -34,7 +34,7 @@ enum FM_NETWORK_PROTOCOL { class CFMNetwork { public: - CFMNetwork(const std::string& callsign, const std::string& protocol, const std::string& localAddress, unsigned short localPort, const std::string& gatewayAddress, unsigned short gatewayPort, unsigned int sampleRate, bool debug); + CFMNetwork(const std::string& callsign, const std::string& protocol, const std::string& localAddress, unsigned short localPort, const std::string& gatewayAddress, unsigned short gatewayPort, unsigned int sampleRate, const std::string& squelchFile, bool debug); ~CFMNetwork(); bool open(); @@ -60,19 +60,23 @@ private: sockaddr_storage m_addr; unsigned int m_addrLen; unsigned int m_sampleRate; + std::string m_squelchFile; bool m_debug; bool m_enabled; CRingBuffer m_buffer; unsigned int m_seqNo; SRC_STATE* m_resampler; int m_error; + int m_fd; bool writeUSRPStart(); + bool writeRawStart(); bool writeUSRPData(const float* data, unsigned int nSamples); bool writeRawData(const float* in, unsigned int nIn); bool writeUSRPEnd(); + bool writeRawEnd(); }; #endif diff --git a/MMDVM.ini b/MMDVM.ini index c72cdd8..9693e38 100644 --- a/MMDVM.ini +++ b/MMDVM.ini @@ -291,6 +291,8 @@ Enable=1 Protocol=USRP # SampleRate is only used in RAW mode SampleRate=48000 +# The squelch file is optional and only used in RAW mode +SquelchFile=/tmp/sql LocalAddress=127.0.0.1 LocalPort=3810 GatewayAddress=127.0.0.1 diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index b807133..4cc1c18 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -1887,6 +1887,7 @@ bool CMMDVMHost::createFMNetwork() std::string callsign = m_conf.getFMCallsign(); std::string protocol = m_conf.getFMNetworkProtocol(); unsigned int sampleRate = m_conf.getFMNetworkSampleRate(); + std::string squelchFile = m_conf.getFMNetworkSquelchFile(); std::string gatewayAddress = m_conf.getFMGatewayAddress(); unsigned short gatewayPort = m_conf.getFMGatewayPort(); std::string localAddress = m_conf.getFMLocalAddress(); @@ -1900,8 +1901,10 @@ bool CMMDVMHost::createFMNetwork() LogInfo("FM Network Parameters"); LogInfo(" Protocol: %s", protocol.c_str()); - if (protocol == "RAW") + if (protocol == "RAW") { LogInfo(" Sample Rate: %u", sampleRate); + LogInfo(" Squelch File: %s", squelchFile.empty() ? "(none)" : squelchFile.c_str()); + } LogInfo(" Gateway Address: %s", gatewayAddress.c_str()); LogInfo(" Gateway Port: %hu", gatewayPort); LogInfo(" Local Address: %s", localAddress.c_str()); @@ -1912,7 +1915,7 @@ bool CMMDVMHost::createFMNetwork() LogInfo(" RX Audio Gain: %.2f", rxAudioGain); LogInfo(" Mode Hang: %us", m_fmNetModeHang); - m_fmNetwork = new CFMNetwork(callsign, protocol, localAddress, localPort, gatewayAddress, gatewayPort, sampleRate, debug); + m_fmNetwork = new CFMNetwork(callsign, protocol, localAddress, localPort, gatewayAddress, gatewayPort, sampleRate, squelchFile, debug); bool ret = m_fmNetwork->open(); if (!ret) { diff --git a/Version.h b/Version.h index e8bf195..439756d 100644 --- a/Version.h +++ b/Version.h @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20231018"; +const char* VERSION = "20231025"; #endif