From 5eff8ef3a3a96b4c48d045764d35250cb9d3b670 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 10 Dec 2019 08:26:53 +0100 Subject: [PATCH] Remote Sink: refactoring of classes --- plugins/channelrx/remotesink/CMakeLists.txt | 18 +- plugins/channelrx/remotesink/remotesink.cpp | 300 +++--------------- plugins/channelrx/remotesink/remotesink.h | 79 +---- .../remotesink/remotesinkbaseband.cpp | 153 +++++++++ .../channelrx/remotesink/remotesinkbaseband.h | 107 +++++++ .../channelrx/remotesink/remotesinkgui.cpp | 19 +- plugins/channelrx/remotesink/remotesinkgui.h | 1 - .../channelrx/remotesink/remotesinksink.cpp | 254 +++++++++++++++ plugins/channelrx/remotesink/remotesinksink.h | 82 +++++ 9 files changed, 663 insertions(+), 350 deletions(-) create mode 100644 plugins/channelrx/remotesink/remotesinkbaseband.cpp create mode 100644 plugins/channelrx/remotesink/remotesinkbaseband.h create mode 100644 plugins/channelrx/remotesink/remotesinksink.cpp create mode 100644 plugins/channelrx/remotesink/remotesinksink.h diff --git a/plugins/channelrx/remotesink/CMakeLists.txt b/plugins/channelrx/remotesink/CMakeLists.txt index dcb42329d..b6845bab5 100644 --- a/plugins/channelrx/remotesink/CMakeLists.txt +++ b/plugins/channelrx/remotesink/CMakeLists.txt @@ -10,7 +10,9 @@ else() endif() set(remotesink_SOURCES - remotesink.cpp + remotesink.cpp + remotesinkbaseband.cpp + remotesinksink.cpp remotesinksettings.cpp remotesinkwebapiadapter.cpp remotesinkthread.cpp @@ -19,6 +21,8 @@ set(remotesink_SOURCES set(remotesink_HEADERS remotesink.h + remotesinkbaseband.h + remotesinksink.h remotesinksettings.h remotesinkwebapiadapter.h remotesinkthread.h @@ -36,14 +40,12 @@ if(NOT SERVER_MODE) set(remotesink_SOURCES ${remotesink_SOURCES} remotesinkgui.cpp - - remotesinkgui.ui + remotesinkgui.ui ) set(remotesink_HEADERS ${remotesink_HEADERS} remotesinkgui.h ) - set(TARGET_NAME remotesink) set(TARGET_LIB "Qt5::Widgets") set(TARGET_LIB_GUI "sdrgui") @@ -64,12 +66,12 @@ if(ENABLE_EXTERNAL_LIBRARIES) endif() target_link_libraries(${TARGET_NAME} - Qt5::Core - ${TARGET_LIB} + Qt5::Core + ${TARGET_LIB} sdrbase ${TARGET_LIB_GUI} - ${CM256CC_LIBRARIES} - swagger + ${CM256CC_LIBRARIES} + swagger ) install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/channelrx/remotesink/remotesink.cpp b/plugins/channelrx/remotesink/remotesink.cpp index 80bd1e67b..1357487df 100644 --- a/plugins/channelrx/remotesink/remotesink.cpp +++ b/plugins/channelrx/remotesink/remotesink.cpp @@ -23,35 +23,23 @@ #include "remotesink.h" -#if (defined _WIN32_) || (defined _MSC_VER) -#include "windows_time.h" -#include -#else -#include -#include -#endif -#include -#include - #include #include #include +#include #include "SWGChannelSettings.h" #include "util/simpleserializer.h" -#include "dsp/threadedbasebandsamplesink.h" -#include "dsp/downchannelizer.h" #include "dsp/dspcommands.h" #include "dsp/hbfilterchainconverter.h" #include "dsp/devicesamplemimo.h" #include "device/deviceapi.h" -#include "remotesinkthread.h" +#include "remotesinkbaseband.h" MESSAGE_CLASS_DEFINITION(RemoteSink::MsgConfigureRemoteSink, Message) -MESSAGE_CLASS_DEFINITION(RemoteSink::MsgSampleRateNotification, Message) -MESSAGE_CLASS_DEFINITION(RemoteSink::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(RemoteSink::MsgBasebandSampleRateNotification, Message) const QString RemoteSink::m_channelIdURI = "sdrangel.channel.remotesink"; const QString RemoteSink::m_channelId = "RemoteSink"; @@ -59,26 +47,18 @@ const QString RemoteSink::m_channelId = "RemoteSink"; RemoteSink::RemoteSink(DeviceAPI *deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), m_deviceAPI(deviceAPI), - m_running(false), - m_sinkThread(0), - m_txBlockIndex(0), - m_frameCount(0), - m_sampleIndex(0), - m_dataBlock(0), - m_centerFrequency(0), m_frequencyOffset(0), - m_sampleRate(48000), - m_deviceSampleRate(48000), - m_nbBlocksFEC(0), - m_txDelay(35), - m_dataAddress("127.0.0.1"), - m_dataPort(9090) + m_basebandSampleRate(48000) { setObjectName(m_channelId); - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addChannelSink(m_threadedChannelizer); + m_thread = new QThread(this); + m_basebandSink = new RemoteSinkBaseband(); + m_basebandSink->moveToThread(m_thread); + + applySettings(m_settings, true); + + m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); m_networkManager = new QNetworkAccessManager(); @@ -89,36 +69,10 @@ RemoteSink::~RemoteSink() { disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); delete m_networkManager; - m_dataBlockMutex.lock(); - - if (m_dataBlock && !m_dataBlock->m_txControlBlock.m_complete) { - delete m_dataBlock; - } - - m_dataBlockMutex.unlock(); m_deviceAPI->removeChannelSinkAPI(this); - m_deviceAPI->removeChannelSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; -} - -void RemoteSink::setTxDelay(int txDelay, int nbBlocksFEC) -{ - double txDelayRatio = txDelay / 100.0; - int samplesPerBlock = RemoteNbBytesPerBlock / sizeof(Sample); - double delay = m_sampleRate == 0 ? 1.0 : (127*samplesPerBlock*txDelayRatio) / m_sampleRate; - delay /= 128 + nbBlocksFEC; - m_txDelay = roundf(delay*1e6); // microseconds - qDebug() << "RemoteSink::setTxDelay:" - << " " << txDelay - << "% m_txDelay: " << m_txDelay << "us" - << " m_sampleRate: " << m_sampleRate << "S/s"; -} - -void RemoteSink::setNbBlocksFEC(int nbBlocksFEC) -{ - qDebug() << "RemoteSink::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC; - m_nbBlocksFEC = nbBlocksFEC; + m_deviceAPI->removeChannelSink(this); + delete m_basebandSink; + delete m_thread; } uint32_t RemoteSink::getNumberOfDeviceStreams() const @@ -129,167 +83,29 @@ uint32_t RemoteSink::getNumberOfDeviceStreams() const void RemoteSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) { (void) firstOfBurst; - SampleVector::const_iterator it = begin; - - while (it != end) - { - int inSamplesIndex = it - begin; - int inRemainingSamples = end - it; - - if (m_txBlockIndex == 0) - { - struct timeval tv; - RemoteMetaDataFEC metaData; - gettimeofday(&tv, 0); - - metaData.m_centerFrequency = m_centerFrequency + m_frequencyOffset; - metaData.m_sampleRate = m_sampleRate; - metaData.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); - metaData.m_sampleBits = SDR_RX_SAMP_SZ; - metaData.m_nbOriginalBlocks = RemoteNbOrginalBlocks; - metaData.m_nbFECBlocks = m_nbBlocksFEC; - metaData.m_tv_sec = tv.tv_sec; - metaData.m_tv_usec = tv.tv_usec; - - if (!m_dataBlock) { // on the very first cycle there is no data block allocated - m_dataBlock = new RemoteDataBlock(); - } - - boost::crc_32_type crc32; - crc32.process_bytes(&metaData, sizeof(RemoteMetaDataFEC)-4); - metaData.m_crc32 = crc32.checksum(); - RemoteSuperBlock& superBlock = m_dataBlock->m_superBlocks[0]; // first block - superBlock.init(); - superBlock.m_header.m_frameIndex = m_frameCount; - superBlock.m_header.m_blockIndex = m_txBlockIndex; - superBlock.m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); - superBlock.m_header.m_sampleBits = SDR_RX_SAMP_SZ; - - RemoteMetaDataFEC *destMeta = (RemoteMetaDataFEC *) &superBlock.m_protectedBlock; - *destMeta = metaData; - - if (!(metaData == m_currentMetaFEC)) - { - qDebug() << "RemoteSink::feed: meta: " - << "|" << metaData.m_centerFrequency - << ":" << metaData.m_sampleRate - << ":" << (int) (metaData.m_sampleBytes & 0xF) - << ":" << (int) metaData.m_sampleBits - << "|" << (int) metaData.m_nbOriginalBlocks - << ":" << (int) metaData.m_nbFECBlocks - << "|" << metaData.m_tv_sec - << ":" << metaData.m_tv_usec; - - m_currentMetaFEC = metaData; - } - - m_txBlockIndex = 1; // next Tx block with data - } // block zero - - // handle different sample sizes... - int samplesPerBlock = RemoteNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8); // two I or Q samples - if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block - { - memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], - (const void *) &(*(begin+inSamplesIndex)), - inRemainingSamples * sizeof(Sample)); - m_sampleIndex += inRemainingSamples; - it = end; // all input samples are consumed - } - else // complete super block and initiate the next if not end of frame - { - memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], - (const void *) &(*(begin+inSamplesIndex)), - (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); - it += samplesPerBlock - m_sampleIndex; - m_sampleIndex = 0; - - m_superBlock.m_header.m_frameIndex = m_frameCount; - m_superBlock.m_header.m_blockIndex = m_txBlockIndex; - m_superBlock.m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); - m_superBlock.m_header.m_sampleBits = SDR_RX_SAMP_SZ; - m_dataBlock->m_superBlocks[m_txBlockIndex] = m_superBlock; - - if (m_txBlockIndex == RemoteNbOrginalBlocks - 1) // frame complete - { - m_dataBlockMutex.lock(); - m_dataBlock->m_txControlBlock.m_frameIndex = m_frameCount; - m_dataBlock->m_txControlBlock.m_processed = false; - m_dataBlock->m_txControlBlock.m_complete = true; - m_dataBlock->m_txControlBlock.m_nbBlocksFEC = m_nbBlocksFEC; - m_dataBlock->m_txControlBlock.m_txDelay = m_txDelay; - m_dataBlock->m_txControlBlock.m_dataAddress = m_dataAddress; - m_dataBlock->m_txControlBlock.m_dataPort = m_dataPort; - - emit dataBlockAvailable(m_dataBlock); - m_dataBlock = new RemoteDataBlock(); // create a new one immediately - m_dataBlockMutex.unlock(); - - m_txBlockIndex = 0; - m_frameCount++; - } - else - { - m_txBlockIndex++; - } - } - } + m_basebandSink->feed(begin, end); } void RemoteSink::start() { qDebug("RemoteSink::start"); - - memset((void *) &m_currentMetaFEC, 0, sizeof(RemoteMetaDataFEC)); - - if (m_running) { - stop(); - } - - m_sinkThread = new RemoteSinkThread(); - connect(this, - SIGNAL(dataBlockAvailable(RemoteDataBlock *)), - m_sinkThread, - SLOT(processDataBlock(RemoteDataBlock *)), - Qt::QueuedConnection); - m_sinkThread->startStop(true); - m_running = true; + m_basebandSink->reset(); + m_thread->start(); + m_basebandSink->startSink(); } void RemoteSink::stop() { qDebug("RemoteSink::stop"); - - if (m_sinkThread != 0) - { - m_sinkThread->startStop(false); - m_sinkThread->deleteLater(); - m_sinkThread = 0; - } - - m_running = false; + m_basebandSink->stopSink(); + m_thread->exit(); + m_thread->wait(); } bool RemoteSink::handleMessage(const Message& cmd) { (void) cmd; - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - qDebug() << "RemoteSink::handleMessage: MsgChannelizerNotification:" - << " channelSampleRate: " << notif.getSampleRate() - << " offsetFrequency: " << notif.getFrequencyOffset(); - - if (notif.getSampleRate() > 0) { - setSampleRate(notif.getSampleRate()); - } - - setTxDelay(m_settings.m_txDelay, m_settings.m_nbFECBlocks); - - return true; - } - else if (DSPSignalNotification::match(cmd)) + if (DSPSignalNotification::match(cmd)) { DSPSignalNotification& notif = (DSPSignalNotification&) cmd; @@ -297,18 +113,18 @@ bool RemoteSink::handleMessage(const Message& cmd) << " inputSampleRate: " << notif.getSampleRate() << " centerFrequency: " << notif.getCenterFrequency(); - setCenterFrequency(notif.getCenterFrequency()); - m_deviceSampleRate = notif.getSampleRate(); - calculateFrequencyOffset(); // This is when device sample rate changes + m_basebandSampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); - // Redo the channelizer stuff with the new sample rate to re-synchronize everything - m_channelizer->set(m_channelizer->getInputMessageQueue(), - m_settings.m_log2Decim, - m_settings.m_filterChainHash); + calculateFrequencyOffset(); // This is when device sample rate changes + //propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex, m_settings.m_log2Decim); + + MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(m_basebandSampleRate); + m_basebandSink->getInputMessageQueue()->push(msg); if (m_guiMessageQueue) { - MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate()); + MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(m_basebandSampleRate); m_guiMessageQueue->push(msg); } @@ -322,24 +138,6 @@ bool RemoteSink::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - m_settings.m_log2Decim = cfg.getLog2Decim(); - m_settings.m_filterChainHash = cfg.getFilterChainHash(); - - qDebug() << "RemoteSink::handleMessage: MsgConfigureChannelizer:" - << " log2Decim: " << m_settings.m_log2Decim - << " filterChainHash: " << m_settings.m_filterChainHash; - - m_channelizer->set(m_channelizer->getInputMessageQueue(), - m_settings.m_log2Decim, - m_settings.m_filterChainHash); - - calculateFrequencyOffset(); // This is when decimation or filter chain changes - - return true; - } else { return false; @@ -381,29 +179,17 @@ void RemoteSink::applySettings(const RemoteSinkSettings& settings, bool force) QList reverseAPIKeys; - if ((m_settings.m_nbFECBlocks != settings.m_nbFECBlocks) || force) - { + if ((m_settings.m_nbFECBlocks != settings.m_nbFECBlocks) || force) { reverseAPIKeys.append("nbFECBlocks"); - setNbBlocksFEC(settings.m_nbFECBlocks); - setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); } - - if ((m_settings.m_txDelay != settings.m_txDelay) || force) - { + if ((m_settings.m_txDelay != settings.m_txDelay) || force) { reverseAPIKeys.append("txDelay"); - setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); } - - if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) - { + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { reverseAPIKeys.append("dataAddress"); - m_dataAddress = settings.m_dataAddress; } - - if ((m_settings.m_dataPort != settings.m_dataPort) || force) - { + if ((m_settings.m_dataPort != settings.m_dataPort) || force) { reverseAPIKeys.append("dataPort"); - m_dataPort = settings.m_dataPort; } if (m_settings.m_streamIndex != settings.m_streamIndex) @@ -411,16 +197,17 @@ void RemoteSink::applySettings(const RemoteSinkSettings& settings, bool force) if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only { m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); - m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); - m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); - // apply stream sample rate to itself - //applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); } reverseAPIKeys.append("streamIndex"); } + RemoteSinkBaseband::MsgConfigureRemoteSinkBaseband *msg = RemoteSinkBaseband::MsgConfigureRemoteSinkBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + if ((settings.m_useReverseAPI) && (reverseAPIKeys.size() != 0)) { bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || @@ -448,7 +235,7 @@ void RemoteSink::validateFilterChainHash(RemoteSinkSettings& settings) void RemoteSink::calculateFrequencyOffset() { double shiftFactor = HBFilterChainConverter::getShiftFactor(m_settings.m_log2Decim, m_settings.m_filterChainHash); - m_frequencyOffset = m_deviceSampleRate * shiftFactor; + m_frequencyOffset = m_basebandSampleRate * shiftFactor; } int RemoteSink::webapiSettingsGet( @@ -475,13 +262,8 @@ int RemoteSink::webapiSettingsPutPatch( MsgConfigureRemoteSink *msg = MsgConfigureRemoteSink::create(settings, force); m_inputMessageQueue.push(msg); - if ((settings.m_log2Decim != m_settings.m_log2Decim) || (settings.m_filterChainHash != m_settings.m_filterChainHash) || force) - { - MsgConfigureChannelizer *msg = MsgConfigureChannelizer::create(settings.m_log2Decim, settings.m_filterChainHash); - m_inputMessageQueue.push(msg); - } - qDebug("RemoteSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any { MsgConfigureRemoteSink *msgToGUI = MsgConfigureRemoteSink::create(settings, force); diff --git a/plugins/channelrx/remotesink/remotesink.h b/plugins/channelrx/remotesink/remotesink.h index 277b33df6..3b34fa622 100644 --- a/plugins/channelrx/remotesink/remotesink.h +++ b/plugins/channelrx/remotesink/remotesink.h @@ -24,21 +24,16 @@ #ifndef INCLUDE_REMOTESINK_H_ #define INCLUDE_REMOTESINK_H_ -#include #include -#include #include #include "dsp/basebandsamplesink.h" #include "channel/channelapi.h" -#include "remotesinksettings.h" +#include "remotesinkbaseband.h" class QNetworkAccessManager; class QNetworkReply; class DeviceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; -class RemoteSinkThread; class RemoteSink : public BasebandSampleSink, public ChannelAPI { Q_OBJECT @@ -66,47 +61,24 @@ public: { } }; - class MsgSampleRateNotification : public Message { + class MsgBasebandSampleRateNotification : public Message { MESSAGE_CLASS_DECLARATION public: - static MsgSampleRateNotification* create(int sampleRate) { - return new MsgSampleRateNotification(sampleRate); + static MsgBasebandSampleRateNotification* create(int sampleRate) { + return new MsgBasebandSampleRateNotification(sampleRate); } - int getSampleRate() const { return m_sampleRate; } + int getBasebandSampleRate() const { return m_basebandSampleRate; } private: - MsgSampleRateNotification(int sampleRate) : + MsgBasebandSampleRateNotification(int sampleRate) : Message(), - m_sampleRate(sampleRate) + m_basebandSampleRate(sampleRate) { } - int m_sampleRate; - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getLog2Decim() const { return m_log2Decim; } - int getFilterChainHash() const { return m_filterChainHash; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - unsigned int m_log2Decim; - unsigned int m_filterChainHash; - - MsgConfigureChannelizer(unsigned int log2Decim, int filterChainHash) : - Message(), - m_log2Decim(log2Decim), - m_filterChainHash(filterChainHash) - { } + int m_basebandSampleRate; }; RemoteSink(DeviceAPI *deviceAPI); @@ -154,18 +126,6 @@ public: const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response); - /** Set center frequency given in Hz */ - void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency; } - - /** Set sample rate given in Hz */ - void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } - - void setNbBlocksFEC(int nbBlocksFEC); - void setTxDelay(int txDelay, int nbBlocksFEC); - void setDataAddress(const QString& address) { m_dataAddress = address; } - void setDataPort(uint16_t port) { m_dataPort = port; } - void setChannelizer(unsigned int log2Decim, unsigned int filterChainHash); - uint32_t getNumberOfDeviceStreams() const; static const QString m_channelIdURI; @@ -176,29 +136,14 @@ signals: private: DeviceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - bool m_running; - + QThread *m_thread; + RemoteSinkBaseband *m_basebandSink; RemoteSinkSettings m_settings; - RemoteSinkThread *m_sinkThread; - - int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row - uint16_t m_frameCount; //!< transmission frame count - int m_sampleIndex; //!< Current sample index in protected block data - RemoteSuperBlock m_superBlock; - RemoteMetaDataFEC m_currentMetaFEC; - RemoteDataBlock *m_dataBlock; - QMutex m_dataBlockMutex; uint64_t m_centerFrequency; int64_t m_frequencyOffset; - uint32_t m_sampleRate; - uint32_t m_deviceSampleRate; - int m_nbBlocksFEC; - int m_txDelay; - QString m_dataAddress; - uint16_t m_dataPort; + int m_basebandSampleRate; + QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; diff --git a/plugins/channelrx/remotesink/remotesinkbaseband.cpp b/plugins/channelrx/remotesink/remotesinkbaseband.cpp new file mode 100644 index 000000000..121430780 --- /dev/null +++ b/plugins/channelrx/remotesink/remotesinkbaseband.cpp @@ -0,0 +1,153 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// 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 // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/downsamplechannelizer.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" + +#include "remotesinkbaseband.h" + +MESSAGE_CLASS_DEFINITION(RemoteSinkBaseband::MsgConfigureRemoteSinkBaseband, Message) +MESSAGE_CLASS_DEFINITION(RemoteSinkBaseband::MsgBasebandSampleRateNotification, Message) + +RemoteSinkBaseband::RemoteSinkBaseband() : + m_localSampleSource(nullptr), + m_mutex(QMutex::Recursive) +{ + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownSampleChannelizer(&m_sink); + + qDebug("RemoteSinkBaseband::RemoteSinkBaseband"); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &RemoteSinkBaseband::handleData, + Qt::QueuedConnection + ); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +RemoteSinkBaseband::~RemoteSinkBaseband() +{ + delete m_channelizer; +} + +void RemoteSinkBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.reset(); +} + +void RemoteSinkBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void RemoteSinkBaseband::handleData() +{ + QMutexLocker mutexLocker(&m_mutex); + + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void RemoteSinkBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool RemoteSinkBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureRemoteSinkBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureRemoteSinkBaseband& cfg = (MsgConfigureRemoteSinkBaseband&) cmd; + qDebug() << "RemoteSinkBaseband::handleMessage: MsgConfigureRemoteSinkBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (MsgBasebandSampleRateNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgBasebandSampleRateNotification& notif = (MsgBasebandSampleRateNotification&) cmd; + qDebug() << "RemoteSinkBaseband::handleMessage: MsgBasebandSampleRateNotification: basebandSampleRate: " << notif.getBasebandSampleRate(); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getBasebandSampleRate())); + m_channelizer->setBasebandSampleRate(notif.getBasebandSampleRate()); + m_sink.applySampleRate(m_channelizer->getChannelSampleRate()); + + return true; + } + else + { + return false; + } +} + +void RemoteSinkBaseband::applySettings(const RemoteSinkSettings& settings, bool force) +{ + qDebug() << "RemoteSinkBaseband::applySettings:" + << "m_log2Decim:" << settings.m_log2Decim + << "m_filterChainHash:" << settings.m_filterChainHash + << " force: " << force; + + if ((settings.m_log2Decim != m_settings.m_log2Decim) + || (settings.m_filterChainHash != m_settings.m_filterChainHash) || force) + { + m_channelizer->setDecimation(settings.m_log2Decim, settings.m_filterChainHash); + m_sink.applySampleRate(m_channelizer->getChannelSampleRate()); + } + + //m_source.applySettings(settings, force); + m_settings = settings; +} + +int RemoteSinkBaseband::getChannelSampleRate() const +{ + return m_channelizer->getChannelSampleRate(); +} diff --git a/plugins/channelrx/remotesink/remotesinkbaseband.h b/plugins/channelrx/remotesink/remotesinkbaseband.h new file mode 100644 index 000000000..72a147010 --- /dev/null +++ b/plugins/channelrx/remotesink/remotesinkbaseband.h @@ -0,0 +1,107 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// 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 // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_REMOTESINKBASEBAND_H +#define INCLUDE_REMOTESINKBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "remotesinksink.h" +#include "remotesinksettings.h" + +class DownSampleChannelizer; + +class RemoteSinkBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureRemoteSinkBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const RemoteSinkSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureRemoteSinkBaseband* create(const RemoteSinkSettings& settings, bool force) + { + return new MsgConfigureRemoteSinkBaseband(settings, force); + } + + private: + RemoteSinkSettings m_settings; + bool m_force; + + MsgConfigureRemoteSinkBaseband(const RemoteSinkSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgBasebandSampleRateNotification : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgBasebandSampleRateNotification* create(int sampleRate) { + return new MsgBasebandSampleRateNotification(sampleRate); + } + + int getBasebandSampleRate() const { return m_basebandSampleRate; } + + private: + + MsgBasebandSampleRateNotification(int sampleRate) : + Message(), + m_basebandSampleRate(sampleRate) + { } + + int m_basebandSampleRate; + }; + + RemoteSinkBaseband(); + ~RemoteSinkBaseband(); + void reset(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + int getChannelSampleRate() const; + void startSink() { m_sink.start(); } + void stopSink() { m_sink.stop(); } + +private: + SampleSinkFifo m_sampleFifo; + DownSampleChannelizer *m_channelizer; + RemoteSinkSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + RemoteSinkSettings m_settings; + DeviceSampleSource *m_localSampleSource; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void applySettings(const RemoteSinkSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + + +#endif // INCLUDE_REMOTESINKBASEBAND_H diff --git a/plugins/channelrx/remotesink/remotesinkgui.cpp b/plugins/channelrx/remotesink/remotesinkgui.cpp index 3270d13a6..4da414977 100644 --- a/plugins/channelrx/remotesink/remotesinkgui.cpp +++ b/plugins/channelrx/remotesink/remotesinkgui.cpp @@ -83,11 +83,11 @@ bool RemoteSinkGUI::deserialize(const QByteArray& data) bool RemoteSinkGUI::handleMessage(const Message& message) { - if (RemoteSink::MsgSampleRateNotification::match(message)) + if (RemoteSink::MsgBasebandSampleRateNotification::match(message)) { - RemoteSink::MsgSampleRateNotification& notif = (RemoteSink::MsgSampleRateNotification&) message; + RemoteSink::MsgBasebandSampleRateNotification& notif = (RemoteSink::MsgBasebandSampleRateNotification&) message; //m_channelMarker.setBandwidth(notif.getSampleRate()); - m_sampleRate = notif.getSampleRate(); + m_sampleRate = notif.getBasebandSampleRate(); updateTxDelayTime(); displayRateAndShift(); return true; @@ -168,17 +168,6 @@ void RemoteSinkGUI::applySettings(bool force) } } -void RemoteSinkGUI::applyChannelSettings() -{ - if (m_doApplySettings) - { - RemoteSink::MsgConfigureChannelizer *msgChan = RemoteSink::MsgConfigureChannelizer::create( - m_settings.m_log2Decim, - m_settings.m_filterChainHash); - m_remoteSink->getInputMessageQueue()->push(msgChan); - } -} - void RemoteSinkGUI::displaySettings() { m_channelMarker.blockSignals(true); @@ -403,7 +392,7 @@ void RemoteSinkGUI::applyPosition() ui->filterChainText->setText(s); displayRateAndShift(); - applyChannelSettings(); + applySettings(); } void RemoteSinkGUI::tick() diff --git a/plugins/channelrx/remotesink/remotesinkgui.h b/plugins/channelrx/remotesink/remotesinkgui.h index dd48ce0f7..d9f31fd9f 100644 --- a/plugins/channelrx/remotesink/remotesinkgui.h +++ b/plugins/channelrx/remotesink/remotesinkgui.h @@ -78,7 +78,6 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); - void applyChannelSettings(); void displaySettings(); void displayStreamIndex(); void displayRateAndShift(); diff --git a/plugins/channelrx/remotesink/remotesinksink.cpp b/plugins/channelrx/remotesink/remotesinksink.cpp new file mode 100644 index 000000000..9d9435eed --- /dev/null +++ b/plugins/channelrx/remotesink/remotesinksink.cpp @@ -0,0 +1,254 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// 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 // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#include "dsp/hbfilterchainconverter.h" +#include "util/timeutil.h" + +#include "remotesinkthread.h" +#include "remotesinksink.h" + +RemoteSinkSink::RemoteSinkSink() : + m_running(false), + m_remoteSinkThread(nullptr), + m_txBlockIndex(0), + m_frameCount(0), + m_sampleIndex(0), + m_dataBlock(nullptr), + m_centerFrequency(0), + m_frequencyOffset(0), + m_sampleRate(48000), + m_nbBlocksFEC(0), + m_txDelay(35), + m_dataAddress("127.0.0.1"), + m_dataPort(9090) +{ + applySettings(m_settings, true); +} + +RemoteSinkSink::~RemoteSinkSink() +{ + QMutexLocker mutexLocker(&m_dataBlockMutex); + + if (m_dataBlock && !m_dataBlock->m_txControlBlock.m_complete) { + delete m_dataBlock; + } +} + +void RemoteSinkSink::setTxDelay(int txDelay, int nbBlocksFEC) +{ + double txDelayRatio = txDelay / 100.0; + int samplesPerBlock = RemoteNbBytesPerBlock / sizeof(Sample); + double delay = m_sampleRate == 0 ? 1.0 : (127*samplesPerBlock*txDelayRatio) / m_sampleRate; + delay /= 128 + nbBlocksFEC; + m_txDelay = roundf(delay*1e6); // microseconds + qDebug() << "RemoteSinkSink::setTxDelay:" + << " " << txDelay + << "% m_txDelay: " << m_txDelay << "us" + << " m_sampleRate: " << m_sampleRate << "S/s"; +} + +void RemoteSinkSink::setNbBlocksFEC(int nbBlocksFEC) +{ + qDebug() << "RemoteSinkSink::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC; + m_nbBlocksFEC = nbBlocksFEC; +} + +void RemoteSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + SampleVector::const_iterator it = begin; + + while (it != end) + { + int inSamplesIndex = it - begin; + int inRemainingSamples = end - it; + + if (m_txBlockIndex == 0) + { + // struct timeval tv; + RemoteMetaDataFEC metaData; + uint64_t nowus = TimeUtil::nowus(); + // gettimeofday(&tv, 0); + + metaData.m_centerFrequency = m_centerFrequency + m_frequencyOffset; + metaData.m_sampleRate = m_sampleRate; + metaData.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + metaData.m_sampleBits = SDR_RX_SAMP_SZ; + metaData.m_nbOriginalBlocks = RemoteNbOrginalBlocks; + metaData.m_nbFECBlocks = m_nbBlocksFEC; + metaData.m_tv_sec = nowus / 1000000UL; // tv.tv_sec; + metaData.m_tv_usec = nowus % 1000000UL; // tv.tv_usec; + + if (!m_dataBlock) { // on the very first cycle there is no data block allocated + m_dataBlock = new RemoteDataBlock(); + } + + boost::crc_32_type crc32; + crc32.process_bytes(&metaData, sizeof(RemoteMetaDataFEC)-4); + metaData.m_crc32 = crc32.checksum(); + RemoteSuperBlock& superBlock = m_dataBlock->m_superBlocks[0]; // first block + superBlock.init(); + superBlock.m_header.m_frameIndex = m_frameCount; + superBlock.m_header.m_blockIndex = m_txBlockIndex; + superBlock.m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + superBlock.m_header.m_sampleBits = SDR_RX_SAMP_SZ; + + RemoteMetaDataFEC *destMeta = (RemoteMetaDataFEC *) &superBlock.m_protectedBlock; + *destMeta = metaData; + + if (!(metaData == m_currentMetaFEC)) + { + qDebug() << "RemoteSinkSink::feed: meta: " + << "|" << metaData.m_centerFrequency + << ":" << metaData.m_sampleRate + << ":" << (int) (metaData.m_sampleBytes & 0xF) + << ":" << (int) metaData.m_sampleBits + << "|" << (int) metaData.m_nbOriginalBlocks + << ":" << (int) metaData.m_nbFECBlocks + << "|" << metaData.m_tv_sec + << ":" << metaData.m_tv_usec; + + m_currentMetaFEC = metaData; + } + + m_txBlockIndex = 1; // next Tx block with data + } // block zero + + // handle different sample sizes... + int samplesPerBlock = RemoteNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8); // two I or Q samples + if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block + { + memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], + (const void *) &(*(begin+inSamplesIndex)), + inRemainingSamples * sizeof(Sample)); + m_sampleIndex += inRemainingSamples; + it = end; // all input samples are consumed + } + else // complete super block and initiate the next if not end of frame + { + memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], + (const void *) &(*(begin+inSamplesIndex)), + (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); + it += samplesPerBlock - m_sampleIndex; + m_sampleIndex = 0; + + m_superBlock.m_header.m_frameIndex = m_frameCount; + m_superBlock.m_header.m_blockIndex = m_txBlockIndex; + m_superBlock.m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + m_superBlock.m_header.m_sampleBits = SDR_RX_SAMP_SZ; + m_dataBlock->m_superBlocks[m_txBlockIndex] = m_superBlock; + + if (m_txBlockIndex == RemoteNbOrginalBlocks - 1) // frame complete + { + m_dataBlockMutex.lock(); + m_dataBlock->m_txControlBlock.m_frameIndex = m_frameCount; + m_dataBlock->m_txControlBlock.m_processed = false; + m_dataBlock->m_txControlBlock.m_complete = true; + m_dataBlock->m_txControlBlock.m_nbBlocksFEC = m_nbBlocksFEC; + m_dataBlock->m_txControlBlock.m_txDelay = m_txDelay; + m_dataBlock->m_txControlBlock.m_dataAddress = m_dataAddress; + m_dataBlock->m_txControlBlock.m_dataPort = m_dataPort; + + emit dataBlockAvailable(m_dataBlock); + m_dataBlock = new RemoteDataBlock(); // create a new one immediately + m_dataBlockMutex.unlock(); + + m_txBlockIndex = 0; + m_frameCount++; + } + else + { + m_txBlockIndex++; + } + } + } +} + +void RemoteSinkSink::start() +{ + qDebug("RemoteSinkSink::start"); + + memset((void *) &m_currentMetaFEC, 0, sizeof(RemoteMetaDataFEC)); + + if (m_running) { + stop(); + } + + m_remoteSinkThread = new RemoteSinkThread(); + connect(this, + SIGNAL(dataBlockAvailable(RemoteDataBlock *)), + m_remoteSinkThread, + SLOT(processDataBlock(RemoteDataBlock *)), + Qt::QueuedConnection); + m_remoteSinkThread->startStop(true); + m_running = true; +} + +void RemoteSinkSink::stop() +{ + qDebug("RemoteSinkSink::stop"); + + if (m_remoteSinkThread) + { + m_remoteSinkThread->startStop(false); + m_remoteSinkThread->deleteLater(); + m_remoteSinkThread = nullptr; + } + + m_running = false; +} + +void RemoteSinkSink::applySettings(const RemoteSinkSettings& settings, bool force) +{ + qDebug() << "RemoteSinkSink::applySettings:" + << " m_nbFECBlocks: " << settings.m_nbFECBlocks + << " m_txDelay: " << settings.m_txDelay + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort + << " m_streamIndex: " << settings.m_streamIndex + << " force: " << force; + + if ((m_settings.m_nbFECBlocks != settings.m_nbFECBlocks) || force) + { + setNbBlocksFEC(settings.m_nbFECBlocks); + setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); + } + + if ((m_settings.m_txDelay != settings.m_txDelay) || force) { + setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); + } + + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { + m_dataAddress = settings.m_dataAddress; + } + + if ((m_settings.m_dataPort != settings.m_dataPort) || force) { + m_dataPort = settings.m_dataPort; + } + + m_settings = settings; +} + +void RemoteSinkSink::applySampleRate(uint32_t sampleRate) +{ + m_sampleRate = sampleRate; + setTxDelay(m_settings.m_txDelay, m_settings.m_nbFECBlocks); +} \ No newline at end of file diff --git a/plugins/channelrx/remotesink/remotesinksink.h b/plugins/channelrx/remotesink/remotesinksink.h new file mode 100644 index 000000000..58a14433c --- /dev/null +++ b/plugins/channelrx/remotesink/remotesinksink.h @@ -0,0 +1,82 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// 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 // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_REMOTESINKSINK_H_ +#define INCLUDE_REMOTESINKSINK_H_ + +#include +#include + +#include "dsp/channelsamplesink.h" +#include "channel/remotedatablock.h" + + +#include "remotesinksettings.h" + +class DeviceSampleSource; +class RemoteSinkThread; + +class RemoteSinkSink : public QObject, public ChannelSampleSink { + Q_OBJECT +public: + RemoteSinkSink(); + ~RemoteSinkSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applySettings(const RemoteSinkSettings& settings, bool force = false); + void applySampleRate(uint32_t sampleRate); + void start(); + void stop(); + bool isRunning() const { return m_running; } + + /** Set center frequency given in Hz */ + void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency; } + + /** Set sample rate given in Hz */ + + void setNbBlocksFEC(int nbBlocksFEC); + void setTxDelay(int txDelay, int nbBlocksFEC); + void setDataAddress(const QString& address) { m_dataAddress = address; } + void setDataPort(uint16_t port) { m_dataPort = port; } + +signals: + void dataBlockAvailable(RemoteDataBlock *dataBlock); + +private: + RemoteSinkSettings m_settings; + RemoteSinkThread *m_remoteSinkThread; + bool m_running; + + int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row + uint16_t m_frameCount; //!< transmission frame count + int m_sampleIndex; //!< Current sample index in protected block data + RemoteSuperBlock m_superBlock; + RemoteMetaDataFEC m_currentMetaFEC; + RemoteDataBlock *m_dataBlock; + QMutex m_dataBlockMutex; + + uint64_t m_centerFrequency; + int64_t m_frequencyOffset; + uint32_t m_sampleRate; + int m_nbBlocksFEC; + int m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; +}; + +#endif // INCLUDE_REMOTESINKSINK_H_ \ No newline at end of file