From e91f640e7e1e4fcc7355aec72fb3224634430d86 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 18 Oct 2023 11:34:38 +0100 Subject: [PATCH] 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 = \