diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 68bde180f..8dcad701a 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -25,6 +25,12 @@ if (FFMPEG_FOUND) endif() endif() +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(daemonsink) +endif(CM256CC_FOUND) + if (BUILD_DEBIAN) add_subdirectory(demoddsd) + add_subdirectory(daemonsink) endif (BUILD_DEBIAN) diff --git a/plugins/channelrx/daemonsink/CMakeLists.txt b/plugins/channelrx/daemonsink/CMakeLists.txt new file mode 100644 index 000000000..5c52986ce --- /dev/null +++ b/plugins/channelrx/daemonsink/CMakeLists.txt @@ -0,0 +1,57 @@ +project(daemonsink) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(daemonsink_SOURCES + daemonsink.cpp +# daemonsinkgui.cpp + daemonsinksettings.cpp + daemonsinkthread.cpp +# daemonsinkplugin.cpp +) + +set(daemonsink_HEADERS + daemonsink.h +# daemonsinkgui.h + daemonsinksettings.h + daemonsinkthread.h +# daemonsinkplugin.h +) + +set(daemonsink_FORMS + daemonsinkgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/sdrdaemon + ${CM256CC_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(daemonsink_FORMS_HEADERS ${daemonsink_FORMS}) + +add_library(daemonsink SHARED + ${daemonsink_SOURCES} + ${daemonsink_HEADERS_MOC} + ${daemonsink_FORMS_HEADERS} +) + +target_link_libraries(daemonsink + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrdaemon + sdrgui + swagger +) + +target_link_libraries(daemonsink Qt5::Core Qt5::Widgets) + +install(TARGETS daemonsink DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp new file mode 100644 index 000000000..b30e140ae --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -0,0 +1,400 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 + +#include "SWGChannelSettings.h" + +#include "util/simpleserializer.h" +#include "dsp/threadedbasebandsamplesink.h" +#include "dsp/downchannelizer.h" +#include "dsp/dspcommands.h" +#include "device/devicesourceapi.h" +#include "daemonsinkthread.h" +#include "daemonsink.h" + +MESSAGE_CLASS_DEFINITION(DaemonSink::MsgConfigureDaemonSink, Message) + +const QString DaemonSink::m_channelIdURI = "sdrangel.channel.daemonsink"; +const QString DaemonSink::m_channelId = "DaemonSink"; + +DaemonSink::DaemonSink(DeviceSourceAPI *deviceAPI) : + ChannelSinkAPI(m_channelIdURI), + 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_sampleRate(48000), + m_sampleBytes(SDR_RX_SAMP_SZ == 24 ? 4 : 2), + m_nbBlocksFEC(0), + m_txDelay(100), + m_dataAddress("127.0.0.1"), + m_dataPort(9090) +{ + setObjectName(m_channelId); + + m_channelizer = new DownChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); + m_deviceAPI->addThreadedSink(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); + + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; +} + +DaemonSink::~DaemonSink() +{ + m_dataBlockMutex.lock(); + if (m_dataBlock && !m_dataBlock->m_txControlBlock.m_complete) { + delete m_dataBlock; + } + m_dataBlockMutex.unlock(); + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSink(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +void DaemonSink::setTxDelay(int txDelay) +{ + qDebug() << "DaemonSink::setTxDelay: txDelay: " << txDelay; + m_txDelay = txDelay; +} + +void DaemonSink::setNbBlocksFEC(int nbBlocksFEC) +{ + qDebug() << "DaemonSink::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC; + m_nbBlocksFEC = nbBlocksFEC; +} + +void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) +{ + SampleVector::const_iterator it = begin; + + while (it != end) + { + int inSamplesIndex = it - begin; + int inRemainingSamples = end - it; + + if (m_txBlockIndex == 0) + { + struct timeval tv; + SDRDaemonMetaDataFEC metaData; + gettimeofday(&tv, 0); + + metaData.m_centerFrequency = m_centerFrequency; + metaData.m_sampleRate = m_sampleRate; + metaData.m_sampleBytes = m_sampleBytes; + metaData.m_sampleBits = 0; // TODO: deprecated + metaData.m_nbOriginalBlocks = SDRDaemonNbOrginalBlocks; + 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 SDRDaemonDataBlock(); + } + + boost::crc_32_type crc32; + crc32.process_bytes(&metaData, 20); + metaData.m_crc32 = crc32.checksum(); + SDRDaemonSuperBlock& superBlock = m_dataBlock->m_superBlocks[0]; // first block + superBlock.init(); + superBlock.m_header.m_frameIndex = m_frameCount; + superBlock.m_header.m_blockIndex = m_txBlockIndex; + memcpy((void *) &superBlock.m_protectedBlock, (const void *) &metaData, sizeof(SDRDaemonMetaDataFEC)); + + if (!(metaData == m_currentMetaFEC)) + { + qDebug() << "SDRDaemonChannelSink::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 + + // TODO: handle different sample sizes... + if (m_sampleIndex + inRemainingSamples < SDRDaemonSamplesPerBlock) // there is still room in the current super block + { + memcpy((void *) &m_superBlock.m_protectedBlock.m_samples[m_sampleIndex], + (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.m_samples[m_sampleIndex], + (const void *) &(*(begin+inSamplesIndex)), + (SDRDaemonSamplesPerBlock - m_sampleIndex) * sizeof(Sample)); + it += SDRDaemonSamplesPerBlock - m_sampleIndex; + m_sampleIndex = 0; + + m_superBlock.m_header.m_frameIndex = m_frameCount; + m_superBlock.m_header.m_blockIndex = m_txBlockIndex; + m_dataBlock->m_superBlocks[m_txBlockIndex] = m_superBlock; + + if (m_txBlockIndex == SDRDaemonNbOrginalBlocks - 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; + + m_dataQueue.push(m_dataBlock); + m_dataBlock = new SDRDaemonDataBlock(); // create a new one immediately + m_dataBlockMutex.unlock(); + + m_txBlockIndex = 0; + m_frameCount++; + } + else + { + m_txBlockIndex++; + } + } + } +} + +void DaemonSink::start() +{ + qDebug("DaemonSink::start"); + + memset((void *) &m_currentMetaFEC, 0, sizeof(SDRDaemonMetaDataFEC)); + + if (m_running) { + stop(); + } + + m_sinkThread = new DaemonSinkThread(&m_dataQueue, m_cm256p); + m_sinkThread->startStop(true); + m_running = true; +} + +void DaemonSink::stop() +{ + qDebug("DaemonSink::stop"); + + if (m_sinkThread != 0) + { + m_sinkThread->startStop(false); + m_sinkThread->deleteLater(); + m_sinkThread = 0; + } + + m_running = false; +} + +bool DaemonSink::handleMessage(const Message& cmd __attribute__((unused))) +{ + if (DownChannelizer::MsgChannelizerNotification::match(cmd)) + { + DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; + + qDebug() << "DaemonSink::handleMessage: MsgChannelizerNotification:" + << " channelSampleRate: " << notif.getSampleRate() + << " offsetFrequency: " << notif.getFrequencyOffset(); + + if (notif.getSampleRate() > 0) { + setSampleRate(notif.getSampleRate()); + } + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + + qDebug() << "DaemonSink::handleMessage: DSPSignalNotification:" + << " inputSampleRate: " << notif.getSampleRate() + << " centerFrequency: " << notif.getCenterFrequency(); + + setCenterFrequency(notif.getCenterFrequency()); + + return true; + } + else if (MsgConfigureDaemonSink::match(cmd)) + { + MsgConfigureDaemonSink& cfg = (MsgConfigureDaemonSink&) cmd; + qDebug() << "DaemonSink::handleMessage: MsgConfigureDaemonSink"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else + { + return false; + } +} + +QByteArray DaemonSink::serialize() const +{ + return m_settings.serialize(); +} + +bool DaemonSink::deserialize(const QByteArray& data __attribute__((unused))) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureDaemonSink *msg = MsgConfigureDaemonSink::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureDaemonSink *msg = MsgConfigureDaemonSink::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void DaemonSink::applySettings(const DaemonSinkSettings& settings, bool force) +{ + qDebug() << "DaemonSink::applySettings:" + << " m_nbFECBlocks: " << settings.m_nbFECBlocks + << " m_txDelay: " << settings.m_txDelay + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort + << " force: " << force; + + if ((m_settings.m_nbFECBlocks != settings.m_nbFECBlocks) || force) { + m_nbBlocksFEC = settings.m_nbFECBlocks; + } + + if ((m_settings.m_txDelay != settings.m_txDelay) || force) { + m_txDelay = settings.m_txDelay; + } + + 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; +} + +int DaemonSink::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setDaemonSinkSettings(new SWGSDRangel::SWGDaemonSinkSettings()); + response.getDaemonSinkSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int DaemonSink::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + DaemonSinkSettings settings = m_settings; + + if (channelSettingsKeys.contains("nbFECBlocks")) + { + int nbFECBlocks = response.getDaemonSinkSettings()->getNbFecBlocks(); + + if ((nbFECBlocks < 0) || (nbFECBlocks > 127)) { + settings.m_nbFECBlocks = 8; + } else { + settings.m_nbFECBlocks = response.getDaemonSinkSettings()->getNbFecBlocks(); + } + } + + if (channelSettingsKeys.contains("txDelay")) + { + int txDelay = response.getDaemonSinkSettings()->getTxDelay(); + + if (txDelay < 0) { + settings.m_txDelay = 100; + } else { + settings.m_txDelay = txDelay; + } + } + + if (channelSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getDaemonSinkSettings()->getDataAddress(); + } + + if (channelSettingsKeys.contains("dataPort")) + { + int dataPort = response.getDaemonSinkSettings()->getDataPort(); + + if ((dataPort < 1024) || (dataPort > 65535)) { + settings.m_dataPort = 9090; + } else { + settings.m_dataPort = dataPort; + } + } + + MsgConfigureDaemonSink *msg = MsgConfigureDaemonSink::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("DaemonSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureDaemonSink *msgToGUI = MsgConfigureDaemonSink::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +void DaemonSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSinkSettings& settings) +{ + response.getDaemonSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); + response.getDaemonSinkSettings()->setTxDelay(settings.m_txDelay); + + if (response.getDaemonSinkSettings()->getDataAddress()) { + *response.getDaemonSinkSettings()->getDataAddress() = settings.m_dataAddress; + } else { + response.getDaemonSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); + } + + response.getDaemonSinkSettings()->setDataPort(settings.m_dataPort); +} diff --git a/plugins/channelrx/daemonsink/daemonsink.h b/plugins/channelrx/daemonsink/daemonsink.h new file mode 100644 index 000000000..247f0e1d9 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsink.h @@ -0,0 +1,139 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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_DAEMONSINK_H_ +#define INCLUDE_DAEMONSINK_H_ + +#include + +#include "cm256.h" + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "daemonsinksettings.h" + +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; +class DaemonSinkThread; + +class DaemonSink : public BasebandSampleSink, public ChannelSinkAPI { + Q_OBJECT +public: + class MsgConfigureDaemonSink : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DaemonSinkSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDaemonSink* create(const DaemonSinkSettings& settings, bool force) + { + return new MsgConfigureDaemonSink(settings, force); + } + + private: + DaemonSinkSettings m_settings; + bool m_force; + + MsgConfigureDaemonSink(const DaemonSinkSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + DaemonSink(DeviceSourceAPI *deviceAPI); + virtual ~DaemonSink(); + virtual void destroy() { delete this; } + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = "SDRDaemon Sink"; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + /** Set center frequency given in Hz */ + void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency / 1000; } + + /** Set sample rate given in Hz */ + void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } + + void setNbBlocksFEC(int nbBlocksFEC); + void setTxDelay(int txDelay); + void setDataAddress(const QString& address) { m_dataAddress = address; } + void setDataPort(uint16_t port) { m_dataPort = port; } + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + bool m_running; + + DaemonSinkSettings m_settings; + SDRDaemonDataQueue m_dataQueue; + DaemonSinkThread *m_sinkThread; + CM256 m_cm256; + CM256 *m_cm256p; + + 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 + SDRDaemonSuperBlock m_superBlock; + SDRDaemonMetaDataFEC m_currentMetaFEC; + SDRDaemonDataBlock *m_dataBlock; + QMutex m_dataBlockMutex; + + uint64_t m_centerFrequency; + uint32_t m_sampleRate; + uint8_t m_sampleBytes; + int m_nbBlocksFEC; + int m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; + + void applySettings(const DaemonSinkSettings& settings, bool force = false); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSinkSettings& settings); +}; + +#endif /* INCLUDE_DAEMONSINK_H_ */ diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.ui b/plugins/channelrx/daemonsink/daemonsinkgui.ui new file mode 100644 index 000000000..138446c19 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkgui.ui @@ -0,0 +1,300 @@ + + + DaemonSinkGUI + + + + 0 + 0 + 320 + 100 + + + + + 0 + 0 + + + + + 320 + 100 + + + + + 320 + 16777215 + + + + + Liberation Sans + 9 + + + + Daemon sink + + + + + 10 + 10 + 301 + 81 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + + 30 + 0 + + + + Data + + + + + + + + 120 + 0 + + + + Local data listener address + + + 000.000.000.000 + + + 0... + + + + + + + : + + + + + + + + 50 + 16777215 + + + + Local data listener port + + + 00000 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 30 + 16777215 + + + + Set local data listener address and port + + + Set + + + + + + + + + + + + 24 + 24 + + + + Number of FEC blocks per frame + + + 32 + + + 1 + + + 0 + + + + + + + + 50 + 0 + + + + Nb total blocks / Nb FEC blocks + + + 000/00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Udly + + + + + + + + 24 + 24 + + + + Delay between consecutive UDP packets in percentage of nominal UDP packet process time + + + 10 + + + 90 + + + 1 + + + 50 + + + + + + + + 20 + 0 + + + + 90 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+
+ + + + +
diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.cpp b/plugins/channelrx/daemonsink/daemonsinksettings.cpp new file mode 100644 index 000000000..6d2cfac79 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinksettings.cpp @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) main settings // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 "util/simpleserializer.h" +#include "settings/serializable.h" +#include "daemonsinksettings.h" + +DaemonSinkSettings::DaemonSinkSettings() +{ + resetToDefaults(); +} + +void DaemonSinkSettings::resetToDefaults() +{ + m_nbFECBlocks = 0; + m_txDelay = 100; + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; +} + +QByteArray DaemonSinkSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeU32(1, m_nbFECBlocks); + s.writeU32(2, m_txDelay); + s.writeString(3, m_dataAddress); + s.writeU32(4, m_dataPort); + + return s.final(); +} + +bool DaemonSinkSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + uint32_t tmp; + QString strtmp; + + d.readU32(1, &tmp, 0); + + if (tmp < 128) { + m_nbFECBlocks = tmp; + } else { + m_nbFECBlocks = 0; + } + + d.readU32(2, &m_txDelay, 100); + d.readString(3, &m_dataAddress, "127.0.0.1"); + d.readU32(4, &tmp, 0); + + if ((tmp > 1023) && (tmp < 65535)) { + m_dataPort = tmp; + } else { + m_dataPort = 9090; + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + + + + diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.h b/plugins/channelrx/daemonsink/daemonsinksettings.h new file mode 100644 index 000000000..e0dd8fc55 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinksettings.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) main settings // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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_SDRDAEMONCHANNELSINKSETTINGS_H_ +#define INCLUDE_SDRDAEMONCHANNELSINKSETTINGS_H_ + +#include + +class Serializable; + +struct DaemonSinkSettings +{ + uint16_t m_nbFECBlocks; + uint32_t m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; + + DaemonSinkSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* INCLUDE_SDRDAEMONCHANNELSINKSETTINGS_H_ */ diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.cpp b/plugins/channelrx/daemonsink/daemonsinkthread.cpp new file mode 100644 index 000000000..8dfb0f9b3 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkthread.cpp @@ -0,0 +1,198 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) UDP sender thread // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "daemonsinkthread.h" + +#include "cm256.h" + +MESSAGE_CLASS_DEFINITION(DaemonSinkThread::MsgStartStop, Message) + +DaemonSinkThread::DaemonSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent) : + QThread(parent), + m_running(false), + m_dataQueue(dataQueue), + m_cm256(cm256), + m_address(QHostAddress::LocalHost), + m_socket(0) +{ + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + connect(m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); +} + +DaemonSinkThread::~DaemonSinkThread() +{ + qDebug("DaemonSinkThread::~DaemonSinkThread"); +} + +void DaemonSinkThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void DaemonSinkThread::startWork() +{ + qDebug("DaemonSinkThread::startWork"); + m_startWaitMutex.lock(); + m_socket = new QUdpSocket(this); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void DaemonSinkThread::stopWork() +{ + qDebug("DaemonSinkThread::stopWork"); + delete m_socket; + m_socket = 0; + m_running = false; + wait(); +} + +void DaemonSinkThread::run() +{ + qDebug("DaemonSinkThread::run: begin"); + m_running = true; + m_startWaiter.wakeAll(); + + while (m_running) + { + sleep(1); // Do nothing as everything is in the data handler (dequeuer) + } + + m_running = false; + qDebug("DaemonSinkThread::run: end"); +} + +bool DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) +{ + CM256::cm256_encoder_params cm256Params; //!< Main interface with CM256 encoder + CM256::cm256_block descriptorBlocks[256]; //!< Pointers to data for CM256 encoder + SDRDaemonProtectedBlock fecBlocks[256]; //!< FEC data + + uint16_t frameIndex = dataBlock.m_txControlBlock.m_frameIndex; + int nbBlocksFEC = dataBlock.m_txControlBlock.m_nbBlocksFEC; + int txDelay = dataBlock.m_txControlBlock.m_txDelay; + m_address.setAddress(dataBlock.m_txControlBlock.m_dataAddress); + uint16_t dataPort = dataBlock.m_txControlBlock.m_dataPort; + SDRDaemonSuperBlock *txBlockx = dataBlock.m_superBlocks; + + if ((nbBlocksFEC == 0) || !m_cm256) // Do not FEC encode + { + if (m_socket) + { + for (int i = 0; i < SDRDaemonNbOrginalBlocks; i++) + { + // send block via UDP + m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, dataPort); + usleep(txDelay); + } + } + } + else + { + cm256Params.BlockBytes = sizeof(SDRDaemonProtectedBlock); + cm256Params.OriginalCount = SDRDaemonNbOrginalBlocks; + cm256Params.RecoveryCount = nbBlocksFEC; + + // Fill pointers to data + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; ++i) + { + if (i >= cm256Params.OriginalCount) { + memset((void *) &txBlockx[i].m_protectedBlock, 0, sizeof(SDRDaemonProtectedBlock)); + } + + txBlockx[i].m_header.m_frameIndex = frameIndex; + txBlockx[i].m_header.m_blockIndex = i; + descriptorBlocks[i].Block = (void *) &(txBlockx[i].m_protectedBlock); + descriptorBlocks[i].Index = txBlockx[i].m_header.m_blockIndex; + } + + // Encode FEC blocks + if (m_cm256->cm256_encode(cm256Params, descriptorBlocks, fecBlocks)) + { + qWarning("SDRDaemonChannelSinkThread::handleDataBlock: CM256 encode failed. No transmission."); + // TODO: send without FEC changing meta data to set indication of no FEC + return true; + } + + // Merge FEC with data to transmit + for (int i = 0; i < cm256Params.RecoveryCount; i++) + { + txBlockx[i + cm256Params.OriginalCount].m_protectedBlock = fecBlocks[i]; + } + + // Transmit all blocks + if (m_socket) + { + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) + { + // send block via UDP + m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, dataPort); + usleep(txDelay); + } + } + } + + dataBlock.m_txControlBlock.m_processed = true; + return true; +} + +void DaemonSinkThread::handleData() +{ + SDRDaemonDataBlock* dataBlock; + + while (m_running && ((dataBlock = m_dataQueue->pop()) != 0)) + { + if (handleDataBlock(*dataBlock)) + { + delete dataBlock; + } + } +} + +void DaemonSinkThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("DaemonSinkThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + } +} diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.h b/plugins/channelrx/daemonsink/daemonsinkthread.h new file mode 100644 index 000000000..10db87e9a --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkthread.h @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) UDP sender thread // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 + +#include "util/message.h" +#include "util/messagequeue.h" + +class SDRDaemonDataQueue; +class SDRDaemonDataBlock; +class CM256; +class QUdpSocket; + +class DaemonSinkThread : public QThread { + Q_OBJECT + +public: + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + DaemonSinkThread(SDRDaemonDataQueue *dataQueue, CM256 *cm256, QObject* parent = 0); + ~DaemonSinkThread(); + + void startStop(bool start); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + SDRDaemonDataQueue *m_dataQueue; + CM256 *m_cm256; //!< CM256 library object + + QHostAddress m_address; + QUdpSocket *m_socket; + + MessageQueue m_inputMessageQueue; + + void startWork(); + void stopWork(); + + void run(); + bool handleDataBlock(SDRDaemonDataBlock& dataBlock); + +private slots: + void handleData(); + void handleInputMessages(); +}; diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index 145378676..9e8e6680a 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -253,7 +253,7 @@ 90 - 0 + 1 50