diff --git a/Conf.cpp b/Conf.cpp index fd3c0b0..9f5eb4c 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2022 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 @@ -296,6 +296,8 @@ m_pocsagNetworkModeHang(3U), m_pocsagNetworkDebug(false), m_fmNetworkEnabled(false), m_fmNetworkProtocol("USRP"), +m_fmNetworkSampleRate(48000U), +m_fmNetworkSquelchFile(), m_fmGatewayAddress(), m_fmGatewayPort(0U), m_fmLocalAddress(), @@ -1052,6 +1054,10 @@ 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, "SquelchFile") == 0) + m_fmNetworkSquelchFile = value; else if (::strcmp(key, "LocalAddress") == 0) m_fmLocalAddress = value; else if (::strcmp(key, "LocalPort") == 0) @@ -2317,6 +2323,16 @@ std::string CConf::getFMNetworkProtocol() const return m_fmNetworkProtocol; } +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 f7de0b3..6e93807 100644 --- a/Conf.h +++ b/Conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2022 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 @@ -310,6 +310,8 @@ public: // The FM Network section 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; @@ -633,6 +635,8 @@ 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/FMControl.cpp b/FMControl.cpp index f91c9b1..a4bf549 100644 --- a/FMControl.cpp +++ b/FMControl.cpp @@ -178,7 +178,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..02ff5bf 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 @@ -25,22 +25,35 @@ #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, 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_seqNo(0U), +m_resampler(NULL), +m_error(0), +m_fd(-1) { 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; @@ -50,12 +63,17 @@ 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; + + m_resampler = ::src_new(SRC_SINC_FASTEST, 1, &m_error); } CFMNetwork::~CFMNetwork() { + ::src_delete(m_resampler); } bool CFMNetwork::open() @@ -67,16 +85,37 @@ 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); } -bool CFMNetwork::writeData(float* data, unsigned int nSamples) +bool CFMNetwork::writeData(const 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(const 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 +125,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 +183,122 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples) return m_socket.write(buffer, length, m_addr, m_addrLen); } +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; + + if (m_sampleRate != MMDVM_SAMPLERATE) { + unsigned int nOut = (nIn * m_sampleRate) / MMDVM_SAMPLERATE; + + float out[1000U]; + + 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) + CUtils::dump(1U, "FM Network Data Sent", buffer, length); + + m_seqNo++; + + return m_socket.write(buffer, length, m_addr, m_addrLen); +} + bool CFMNetwork::writeEnd() +{ + if (m_protocol == FMNP_USRP) + return writeUSRPEnd(); + else + return writeRawEnd(); +} + +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; @@ -212,6 +312,21 @@ bool CFMNetwork::writeEnd() } } +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]; @@ -223,9 +338,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) @@ -250,30 +372,65 @@ void CFMNetwork::clock(unsigned int ms) if (type == 0U) m_buffer.addData(buffer + 32U, length - 32U); + } else if (m_protocol == FMNP_RAW) { + m_buffer.addData(buffer, length); } } -unsigned int CFMNetwork::read(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() @@ -285,6 +442,11 @@ void CFMNetwork::close() { m_socket.close(); + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } + LogMessage("Closing FM network connection"); } @@ -298,94 +460,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) @@ -396,3 +556,17 @@ bool CFMNetwork::writeStart() return true; } } + +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 606bad6..9940f90 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 @@ -22,27 +22,30 @@ #include "RingBuffer.h" #include "UDPSocket.h" +#include + #include #include enum FM_NETWORK_PROTOCOL { - FMNP_USRP + FMNP_USRP, + FMNP_RAW }; 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, const std::string& squelchFile, 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 read(float* data, unsigned int nSamples); + unsigned int readData(float* out, unsigned int nOut); void reset(); @@ -56,12 +59,25 @@ private: CUDPSocket m_socket; 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 writeStart(); + 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 31a8a59..0a18f74 100644 --- a/MMDVM.ini +++ b/MMDVM.ini @@ -294,7 +294,12 @@ Debug=0 [FM Network] Enable=1 -# Protocol=USRP +# Protocol may be USRP or RAW +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 7bdf8b7..dbd9133 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -1912,6 +1912,8 @@ 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(); @@ -1925,6 +1927,10 @@ bool CMMDVMHost::createFMNetwork() LogInfo("FM Network Parameters"); LogInfo(" Protocol: %s", protocol.c_str()); + 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()); @@ -1935,7 +1941,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, squelchFile, debug); bool ret = m_fmNetwork->open(); if (!ret) { diff --git a/Makefile b/Makefile index f9dcb4b..b59126f 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 -lmosquitto +LIBS = -lpthread -lutil -lsamplerate -lmosquitto LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Makefile.Pi.Adafruit b/Makefile.Pi.Adafruit index 7e86204..2ae7bfb 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 -lmosquitto +LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil -lsamplerate -lmosquitto LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Makefile.Pi.HD44780 b/Makefile.Pi.HD44780 index f61e77b..5b68768 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 -lmosquitto +LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil -lsamplerate -lmosquitto LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Makefile.Pi.I2C b/Makefile.Pi.I2C index fde1c41..1da6c36 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 -lmosquitto +LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil -lsamplerate -lmosquitto LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Makefile.Pi.OLED b/Makefile.Pi.OLED index 39ba604..b9554db 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 -lmosquitto +LIBS = -lArduiPi_OLED -lpthread -lutil -lsamplerate -lmosquitto # 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 1207ccc..036ce50 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 -lmosquitto +LIBS = -lwiringPi -lwiringPiDev -lpthread -lutil -lsamplerate -lmosquitto LDFLAGS = -g -L/usr/local/lib OBJECTS = \ diff --git a/Version.h b/Version.h index 19548a7..439756d 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 = "20231025"; #endif