kopia lustrzana https://github.com/f4exb/sdrangel
				
				
				
			DaemonSink (1)
							rodzic
							
								
									dc51f96b3f
								
							
						
					
					
						commit
						96e7d49fbe
					
				|  | @ -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) | ||||
|  |  | |||
|  | @ -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) | ||||
|  | @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <sys/time.h> | ||||
| #include <unistd.h> | ||||
| #include <boost/crc.hpp> | ||||
| #include <boost/cstdint.hpp> | ||||
| 
 | ||||
| #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); | ||||
| } | ||||
|  | @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef INCLUDE_DAEMONSINK_H_ | ||||
| #define INCLUDE_DAEMONSINK_H_ | ||||
| 
 | ||||
| #include <QMutex> | ||||
| 
 | ||||
| #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_ */ | ||||
|  | @ -0,0 +1,300 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>DaemonSinkGUI</class> | ||||
|  <widget class="RollupWidget" name="DaemonSinkGUI"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>320</width> | ||||
|     <height>100</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="sizePolicy"> | ||||
|    <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> | ||||
|     <horstretch>0</horstretch> | ||||
|     <verstretch>0</verstretch> | ||||
|    </sizepolicy> | ||||
|   </property> | ||||
|   <property name="minimumSize"> | ||||
|    <size> | ||||
|     <width>320</width> | ||||
|     <height>100</height> | ||||
|    </size> | ||||
|   </property> | ||||
|   <property name="maximumSize"> | ||||
|    <size> | ||||
|     <width>320</width> | ||||
|     <height>16777215</height> | ||||
|    </size> | ||||
|   </property> | ||||
|   <property name="font"> | ||||
|    <font> | ||||
|     <family>Liberation Sans</family> | ||||
|     <pointsize>9</pointsize> | ||||
|    </font> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Daemon sink</string> | ||||
|   </property> | ||||
|   <widget class="QWidget" name="settingsContainer" native="true"> | ||||
|    <property name="geometry"> | ||||
|     <rect> | ||||
|      <x>10</x> | ||||
|      <y>10</y> | ||||
|      <width>301</width> | ||||
|      <height>81</height> | ||||
|     </rect> | ||||
|    </property> | ||||
|    <property name="windowTitle"> | ||||
|     <string>Settings</string> | ||||
|    </property> | ||||
|    <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|     <property name="spacing"> | ||||
|      <number>3</number> | ||||
|     </property> | ||||
|     <property name="leftMargin"> | ||||
|      <number>2</number> | ||||
|     </property> | ||||
|     <property name="topMargin"> | ||||
|      <number>2</number> | ||||
|     </property> | ||||
|     <property name="rightMargin"> | ||||
|      <number>2</number> | ||||
|     </property> | ||||
|     <property name="bottomMargin"> | ||||
|      <number>2</number> | ||||
|     </property> | ||||
|     <item> | ||||
|      <layout class="QHBoxLayout" name="dataAddressLayout"> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="dataAddressLabel"> | ||||
|         <property name="minimumSize"> | ||||
|          <size> | ||||
|           <width>30</width> | ||||
|           <height>0</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Data</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLineEdit" name="dataAddress"> | ||||
|         <property name="minimumSize"> | ||||
|          <size> | ||||
|           <width>120</width> | ||||
|           <height>0</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Local data listener address</string> | ||||
|         </property> | ||||
|         <property name="inputMask"> | ||||
|          <string>000.000.000.000</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>0...</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="dataPortSeparator"> | ||||
|         <property name="text"> | ||||
|          <string>:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLineEdit" name="dataPort"> | ||||
|         <property name="maximumSize"> | ||||
|          <size> | ||||
|           <width>50</width> | ||||
|           <height>16777215</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Local data listener port</string> | ||||
|         </property> | ||||
|         <property name="inputMask"> | ||||
|          <string>00000</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>0</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <spacer name="horizontalSpacer"> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Horizontal</enum> | ||||
|         </property> | ||||
|         <property name="sizeHint" stdset="0"> | ||||
|          <size> | ||||
|           <width>40</width> | ||||
|           <height>20</height> | ||||
|          </size> | ||||
|         </property> | ||||
|        </spacer> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QPushButton" name="pushButton"> | ||||
|         <property name="maximumSize"> | ||||
|          <size> | ||||
|           <width>30</width> | ||||
|           <height>16777215</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Set local data listener address and port</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Set</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|     <item> | ||||
|      <layout class="QHBoxLayout" name="nominalValuesLayout"> | ||||
|       <item> | ||||
|        <widget class="QDial" name="nbFECBlocks"> | ||||
|         <property name="maximumSize"> | ||||
|          <size> | ||||
|           <width>24</width> | ||||
|           <height>24</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Number of FEC blocks per frame</string> | ||||
|         </property> | ||||
|         <property name="maximum"> | ||||
|          <number>32</number> | ||||
|         </property> | ||||
|         <property name="pageStep"> | ||||
|          <number>1</number> | ||||
|         </property> | ||||
|         <property name="value"> | ||||
|          <number>0</number> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="nominalNbBlocksText"> | ||||
|         <property name="minimumSize"> | ||||
|          <size> | ||||
|           <width>50</width> | ||||
|           <height>0</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Nb total blocks / Nb FEC blocks</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>000/00</string> | ||||
|         </property> | ||||
|         <property name="alignment"> | ||||
|          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="Line" name="line"> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Vertical</enum> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="txDelayLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Udly</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QDial" name="txDelay"> | ||||
|         <property name="maximumSize"> | ||||
|          <size> | ||||
|           <width>24</width> | ||||
|           <height>24</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Delay between consecutive UDP packets in percentage of nominal UDP packet process time</string> | ||||
|         </property> | ||||
|         <property name="minimum"> | ||||
|          <number>10</number> | ||||
|         </property> | ||||
|         <property name="maximum"> | ||||
|          <number>90</number> | ||||
|         </property> | ||||
|         <property name="pageStep"> | ||||
|          <number>1</number> | ||||
|         </property> | ||||
|         <property name="value"> | ||||
|          <number>50</number> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="txDelayText"> | ||||
|         <property name="minimumSize"> | ||||
|          <size> | ||||
|           <width>20</width> | ||||
|           <height>0</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>90</string> | ||||
|         </property> | ||||
|         <property name="alignment"> | ||||
|          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <spacer name="horizontalSpacer_3"> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Horizontal</enum> | ||||
|         </property> | ||||
|         <property name="sizeHint" stdset="0"> | ||||
|          <size> | ||||
|           <width>40</width> | ||||
|           <height>20</height> | ||||
|          </size> | ||||
|         </property> | ||||
|        </spacer> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|     <item> | ||||
|      <spacer name="verticalSpacer"> | ||||
|       <property name="orientation"> | ||||
|        <enum>Qt::Vertical</enum> | ||||
|       </property> | ||||
|       <property name="sizeHint" stdset="0"> | ||||
|        <size> | ||||
|         <width>20</width> | ||||
|         <height>40</height> | ||||
|        </size> | ||||
|       </property> | ||||
|      </spacer> | ||||
|     </item> | ||||
|    </layout> | ||||
|   </widget> | ||||
|  </widget> | ||||
|  <customwidgets> | ||||
|   <customwidget> | ||||
|    <class>RollupWidget</class> | ||||
|    <extends>QWidget</extends> | ||||
|    <header>gui/rollupwidget.h</header> | ||||
|    <container>1</container> | ||||
|   </customwidget> | ||||
|  </customwidgets> | ||||
|  <resources> | ||||
|   <include location="../../../sdrgui/resources/res.qrc"/> | ||||
|  </resources> | ||||
|  <connections/> | ||||
| </ui> | ||||
|  | @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef INCLUDE_SDRDAEMONCHANNELSINKSETTINGS_H_ | ||||
| #define INCLUDE_SDRDAEMONCHANNELSINKSETTINGS_H_ | ||||
| 
 | ||||
| #include <QByteArray> | ||||
| 
 | ||||
| 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_ */ | ||||
|  | @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QUdpSocket> | ||||
| 
 | ||||
| #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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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 <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QThread> | ||||
| #include <QMutex> | ||||
| #include <QWaitCondition> | ||||
| #include <QHostAddress> | ||||
| 
 | ||||
| #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(); | ||||
| }; | ||||
|  | @ -253,7 +253,7 @@ | |||
|         <number>90</number> | ||||
|        </property> | ||||
|        <property name="pageStep"> | ||||
|         <number>0</number> | ||||
|         <number>1</number> | ||||
|        </property> | ||||
|        <property name="value"> | ||||
|         <number>50</number> | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 f4exb
						f4exb